mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-13 17:22:15 -05:00
Adding wizard and dialog footer loading spinner (#21230)
* Adding wizard and dialog loading * Moving apis to proposed * fixing namespace * Only firing event when the value changes * Only firing when value is changed * Adding loading complete message to dialog and wizard * Registering listeners and making a new base interface for loading components * Fixing api comment * Renaming prop to loadingCompleted * old loading icon
This commit is contained in:
@@ -306,7 +306,10 @@ export function createViewContext(): ViewTestContext {
|
||||
onClosed: new vscode.EventEmitter<azdata.window.CloseReason>().event,
|
||||
registerContent: () => { },
|
||||
modelView: undefined!,
|
||||
valid: true
|
||||
valid: true,
|
||||
loading: false,
|
||||
loadingText: '',
|
||||
loadingCompletedText: ''
|
||||
};
|
||||
let wizard: azdata.window.Wizard = {
|
||||
title: '',
|
||||
@@ -327,7 +330,10 @@ export function createViewContext(): ViewTestContext {
|
||||
close: () => { return Promise.resolve(); },
|
||||
registerNavigationValidator: () => { },
|
||||
message: dialogMessage,
|
||||
registerOperation: () => { }
|
||||
registerOperation: () => { },
|
||||
loading: false,
|
||||
loadingText: '',
|
||||
loadingCompletedText: ''
|
||||
};
|
||||
let wizardPage: azdata.window.WizardPage = {
|
||||
title: '',
|
||||
|
||||
26
src/sql/azdata.proposed.d.ts
vendored
26
src/sql/azdata.proposed.d.ts
vendored
@@ -134,6 +134,24 @@ declare module 'azdata' {
|
||||
}
|
||||
}
|
||||
|
||||
export interface LoadingComponentBase {
|
||||
/**
|
||||
* When true, the component will display a loading spinner.
|
||||
*/
|
||||
loading?: boolean;
|
||||
|
||||
/**
|
||||
* This sets the alert text which gets announced when the loading spinner is shown.
|
||||
*/
|
||||
loadingText?: string;
|
||||
|
||||
/**
|
||||
* The text to display while loading is set to false. Will also be announced through screen readers
|
||||
* once loading is completed.
|
||||
*/
|
||||
loadingCompletedText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The column information of a data set.
|
||||
*/
|
||||
@@ -1702,4 +1720,12 @@ declare module 'azdata' {
|
||||
*/
|
||||
objectType?: string;
|
||||
}
|
||||
|
||||
export namespace window {
|
||||
export interface Wizard extends LoadingComponentBase {
|
||||
}
|
||||
|
||||
export interface Dialog extends LoadingComponentBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
circle {
|
||||
animation: ball 0.6s linear infinite;
|
||||
}
|
||||
|
||||
circle:nth-child(2) { animation-delay: 0.075s; }
|
||||
circle:nth-child(3) { animation-delay: 0.15s; }
|
||||
circle:nth-child(4) { animation-delay: 0.225s; }
|
||||
@@ -12,7 +11,6 @@
|
||||
circle:nth-child(6) { animation-delay: 0.375s; }
|
||||
circle:nth-child(7) { animation-delay: 0.45s; }
|
||||
circle:nth-child(8) { animation-delay: 0.525s; }
|
||||
|
||||
@keyframes ball {
|
||||
0% { opacity: 1; }
|
||||
100% { opacity: 0.3; }
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -144,6 +144,9 @@ export class MainThreadModelViewDialog extends Disposable implements MainThreadM
|
||||
dialog.customButtons = details.customButtons.map(buttonHandle => this.getButton(buttonHandle));
|
||||
}
|
||||
dialog.message = details.message;
|
||||
dialog.loading = details.loading;
|
||||
dialog.loadingText = dialog.loadingText;
|
||||
dialog.loadingCompletedText = dialog.loadingCompletedText;
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -223,7 +226,9 @@ export class MainThreadModelViewDialog extends Disposable implements MainThreadM
|
||||
wizard.customButtons = details.customButtons.map(buttonHandle => this.getButton(buttonHandle));
|
||||
}
|
||||
wizard.message = details.message;
|
||||
|
||||
wizard.loading = details.loading;
|
||||
wizard.loadingText = details.loadingText;
|
||||
wizard.loadingCompletedText = details.loadingCompletedText;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
||||
@@ -133,6 +133,9 @@ class DialogImpl extends ModelViewPanelImpl implements azdata.window.Dialog {
|
||||
private _renderHeader: boolean;
|
||||
private _renderFooter: boolean;
|
||||
private _dialogProperties: IDialogProperties;
|
||||
private _loading: boolean;
|
||||
private _loadingText: string;
|
||||
private _loadingCompletedText: string;
|
||||
|
||||
private _onClosed = new Emitter<azdata.window.CloseReason>();
|
||||
public onClosed = this._onClosed.event;
|
||||
@@ -216,6 +219,33 @@ class DialogImpl extends ModelViewPanelImpl implements azdata.window.Dialog {
|
||||
this._extHostModelViewDialog.updateDialogContent(this);
|
||||
}
|
||||
|
||||
public get loading(): boolean {
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
public set loading(value: boolean) {
|
||||
this._loading = value;
|
||||
this._extHostModelViewDialog.updateDialogContent(this);
|
||||
}
|
||||
|
||||
public get loadingText(): string {
|
||||
return this._loadingText;
|
||||
}
|
||||
|
||||
public set loadingText(value: string) {
|
||||
this._loadingText = value;
|
||||
this._extHostModelViewDialog.updateDialogContent(this);
|
||||
}
|
||||
|
||||
public get loadingCompletedText(): string {
|
||||
return this._loadingCompletedText;
|
||||
}
|
||||
|
||||
public set loadingCompletedText(value: string) {
|
||||
this._loadingCompletedText = value;
|
||||
this._extHostModelViewDialog.updateDialogContent(this);
|
||||
}
|
||||
|
||||
public get dialogName(): string {
|
||||
return this._dialogName;
|
||||
}
|
||||
@@ -443,6 +473,9 @@ class WizardImpl implements azdata.window.Wizard {
|
||||
public readonly onPageChanged = this._pageChangedEmitter.event;
|
||||
private _navigationValidator: (info: azdata.window.WizardPageChangeInfo) => boolean | Thenable<boolean>;
|
||||
private _message: azdata.window.DialogMessage;
|
||||
private _loading: boolean;
|
||||
private _loadingText: string;
|
||||
private _loadingCompletedText: string;
|
||||
private _displayPageTitles: boolean = true;
|
||||
private _operationHandler: BackgroundOperationHandler;
|
||||
private _width: DialogWidth;
|
||||
@@ -487,6 +520,33 @@ class WizardImpl implements azdata.window.Wizard {
|
||||
this._extHostModelViewDialog.updateWizard(this);
|
||||
}
|
||||
|
||||
public get loading(): boolean {
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
public set loading(value: boolean) {
|
||||
this._loading = value;
|
||||
this._extHostModelViewDialog.updateWizard(this);
|
||||
}
|
||||
|
||||
public get loadingText(): string {
|
||||
return this._loadingText
|
||||
}
|
||||
|
||||
public set loadingText(value: string) {
|
||||
this._loadingText = value;
|
||||
this._extHostModelViewDialog.updateWizard(this);
|
||||
}
|
||||
|
||||
public get loadingCompletedText(): string {
|
||||
return this._loadingCompletedText;
|
||||
}
|
||||
|
||||
public set loadingCompletedText(value: string) {
|
||||
this._loadingCompletedText = value;
|
||||
this._extHostModelViewDialog.updateWizard(this);
|
||||
}
|
||||
|
||||
public get displayPageTitles(): boolean {
|
||||
return this._displayPageTitles;
|
||||
}
|
||||
@@ -797,7 +857,10 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
|
||||
cancelButton: this.getHandle(dialog.cancelButton),
|
||||
content: dialog.content && typeof dialog.content !== 'string' ? dialog.content.map(tab => this.getHandle(tab)) : dialog.content as string,
|
||||
customButtons: dialog.customButtons ? dialog.customButtons.map(button => this.getHandle(button)) : undefined,
|
||||
message: dialog.message
|
||||
message: dialog.message,
|
||||
loading: dialog.loading,
|
||||
loadingText: dialog.loadingText,
|
||||
loadingCompletedText: dialog.loadingCompletedText
|
||||
});
|
||||
}
|
||||
|
||||
@@ -944,7 +1007,10 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
|
||||
nextButton: this.getHandle(wizard.nextButton),
|
||||
customButtons: wizard.customButtons ? wizard.customButtons.map(button => this.getHandle(button)) : undefined,
|
||||
message: wizard.message,
|
||||
displayPageTitles: wizard.displayPageTitles
|
||||
displayPageTitles: wizard.displayPageTitles,
|
||||
loading: wizard.loading,
|
||||
loadingText: wizard.loadingText,
|
||||
loadingCompletedText: wizard.loadingCompletedText,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -268,6 +268,9 @@ export interface IModelViewDialogDetails {
|
||||
renderHeader: boolean;
|
||||
renderFooter: boolean;
|
||||
dialogProperties: IDialogProperties;
|
||||
loading: boolean;
|
||||
loadingText: string;
|
||||
loadingCompletedText: string;
|
||||
}
|
||||
|
||||
export interface IModelViewTabDetails {
|
||||
@@ -307,6 +310,9 @@ export interface IModelViewWizardDetails {
|
||||
message: DialogMessage;
|
||||
displayPageTitles: boolean;
|
||||
width: DialogWidth;
|
||||
loading: boolean;
|
||||
loadingText: string;
|
||||
loadingCompletedText: string;
|
||||
}
|
||||
|
||||
export type DialogWidth = 'narrow' | 'medium' | 'wide' | number | string;
|
||||
|
||||
@@ -81,6 +81,7 @@ export interface IModalOptions {
|
||||
hasErrors?: boolean;
|
||||
hasSpinner?: boolean;
|
||||
spinnerTitle?: string;
|
||||
onSpinnerHideText?: string;
|
||||
renderHeader?: boolean;
|
||||
renderFooter?: boolean;
|
||||
dialogProperties?: IDialogProperties;
|
||||
@@ -94,7 +95,7 @@ const defaultOptions: IModalOptions = {
|
||||
hasBackButton: false,
|
||||
hasTitleIcon: false,
|
||||
hasErrors: false,
|
||||
hasSpinner: false,
|
||||
hasSpinner: true,
|
||||
renderHeader: true,
|
||||
renderFooter: true,
|
||||
dialogProperties: undefined
|
||||
@@ -638,6 +639,9 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
}
|
||||
} else {
|
||||
DOM.hide(this._spinnerElement!);
|
||||
if (this._modalOptions.onSpinnerHideText) {
|
||||
alert(this._modalOptions.onSpinnerHideText);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
export const DefaultDialogOptions: IModalOptions = { hasBackButton: false, width: 'narrow', hasErrors: true };
|
||||
export const DefaultWizardOptions: IModalOptions = { hasBackButton: false, width: 'wide', hasErrors: true };
|
||||
export const DefaultDialogOptions: IModalOptions = { hasBackButton: false, width: 'narrow', hasErrors: true, hasSpinner: true };
|
||||
export const DefaultWizardOptions: IModalOptions = { hasBackButton: false, width: 'wide', hasErrors: true, hasSpinner: true };
|
||||
|
||||
export class CustomDialogService {
|
||||
private _dialogModals = new Map<Dialog, DialogModal>();
|
||||
|
||||
@@ -93,7 +93,17 @@ export class DialogModal extends Modal {
|
||||
};
|
||||
|
||||
messageChangeHandler(this._dialog.message);
|
||||
this._dialog.onMessageChange(message => messageChangeHandler(message));
|
||||
this._register(this._dialog.onMessageChange(message => messageChangeHandler(message)));
|
||||
this._register(this._dialog.onLoadingChange((loadingState) => {
|
||||
this.spinner = loadingState;
|
||||
}));
|
||||
this._register(this._dialog.onLoadingTextChange((loadingText) => {
|
||||
this._modalOptions.spinnerTitle = loadingText;
|
||||
|
||||
}));
|
||||
this._register(this._dialog.onLoadingCompletedTextChange((loadingCompletedText) => {
|
||||
this._modalOptions.onSpinnerHideText = loadingCompletedText;
|
||||
}));
|
||||
}
|
||||
|
||||
private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined, registerClickEvent: boolean = true, requireDialogValid: boolean = false): Button {
|
||||
|
||||
@@ -96,7 +96,21 @@ export class WizardModal extends Modal {
|
||||
};
|
||||
|
||||
messageChangeHandler(this._wizard.message);
|
||||
this._wizard.onMessageChange(message => messageChangeHandler(message));
|
||||
this._register(this._wizard.onMessageChange(message => messageChangeHandler(message)));
|
||||
|
||||
this._register(this._wizard.onLoadingChange((loadingState) => {
|
||||
this.spinner = loadingState;
|
||||
}));
|
||||
this._register(this._wizard.onLoadingChange((loadingState) => {
|
||||
this.spinner = loadingState;
|
||||
}));
|
||||
this._register(this._wizard.onLoadingTextChange((loadingText) => {
|
||||
this._modalOptions.spinnerTitle = loadingText;
|
||||
|
||||
}));
|
||||
this._register(this._wizard.onLoadingCompletedTextChange((loadingCompletedText) => {
|
||||
this._modalOptions.onSpinnerHideText = loadingCompletedText;
|
||||
}));
|
||||
}
|
||||
|
||||
private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined, registerClickEvent: boolean = true, requirePageValid: boolean = false, index?: number): Button {
|
||||
|
||||
@@ -53,6 +53,16 @@ export class Dialog extends ModelViewPane {
|
||||
public customButtons: DialogButton[] = [];
|
||||
private _onMessageChange = new Emitter<DialogMessage | undefined>();
|
||||
public readonly onMessageChange = this._onMessageChange.event;
|
||||
private _loading: boolean = false;
|
||||
private _loadingText: string;
|
||||
private _loadingCompletedText: string;
|
||||
private _onLoadingChange = new Emitter<boolean | undefined>();
|
||||
private _onLoadingTextChange = new Emitter<string | undefined>();
|
||||
private _onLoadingCompletedTextChange = new Emitter<string | undefined>();
|
||||
public readonly onLoadingChange = this._onLoadingChange.event;
|
||||
public readonly onLoadingTextChange = this._onLoadingTextChange.event;
|
||||
public readonly onLoadingCompletedTextChange = this._onLoadingCompletedTextChange.event;
|
||||
|
||||
private _message: DialogMessage | undefined;
|
||||
private _closeValidator: CloseValidator | undefined;
|
||||
|
||||
@@ -87,6 +97,39 @@ export class Dialog extends ModelViewPane {
|
||||
this._onMessageChange.fire(this._message);
|
||||
}
|
||||
|
||||
public get loading(): boolean {
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
public set loading(value: boolean) {
|
||||
if (this.loading !== value) {
|
||||
this._loading = value;
|
||||
this._onLoadingChange.fire(this._loading);
|
||||
}
|
||||
}
|
||||
|
||||
public get loadingText(): string | undefined {
|
||||
return this._loadingText;
|
||||
}
|
||||
|
||||
public set loadingText(value: string | undefined) {
|
||||
if (this.loadingText !== value) {
|
||||
this._loadingText = value;
|
||||
this._onLoadingTextChange.fire(this._loadingText);
|
||||
}
|
||||
}
|
||||
|
||||
public get loadingCompletedText(): string | undefined {
|
||||
return this._loadingCompletedText;
|
||||
}
|
||||
|
||||
public set loadingCompletedText(value: string | undefined) {
|
||||
if (this._loadingCompletedText !== value) {
|
||||
this._loadingCompletedText = value;
|
||||
this._onLoadingCompletedTextChange.fire(this._loadingCompletedText);
|
||||
}
|
||||
}
|
||||
|
||||
public registerCloseValidator(validator: CloseValidator): void {
|
||||
this._closeValidator = validator;
|
||||
}
|
||||
@@ -247,6 +290,15 @@ export class Wizard {
|
||||
private _message: DialogMessage | undefined;
|
||||
public displayPageTitles: boolean = false;
|
||||
public width: DialogWidth | undefined;
|
||||
private _loading: boolean = false;
|
||||
private _loadingText: string;
|
||||
private _loadingCompletedText: string;
|
||||
private _onLoadingChange = new Emitter<boolean | undefined>();
|
||||
private _onLoadingTextChange = new Emitter<string | undefined>();
|
||||
private _onLoadingCompletedTextChange = new Emitter<string | undefined>();
|
||||
public readonly onLoadingChange = this._onLoadingChange.event;
|
||||
public readonly onLoadingTextChange = this._onLoadingTextChange.event;
|
||||
public readonly onLoadingCompletedTextChange = this._onLoadingCompletedTextChange.event;
|
||||
|
||||
constructor(public title: string,
|
||||
public readonly name: string,
|
||||
@@ -329,4 +381,39 @@ export class Wizard {
|
||||
this._message = value;
|
||||
this._onMessageChange.fire(this._message);
|
||||
}
|
||||
|
||||
public get loading(): boolean {
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
public set loading(value: boolean) {
|
||||
if (this.loading !== value) {
|
||||
this._loading = value;
|
||||
this._onLoadingChange.fire(this._loading);
|
||||
}
|
||||
}
|
||||
|
||||
public get loadingText(): string | undefined {
|
||||
return this._loadingText;
|
||||
}
|
||||
|
||||
public set loadingText(value: string | undefined) {
|
||||
if (this.loadingText !== value) {
|
||||
this._loadingText = value;
|
||||
this._onLoadingTextChange.fire(this._loadingText);
|
||||
}
|
||||
}
|
||||
|
||||
public get loadingCompletedText(): string | undefined {
|
||||
return this._loadingCompletedText;
|
||||
}
|
||||
|
||||
public set loadingCompletedText(value: string | undefined) {
|
||||
if (this._loadingCompletedText !== value) {
|
||||
this._loadingCompletedText = value;
|
||||
this._onLoadingCompletedTextChange.fire(this._loadingCompletedText);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -136,7 +136,10 @@ suite('MainThreadModelViewDialog Tests', () => {
|
||||
okButton: okButtonHandle,
|
||||
cancelButton: cancelButtonHandle,
|
||||
customButtons: [button1Handle, button2Handle],
|
||||
message: undefined
|
||||
message: undefined,
|
||||
loading: false,
|
||||
loadingText: undefined,
|
||||
loadingCompletedText: undefined,
|
||||
};
|
||||
|
||||
// Set up the wizard details
|
||||
@@ -183,7 +186,10 @@ suite('MainThreadModelViewDialog Tests', () => {
|
||||
pages: [page1Handle, page2Handle],
|
||||
message: undefined,
|
||||
displayPageTitles: false,
|
||||
width: 'wide'
|
||||
width: 'wide',
|
||||
loading: false,
|
||||
loadingText: undefined,
|
||||
loadingCompletedText: undefined
|
||||
};
|
||||
|
||||
// Register the buttons, tabs, and dialog
|
||||
|
||||
Reference in New Issue
Block a user