diff --git a/samples/sqlservices/src/controllers/mainController.ts b/samples/sqlservices/src/controllers/mainController.ts index 7f02a3ca63..f836ffa032 100644 --- a/samples/sqlservices/src/controllers/mainController.ts +++ b/samples/sqlservices/src/controllers/mainController.ts @@ -188,6 +188,45 @@ export default class MainController implements vscode.Disposable { selectedRow: 2 }).component(); + let declarativeTable = view.modelBuilder.declarativeTable() + .withProperties({ + columns: [{ + displayName: 'Column 1', + valueType: sqlops.DeclarativeDataType.string, + width: '20px', + isReadOnly: true + }, { + displayName: 'Column 2', + valueType: sqlops.DeclarativeDataType.string, + width: '100px', + isReadOnly: false + }, { + displayName: 'Column 3', + valueType: sqlops.DeclarativeDataType.boolean, + width: '20px', + isReadOnly: false + }, { + displayName: 'Column 4', + valueType: sqlops.DeclarativeDataType.category, + isReadOnly: false, + width: '120px', + categoryValues: [ + { name: 'options1', displayName: 'option 1' }, + { name: 'options2', displayName: 'option 2' } + ] + } + ], + data: [ + ['Data00', 'Data01', false, 'options2'], + ['Data10', 'Data11', true, 'options1'] + ] + }).component(); + + declarativeTable.onDataChanged(e => { + inputBox2.value = e.row.toString() + ' ' + e.column.toString() + ' ' + e.value.toString(); + inputBox3.value = declarativeTable.data[e.row][e.column]; + }); + let flexRadioButtonsModel = view.modelBuilder.flexContainer() .withLayout({ flexFlow: 'column', @@ -216,6 +255,9 @@ export default class MainController implements vscode.Disposable { }, { component: flexRadioButtonsModel, title: 'Options' + }, { + component: declarativeTable, + title: 'Declarative Table' }, { component: table, title: 'Table' diff --git a/src/sql/base/browser/ui/inputBox/inputBox.component.ts b/src/sql/base/browser/ui/inputBox/inputBox.component.ts index 7109acd117..4684eb1fc0 100644 --- a/src/sql/base/browser/ui/inputBox/inputBox.component.ts +++ b/src/sql/base/browser/ui/inputBox/inputBox.component.ts @@ -30,6 +30,7 @@ export class InputBox extends AngularDisposable implements OnInit, OnChanges { @Input() type: string; @Input() placeholder: string; @Input() ariaLabel: string; + @Input() value: string; @Output() onDidChange = new EventEmitter(); @@ -49,6 +50,9 @@ export class InputBox extends AngularDisposable implements OnInit, OnChanges { placeholder: this.placeholder, ariaLabel: this.ariaLabel }); + if (this.value) { + this._inputbox.value = this.value; + } this._inputbox.onDidChange(e => { switch (this.type) { case 'number': diff --git a/src/sql/parts/dashboard/dashboard.module.ts b/src/sql/parts/dashboard/dashboard.module.ts index 85da2a8eec..2e0a7ead12 100644 --- a/src/sql/parts/dashboard/dashboard.module.ts +++ b/src/sql/parts/dashboard/dashboard.module.ts @@ -52,11 +52,16 @@ import { DashboardControlHostContainer } from 'sql/parts/dashboard/containers/da import { JobsViewComponent } from 'sql/parts/jobManagement/views/jobsView.component'; import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component'; import { JobHistoryComponent } from 'sql/parts/jobManagement/views/jobHistory.component'; +import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component'; +import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component'; +import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component'; let baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer, DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, ModelViewContent, WebviewContent, WidgetContent, ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer, - JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent, DashboardModelViewContainer, ModelComponentWrapper]; + JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent, DashboardModelViewContainer, ModelComponentWrapper, Checkbox, + SelectBox, + InputBox,]; /* Panel */ import { PanelModule } from 'sql/base/browser/ui/panel/panel.module'; diff --git a/src/sql/parts/modelComponents/componentBase.ts b/src/sql/parts/modelComponents/componentBase.ts index c25bc33f8a..3c6ecad832 100644 --- a/src/sql/parts/modelComponents/componentBase.ts +++ b/src/sql/parts/modelComponents/componentBase.ts @@ -112,6 +112,14 @@ export abstract class ComponentBase extends Disposable implements IComponent, On this.setProperties(properties); } + protected convertSize(size: number | string): string { + let convertedSize: string = size ? size.toString() : '100%'; + if (!convertedSize.toLowerCase().endsWith('px') && !convertedSize.toLowerCase().endsWith('%')) { + convertedSize = convertedSize + 'px'; + } + return convertedSize; + } + public get valid(): boolean { return this._valid; } diff --git a/src/sql/parts/modelComponents/components.contribution.ts b/src/sql/parts/modelComponents/components.contribution.ts index 508567f6a2..7bd5b191fa 100644 --- a/src/sql/parts/modelComponents/components.contribution.ts +++ b/src/sql/parts/modelComponents/components.contribution.ts @@ -10,6 +10,7 @@ import GroupContainer from './groupContainer.component'; import CardComponent from './card.component'; import InputBoxComponent from './inputbox.component'; import DropDownComponent from './dropdown.component'; +import DeclarativeTableComponent from './declarativeTable.component'; import ListBoxComponent from './listbox.component'; import ButtonComponent from './button.component'; import CheckBoxComponent from './checkbox.component'; @@ -42,6 +43,9 @@ registerComponentType(INPUTBOX_COMPONENT, ModelComponentTypes.InputBox, InputBox export const DROPDOWN_COMPONENT = 'dropdown-component'; registerComponentType(DROPDOWN_COMPONENT, ModelComponentTypes.DropDown, DropDownComponent); +export const DECLARATIVETABLE_COMPONENT = 'declarativeTable-component'; +registerComponentType(DECLARATIVETABLE_COMPONENT, ModelComponentTypes.DeclarativeTable, DeclarativeTableComponent); + export const LISTBOX_COMPONENT = 'lisbox-component'; registerComponentType(LISTBOX_COMPONENT, ModelComponentTypes.ListBox, ListBoxComponent); diff --git a/src/sql/parts/modelComponents/declarativeTable.component.ts b/src/sql/parts/modelComponents/declarativeTable.component.ts new file mode 100644 index 0000000000..d0dcaee12e --- /dev/null +++ b/src/sql/parts/modelComponents/declarativeTable.component.ts @@ -0,0 +1,194 @@ +/*--------------------------------------------------------------------------------------------- + * 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!./declarativeTable'; +import { + Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver, + ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit +} from '@angular/core'; + +import * as sqlops from 'sqlops'; + +import { ComponentBase } from 'sql/parts/modelComponents/componentBase'; +import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { Event, Emitter } from 'vs/base/common/event'; +import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component'; +import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component'; +import { ISelectData } from 'vs/base/browser/ui/selectBox/selectBox'; +import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component'; +import * as nls from 'vs/nls'; + +export enum DeclarativeDataType { + string = 'string', + category = 'category', + boolean = 'boolean' +} + +@Component({ + selector: 'modelview-declarativeTable', + template: ` + + + + + + + + + + + + + +
{{column.displayName}}
+ + + + {{cellData}} +
+ ` +}) +export default class DeclarativeTableComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit { + @Input() descriptor: IComponentDescriptor; + @Input() modelStore: IModelStore; + + @ViewChild('container', { read: ElementRef }) private _tableContainer: ElementRef; + constructor( + @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef, + @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService, + @Inject(IContextViewService) private contextViewService: IContextViewService + ) { + super(changeRef); + } + + ngOnInit(): void { + this.baseInit(); + } + + ngAfterViewInit(): void { + } + + public validate(): Thenable { + return super.validate().then(valid => { + return valid; + }); + } + + ngOnDestroy(): void { + this.baseDestroy(); + } + + private isCheckBox(cell: number): boolean { + let column: sqlops.DeclarativeTableColumn = this.columns[cell]; + return column.valueType === DeclarativeDataType.boolean; + } + + private isControlEnabled(cell: number): boolean { + let column: sqlops.DeclarativeTableColumn = this.columns[cell]; + return !column.isReadOnly; + } + + private isLabel(cell: number): boolean { + let column: sqlops.DeclarativeTableColumn = this.columns[cell]; + return column.isReadOnly && column.valueType === DeclarativeDataType.string; + } + + private isChecked(row: number, cell: number): boolean { + let cellData = this.data[row][cell]; + return cellData; + } + + private onInputBoxChanged(e: string, row: number, cell: number): void { + this.onCellDataChanged(e, row, cell); + } + + private onCheckBoxChanged(e: boolean, row: number, cell: number): void { + this.onCellDataChanged(e, row, cell); + } + + private onSelectBoxChanged(e: ISelectData, row: number, cell: number): void { + let column: sqlops.DeclarativeTableColumn = this.columns[cell]; + if (column.categoryValues) { + this.onCellDataChanged(column.categoryValues[e.index].name, row, cell); + } + } + + private onCellDataChanged(newValue: any, row: number, cell: number): void { + this.data[row][cell] = newValue; + this.data = this.data; + let newCellData : sqlops.TableCell = { + row: row, + column: cell, + value: newValue + }; + this._onEventEmitter.fire({ + eventType: ComponentEventType.onDidChange, + args: newCellData + }); + } + + private isSelectBox(cell: number): boolean { + let column: sqlops.DeclarativeTableColumn = this.columns[cell]; + return column.valueType === DeclarativeDataType.category; + } + + private isInputBox(cell: number): boolean { + let column: sqlops.DeclarativeTableColumn = this.columns[cell]; + return column.valueType === DeclarativeDataType.string && !column.isReadOnly; + } + + private getColumnWidth(cell: number): string { + let column: sqlops.DeclarativeTableColumn = this.columns[cell]; + return this.convertSize(column.width); + } + + private GetOptions(cell: number): string[] { + let column: sqlops.DeclarativeTableColumn = this.columns[cell]; + return column.categoryValues ? column.categoryValues.map(x => x.displayName) : []; + } + + private GetSelectedOptionDisplayName(row: number, cell: number): string { + let column: sqlops.DeclarativeTableColumn = this.columns[cell]; + let cellData = this.data[row][cell]; + if (cellData && column.categoryValues) { + let category = column.categoryValues.find(v => v.name === cellData); + return category.displayName; + } else { + return ''; + } + } + + /// 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); + } + + public get data(): any[][] { + return this.getPropertyOrDefault((props) => props.data, []); + } + + public set data(newValue: any[][]) { + this.setPropertyFromUI((props, value) => props.data = value, newValue); + } + + public get columns(): sqlops.DeclarativeTableColumn[] { + return this.getPropertyOrDefault((props) => props.columns, []); + } + + public set columns(newValue: sqlops.DeclarativeTableColumn[]) { + this.setPropertyFromUI((props, value) => props.columns = value, newValue); + } +} diff --git a/src/sql/parts/modelComponents/declarativeTable.css b/src/sql/parts/modelComponents/declarativeTable.css new file mode 100644 index 0000000000..4c9ea91abd --- /dev/null +++ b/src/sql/parts/modelComponents/declarativeTable.css @@ -0,0 +1,41 @@ + +.declarative-table { + padding: 5px 30px 0px 30px; + box-sizing: border-box; + width:100%; + border-collapse: collapse; +} + +.declarative-table-row { +} + +.declarative-table-header { + padding: 5px; + border: 1px solid gray; + background-color: #F5F5F5; +} + +.vs-dark .declarative-table-header { + padding: 5px; + border: 1px solid gray; + background-color: #333334; +} + +.hc-black .declarative-table-header { + padding: 5px; + border: 1px solid gray; + background-color: #333334; +} + +.declarative-table-cell { + padding: 5px; + border: 1px solid gray; +} + +[role="gridcell"]:focus, +[role="gridcell"] *:focus, +[role="grid"] [tabindex="0"]:focus { + outline: #005a9c; + outline-style: dotted; + outline-width: 3px; +} diff --git a/src/sql/platform/dialog/dialog.module.ts b/src/sql/platform/dialog/dialog.module.ts index 88701332f3..ae40ea1256 100644 --- a/src/sql/platform/dialog/dialog.module.ts +++ b/src/sql/platform/dialog/dialog.module.ts @@ -19,6 +19,9 @@ import { ComponentHostDirective } from 'sql/parts/dashboard/common/componentHost import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService'; import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service'; import { Registry } from 'vs/platform/registry/common/platform'; +import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component'; +import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component'; +import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component'; /* Model-backed components */ let extensionComponents = Registry.as(Extensions.ComponentContribution).getAllCtors(); @@ -26,6 +29,9 @@ let extensionComponents = Registry.as(Extensions.ComponentCo export const DialogModule = (params, selector: string): any => { @NgModule({ declarations: [ + Checkbox, + SelectBox, + InputBox, DialogContainer, ModelViewContent, ModelComponentWrapper, diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts index edb345ddd0..f5f6ba998b 100644 --- a/src/sql/sqlops.proposed.d.ts +++ b/src/sql/sqlops.proposed.d.ts @@ -28,6 +28,7 @@ declare module 'sqlops' { dropDown(): ComponentBuilder; listBox(): ComponentBuilder; table(): ComponentBuilder; + declarativeTable(): ComponentBuilder; dashboardWidget(widgetId: string): ComponentBuilder; dashboardWebview(webviewId: string): ComponentBuilder; formContainer(): FormBuilder; @@ -318,6 +319,12 @@ declare module 'sqlops' { label?: string; } + export enum DeclarativeDataType { + string = 'string', + category = 'category', + boolean = 'boolean' + } + export interface RadioButtonProperties { name?: string; label?: string; @@ -335,9 +342,23 @@ declare module 'sqlops' { editable?: boolean; } + export interface DeclarativeTableColumn { + displayName: string; + categoryValues: CategoryValue[]; + valueType: DeclarativeDataType; + isReadOnly: boolean; + width: number|string; + } + + export interface DeclarativeTableProperties { + data: any[][]; + columns: DeclarativeTableColumn[]; + } + export interface ListBoxProperties { selectedRow?: number; values?: string[]; + } export interface WebViewProperties { @@ -385,6 +406,16 @@ declare module 'sqlops' { onValueChanged: vscode.Event; } + export interface TableCell { + row: number; + column: number; + value: any; + } + + export interface DeclarativeTableComponent extends Component, DeclarativeTableProperties { + onDataChanged: vscode.Event; + } + export interface ListBoxComponent extends Component, ListBoxProperties { selectedRow?: number; values: string[]; diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts index c6cf6649c7..f677ac10aa 100644 --- a/src/sql/workbench/api/common/sqlExtHostTypes.ts +++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts @@ -69,6 +69,7 @@ export enum ModelComponentTypes { Card, InputBox, DropDown, + DeclarativeTable, ListBox, Button, CheckBox, @@ -186,3 +187,9 @@ export enum DataProviderType { AgentServicesProvider = 'AgentServicesProvider', CapabilitiesProvider = 'CapabilitiesProvider' } + +export enum DeclarativeDataType { + string = 'string', + category = 'category', + boolean = 'boolean' +} \ No newline at end of file diff --git a/src/sql/workbench/api/node/extHostModelView.ts b/src/sql/workbench/api/node/extHostModelView.ts index ed943481a5..319b248a8d 100644 --- a/src/sql/workbench/api/node/extHostModelView.ts +++ b/src/sql/workbench/api/node/extHostModelView.ts @@ -130,6 +130,13 @@ class ModelBuilderImpl implements sqlops.ModelBuilder { return builder; } + declarativeTable(): sqlops.ComponentBuilder { + let id = this.getNextComponentId(); + let builder: ComponentBuilderImpl = this.getComponentBuilder(new DeclarativeTableWrapper(this._proxy, this._handle, id), id); + this._componentBuilders.set(id, builder); + return builder; + } + dashboardWidget(widgetId: string): sqlops.ComponentBuilder { let id = this.getNextComponentId(); let builder = this.getComponentBuilder(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWidget, id), id); @@ -755,6 +762,35 @@ class DropDownWrapper extends ComponentWrapper implements sqlops.DropDownCompone } } +class DeclarativeTableWrapper extends ComponentWrapper implements sqlops.DeclarativeTableComponent { + + constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { + super(proxy, handle, ModelComponentTypes.DeclarativeTable, id); + this.properties = {}; + this._emitterMap.set(ComponentEventType.onDidChange, new Emitter()); + } + + public get data(): any[][] { + return this.properties['data']; + } + public set data(v: any[][]) { + this.setProperty('data', v); + } + + public get columns(): sqlops.DeclarativeTableColumn[] { + return this.properties['columns']; + } + + public set columns(v: sqlops.DeclarativeTableColumn[]) { + this.setProperty('columns', v); + } + + public get onDataChanged(): vscode.Event { + let emitter = this._emitterMap.get(ComponentEventType.onDidChange); + return emitter && emitter.event; + } +} + class ListBoxWrapper extends ComponentWrapper implements sqlops.ListBoxComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { diff --git a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts index 8cb0329797..c423c13ea6 100644 --- a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts @@ -369,6 +369,7 @@ export function createApiFactory( serialization, dataprotocol, DataProviderType: sqlExtHostTypes.DataProviderType, + DeclarativeDataType: sqlExtHostTypes.DeclarativeDataType, ServiceOptionType: sqlExtHostTypes.ServiceOptionType, ConnectionOptionSpecialType: sqlExtHostTypes.ConnectionOptionSpecialType, EditRowState: sqlExtHostTypes.EditRowState,