diff --git a/src/sql/base/browser/ui/table/table.ts b/src/sql/base/browser/ui/table/table.ts index 9cf173bd9e..ba7d399d89 100644 --- a/src/sql/base/browser/ui/table/table.ts +++ b/src/sql/base/browser/ui/table/table.ts @@ -137,7 +137,9 @@ export class Table extends Widget implements IThemabl this._grid.onSelectedRowsChanged.subscribe(fn); return { dispose() { - this._grid.onSelectedRowsChanged.unsubscribe(fn); + if (this._grid && this._grid.onSelectedRowsChanged) { + this._grid.onSelectedRowsChanged.unsubscribe(fn); + } } }; } diff --git a/src/sql/parts/modelComponents/components.contribution.ts b/src/sql/parts/modelComponents/components.contribution.ts index 79da46f03d..1de60e57e3 100644 --- a/src/sql/parts/modelComponents/components.contribution.ts +++ b/src/sql/parts/modelComponents/components.contribution.ts @@ -14,6 +14,7 @@ import ButtonComponent from './button.component'; import CheckBoxComponent from './checkbox.component'; import RadioButtonComponent from './radioButton.component'; import WebViewComponent from './webview.component'; +import TableComponent from './table.component'; import TextComponent from './text.component'; import { registerComponentType } from 'sql/platform/dashboard/common/modelComponentRegistry'; import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes'; @@ -54,3 +55,6 @@ registerComponentType(WEBVIEW_COMPONENT, ModelComponentTypes.WebView, WebViewCom export const TEXT_COMPONENT = 'text-component'; registerComponentType(TEXT_COMPONENT, ModelComponentTypes.Text, TextComponent); + +export const TABLE_COMPONENT = 'table-component'; +registerComponentType(TABLE_COMPONENT, ModelComponentTypes.Table, TableComponent); diff --git a/src/sql/parts/modelComponents/interfaces.ts b/src/sql/parts/modelComponents/interfaces.ts index c6e281aeee..5dd904f709 100644 --- a/src/sql/parts/modelComponents/interfaces.ts +++ b/src/sql/parts/modelComponents/interfaces.ts @@ -65,7 +65,8 @@ export enum ComponentEventType { onDidChange, onDidClick, validityChanged, - onMessage + onMessage, + onSelectedRowChanged } export interface IModelStore { diff --git a/src/sql/parts/modelComponents/table.component.ts b/src/sql/parts/modelComponents/table.component.ts new file mode 100644 index 0000000000..58d0dc1575 --- /dev/null +++ b/src/sql/parts/modelComponents/table.component.ts @@ -0,0 +1,206 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ComponentBase } from 'sql/parts/modelComponents/componentBase'; +import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces'; + +import { Table } from 'sql/base/browser/ui/table/table'; +import { TableDataView } from 'sql/base/browser/ui/table/tableDataView'; +import { attachTableStyler } from 'sql/common/theme/styler'; +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 { Dimension } from 'vs/base/browser/builder'; +import * as nls from 'vs/nls'; +import { getContentHeight, getContentWidth } from 'vs/base/browser/dom'; +import { RowSelectionModel } from 'sql/base/browser/ui/table/plugins/rowSelectionModel.plugin'; + +@Component({ + selector: 'modelview-table', + template: ` +
+ ` +}) +export default class TableComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit { + @Input() descriptor: IComponentDescriptor; + @Input() modelStore: IModelStore; + private _table: Table; + private _tableData: TableDataView; + private _tableColumns; + + @ViewChild('table', { read: ElementRef }) private _inputContainer: ElementRef; + constructor( + @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef, + @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService, + @Inject(IContextViewService) private contextViewService: IContextViewService + ) { + super(changeRef); + } + + ngOnInit(): void { + this.baseInit(); + + } + + transformColumns(columns: string[] | sqlops.TableColumn[]): Slick.Column[] { + let tableColumns: any[] = columns; + if (tableColumns) { + return (columns).map(col => { + if (col.value) { + return >{ + name: col.value, + id: col.value, + field: col.value + }; + } else { + return >{ + name: col, + id: col, + field: col + }; + } + }); + } else { + return (columns).map(col => { + return >{ + name: col, + id: col, + field: col + }; + }); + } + } + + transformData(rows: string[][], columns: any[]): { [key: string]: string }[] { + return rows.map(row => { + let object: { [key: string]: string } = {}; + row.forEach((val, index) => { + let columnName: string = (columns[index].value) ? columns[index].value : columns[index]; + object[columnName] = val; + }); + return object; + }); + } + + ngAfterViewInit(): void { + if (this._inputContainer) { + this._tableData = new TableDataView(); + + let options = >{ + syncColumnCellResize: true, + enableColumnReorder: false, + rowHeight: 45, + enableCellNavigation: true, + forceFitColumns: true + }; + + this._table = new Table(this._inputContainer.nativeElement, this._tableData, this._tableColumns, options); + this._table.setData(this._tableData); + this._table.setSelectionModel(new RowSelectionModel({ selectActiveRow: true })); + + this._register(this._table); + this._register(attachTableStyler(this._table, this.themeService)); + this._register(this._table.onSelectedRowsChanged((e, data) => { + this.selectedRows = data.rows; + this._onEventEmitter.fire({ + eventType: ComponentEventType.onSelectedRowChanged, + args: e + }); + })); + } + } + + public validate(): Thenable { + return super.validate().then(valid => { + // TODO: table validation? + return valid; + }); + } + + ngOnDestroy(): void { + this.baseDestroy(); + } + + /// IComponent implementation + + public layout(): void { + this._table.layout(new Dimension( + this.width ? this.width : getContentWidth(this._inputContainer.nativeElement), + this.height ? this.height : getContentHeight(this._inputContainer.nativeElement))); + + 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._tableData.clear(); + this._tableData.push(this.transformData(this.data, this.columns)); + this._tableColumns = this.transformColumns(this.columns); + this._table.columns = this._tableColumns; + this._table.setData(this._tableData); + if (this.selectedRows) { + this._table.setSelectedRows(this.selectedRows); + } + this._table.layout(new Dimension( + this.width ? this.width : getContentWidth(this._inputContainer.nativeElement), + this.height ? this.height : getContentHeight(this._inputContainer.nativeElement))); + + this.validate(); + } + + // CSS-bound 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(): string[] { + return this.getPropertyOrDefault((props) => props.columns, []); + } + + public set columns(newValue: string[]) { + this.setPropertyFromUI((props, value) => props.columns = value, newValue); + } + + public get selectedRows(): number[] { + return this.getPropertyOrDefault((props) => props.selectedRows, []); + } + + public set selectedRows(newValue: number[]) { + this.setPropertyFromUI((props, value) => props.selectedRows = value, newValue); + } + + public get height(): number { + return this.getPropertyOrDefault((props) => props.height, undefined); + } + + public set height(newValue: number) { + this.setPropertyFromUI((props, value) => props.height = value, newValue); + } + + public get width(): number { + return this.getPropertyOrDefault((props) => props.width, undefined); + } + + public set width(newValue: number) { + this.setPropertyFromUI((props, value) => props.width = value, newValue); + } +} diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts index 196cc163e2..41a7cf5687 100644 --- a/src/sql/sqlops.proposed.d.ts +++ b/src/sql/sqlops.proposed.d.ts @@ -26,6 +26,7 @@ declare module 'sqlops' { text(): ComponentBuilder; button(): ComponentBuilder; dropDown(): ComponentBuilder; + table(): ComponentBuilder; dashboardWidget(widgetId: string): ComponentBuilder; dashboardWebview(webviewId: string): ComponentBuilder; formContainer(): FormBuilder; @@ -292,6 +293,16 @@ declare module 'sqlops' { required?: boolean; } + export interface TableColumn { + value: string + } + + export interface TableComponentProperties { + data: any[][]; + columns: string[] | TableColumn[]; + selectedRows?: number[]; + } + export interface CheckBoxProperties { checked?: boolean; label?: string; @@ -354,6 +365,10 @@ declare module 'sqlops' { onValueChanged: vscode.Event; } + export interface TableComponent extends Component, TableComponentProperties { + onRowSelected: vscode.Event; + } + export interface WebViewComponent extends Component { html: string; message: any; diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts index ba7239d209..adb3248e67 100644 --- a/src/sql/workbench/api/common/sqlExtHostTypes.ts +++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts @@ -74,6 +74,7 @@ export enum ModelComponentTypes { RadioButton, WebView, Text, + Table, DashboardWidget, DashboardWebview, Form, @@ -99,7 +100,8 @@ export enum ComponentEventType { onDidChange, onDidClick, validityChanged, - onMessage + onMessage, + onSelectedRowChanged } export interface IComponentEventArgs { diff --git a/src/sql/workbench/api/node/extHostModelView.ts b/src/sql/workbench/api/node/extHostModelView.ts index 3d3a804a07..168836ee16 100644 --- a/src/sql/workbench/api/node/extHostModelView.ts +++ b/src/sql/workbench/api/node/extHostModelView.ts @@ -115,6 +115,13 @@ class ModelBuilderImpl implements sqlops.ModelBuilder { return builder; } + table(): sqlops.ComponentBuilder { + let id = this.getNextComponentId(); + let builder: ComponentBuilderImpl = this.getComponentBuilder(new TableComponentWrapper(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); @@ -655,6 +662,41 @@ class TextComponentWrapper extends ComponentWrapper implements sqlops.TextCompon } } +class TableComponentWrapper extends ComponentWrapper implements sqlops.TableComponent { + + constructor(proxy: MainThreadModelViewShape, handle: number, id: string) { + super(proxy, handle, ModelComponentTypes.Table, id); + this.properties = {}; + this._emitterMap.set(ComponentEventType.onSelectedRowChanged, new Emitter()); + } + + public get data(): any[][] { + return this.properties['data']; + } + public set data(v: any[][]) { + this.setProperty('data', v); + } + + public get columns(): string[] | sqlops.TableColumn[] { + return this.properties['columns']; + } + public set columns(v: string[] | sqlops.TableColumn[]) { + this.setProperty('columns', v); + } + + public get selectedRows(): number[] { + return this.properties['selectedRows']; + } + public set selectedRows(v: number[]) { + this.setProperty('selectedRows', v); + } + + public get onRowSelected(): vscode.Event { + let emitter = this._emitterMap.get(ComponentEventType.onSelectedRowChanged); + return emitter && emitter.event; + } +} + class DropDownWrapper extends ComponentWrapper implements sqlops.DropDownComponent { constructor(proxy: MainThreadModelViewShape, handle: number, id: string) {