diff --git a/src/sql/azdata.d.ts b/src/sql/azdata.d.ts index 8f7d5deaed..d050496a53 100644 --- a/src/sql/azdata.d.ts +++ b/src/sql/azdata.d.ts @@ -3659,6 +3659,7 @@ declare module 'azdata' { export function createWebViewDialog(title: string): ModalDialog; /** + * @deprecated please use the method createModelViewDialog(title: string, dialogName?: string, width?: DialogWidth) instead. * Create a dialog with the given title * @param title The title of the dialog, displayed at the top * @param isWide Indicates whether the dialog is wide or normal diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index 7d1c6edc64..c051db5cbf 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -362,6 +362,37 @@ declare module 'azdata' { } export function createModelViewDashboard(title: string, options?: ModelViewDashboardOptions): ModelViewDashboard; + + export interface Dialog { + /** + * Width of the dialog + */ + width?: DialogWidth; + } + + export interface Wizard { + /** + * Width of the wizard + */ + width?: DialogWidth; + } + + export type DialogWidth = 'narrow' | 'medium' | 'wide' | number; + + /** + * Create a dialog with the given title + * @param title The title of the dialog, displayed at the top + * @param dialogName the name of the dialog + * @param width width of the dialog, default is 'wide' + */ + export function createModelViewDialog(title: string, dialogName?: string, width?: DialogWidth): Dialog; + + /** + * Create a wizard with the given title and width + * @param title The title of the wizard + * @param width The width of the wizard, default value is 'narrow' + */ + export function createWizard(title: string, width?: DialogWidth): Wizard; } export interface DashboardTab extends Tab { diff --git a/src/sql/workbench/api/browser/mainThreadModelViewDialog.ts b/src/sql/workbench/api/browser/mainThreadModelViewDialog.ts index 0caae074f9..502f591879 100644 --- a/src/sql/workbench/api/browser/mainThreadModelViewDialog.ts +++ b/src/sql/workbench/api/browser/mainThreadModelViewDialog.ts @@ -10,12 +10,13 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { MainThreadModelViewDialogShape, SqlMainContext, ExtHostModelViewDialogShape, SqlExtHostContext } from 'sql/workbench/api/common/sqlExtHost.protocol'; import { Dialog, DialogTab, DialogButton, WizardPage, Wizard } from 'sql/workbench/services/dialog/common/dialogTypes'; -import { CustomDialogService } from 'sql/workbench/services/dialog/browser/customDialogService'; +import { CustomDialogService, DefaultWizardOptions, DefaultDialogOptions } from 'sql/workbench/services/dialog/browser/customDialogService'; import { IModelViewDialogDetails, IModelViewTabDetails, IModelViewButtonDetails, IModelViewWizardPageDetails, IModelViewWizardDetails } from 'sql/workbench/api/common/sqlExtHostTypes'; import { ModelViewInput, ModelViewInputModel, ModeViewSaveHandler } from 'sql/workbench/browser/modelComponents/modelViewInput'; import * as vscode from 'vscode'; import * as azdata from 'azdata'; +import { assign } from 'vs/base/common/objects'; @extHostNamedCustomer(SqlMainContext.MainThreadModelViewDialog) export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape { @@ -67,7 +68,9 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape public $openDialog(handle: number, dialogName?: string): Thenable { let dialog = this.getDialog(handle); - this._dialogService.showDialog(dialog, dialogName, { hasBackButton: false, isWide: dialog.isWide, hasErrors: true }); + const options = assign({}, DefaultDialogOptions); + options.width = dialog.width; + this._dialogService.showDialog(dialog, dialogName, options); return Promise.resolve(); } @@ -91,7 +94,7 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape } dialog.title = details.title; - dialog.isWide = details.isWide; + dialog.width = details.width; if (details.content && typeof details.content !== 'string') { dialog.content = details.content.map(tabHandle => this.getTab(tabHandle)); } else { @@ -163,6 +166,7 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape let wizard = this._wizards.get(handle); if (!wizard) { wizard = new Wizard(details.title); + wizard.width = details.width; wizard.backButton = this.getButton(details.backButton); wizard.cancelButton = this.getButton(details.cancelButton); wizard.generateScriptButton = this.getButton(details.generateScriptButton); @@ -213,7 +217,9 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape public $openWizard(handle: number): Thenable { let wizard = this.getWizard(handle); - this._dialogService.showWizard(wizard); + const options = assign({}, DefaultWizardOptions); + options.width = wizard.width; + this._dialogService.showWizard(wizard, options); return Promise.resolve(); } diff --git a/src/sql/workbench/api/common/extHostModelViewDialog.ts b/src/sql/workbench/api/common/extHostModelViewDialog.ts index a0c93bee1d..918fb63633 100644 --- a/src/sql/workbench/api/common/extHostModelViewDialog.ts +++ b/src/sql/workbench/api/common/extHostModelViewDialog.ts @@ -13,7 +13,7 @@ import * as azdata from 'azdata'; import { SqlMainContext, ExtHostModelViewDialogShape, MainThreadModelViewDialogShape, ExtHostModelViewShape, ExtHostBackgroundTaskManagementShape } from 'sql/workbench/api/common/sqlExtHost.protocol'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { TabOrientation } from 'sql/workbench/api/common/sqlExtHostTypes'; +import { TabOrientation, DialogWidth } from 'sql/workbench/api/common/sqlExtHostTypes'; const DONE_LABEL = nls.localize('dialogDoneLabel', "Done"); const CANCEL_LABEL = nls.localize('dialogCancelLabel', "Cancel"); @@ -125,6 +125,7 @@ class DialogImpl extends ModelViewPanelImpl implements azdata.window.Dialog { private _operationHandler: BackgroundOperationHandler; private _dialogName: string; private _isWide: boolean; + private _width: DialogWidth; constructor(extHostModelViewDialog: ExtHostModelViewDialog, extHostModelView: ExtHostModelViewShape, @@ -139,6 +140,14 @@ class DialogImpl extends ModelViewPanelImpl implements azdata.window.Dialog { }); } + public get width(): azdata.window.DialogWidth { + return this._width; + } + + public set width(value: azdata.window.DialogWidth) { + this._width = value; + } + public registerOperation(operationInfo: azdata.BackgroundOperationInfo): void { this._operationHandler.registerOperation(operationInfo); } @@ -372,6 +381,7 @@ class WizardImpl implements azdata.window.Wizard { private _message: azdata.window.DialogMessage; private _displayPageTitles: boolean = true; private _operationHandler: BackgroundOperationHandler; + private _width: DialogWidth; constructor(public title: string, private _extHostModelViewDialog: ExtHostModelViewDialog, extHostTaskManagement: ExtHostBackgroundTaskManagementShape) { this.doneButton = this._extHostModelViewDialog.createButton(DONE_LABEL); @@ -392,6 +402,14 @@ class WizardImpl implements azdata.window.Wizard { this._operationHandler.registerOperation(operationInfo); } + public get width(): azdata.window.DialogWidth { + return this._width; + } + + public set width(value: azdata.window.DialogWidth) { + this._width = value; + } + public get currentPage(): number { return this._currentPage; } @@ -653,9 +671,17 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape { } this.updateButton(dialog.okButton); this.updateButton(dialog.cancelButton); + let dialogWidth: DialogWidth = 'narrow'; + if (dialog.isWide !== undefined) { + dialogWidth = dialog.isWide ? 'wide' : 'narrow'; + } else if (dialog.width !== undefined) { + dialogWidth = dialog.width; + } else { + dialogWidth = 'narrow'; + } this._proxy.$setDialogDetails(handle, { title: dialog.title, - isWide: dialog.isWide, + width: dialogWidth, okButton: this.getHandle(dialog.okButton), cancelButton: this.getHandle(dialog.cancelButton), content: dialog.content && typeof dialog.content !== 'string' ? dialog.content.map(tab => this.getHandle(tab)) : dialog.content as string, @@ -688,13 +714,13 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape { this._onClickCallbacks.set(handle, callback); } - public createDialog(title: string, dialogName?: string, extension?: IExtensionDescription, isWide?: boolean): azdata.window.Dialog { + public createDialog(title: string, dialogName?: string, extension?: IExtensionDescription, width?: azdata.window.DialogWidth): azdata.window.Dialog { let dialog = new DialogImpl(this, this._extHostModelView, this._extHostTaskManagement, extension); if (dialogName) { dialog.dialogName = dialogName; } dialog.title = title; - dialog.isWide = isWide; + dialog.width = width ?? 'narrow'; dialog.handle = this.getHandle(dialog); return dialog; } @@ -736,8 +762,9 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape { return page; } - public createWizard(title: string): azdata.window.Wizard { + public createWizard(title: string, width: azdata.window.DialogWidth = 'wide'): azdata.window.Wizard { let wizard = new WizardImpl(title, this, this._extHostTaskManagement); + wizard.width = width; this.getHandle(wizard); return wizard; } @@ -769,6 +796,7 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape { } return this._proxy.$setWizardDetails(handle, { title: wizard.title, + width: wizard.width, pages: wizard.pages.map(page => this.getHandle(page)), currentPage: wizard.currentPage, backButton: this.getHandle(wizard.backButton), diff --git a/src/sql/workbench/api/common/sqlExtHost.api.impl.ts b/src/sql/workbench/api/common/sqlExtHost.api.impl.ts index 10cb67a552..6ab730ad19 100644 --- a/src/sql/workbench/api/common/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/common/sqlExtHost.api.impl.ts @@ -402,8 +402,15 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp createWebViewDialog(name: string) { return extHostModalDialogs.createDialog(name); }, - createModelViewDialog(title: string, dialogName?: string, isWide?: boolean): azdata.window.Dialog { - return extHostModelViewDialog.createDialog(title, dialogName, extension, !!isWide); + // the 'width' parameter used to be boolean type named 'isWide', the optional boolean type for 'width' parameter is added for backward compatibility support of 'isWide' parameter. + createModelViewDialog(title: string, dialogName?: string, width?: boolean | azdata.window.DialogWidth): azdata.window.Dialog { + let dialogWidth: azdata.window.DialogWidth; + if (typeof width === 'boolean') { + dialogWidth = width === true ? 'wide' : 'narrow'; + } else { + dialogWidth = width; + } + return extHostModelViewDialog.createDialog(title, dialogName, extension, dialogWidth); }, createTab(title: string): azdata.window.DialogTab { return extHostModelViewDialog.createTab(title, extension); @@ -420,8 +427,8 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp createWizardPage(title: string): azdata.window.WizardPage { return extHostModelViewDialog.createWizardPage(title, extension); }, - createWizard(title: string): azdata.window.Wizard { - return extHostModelViewDialog.createWizard(title); + createWizard(title: string, width?: azdata.window.DialogWidth): azdata.window.Wizard { + return extHostModelViewDialog.createWizard(title, width); }, createModelViewDashboard(title: string, options?: azdata.ModelViewDashboardOptions): azdata.window.ModelViewDashboard { return extHostModelViewDialog.createModelViewDashboard(title, options, extension); diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts index 3d2daaf9bc..b008c12251 100644 --- a/src/sql/workbench/api/common/sqlExtHostTypes.ts +++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts @@ -249,12 +249,12 @@ export interface IComponentEventArgs { export interface IModelViewDialogDetails { title: string; - isWide: boolean; content: string | number[]; okButton: number; cancelButton: number; customButtons: number[]; message: DialogMessage; + width: DialogWidth; } export interface IModelViewTabDetails { @@ -290,8 +290,11 @@ export interface IModelViewWizardDetails { customButtons: number[]; message: DialogMessage; displayPageTitles: boolean; + width: DialogWidth; } +export type DialogWidth = 'narrow' | 'medium' | 'wide' | number; + export enum MessageLevel { Error = 0, Warning = 1, diff --git a/src/sql/workbench/browser/modal/media/modal.css b/src/sql/workbench/browser/modal/media/modal.css index 1031c050d6..6137be9b9a 100644 --- a/src/sql/workbench/browser/modal/media/modal.css +++ b/src/sql/workbench/browser/modal/media/modal.css @@ -35,12 +35,20 @@ .modal.flyout-dialog .modal-dialog { margin: auto auto auto auto; height: 100%; - width: 500px; right: 0; position: absolute; overflow-y: hidden; } -.modal.flyout-dialog.wide .modal-dialog { + +.modal.flyout-dialog .modal-dialog.narrow-dialog { + width: 500px; +} + +.modal.flyout-dialog .modal-dialog.medium-dialog { + width: 800px; +} + +.modal.flyout-dialog .modal-dialog.wide-dialog { width: 1200px; max-width: 95%; min-width: 400px; diff --git a/src/sql/workbench/browser/modal/modal.ts b/src/sql/workbench/browser/modal/modal.ts index 0c1c2eb888..6fb1578a2f 100644 --- a/src/sql/workbench/browser/modal/modal.ts +++ b/src/sql/workbench/browser/modal/modal.ts @@ -56,9 +56,11 @@ export interface IModalDialogStyles { footerBorderTopColor?: Color; } +export type DialogWidth = 'narrow' | 'medium' | 'wide' | number; + export interface IModalOptions { isFlyout?: boolean; - isWide?: boolean; + width?: DialogWidth; isAngular?: boolean; hasBackButton?: boolean; hasTitleIcon?: boolean; @@ -69,7 +71,7 @@ export interface IModalOptions { const defaultOptions: IModalOptions = { isFlyout: true, - isWide: false, + width: 'narrow', isAngular: false, hasBackButton: false, hasTitleIcon: false, @@ -175,16 +177,18 @@ export abstract class Modal extends Disposable implements IThemable { builderClass += ' flyout-dialog'; } - if (this._modalOptions.isWide) { - builderClass += ' wide'; - } - this._bodyContainer = DOM.$(`.${builderClass}`, { role: 'dialog', 'aria-label': this._title }); const top = this.layoutService.offset?.top ?? 0; this._bodyContainer.style.top = `${top}px`; this._modalDialog = DOM.append(this._bodyContainer, DOM.$('.modal-dialog')); this._modalContent = DOM.append(this._modalDialog, DOM.$('.modal-content')); + if (typeof this._modalOptions.width === 'number') { + this._modalDialog.style.width = `${this._modalOptions.width}px`; + } else { + this._modalDialog.classList.add(`${this._modalOptions.width}-dialog`); + } + if (!isUndefinedOrNull(this._title)) { this._modalHeaderSection = DOM.append(this._modalContent, DOM.$('.modal-header')); if (this._modalOptions.hasBackButton) { diff --git a/src/sql/workbench/services/dialog/browser/customDialogService.ts b/src/sql/workbench/services/dialog/browser/customDialogService.ts index abf75b10a6..c21ec0ae9f 100644 --- a/src/sql/workbench/services/dialog/browser/customDialogService.ts +++ b/src/sql/workbench/services/dialog/browser/customDialogService.ts @@ -9,8 +9,8 @@ import { Dialog, Wizard } from 'sql/workbench/services/dialog/common/dialogTypes import { IModalOptions } from 'sql/workbench/browser/modal/modal'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -const defaultOptions: IModalOptions = { hasBackButton: false, isWide: false, hasErrors: true }; -const defaultWizardOptions: IModalOptions = { hasBackButton: false, isWide: true, hasErrors: true }; +export const DefaultDialogOptions: IModalOptions = { hasBackButton: false, width: 'narrow', hasErrors: true }; +export const DefaultWizardOptions: IModalOptions = { hasBackButton: false, width: 'wide', hasErrors: true }; export class CustomDialogService { private _dialogModals = new Map(); @@ -20,14 +20,14 @@ export class CustomDialogService { public showDialog(dialog: Dialog, dialogName?: string, options?: IModalOptions): void { let name = dialogName ? dialogName : 'CustomDialog'; - let dialogModal = this._instantiationService.createInstance(DialogModal, dialog, name, options || defaultOptions); + let dialogModal = this._instantiationService.createInstance(DialogModal, dialog, name, options || DefaultDialogOptions); this._dialogModals.set(dialog, dialogModal); dialogModal.render(); dialogModal.open(); } public showWizard(wizard: Wizard, options?: IModalOptions): void { - let wizardModal = this._instantiationService.createInstance(WizardModal, wizard, 'WizardPage', options || defaultWizardOptions); + let wizardModal = this._instantiationService.createInstance(WizardModal, wizard, 'WizardPage', options || DefaultWizardOptions); this._wizardModals.set(wizard, wizardModal); wizardModal.render(); wizardModal.open(); diff --git a/src/sql/workbench/services/dialog/common/dialogTypes.ts b/src/sql/workbench/services/dialog/common/dialogTypes.ts index 30e8e23230..c675c2d7f9 100644 --- a/src/sql/workbench/services/dialog/common/dialogTypes.ts +++ b/src/sql/workbench/services/dialog/common/dialogTypes.ts @@ -6,7 +6,7 @@ import * as azdata from 'azdata'; import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; -import { DialogMessage } from 'sql/workbench/api/common/sqlExtHostTypes'; +import { DialogMessage, DialogWidth } from 'sql/workbench/api/common/sqlExtHostTypes'; export class ModelViewPane { private _valid: boolean = true; @@ -41,7 +41,7 @@ export class Dialog extends ModelViewPane { private static readonly CANCEL_BUTTON_LABEL = localize('dialogModalCancelButtonLabel', "Cancel"); public content: string | DialogTab[]; - public isWide: boolean; + public width: DialogWidth; public okButton: DialogButton = new DialogButton(Dialog.DONE_BUTTON_LABEL, true); public cancelButton: DialogButton = new DialogButton(Dialog.CANCEL_BUTTON_LABEL, true); public customButtons: DialogButton[]; @@ -199,6 +199,7 @@ export class Wizard { public readonly onMessageChange = this._onMessageChange.event; private _message: DialogMessage; public displayPageTitles: boolean; + public width: DialogWidth; constructor(public title: string) { } diff --git a/src/sql/workbench/services/restore/browser/restoreDialog.ts b/src/sql/workbench/services/restore/browser/restoreDialog.ts index 113dbbe494..adad186153 100644 --- a/src/sql/workbench/services/restore/browser/restoreDialog.ts +++ b/src/sql/workbench/services/restore/browser/restoreDialog.ts @@ -141,7 +141,7 @@ export class RestoreDialog extends Modal { @ILogService logService: ILogService, @ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService ) { - super(localize('RestoreDialogTitle', "Restore database"), TelemetryKeys.Restore, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, { hasErrors: true, isWide: true, hasSpinner: true }); + super(localize('RestoreDialogTitle', "Restore database"), TelemetryKeys.Restore, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, { hasErrors: true, width: 'wide', hasSpinner: true }); this._restoreTitle = localize('restoreDialog.restoreTitle', "Restore database"); this._databaseTitle = localize('restoreDialog.database', "Database"); this._backupFileTitle = localize('restoreDialog.backupFile', "Backup file"); diff --git a/src/sql/workbench/test/electron-browser/api/mainThreadModelViewDialog.test.ts b/src/sql/workbench/test/electron-browser/api/mainThreadModelViewDialog.test.ts index cd66d67f1e..7a25ed54f7 100644 --- a/src/sql/workbench/test/electron-browser/api/mainThreadModelViewDialog.test.ts +++ b/src/sql/workbench/test/electron-browser/api/mainThreadModelViewDialog.test.ts @@ -73,7 +73,7 @@ suite('MainThreadModelViewDialog Tests', () => { mockDialogService.setup(x => x.showDialog(It.isAny(), undefined, It.isAny())).callback((dialog) => { openedDialog = dialog; }); - mockDialogService.setup(x => x.showWizard(It.isAny())).callback(wizard => { + mockDialogService.setup(x => x.showWizard(It.isAny(), It.isAny())).callback(wizard => { openedWizard = wizard; // The actual service will set the page to 0 when it opens the wizard openedWizard.setCurrentPage(0); @@ -111,7 +111,7 @@ suite('MainThreadModelViewDialog Tests', () => { }; dialogDetails = { title: 'dialog1', - isWide: false, + width: 'narrow', content: [tab1Handle, tab2Handle], okButton: okButtonHandle, cancelButton: cancelButtonHandle, @@ -160,7 +160,8 @@ suite('MainThreadModelViewDialog Tests', () => { customButtons: [], pages: [page1Handle, page2Handle], message: undefined, - displayPageTitles: false + displayPageTitles: false, + width: 'wide' }; // Register the buttons, tabs, and dialog @@ -242,7 +243,7 @@ suite('MainThreadModelViewDialog Tests', () => { mainThreadModelViewDialog.$openWizard(wizardHandle); // Then the opened wizard's content and buttons match what was set - mockDialogService.verify(x => x.showWizard(It.isAny()), Times.once()); + mockDialogService.verify(x => x.showWizard(It.isAny(), It.isAny()), Times.once()); assert.notEqual(openedWizard, undefined); assert.equal(openedWizard.title, wizardDetails.title); assert.equal(openedWizard.doneButton.label, okButtonDetails.label);