mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-04 09:35:38 -05:00
loading indicator for table designer (#17407)
* loading indicator for table designer * fix layering error * bug fix
This commit is contained in:
@@ -27,6 +27,7 @@ import { Button, IButtonStyles } from 'sql/base/browser/ui/button/button';
|
||||
import { ButtonColumn } from 'sql/base/browser/ui/table/plugins/buttonColumn.plugin';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { LoadingSpinner } from 'sql/base/browser/ui/loadingSpinner/loadingSpinner';
|
||||
|
||||
export interface IDesignerStyle {
|
||||
tabbedPanelStyles?: ITabbedPanelStyles;
|
||||
@@ -44,6 +45,7 @@ export type CreateComponentFunc = (container: HTMLElement, component: DesignerDa
|
||||
export type SetComponentValueFunc = (definition: DesignerDataPropertyInfo, component: DesignerUIComponent, data: DesignerViewModel) => void;
|
||||
|
||||
export class Designer extends Disposable implements IThemable {
|
||||
private _loadingSpinner: LoadingSpinner;
|
||||
private _horizontalSplitViewContainer: HTMLElement;
|
||||
private _verticalSplitViewContainer: HTMLElement;
|
||||
private _tabbedPanelContainer: HTMLElement;
|
||||
@@ -89,6 +91,7 @@ export class Designer extends Disposable implements IThemable {
|
||||
}
|
||||
}, this._contextViewProvider
|
||||
);
|
||||
this._loadingSpinner = new LoadingSpinner(this._container, { showText: true, fullSize: true });
|
||||
this._verticalSplitViewContainer = DOM.$('.designer-component');
|
||||
this._horizontalSplitViewContainer = DOM.$('.container');
|
||||
this._contentContainer = DOM.$('.content-container');
|
||||
@@ -201,7 +204,10 @@ export class Designer extends Disposable implements IThemable {
|
||||
private async initializeDesignerView(): Promise<void> {
|
||||
this._propertiesPane.clear();
|
||||
DOM.clearNode(this._topContentContainer);
|
||||
// For initialization, we would want to show the loading indicator immediately.
|
||||
const handle = this.startLoading(localize('designer.loadingDesigner', "Loading designer..."), 0);
|
||||
const view = await this._input.getView();
|
||||
this.stopLoading(handle, localize('designer.loadingDesignerCompleted', "Loading designer completed"));
|
||||
if (view.components) {
|
||||
view.components.forEach(component => {
|
||||
this.createComponent(this._topContentContainer, component, component.propertyName, true, true);
|
||||
@@ -268,7 +274,9 @@ export class Designer extends Disposable implements IThemable {
|
||||
return;
|
||||
}
|
||||
await this.applyEdit(edit);
|
||||
const handle = this.startLoading(localize('designer.processingChanges', "Processing changes..."));
|
||||
const result = await this._input.processEdit(edit);
|
||||
this.stopLoading(handle, localize('designer.processingChangesCompleted', "Processing changes completed"));
|
||||
if (result.isValid) {
|
||||
this._supressEditProcessing = true;
|
||||
await this.updateComponentValues();
|
||||
@@ -415,8 +423,10 @@ export class Designer extends Disposable implements IThemable {
|
||||
ariaLabel: inputProperties.title,
|
||||
type: inputProperties.inputType,
|
||||
});
|
||||
input.onDidChange(async (newValue) => {
|
||||
await this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: newValue });
|
||||
input.onLoseFocus(async (args) => {
|
||||
if (args.hasChanged) {
|
||||
await this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: args.value });
|
||||
}
|
||||
});
|
||||
if (setWidth && inputProperties.width !== undefined) {
|
||||
input.width = inputProperties.width as number;
|
||||
@@ -567,4 +577,22 @@ export class Designer extends Disposable implements IThemable {
|
||||
this.styleComponent(component);
|
||||
return component;
|
||||
}
|
||||
|
||||
private startLoading(message: string, timeout: number = 500): any {
|
||||
// To make the experience smoother, only show the loading indicator if the request is not returning in 500ms(default value).
|
||||
return setTimeout(() => {
|
||||
this._loadingSpinner.loadingMessage = message;
|
||||
this._loadingSpinner.loading = true;
|
||||
this._container.removeChild(this._verticalSplitViewContainer);
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
private stopLoading(handle: any, message: string): void {
|
||||
clearTimeout(handle);
|
||||
if (this._loadingSpinner.loading) {
|
||||
this._loadingSpinner.loadingCompletedMessage = message;
|
||||
this._loadingSpinner.loading = false;
|
||||
this._container.appendChild(this._verticalSplitViewContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ export interface DesignerState {
|
||||
valid: boolean;
|
||||
dirty: boolean;
|
||||
saving: boolean;
|
||||
processing: boolean;
|
||||
}
|
||||
|
||||
export const NameProperty = 'name';
|
||||
|
||||
@@ -176,4 +176,13 @@ export class InputBox extends vsInputBox {
|
||||
super.width = width;
|
||||
this.element.style.width = 'fit-content';
|
||||
}
|
||||
|
||||
public override get value() {
|
||||
return super.value;
|
||||
}
|
||||
|
||||
public override set value(newValue: string) {
|
||||
this._lastLoseFocusValue = newValue;
|
||||
super.value = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
85
src/sql/base/browser/ui/loadingSpinner/loadingSpinner.ts
Normal file
85
src/sql/base/browser/ui/loadingSpinner/loadingSpinner.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/loadingSpinner';
|
||||
import * as nls from 'vs/nls';
|
||||
import { status } from 'vs/base/browser/ui/aria/aria';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
|
||||
const DefaultLoadingMessage = nls.localize('loadingMessage', "Loading");
|
||||
const DefaultLoadingCompletedMessage = nls.localize('loadingCompletedMessage', "Loading completed");
|
||||
|
||||
export interface LoadingSpinnerOptions {
|
||||
/**
|
||||
* Whether to show the messages. The default value is false.
|
||||
*/
|
||||
showText?: boolean;
|
||||
/**
|
||||
* Whether the loading spinner should take up all the avaliable spaces. The default value is false.
|
||||
*/
|
||||
fullSize?: boolean;
|
||||
}
|
||||
|
||||
const defaultLoadingSpinnerOptions: LoadingSpinnerOptions = {
|
||||
showText: false,
|
||||
fullSize: false
|
||||
};
|
||||
|
||||
export class LoadingSpinner extends Disposable {
|
||||
private _loading: boolean = false;
|
||||
private _loadingMessage?: string;
|
||||
private _loadingCompletedMessage?: string;
|
||||
private _loadingSpinner: HTMLElement;
|
||||
private _loadingSpinnerText: HTMLElement;
|
||||
private _options: LoadingSpinnerOptions;
|
||||
|
||||
constructor(private _container: HTMLElement, options?: LoadingSpinnerOptions) {
|
||||
super();
|
||||
this._options = mixin(options || {}, defaultLoadingSpinnerOptions, false);
|
||||
this._loadingSpinner = DOM.$(`.loading-spinner-component-container${this._options.fullSize ? '.full-size' : ''}`);
|
||||
this._loadingSpinner.appendChild(DOM.$('.loading-spinner.codicon.in-progress'));
|
||||
if (this._options.showText) {
|
||||
this._loadingSpinnerText = this._loadingSpinner.appendChild(DOM.$(''));
|
||||
}
|
||||
}
|
||||
|
||||
get loadingMessage(): string {
|
||||
return this._loadingMessage ?? DefaultLoadingMessage;
|
||||
}
|
||||
|
||||
set loadingMessage(v: string) {
|
||||
this._loadingMessage = v;
|
||||
}
|
||||
|
||||
get loadingCompletedMessage(): string {
|
||||
return this._loadingCompletedMessage ?? DefaultLoadingCompletedMessage;
|
||||
}
|
||||
|
||||
set loadingCompletedMessage(v: string) {
|
||||
this._loadingCompletedMessage = v;
|
||||
}
|
||||
|
||||
get loading(): boolean {
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
set loading(v: boolean) {
|
||||
if (v !== this._loading) {
|
||||
this._loading = v;
|
||||
const message = this._loading ? this.loadingMessage : this.loadingCompletedMessage;
|
||||
status(message);
|
||||
if (this._loading) {
|
||||
this._container.appendChild(this._loadingSpinner);
|
||||
} else {
|
||||
this._container.removeChild(this._loadingSpinner);
|
||||
}
|
||||
if (this._options.showText) {
|
||||
this._loadingSpinnerText.innerText = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.loading-spinner-component-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.loading-spinner-component-container .loading-spinner {
|
||||
height: 20px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.loading-spinner-component-container.full-size {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
Reference in New Issue
Block a user