diff --git a/samples/sqlservices/src/controllers/mainController.ts b/samples/sqlservices/src/controllers/mainController.ts
index d2f661ea45..dbbe38c633 100644
--- a/samples/sqlservices/src/controllers/mainController.ts
+++ b/samples/sqlservices/src/controllers/mainController.ts
@@ -74,9 +74,9 @@ export default class MainController implements vscode.Disposable {
dialog.cancelButton.onClick(() => console.log('cancel clicked!'));
dialog.okButton.label = 'ok';
dialog.cancelButton.label = 'no';
- let customButton1 = sqlops.window.modelviewdialog.createButton('Test button 1');
+ let customButton1 = sqlops.window.modelviewdialog.createButton('Load name');
customButton1.onClick(() => console.log('button 1 clicked!'));
- let customButton2 = sqlops.window.modelviewdialog.createButton('Test button 2');
+ let customButton2 = sqlops.window.modelviewdialog.createButton('Load all');
customButton2.onClick(() => console.log('button 2 clicked!'));
dialog.customButtons = [customButton1, customButton2];
tab1.registerContent(async (view) => {
@@ -84,7 +84,14 @@ export default class MainController implements vscode.Disposable {
.withProperties({
//width: 300
}).component();
+ let inputBoxWrapper = view.modelBuilder.loadingComponent().withItem(inputBox).component();
+ inputBoxWrapper.loading = false;
+ customButton1.onClick(() => {
+ inputBoxWrapper.loading = true;
+ setTimeout(() => inputBoxWrapper.loading = false, 5000);
+ });
let inputBox2 = view.modelBuilder.inputBox().component();
+ let backupFilesInputBox = view.modelBuilder.inputBox().component();
let checkbox = view.modelBuilder.checkBox()
.withProperties({
@@ -107,7 +114,7 @@ export default class MainController implements vscode.Disposable {
let button2 = view.modelBuilder.button()
.component();
button.onDidClick(e => {
- inputBox2.value = 'Button clicked';
+ backupFilesInputBox.value = 'Button clicked';
});
let dropdown = view.modelBuilder.dropDown()
.withProperties({
@@ -175,7 +182,7 @@ export default class MainController implements vscode.Disposable {
, { flex: '1 1 50%' }).component();
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
- component: inputBox,
+ component: inputBoxWrapper,
title: 'Backup name'
}, {
component: inputBox2,
@@ -187,7 +194,7 @@ export default class MainController implements vscode.Disposable {
component: checkbox,
title: ''
}, {
- component: inputBox2,
+ component: backupFilesInputBox,
title: 'Backup files',
actions: [button, button3]
}, {
@@ -197,7 +204,13 @@ export default class MainController implements vscode.Disposable {
horizontal: false,
componentWidth: 400
}).component();
- await view.initializeModel(formModel);
+ let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
+ formWrapper.loading = false;
+ customButton2.onClick(() => {
+ formWrapper.loading = true;
+ setTimeout(() => formWrapper.loading = false, 5000);
+ });
+ await view.initializeModel(formWrapper);
});
sqlops.window.modelviewdialog.openDialog(dialog);
diff --git a/src/sql/parts/modelComponents/components.contribution.ts b/src/sql/parts/modelComponents/components.contribution.ts
index 1de60e57e3..e694cdf54a 100644
--- a/src/sql/parts/modelComponents/components.contribution.ts
+++ b/src/sql/parts/modelComponents/components.contribution.ts
@@ -16,6 +16,7 @@ import RadioButtonComponent from './radioButton.component';
import WebViewComponent from './webview.component';
import TableComponent from './table.component';
import TextComponent from './text.component';
+import LoadingComponent from './loadingComponent.component';
import { registerComponentType } from 'sql/platform/dashboard/common/modelComponentRegistry';
import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes';
@@ -58,3 +59,6 @@ registerComponentType(TEXT_COMPONENT, ModelComponentTypes.Text, TextComponent);
export const TABLE_COMPONENT = 'table-component';
registerComponentType(TABLE_COMPONENT, ModelComponentTypes.Table, TableComponent);
+
+export const LOADING_COMPONENT = 'loading-component';
+registerComponentType(LOADING_COMPONENT, ModelComponentTypes.LoadingComponent, LoadingComponent);
diff --git a/src/sql/parts/modelComponents/loading.svg b/src/sql/parts/modelComponents/loading.svg
new file mode 100644
index 0000000000..e762f06d5e
--- /dev/null
+++ b/src/sql/parts/modelComponents/loading.svg
@@ -0,0 +1,31 @@
+
+
diff --git a/src/sql/parts/modelComponents/loadingComponent.component.ts b/src/sql/parts/modelComponents/loadingComponent.component.ts
new file mode 100644
index 0000000000..3cc15443a1
--- /dev/null
+++ b/src/sql/parts/modelComponents/loadingComponent.component.ts
@@ -0,0 +1,88 @@
+/*---------------------------------------------------------------------------------------------
+ * 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!./loadingComponent';
+import {
+ Component, Input, Inject, ChangeDetectorRef, forwardRef, OnDestroy, AfterViewInit, ViewChild, ElementRef
+} from '@angular/core';
+
+import * as sqlops from 'sqlops';
+
+import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
+import { IComponent, IComponentDescriptor, IModelStore } from 'sql/parts/modelComponents/interfaces';
+import * as nls from 'vs/nls';
+
+@Component({
+ selector: 'modelview-loadingComponent',
+ template: `
+
+
+
+ `
+})
+export default class LoadingComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
+ private readonly _loadingTitle = nls.localize('loadingMessage', 'Loading');
+ private _component: IComponentDescriptor;
+
+ @Input() descriptor: IComponentDescriptor;
+ @Input() modelStore: IModelStore;
+
+ @ViewChild('spinnerElement', { read: ElementRef }) private _spinnerElement: ElementRef;
+ @ViewChild('childElement', { read: ElementRef }) private _childElement: ElementRef;
+
+ constructor(
+ @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef) {
+ super(changeRef);
+ this._validations.push(() => {
+ if (!this._component) {
+ return true;
+ }
+ return this.modelStore.getComponent(this._component.id).validate();
+ });
+ }
+
+ ngOnInit(): void {
+ this.baseInit();
+
+ }
+
+ ngAfterViewInit(): void {
+ this.setLayout();
+ }
+
+ ngOnDestroy(): void {
+ this.baseDestroy();
+ }
+
+ /// IComponent implementation
+
+ public layout(): void {
+ this._changeRef.detectChanges();
+ }
+
+ public setLayout(): void {
+ this.layout();
+ }
+
+ public setProperties(properties: { [key: string]: any; }): void {
+ super.setProperties(properties);
+ }
+
+ public get loading(): boolean {
+ return this.getPropertyOrDefault((props) => props.loading, false);
+ }
+
+ public set loading(newValue: boolean) {
+ this.setPropertyFromUI((properties, value) => { properties.loading = value; }, newValue);
+ this.layout();
+ }
+
+ public addToContainer(componentDescriptor: IComponentDescriptor): void {
+ this._component = componentDescriptor;
+ this.layout();
+ }
+}
diff --git a/src/sql/parts/modelComponents/loadingComponent.css b/src/sql/parts/modelComponents/loadingComponent.css
new file mode 100644
index 0000000000..a3e44083f9
--- /dev/null
+++ b/src/sql/parts/modelComponents/loadingComponent.css
@@ -0,0 +1,24 @@
+.modelview-loadingComponent-container {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+}
+
+.vs .modelview-loadingComponent-spinner {
+ content: url("loading.svg");
+}
+
+.vs-dark .modelview-loadingComponent-spinner,
+.hc-black .modelview-loadingComponent-spinner {
+ content: url("loading_inverse.svg");
+}
+
+.modelview-loadingComponent-spinner {
+ height: 20px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+
+.modelview-loadingComponent-content-loading {
+ display: none;
+}
\ No newline at end of file
diff --git a/src/sql/parts/modelComponents/loading_inverse.svg b/src/sql/parts/modelComponents/loading_inverse.svg
new file mode 100644
index 0000000000..c3633c0dda
--- /dev/null
+++ b/src/sql/parts/modelComponents/loading_inverse.svg
@@ -0,0 +1,31 @@
+
+
diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts
index a4ccb8917d..2bf1d16cf9 100644
--- a/src/sql/sqlops.proposed.d.ts
+++ b/src/sql/sqlops.proposed.d.ts
@@ -32,6 +32,7 @@ declare module 'sqlops' {
formContainer(): FormBuilder;
groupContainer(): GroupBuilder;
toolbarContainer(): ToolbarBuilder;
+ loadingComponent(): LoadingComponentBuilder;
}
export interface ComponentBuilder {
@@ -69,6 +70,14 @@ declare module 'sqlops' {
addToolbarItem(toolbarComponent: ToolbarComponent): void;
}
+ export interface LoadingComponentBuilder extends ComponentBuilder {
+ /**
+ * Set the component wrapped by the LoadingComponent
+ * @param component The component to wrap
+ */
+ withItem(component: Component): LoadingComponentBuilder;
+ }
+
export interface FormBuilder extends ContainerBuilder {
withFormItems(components: FormComponent[], itemLayout?: FormItemLayout): ContainerBuilder;
@@ -335,6 +344,10 @@ declare module 'sqlops' {
iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri };
}
+ export interface LoadingComponentProperties {
+ loading?: boolean;
+ }
+
export interface CardComponent extends Component {
label: string;
value: string;
@@ -390,6 +403,22 @@ declare module 'sqlops' {
webviewId: string;
}
+ /**
+ * Component used to wrap another component that needs to be loaded, and show a loading spinner
+ * while the contained component is loading
+ */
+ export interface LoadingComponent extends Component {
+ /**
+ * Whether to show the loading spinner instead of the contained component. True by default
+ */
+ loading: boolean;
+
+ /**
+ * The component displayed when the loading property is false
+ */
+ component: Component;
+ }
+
/**
* A view backed by a model provided by an extension.
* This model contains enough information to lay out the view
diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts
index adb3248e67..e6dfd9b746 100644
--- a/src/sql/workbench/api/common/sqlExtHostTypes.ts
+++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts
@@ -79,7 +79,8 @@ export enum ModelComponentTypes {
DashboardWebview,
Form,
Group,
- Toolbar
+ Toolbar,
+ LoadingComponent
}
export interface IComponentShape {
diff --git a/src/sql/workbench/api/node/extHostModelView.ts b/src/sql/workbench/api/node/extHostModelView.ts
index e29e788906..db805e8130 100644
--- a/src/sql/workbench/api/node/extHostModelView.ts
+++ b/src/sql/workbench/api/node/extHostModelView.ts
@@ -137,6 +137,13 @@ class ModelBuilderImpl implements sqlops.ModelBuilder {
return builder;
}
+ loadingComponent(): sqlops.LoadingComponentBuilder {
+ let id = this.getNextComponentId();
+ let builder = new LoadingComponentBuilder(new LoadingComponentWrapper(this._proxy, this._handle, id));
+ this._componentBuilders.set(id, builder);
+ return builder;
+ }
+
getComponentBuilder(component: ComponentWrapper, id: string): ComponentBuilderImpl {
let componentBuilder: ComponentBuilderImpl = new ComponentBuilderImpl(component);
this._componentBuilders.set(id, componentBuilder);
@@ -299,6 +306,13 @@ class ToolbarContainerBuilder extends ContainerBuilderImpl implements sqlops.LoadingComponentBuilder {
+ withItem(component: sqlops.Component) {
+ this.component().component = component;
+ return this;
+ }
+}
+
class InternalItemConfig {
constructor(private _component: ComponentWrapper, public config: any) { }
@@ -761,6 +775,30 @@ class ButtonWrapper extends ComponentWrapper implements sqlops.ButtonComponent {
}
}
+class LoadingComponentWrapper extends ComponentWrapper implements sqlops.LoadingComponent {
+ constructor(proxy: MainThreadModelViewShape, handle: number, id: string) {
+ super(proxy, handle, ModelComponentTypes.LoadingComponent, id);
+ this.properties = {};
+ this.loading = true;
+ }
+
+ public get loading(): boolean {
+ return this.properties['loading'];
+ }
+
+ public set loading(value: boolean) {
+ this.setProperty('loading', value);
+ }
+
+ public get component(): sqlops.Component {
+ return this.items[0];
+ }
+
+ public set component(value: sqlops.Component) {
+ this.addItem(value);
+ }
+}
+
class ModelViewImpl implements sqlops.ModelView {
public onClosedEmitter = new Emitter();