diff --git a/src/sql/parts/modelComponents/button.component.ts b/src/sql/parts/modelComponents/button.component.ts new file mode 100644 index 0000000000..e884d71a1b --- /dev/null +++ b/src/sql/parts/modelComponents/button.component.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { + Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver, + ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit +} from '@angular/core'; + +import * as sqlops from 'sqlops'; +import Event, { Emitter } from 'vs/base/common/event'; + +import { ComponentBase } from 'sql/parts/modelComponents/componentBase'; +import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces'; +import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service'; +import { attachListStyler } from 'vs/platform/theme/common/styler'; +import { attachButtonStyler } from 'sql/common/theme/styler'; +import { Button } from 'sql/base/browser/ui/button/button'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; + +@Component({ + selector: 'button', + template: ` +
+ ` +}) +export default class ButtonComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit { + @Input() descriptor: IComponentDescriptor; + @Input() modelStore: IModelStore; + private _button: Button; + + @ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef; + constructor( + @Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface, + @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef) { + super(changeRef); + } + + ngOnInit(): void { + this.baseInit(); + + } + + ngAfterViewInit(): void { + if (this._inputContainer) { + + + this._button = new Button(this._inputContainer.nativeElement); + + this._register(this._button); + this._register(attachButtonStyler(this._button, this._commonService.themeService, { + buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND + })); + this._register(this._button.onDidClick(e => { + this._onEventEmitter.fire({ + eventType: ComponentEventType.onDidClick, + args: e + }); + })); + } + } + + ngOnDestroy(): void { + this.baseDestroy(); + } + + /// IComponent implementation + + public layout(): void { + this._changeRef.detectChanges(); + } + + public setLayout(layout: any): void { + // TODO allow configuring the look and feel + this.layout(); + } + + public setProperties(properties: { [key: string]: any; }): void { + super.setProperties(properties); + this._button.label = this.label; + } + + // CSS-bound properties + + private get label(): string { + return this.getPropertyOrDefault((props) => props.label, ''); + } + + private set label(newValue: string) { + this.setPropertyFromUI(this.setValueProperties, newValue); + } + + private setValueProperties(properties: sqlops.ButtonProperties, label: string): void { + properties.label = label; + } +} diff --git a/src/sql/parts/modelComponents/componentBase.ts b/src/sql/parts/modelComponents/componentBase.ts index 558019c63a..3ffb9d4827 100644 --- a/src/sql/parts/modelComponents/componentBase.ts +++ b/src/sql/parts/modelComponents/componentBase.ts @@ -75,7 +75,7 @@ export abstract class ComponentBase extends Disposable implements IComponent, On return types.isUndefinedOrNull(property) ? defaultVal : property; } - protected setProperty(propertySetter: (TPropertyBag, TValue) => void, value: TValue) { + protected setPropertyFromUI(propertySetter: (TPropertyBag, TValue) => void, value: TValue) { propertySetter(this.getProperties(), value); this._onEventEmitter.fire({ eventType: ComponentEventType.PropertiesChanged, @@ -86,6 +86,12 @@ export abstract class ComponentBase extends Disposable implements IComponent, On public get onEvent(): Event { return this._onEventEmitter.event; } + + public get title(): string { + let properties = this.getProperties(); + let title = properties['title']; + return title ? title : ''; + } } export abstract class ContainerBase extends ComponentBase { diff --git a/src/sql/parts/modelComponents/components.contribution.ts b/src/sql/parts/modelComponents/components.contribution.ts index 25485ff4fb..45047b89d8 100644 --- a/src/sql/parts/modelComponents/components.contribution.ts +++ b/src/sql/parts/modelComponents/components.contribution.ts @@ -4,16 +4,28 @@ *--------------------------------------------------------------------------------------------*/ import FlexContainer from './flexContainer.component'; +import FormContainer from './formContainer.component'; import CardComponent from './card.component'; import InputBoxComponent from './inputbox.component'; +import DropDownComponent from './dropdown.component'; +import ButtonComponent from './button.component'; import { registerComponentType } from 'sql/platform/dashboard/common/modelComponentRegistry'; import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes'; export const FLEX_CONTAINER = 'flex-container'; registerComponentType(FLEX_CONTAINER, ModelComponentTypes.FlexContainer, FlexContainer); +export const FORM_CONTAINER = 'form-container'; +registerComponentType(FORM_CONTAINER, ModelComponentTypes.Form, FormContainer); + export const CARD_COMPONENT = 'card-component'; registerComponentType(CARD_COMPONENT, ModelComponentTypes.Card, CardComponent); export const INPUTBOX_COMPONENT = 'inputbox-component'; registerComponentType(INPUTBOX_COMPONENT, ModelComponentTypes.InputBox, InputBoxComponent); + +export const DROPDOWN_COMPONENT = 'dropdown-component'; +registerComponentType(DROPDOWN_COMPONENT, ModelComponentTypes.DropDown, DropDownComponent); + +export const BUTTON_COMPONENT = 'button-component'; +registerComponentType(BUTTON_COMPONENT, ModelComponentTypes.Button, ButtonComponent); diff --git a/src/sql/parts/modelComponents/dropdown.component.ts b/src/sql/parts/modelComponents/dropdown.component.ts new file mode 100644 index 0000000000..c4afb27ac4 --- /dev/null +++ b/src/sql/parts/modelComponents/dropdown.component.ts @@ -0,0 +1,117 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { + Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver, + ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit +} from '@angular/core'; + +import * as sqlops from 'sqlops'; +import Event, { Emitter } from 'vs/base/common/event'; + +import { ComponentBase } from 'sql/parts/modelComponents/componentBase'; +import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces'; +import { Dropdown, IDropdownOptions } from 'sql/base/browser/ui/editableDropdown/dropdown'; +import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service'; +import { attachListStyler } from 'vs/platform/theme/common/styler'; +import { attachEditableDropdownStyler } from 'sql/common/theme/styler'; + +@Component({ + selector: 'inputBox', + template: ` +
+ ` +}) +export default class DropDownComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit { + @Input() descriptor: IComponentDescriptor; + @Input() modelStore: IModelStore; + private _dropdown: Dropdown; + + @ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef; + constructor( + @Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface, + @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef) { + super(changeRef); + } + + ngOnInit(): void { + this.baseInit(); + + } + + ngAfterViewInit(): void { + if (this._inputContainer) { + let dropdownOptions: IDropdownOptions = { + values: [], + strictSelection: false, + placeholder: '', + maxHeight: 125, + ariaLabel: '' + }; + + this._dropdown = new Dropdown(this._inputContainer.nativeElement, this._commonService.contextViewService, this._commonService.themeService, + dropdownOptions); + + this._register(this._dropdown); + this._register(attachEditableDropdownStyler(this._dropdown, this._commonService.themeService)); + this._register(this._dropdown.onValueChange(e => { + this.value = this._dropdown.value; + this._onEventEmitter.fire({ + eventType: ComponentEventType.onDidChange, + args: e + }); + })); + } + } + + ngOnDestroy(): void { + this.baseDestroy(); + } + + /// IComponent implementation + + public layout(): void { + this._changeRef.detectChanges(); + } + + public setLayout(layout: any): void { + // TODO allow configuring the look and feel + this.layout(); + } + + public setProperties(properties: { [key: string]: any; }): void { + super.setProperties(properties); + this._dropdown.values = this.values ? this.values : []; + if (this.value) { + this._dropdown.value = this.value; + } + } + + // CSS-bound properties + + private get value(): string { + return this.getPropertyOrDefault((props) => props.value, ''); + } + + private set value(newValue: string) { + this.setPropertyFromUI(this.setValueProperties, newValue); + } + + private get values(): string[] { + return this.getPropertyOrDefault((props) => props.values, undefined); + } + + private set values(newValue: string[]) { + this.setPropertyFromUI(this.setValuesProperties, newValue); + } + + private setValueProperties(properties: sqlops.DropDownProperties, value: string): void { + properties.value = value; + } + + private setValuesProperties(properties: sqlops.DropDownProperties, values: string[]): void { + properties.values = values; + } +} diff --git a/src/sql/parts/modelComponents/formContainer.component.ts b/src/sql/parts/modelComponents/formContainer.component.ts new file mode 100644 index 0000000000..0190b9dab8 --- /dev/null +++ b/src/sql/parts/modelComponents/formContainer.component.ts @@ -0,0 +1,128 @@ +/*--------------------------------------------------------------------------------------------- + * 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!./formLayout'; + +import { + Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver, + ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit +} from '@angular/core'; + +import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces'; +import { FormLayout, FormItemLayout } from 'sqlops'; + +import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service'; +import { ContainerBase } from 'sql/parts/modelComponents/componentBase'; +import { ModelComponentWrapper } from 'sql/parts/modelComponents/modelComponentWrapper.component'; +import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service'; + +export interface TitledFormItemLayout { + title: string; + actions?: string[]; + isFormComponent: Boolean; +} +class FormItem { + constructor(public descriptor: IComponentDescriptor, public config: TitledFormItemLayout) { } +} + +@Component({ + template: ` +
+
+ +
{{getItemTitle(item)}}
+
+ + +
+
+
+ + +
+
+
+
+
+ ` +}) +export default class FormContainer extends ContainerBase implements IComponent, OnDestroy, AfterViewInit { + @Input() descriptor: IComponentDescriptor; + @Input() modelStore: IModelStore; + + private _alignItems: string; + private _alignContent: string; + + @ViewChildren(ModelComponentWrapper) private _componentWrappers: QueryList; + @ViewChild('container', { read: ElementRef }) private _container: ElementRef; + + constructor ( + @Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface, + @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef) { + super(changeRef); + } + + ngOnInit(): void { + this.baseInit(); + } + + ngOnDestroy(): void { + this.baseDestroy(); + } + + ngAfterViewInit(): void { + } + + /// IComponent implementation + + public layout(): void { + if (this._componentWrappers) { + this._componentWrappers.forEach(wrapper => { + wrapper.layout(); + }); + } + } + + public get alignItems(): string { + return this._alignItems; + } + + public get alignContent(): string { + return this._alignContent; + } + + private getItemTitle(item: FormItem): string { + let itemConfig = item.config; + return itemConfig ? itemConfig.title : ''; + } + + private getActionComponents(item: FormItem): FormItem[]{ + let items = this.items; + let itemConfig = item.config; + if (itemConfig && itemConfig.actions) { + let resultItems = itemConfig.actions.map(x => { + let actionComponent = items.find(i => i.descriptor.id === x); + return actionComponent; + }); + + return resultItems.filter(r => r && r.descriptor); + } + + return []; + } + + private isFormComponent(item: FormItem): Boolean { + return item && item.config && item.config.isFormComponent; + } + + private itemHasActions(item: FormItem): Boolean { + let itemConfig = item.config; + return itemConfig && itemConfig.actions !== undefined && itemConfig.actions.length > 0; + } + + public setLayout(layout: any): void { + this.layout(); + } +} diff --git a/src/sql/parts/modelComponents/formLayout.css b/src/sql/parts/modelComponents/formLayout.css new file mode 100644 index 0000000000..fa4291768a --- /dev/null +++ b/src/sql/parts/modelComponents/formLayout.css @@ -0,0 +1,20 @@ + +.form-table { + width:400px; + display:table; + padding: 30px; +} + +.form-row { + display: table-row; + width: 100px; +} + +.form-cell { + padding: 5px; + display: table-cell; +} + +.form-action { + width: 20px; +} \ No newline at end of file diff --git a/src/sql/parts/modelComponents/inputbox.component.ts b/src/sql/parts/modelComponents/inputbox.component.ts index af8ec53fe2..680e5a5689 100644 --- a/src/sql/parts/modelComponents/inputbox.component.ts +++ b/src/sql/parts/modelComponents/inputbox.component.ts @@ -3,7 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver, +import { + Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver, ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit } from '@angular/core'; @@ -70,7 +71,7 @@ export default class InputBoxComponent extends ComponentBase implements ICompone this._changeRef.detectChanges(); } - public setLayout (layout: any): void { + public setLayout(layout: any): void { // TODO allow configuring the look and feel this.layout(); } @@ -87,7 +88,7 @@ export default class InputBoxComponent extends ComponentBase implements ICompone } public set value(newValue: string) { - this.setProperty(this.setInputBoxProperties, newValue); + this.setPropertyFromUI(this.setInputBoxProperties, newValue); } private setInputBoxProperties(properties: sqlops.InputBoxProperties, value: string): void { diff --git a/src/sql/parts/modelComponents/interfaces.ts b/src/sql/parts/modelComponents/interfaces.ts index b455d6d63e..d457839444 100644 --- a/src/sql/parts/modelComponents/interfaces.ts +++ b/src/sql/parts/modelComponents/interfaces.ts @@ -21,6 +21,7 @@ export interface IComponent { addToContainer?: (componentDescriptor: IComponentDescriptor, config: any) => void; setLayout?: (layout: any) => void; setProperties?: (properties: { [key: string]: any; }) => void; + title?: string; onEvent?: Event; } @@ -53,11 +54,13 @@ export interface IComponentDescriptor { export interface IComponentEventArgs { eventType: ComponentEventType; args: any; + componentId?: string; } export enum ComponentEventType { PropertiesChanged, - onDidChange + onDidChange, + onDidClick } export interface IModelStore { diff --git a/src/sql/parts/modelComponents/viewBase.ts b/src/sql/parts/modelComponents/viewBase.ts index ab203ff81e..ce3a37f757 100644 --- a/src/sql/parts/modelComponents/viewBase.ts +++ b/src/sql/parts/modelComponents/viewBase.ts @@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import nls = require('vs/nls'); import * as sqlops from 'sqlops'; -import { IModelStore, IComponentDescriptor, IComponent } from './interfaces'; +import { IModelStore, IComponentDescriptor, IComponent, IComponentEventArgs } from './interfaces'; import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes'; import { IModelView } from 'sql/services/model/modelViewService'; import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry'; @@ -18,7 +18,7 @@ import { AngularDisposable } from 'sql/base/common/lifecycle'; import { ModelStore } from 'sql/parts/modelComponents/modelStore'; import Event, { Emitter } from 'vs/base/common/event'; -const componentRegistry = Registry.as(Extensions.ComponentContribution); +const componentRegistry = Registry.as(Extensions.ComponentContribution); /** * Provides common logic required for any implementation that hooks to a model provided by @@ -57,7 +57,7 @@ export abstract class ViewBase extends AngularDisposable implements IModelView { this.setLayout(component.id, component.layout); this.registerEvent(component.id); if (component.itemConfigs) { - for(let item of component.itemConfigs) { + for (let item of component.itemConfigs) { this.addToContainer(component.id, item); } } @@ -66,12 +66,12 @@ export abstract class ViewBase extends AngularDisposable implements IModelView { } clearContainer(componentId: string): void { - this.queueAction(componentId, (component) => component.clearContainer()); + this.queueAction(componentId, (component) => component.clearContainer()); } addToContainer(containerId: string, itemConfig: IItemConfig): void { // Do not return the promise as this should be non-blocking - this.queueAction(containerId, (component) => { + this.queueAction(containerId, (component) => { let childDescriptor = this.defineComponent(itemConfig.componentShape); component.addToContainer(childDescriptor, itemConfig.config); }); @@ -81,14 +81,14 @@ export abstract class ViewBase extends AngularDisposable implements IModelView { if (!layout) { return; } - this.queueAction(componentId, (component) => component.setLayout(layout)); + this.queueAction(componentId, (component) => component.setLayout(layout)); } setProperties(componentId: string, properties: { [key: string]: any; }): void { if (!properties) { return; } - this.queueAction(componentId, (component) => component.setProperties(properties)); + this.queueAction(componentId, (component) => component.setProperties(properties)); } private queueAction(componentId: string, action: (component: IComponent) => T): void { @@ -98,16 +98,17 @@ export abstract class ViewBase extends AngularDisposable implements IModelView { } registerEvent(componentId: string) { - this.queueAction(componentId, (component) => { + this.queueAction(componentId, (component) => { if (component.onEvent) { this._register(component.onEvent(e => { + e.componentId = componentId; this._onEventEmitter.fire(e); })); } }); } - public get onEvent(): Event { + public get onEvent(): Event { return this._onEventEmitter.event; } } \ No newline at end of file diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts index 13e93af70e..895ee4c6c4 100644 --- a/src/sql/sqlops.proposed.d.ts +++ b/src/sql/sqlops.proposed.d.ts @@ -20,23 +20,30 @@ declare module 'sqlops' { flexContainer(): FlexBuilder; card(): ComponentBuilder; inputBox(): ComponentBuilder; + button(): ComponentBuilder; + dropDown(): ComponentBuilder; dashboardWidget(widgetId: string): ComponentBuilder; dashboardWebview(webviewId: string): ComponentBuilder; + formContainer(): FormBuilder; } export interface ComponentBuilder { component(): T; withProperties(properties: U): ComponentBuilder; } - export interface ContainerBuilder extends ComponentBuilder { + export interface ContainerBuilder extends ComponentBuilder { withLayout(layout: TLayout): ContainerBuilder; - withItems(components: Array, itemLayout ?: TItemLayout): ContainerBuilder; + withItems(components: Array, itemLayout?: TItemLayout): ContainerBuilder; } export interface FlexBuilder extends ContainerBuilder { } + export interface FormBuilder extends ContainerBuilder { + withFormItems(components: FormComponent[], itemLayout?: FormItemLayout): ContainerBuilder; + } + export interface Component { readonly id: string; @@ -50,10 +57,16 @@ declare module 'sqlops' { updateProperties(properties: { [key: string]: any }): Thenable; } + export interface FormComponent { + component: Component; + title: string; + actions?: Component[]; + } + /** * A component that contains other components */ - export interface Container extends Component { + export interface Container extends Component { /** * A copy of the child items array. This cannot be added to directly - * components must be created using the create methods instead @@ -70,7 +83,7 @@ declare module 'sqlops' { * @param itemConfigs the definitions * @param {*} [itemLayout] Optional layout for the child items */ - addItems(itemConfigs: Array, itemLayout ?: TItemLayout): void; + addItems(itemConfigs: Array, itemLayout?: TItemLayout): void; /** * Creates a child component and adds it to this container. @@ -78,7 +91,7 @@ declare module 'sqlops' { * @param {Component} component the component to be added * @param {*} [itemLayout] Optional layout for this child item */ - addItem(component: Component, itemLayout ?: TItemLayout): void; + addItem(component: Component, itemLayout?: TItemLayout): void; /** * Defines the layout for this container @@ -130,9 +143,21 @@ declare module 'sqlops' { flex?: string; } + export interface FormItemLayout { + + } + + export interface FormLayout { + + } + export interface FlexContainer extends Container { } + export interface FormContainer extends Container { + } + + /** * Describes an action to be shown in the UI, with a user-readable label * and a callback to execute the action @@ -153,16 +178,25 @@ declare module 'sqlops' { * Properties representing the card component, can be used * when using ModelBuilder to create the component */ - export interface CardProperties { + export interface CardProperties { label: string; value?: string; actions?: ActionDescriptor[]; } - export interface InputBoxProperties { + export interface InputBoxProperties { value?: string; } + export interface DropDownProperties { + value?: string; + values?: string[]; + } + + export interface ButtonProperties { + label?: string; + } + export interface CardComponent extends Component { label: string; value: string; @@ -174,6 +208,17 @@ declare module 'sqlops' { onTextChanged: vscode.Event; } + export interface DropDownComponent extends Component { + value: string; + values: string[]; + onValueChanged: vscode.Event; + } + + export interface ButtonComponent extends Component { + label: string; + onDidClick: vscode.Event; + } + export interface WidgetComponent extends Component { widgetId: string; } diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts index 9fc4ab3bbe..24f98ad7f1 100644 --- a/src/sql/workbench/api/common/sqlExtHostTypes.ts +++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts @@ -68,14 +68,17 @@ export enum ModelComponentTypes { FlexContainer, Card, InputBox, + DropDown, + Button, DashboardWidget, - DashboardWebview + DashboardWebview, + Form } export interface IComponentShape { type: ModelComponentTypes; id: string; - properties?: { [key: string]: any }; + properties?: { [key: string]: any }; layout?: any; itemConfigs?: IItemConfig[]; } @@ -87,7 +90,8 @@ export interface IItemConfig { export enum ComponentEventType { PropertiesChanged, - onDidChange + onDidChange, + onDidClick } export interface IComponentEventArgs { diff --git a/src/sql/workbench/api/node/extHostModelView.ts b/src/sql/workbench/api/node/extHostModelView.ts index c0458bd97f..d5d517da19 100644 --- a/src/sql/workbench/api/node/extHostModelView.ts +++ b/src/sql/workbench/api/node/extHostModelView.ts @@ -33,6 +33,11 @@ class ModelBuilderImpl implements sqlops.ModelBuilder { return new ContainerBuilderImpl(this._proxy, this._handle, ModelComponentTypes.FlexContainer, id); } + formContainer(): sqlops.FormBuilder { + let id = this.getNextComponentId(); + return new FormContainerBuilder(this._proxy, this._handle, ModelComponentTypes.Form, id); + } + card(): sqlops.ComponentBuilder { let id = this.getNextComponentId(); return this.withEventHandler(new CardWrapper(this._proxy, this._handle, id), id); @@ -43,6 +48,16 @@ class ModelBuilderImpl implements sqlops.ModelBuilder { return this.withEventHandler(new InputBoxWrapper(this._proxy, this._handle, id), id); } + button(): sqlops.ComponentBuilder { + let id = this.getNextComponentId(); + return this.withEventHandler(new ButtonWrapper(this._proxy, this._handle, id), id); + } + + dropDown(): sqlops.ComponentBuilder { + let id = this.getNextComponentId(); + return this.withEventHandler(new DropDownWrapper(this._proxy, this._handle, id), id); + } + dashboardWidget(widgetId: string): sqlops.ComponentBuilder { let id = this.getNextComponentId(); return this.withEventHandler(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWidget, id), id); @@ -122,9 +137,40 @@ class ContainerBuilderImpl ext } } +class FormContainerBuilder extends ContainerBuilderImpl { + + withFormItems(components: sqlops.FormComponent[], itemLayout?: sqlops.FormItemLayout): sqlops.ContainerBuilder { + + this._component.itemConfigs = components.map(item => { + let componentWrapper = item.component as ComponentWrapper; + let actions: string[] = undefined; + if (item.actions) { + actions = item.actions.map(action => { + let actionComponentWrapper = action as ComponentWrapper; + return actionComponentWrapper.id; + }); + } + return new InternalItemConfig(componentWrapper, Object.assign({}, itemLayout, { + title: item.title, + actions: actions, + isFormComponent: true + })); + }); + + components.forEach(formItem => { + if (formItem.actions) { + formItem.actions.forEach(component => { + let componentWrapper = component as ComponentWrapper; + this._component.itemConfigs.push(new InternalItemConfig(componentWrapper, itemLayout)); + }); + } + }); + return this; + } +} class InternalItemConfig { - constructor(private _component: ComponentWrapper, public config: any) {} + constructor(private _component: ComponentWrapper, public config: any) { } public toIItemConfig(): IItemConfig { return { @@ -146,6 +192,7 @@ class ComponentWrapper implements sqlops.Component { private _onErrorEmitter = new Emitter(); public readonly onError: vscode.Event = this._onErrorEmitter.event; + protected _emitterMap = new Map>(); constructor(protected readonly _proxy: MainThreadModelViewShape, protected readonly _handle: number, @@ -169,7 +216,7 @@ class ComponentWrapper implements sqlops.Component { } public toComponentShape(): IComponentShape { - return { + return { id: this.id, type: this.type, layout: this.layout, @@ -183,13 +230,13 @@ class ComponentWrapper implements sqlops.Component { return this._proxy.$clearContainer(this._handle, this.id); } - public addItems(items: Array, itemLayout ?: any): void { - for(let item of items) { + public addItems(items: Array, itemLayout?: any): void { + for (let item of items) { this.addItem(item, itemLayout); } } - public addItem(item: sqlops.Component, itemLayout ?: any): void { + public addItem(item: sqlops.Component, itemLayout?: any): void { let itemImpl = item as ComponentWrapper; if (!itemImpl) { throw new Error(nls.localize('unknownComponentType', 'Unkown component type. Must use ModelBuilder to create objects')); @@ -218,7 +265,12 @@ class ComponentWrapper implements sqlops.Component { public onEvent(eventArgs: IComponentEventArgs) { if (eventArgs && eventArgs.eventType === ComponentEventType.PropertiesChanged) { this.properties = eventArgs.args; - } + } else if (eventArgs) { + let emitter = this._emitterMap.get(eventArgs.eventType); + if (emitter) { + emitter.fire(); + } + } } protected setProperty(key: string, value: any): Thenable { @@ -278,9 +330,6 @@ class InputBoxWrapper extends ComponentWrapper implements sqlops.InputBoxCompone this._emitterMap.set(ComponentEventType.onDidChange, new Emitter()); } - private _onTextChangedEmitter = new Emitter(); - private _emitterMap = new Map>(); - public get value(): string { return this.properties['value']; } @@ -292,15 +341,54 @@ class InputBoxWrapper extends ComponentWrapper implements sqlops.InputBoxCompone let emitter = this._emitterMap.get(ComponentEventType.onDidChange); return emitter && emitter.event; } +} - public onEvent(eventArgs: IComponentEventArgs) { - super.onEvent(eventArgs); - if (eventArgs) { - let emitter = this._emitterMap.get(eventArgs.eventType); - if (emitter) { - emitter.fire(); - } - } +class DropDownWrapper extends ComponentWrapper implements sqlops.DropDownComponent { + + constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { + super(proxy, handle, ModelComponentTypes.DropDown, id); + this.properties = {}; + this._emitterMap.set(ComponentEventType.onDidChange, new Emitter()); + } + + public get value(): string { + return this.properties['value']; + } + public set value(v: string) { + this.setProperty('value', v); + } + + public get values(): string[] { + return this.properties['values']; + } + public set values(v: string[]) { + this.setProperty('values', v); + } + + public get onValueChanged(): vscode.Event { + let emitter = this._emitterMap.get(ComponentEventType.onDidChange); + return emitter && emitter.event; + } +} + +class ButtonWrapper extends ComponentWrapper implements sqlops.ButtonComponent { + + constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { + super(proxy, handle, ModelComponentTypes.Button, id); + this.properties = {}; + this._emitterMap.set(ComponentEventType.onDidClick, new Emitter()); + } + + public get label(): string { + return this.properties['label']; + } + public set label(v: string) { + this.setProperty('label', v); + } + + public get onDidClick(): vscode.Event { + let emitter = this._emitterMap.get(ComponentEventType.onDidClick); + return emitter && emitter.event; } } diff --git a/src/sql/workbench/api/node/mainThreadModelView.ts b/src/sql/workbench/api/node/mainThreadModelView.ts index 4c30fe6f5e..0cb1726819 100644 --- a/src/sql/workbench/api/node/mainThreadModelView.ts +++ b/src/sql/workbench/api/node/mainThreadModelView.ts @@ -45,7 +45,7 @@ export class MainThreadModelView extends Disposable implements MainThreadModelVi } $initializeModel(handle: number, rootComponent: IComponentShape): Thenable { - return this.execModelViewAction(handle, (modelView) => { + return this.execModelViewAction(handle, (modelView) => { modelView.initializeModel(rootComponent); }); } @@ -67,11 +67,13 @@ export class MainThreadModelView extends Disposable implements MainThreadModelVi this._proxy.$handleEvent(handle, componentId, eventArgs); } - $registerEvent(handle: number, componentId: string): Thenable { + $registerEvent(handle: number, componentId: string): Thenable { let properties: { [key: string]: any; } = { eventName: this.onEvent }; return this.execModelViewAction(handle, (modelView) => { - this._register(modelView.onEvent (e => { - this.onEvent(handle, componentId, e); + this._register(modelView.onEvent(e => { + if (e.componentId && e.componentId === componentId) { + this.onEvent(handle, componentId, e); + } })); }); }