EditDataTest Merge (removal of angular in edit data) (#8951)

* another commmit

* Now shows blank grid, nullcheck in queryhistory

* renamed onAngularLoaded to onComponentLoaded

* removed whitespace

* removed unused dataservice import

* now displays data, need to fix contextmenu actions

* minor changes

* another small commit

* added timeout for context menu

* updated queryhistoryserviceimpl

* removed log

* added commented out contextmenuregistrations

* context menu now shows up need to test

* added plugin registration WIP

* another commit

* yet another commit

* added wip function

* Clean up commit

* more cleaning up

* removed accessor

* renamed instances of parts

* updated

* fixed merge conflicts

* refactored bootstrapparams

* fixed code

* small changes to format

* set editable to true for testing

* added more options

* moved options to separate variable

* added texteditorclass for later

* added rudimentary create editor support

* changed grid.resize.emit to fire

* added formatterfactory

* added tslint disable

* removed debug message

* added more functions from Slickgrid.ts

* added wip handlechanges function

* another change

* added columndefinitions

* Managed to display table using handlechange

* added ability to edit for now

* added changes to table creation

* added setupevents

* added onInit

* fixed sql.xlf

* minor changes

* tidying up

* more cleaning up

* changed console.log messages to debug ones.

* added this.enableEditing

* made changes to getoverridabletexteditor

* fixed opencontextmenu

* added timeout for detectChange

* need to find way to run oncontext asynchronously

* check stuff

* oncontextmenu now no longer constantly refreshes

* added oldDataRows for future use

* add check for datarows

* small changes made

* set enableediting to true

* more changes

* added additional information for handlechanges

* another change

* more changes

* set enableediting to true

* fixed rerender

* added small test mssage for jquery

* text editor is in getOverridableTextEditorClass()

* removed debug messages

* added transparency for input.editor for table.

* need to find out how to add editing for input

* Added grid div to make slickgrid style work

* reinstated selected.

* disabled selectedcellcssclass

* restored selected

* removed selectionmodel due to not being found in the original code

* Added externalSelectionModel for correct results

* removed selectionmodel as its not used.

* WIP work on refreshresultsets

* temporarily bringing back selection model for now

* added getSelectedRanges from slickgrid into Table

* added getselectedranges from slickgrid into table

* small cleanup changes

* removed detectchanges

* removed last of detectchanges

* return of toprownumber

* no need for toprownumber

* removed isColumnLoading

* some small formatting

* fixed null check

* added back todo comment

* Added fix for context menu

* small change

* added missing value to getFormatter in grid panel

* added fix for last row italics

* added fix for null inconsistencies

* Some consolidation

* added new check for null cells

* minor change

* add check for selections (usually undefined)

* removed null check in formatters

* Some changes made

* changed plugins array

* removed todo

* renamed some variables

* deleted html file

* Moved height and width to editData.css

* added box-sizing for slickgridcontainer

* fixed editdatagridpanel css

* added small changes

* More minor changes

* removed params

* renamed refreshResultsets to refreshDatasets

* removed the  stylesheet.remove lines

* added fix for null

* removed tables

* removed spaces in refreshGrid

* More minor changes

* optimization and formatting

* removal of unnecessary lines

* replaced firstRender in some parts with firstLoad

* Added timeout fix

* minor changes

* Still testing

* cleanup

* restored 200 timeout

* added styling changes for editdata

* removed angular2-slickgrid and added styling

* Small formatting changes to editDataGridPanel

* consolidation
This commit is contained in:
Alex Ma
2020-01-28 14:24:39 -08:00
committed by GitHub
parent 461dd79bc2
commit 32a89676f1
18 changed files with 501 additions and 262 deletions

View File

@@ -43,7 +43,6 @@
"@angular/platform-browser-dynamic": "~4.1.3", "@angular/platform-browser-dynamic": "~4.1.3",
"@angular/router": "~4.1.3", "@angular/router": "~4.1.3",
"angular2-grid": "2.0.6", "angular2-grid": "2.0.6",
"angular2-slickgrid": "github:Microsoft/angular2-slickgrid#1.4.6",
"ansi_up": "^3.0.0", "ansi_up": "^3.0.0",
"applicationinsights": "1.0.8", "applicationinsights": "1.0.8",
"chart.js": "^2.6.0", "chart.js": "^2.6.0",

View File

@@ -15,6 +15,10 @@ export interface IObservableCollection<T> {
dispose(): void; dispose(): void;
} }
export interface ISlickColumn<T> extends Slick.Column<T> {
isEditable?: boolean;
}
class DataWindow<T> { class DataWindow<T> {
private _data: T[] | undefined; private _data: T[] | undefined;
private _length: number = 0; private _length: number = 0;
@@ -179,7 +183,7 @@ export class VirtualizedCollection<T extends Slick.SlickData> implements IObserv
return this.placeHolderGenerator(index); return this.placeHolderGenerator(index);
} }
private resetWindowsAroundIndex(index: number): void { public resetWindowsAroundIndex(index: number): void {
let bufferWindowBeforeStart = Math.max(0, index - this.windowSize * 1.5); let bufferWindowBeforeStart = Math.max(0, index - this.windowSize * 1.5);
let bufferWindowBeforeEnd = Math.max(0, index - this.windowSize / 2); let bufferWindowBeforeEnd = Math.max(0, index - this.windowSize / 2);

View File

@@ -69,8 +69,8 @@
--color-grid-dirty-text: #101010; --color-grid-dirty-text: #101010;
} }
/* grid styling */ /* grid styling */
.vs .slickgridContainer .grid .slick-cell .active,
.vs slick-grid.active .grid .slick-cell.active { .vs slick-grid.active .grid .slick-cell .active {
border-color: var(--color-cell-border-active); border-color: var(--color-cell-border-active);
} }
@@ -204,7 +204,7 @@
} }
/* grid styling */ /* grid styling */
.vs-dark .slickgridContainer .grid .slick-cell.active,
.vs-dark slick-grid.active .grid .slick-cell.active { .vs-dark slick-grid.active .grid .slick-cell.active {
border-color: var(--color-cell-border-active); border-color: var(--color-cell-border-active);
} }

View File

@@ -19,6 +19,7 @@ import { Widget } from 'vs/base/browser/ui/widget';
import { isArray, isBoolean } from 'vs/base/common/types'; import { isArray, isBoolean } from 'vs/base/common/types';
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import { range } from 'vs/base/common/arrays'; import { range } from 'vs/base/common/arrays';
import { AsyncDataProvider } from 'sql/base/browser/ui/table/asyncDataView';
function getDefaultOptions<T>(): Slick.GridOptions<T> { function getDefaultOptions<T>(): Slick.GridOptions<T> {
return <Slick.GridOptions<T>>{ return <Slick.GridOptions<T>>{
@@ -49,6 +50,9 @@ export class Table<T extends Slick.SlickData> extends Widget implements IDisposa
private _onClick = new Emitter<ITableMouseEvent>(); private _onClick = new Emitter<ITableMouseEvent>();
public readonly onClick: Event<ITableMouseEvent> = this._onClick.event; public readonly onClick: Event<ITableMouseEvent> = this._onClick.event;
private _onHeaderClick = new Emitter<ITableMouseEvent>();
public readonly onHeaderClick: Event<ITableMouseEvent> = this._onHeaderClick.event;
private _onColumnResize = new Emitter<void>(); private _onColumnResize = new Emitter<void>();
public readonly onColumnResize = this._onColumnResize.event; public readonly onColumnResize = this._onColumnResize.event;
@@ -111,9 +115,17 @@ export class Table<T extends Slick.SlickData> extends Widget implements IDisposa
this.mapMouseEvent(this._grid.onContextMenu, this._onContextMenu); this.mapMouseEvent(this._grid.onContextMenu, this._onContextMenu);
this.mapMouseEvent(this._grid.onClick, this._onClick); this.mapMouseEvent(this._grid.onClick, this._onClick);
this.mapMouseEvent(this._grid.onHeaderClick, this._onHeaderClick);
this._grid.onColumnsResized.subscribe(() => this._onColumnResize.fire()); this._grid.onColumnsResized.subscribe(() => this._onColumnResize.fire());
} }
public rerenderGrid(start: number, end: number) {
this._grid.updateRowCount();
this._grid.setColumns(this._grid.getColumns());
this._grid.invalidateAllRows();
this._grid.render();
}
private mapMouseEvent(slickEvent: Slick.Event<any>, emitter: Emitter<ITableMouseEvent>) { private mapMouseEvent(slickEvent: Slick.Event<any>, emitter: Emitter<ITableMouseEvent>) {
slickEvent.subscribe((e: JQuery.Event) => { slickEvent.subscribe((e: JQuery.Event) => {
const originalEvent = e.originalEvent; const originalEvent = e.originalEvent;
@@ -151,8 +163,9 @@ export class Table<T extends Slick.SlickData> extends Widget implements IDisposa
setData(data: Array<T>): void; setData(data: Array<T>): void;
setData(data: TableDataView<T>): void; setData(data: TableDataView<T>): void;
setData(data: Array<T> | TableDataView<T>): void { setData(data: AsyncDataProvider<T>): void;
if (data instanceof TableDataView) { setData(data: Array<T> | TableDataView<T> | AsyncDataProvider<T>): void {
if (data instanceof TableDataView || data instanceof AsyncDataProvider) {
this._data = data; this._data = data;
} else { } else {
this._data = new TableDataView<T>(data); this._data = new TableDataView<T>(data);
@@ -197,6 +210,18 @@ export class Table<T extends Slick.SlickData> extends Widget implements IDisposa
this._grid.setSelectionModel(model); this._grid.setSelectionModel(model);
} }
getSelectionModel(): Slick.SelectionModel<T, Array<Slick.Range>> {
return this._grid.getSelectionModel();
}
getSelectedRanges(): Slick.Range[] {
let selectionModel = this._grid.getSelectionModel();
if (selectionModel && selectionModel.getSelectedRanges) {
return selectionModel.getSelectedRanges();
}
return <Slick.Range[]><unknown>undefined;
}
focus(): void { focus(): void {
this._grid.focus(); this._grid.focus();
} }
@@ -205,6 +230,10 @@ export class Table<T extends Slick.SlickData> extends Widget implements IDisposa
this._grid.setActiveCell(row, cell); this._grid.setActiveCell(row, cell);
} }
setActive(): void {
this._grid.setActiveCell(0, 1);
}
get activeCell(): Slick.Cell { get activeCell(): Slick.Cell {
return this._grid.getActiveCell(); return this._grid.getActiveCell();
} }

View File

@@ -63,7 +63,7 @@ export interface IQueryModelService {
refreshResultsets(uri: string): void; refreshResultsets(uri: string): void;
sendGridContentEvent(uri: string, eventName: string): void; sendGridContentEvent(uri: string, eventName: string): void;
resizeResultsets(uri: string): void; resizeResultsets(uri: string): void;
onAngularLoaded(uri: string): void; onLoaded(uri: string): void;
copyResults(uri: string, selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): void; copyResults(uri: string, selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): void;
showCommitError(error: string): void; showCommitError(error: string): void;

View File

@@ -116,12 +116,11 @@ export class QueryModelService implements IQueryModelService {
} }
/** /**
* To be called by an angular component's DataService when the component has finished loading. * To be called by a component's DataService when the component has finished loading.
* Sends all previously enqueued query events to the DataService and signals to stop enqueuing * Sends all previously enqueued query events to the DataService and signals to stop enqueuing
* any further events. This prevents QueryEvents from getting lost if they are sent before * any further events.
* angular is listening for them.
*/ */
public onAngularLoaded(uri: string) { public onLoaded(uri: string) {
if (this._queryInfoMap.has(uri)) { if (this._queryInfoMap.has(uri)) {
let info = this._getQueryInfo(uri)!; let info = this._getQueryInfo(uri)!;
info.dataServiceReady = true; info.dataServiceReady = true;

View File

@@ -56,7 +56,7 @@ export class TestQueryModelService implements IQueryModelService {
resizeResultsets(uri: string): void { resizeResultsets(uri: string): void {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
onAngularLoaded(uri: string): void { onLoaded(uri: string): void {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
copyResults(uri: string, selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): void { copyResults(uri: string, selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): void {

View File

@@ -1,33 +0,0 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div class="fullsize vertBox editdata-component">
<div id="results" *ngIf="renderedDataSets.length > 0" class="results vertBox scrollable"
(onScroll)="onScroll($event)" [class.hidden]="!resultActive">
<div class="boxRow content horzBox slickgrid editable" *ngFor="let dataSet of renderedDataSets; let i = index"
[style.max-height]="dataSet.maxHeight" [style.min-height]="dataSet.minHeight">
<slick-grid #slickgrid id="slickgrid_{{i}}" [columnDefinitions]="dataSet.columnDefinitions"
[ngClass]="i === activeGrid ? 'active' : ''"
[dataRows]="dataSet.dataRows"
enableAsyncPostRender="true"
showDataTypeIcon="false"
showHeader="true"
[resized]="dataSet.resized"
[plugins]="plugins[i]"
(onActiveCellChanged)="onActiveCellChanged($event)"
(onCellChange)="onCellEditEnd($event)"
(onContextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)"
(onRendered)="onGridRendered($event)"
[isCellEditValid]="onIsCellEditValid"
[overrideCellFn]="overrideCellFn"
[onBeforeAppendCell]="onBeforeAppendCell"
[enableEditing]="true"
class="boxCol content vertBox slickgrid">
</slick-grid>
</div>
</div>
</div>

View File

@@ -1,57 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ApplicationRef, ComponentFactoryResolver, NgModule, Inject, forwardRef, Type } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { SlickGrid } from 'angular2-slickgrid';
import { EditDataComponent } from 'sql/workbench/contrib/editData/browser/editData.component';
import { providerIterator } from 'sql/workbench/services/bootstrap/browser/bootstrapService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IBootstrapParams, ISelector } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
export const EditDataModule = (params: IBootstrapParams, selector: string, instantiationService: IInstantiationService): Type<any> => {
@NgModule({
imports: [
CommonModule,
BrowserModule
],
declarations: [
EditDataComponent,
SlickGrid
],
entryComponents: [
EditDataComponent
],
providers: [
{ provide: IBootstrapParams, useValue: params },
{ provide: ISelector, useValue: selector },
...providerIterator(instantiationService)
]
})
class ModuleClass {
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
@Inject(ISelector) private selector: string
) {
}
ngDoBootstrap(appRef: ApplicationRef) {
const factory = this._resolver.resolveComponentFactory(EditDataComponent);
(<any>factory).factory.selector = this.selector;
appRef.bootstrap(factory);
}
}
return ModuleClass;
};

View File

@@ -5,12 +5,11 @@
import 'vs/css!./media/editData'; import 'vs/css!./media/editData';
import { ElementRef, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject, forwardRef, EventEmitter } from '@angular/core'; import { VirtualizedCollection, AsyncDataProvider, ISlickColumn } from 'sql/base/browser/ui/table/asyncDataView';
import { VirtualizedCollection } from 'angular2-slickgrid'; import { Table } from 'sql/base/browser/ui/table/table';
import { IGridDataSet } from 'sql/workbench/contrib/grid/common/interfaces'; import { IGridDataSet } from 'sql/workbench/contrib/grid/common/interfaces';
import * as Services from 'sql/base/browser/ui/table/formatters'; import * as Services from 'sql/base/browser/ui/table/formatters';
import { IEditDataComponentParams, IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
import { GridParentComponent } from 'sql/workbench/contrib/editData/browser/gridParentComponent'; import { GridParentComponent } from 'sql/workbench/contrib/editData/browser/gridParentComponent';
import { EditDataGridActionProvider } from 'sql/workbench/contrib/editData/browser/editDataGridActions'; import { EditDataGridActionProvider } from 'sql/workbench/contrib/editData/browser/editDataGridActions';
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService'; import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
@@ -18,7 +17,7 @@ import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColu
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin'; import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin'; import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
import { escape } from 'sql/base/common/strings'; import { escape } from 'sql/base/common/strings';
import { DataService } from 'sql/workbench/contrib/grid/common/dataService';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -32,14 +31,10 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { EditUpdateCellResult } from 'azdata'; import { EditUpdateCellResult } from 'azdata';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { deepClone, assign } from 'vs/base/common/objects'; import { deepClone, assign } from 'vs/base/common/objects';
export const EDITDATA_SELECTOR: string = 'editdata-component'; import { Emitter, Event } from 'vs/base/common/event';
import { equals } from 'vs/base/common/arrays';
@Component({ export class EditDataGridPanel extends GridParentComponent {
selector: EDITDATA_SELECTOR,
host: { '(window:keydown)': 'keyEvent($event)', '(window:gridnav)': 'keyEvent($event)' },
templateUrl: decodeURI(require.toUrl('./editData.component.html'))
})
export class EditDataComponent extends GridParentComponent implements OnInit, OnDestroy {
// The time(in milliseconds) we wait before refreshing the grid. // The time(in milliseconds) we wait before refreshing the grid.
// We use clearTimeout and setTimeout pair to avoid unnecessary refreshes. // We use clearTimeout and setTimeout pair to avoid unnecessary refreshes.
private refreshGridTimeoutInMs = 200; private refreshGridTimeoutInMs = 200;
@@ -53,8 +48,12 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
// FIELDS // FIELDS
// All datasets // All datasets
private gridDataProvider: AsyncDataProvider<any>;
private dataSet: IGridDataSet; private dataSet: IGridDataSet;
private oldDataRows: VirtualizedCollection<any>;
private firstRender = true; private firstRender = true;
private firstLoad = true;
private enableEditing = true;
// Current selected cell state // Current selected cell state
private currentCell: { row: number, column: number, isEditable: boolean, isDirty: boolean }; private currentCell: { row: number, column: number, isEditable: boolean, isDirty: boolean };
private currentEditCellValue: string; private currentEditCellValue: string;
@@ -62,8 +61,9 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
private removingNewRow: boolean; private removingNewRow: boolean;
private rowIdMappings: { [gridRowId: number]: number } = {}; private rowIdMappings: { [gridRowId: number]: number } = {};
private dirtyCells: number[] = []; private dirtyCells: number[] = [];
protected plugins = new Array<Array<Slick.Plugin<any>>>(); protected plugins = new Array<Slick.Plugin<any>>();
// List of column names with their indexes stored.
private columnNameToIndex: { [columnNumber: number]: string } = {};
// Edit Data functions // Edit Data functions
public onActiveCellChanged: (event: Slick.OnActiveCellChangedEventArgs<any>) => void; public onActiveCellChanged: (event: Slick.OnActiveCellChangedEventArgs<any>) => void;
public onCellEditEnd: (event: Slick.OnCellChangeEventArgs<any>) => void; public onCellEditEnd: (event: Slick.OnCellChangeEventArgs<any>) => void;
@@ -81,31 +81,33 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
}; };
constructor( constructor(
@Inject(forwardRef(() => ElementRef)) el: ElementRef, dataService: DataService,
@Inject(forwardRef(() => ChangeDetectorRef)) cd: ChangeDetectorRef, onSaveViewState: Event<void>,
@Inject(IBootstrapParams) params: IEditDataComponentParams, onRestoreViewState: Event<void>,
@Inject(IInstantiationService) private instantiationService: IInstantiationService, @IInstantiationService protected instantiationService: IInstantiationService,
@Inject(INotificationService) private notificationService: INotificationService, @INotificationService protected notificationService: INotificationService,
@Inject(IContextMenuService) contextMenuService: IContextMenuService, @IContextMenuService protected contextMenuService: IContextMenuService,
@Inject(IKeybindingService) keybindingService: IKeybindingService, @IKeybindingService protected keybindingService: IKeybindingService,
@Inject(IContextKeyService) contextKeyService: IContextKeyService, @IContextKeyService protected contextKeyService: IContextKeyService,
@Inject(IConfigurationService) configurationService: IConfigurationService, @IConfigurationService protected configurationService: IConfigurationService,
@Inject(IClipboardService) clipboardService: IClipboardService, @IClipboardService protected clipboardService: IClipboardService,
@Inject(IQueryEditorService) queryEditorService: IQueryEditorService, @IQueryEditorService protected queryEditorService: IQueryEditorService,
@Inject(ILogService) logService: ILogService @ILogService protected logService: ILogService
) { ) {
super(el, cd, contextMenuService, keybindingService, contextKeyService, configurationService, clipboardService, queryEditorService, logService); super(contextMenuService, keybindingService, contextKeyService, configurationService, clipboardService, queryEditorService, logService);
this._el.nativeElement.className = 'slickgridContainer'; this.nativeElement = document.createElement('editdatagridpanel');
this.dataService = params.dataService; this.nativeElement.className = 'slickgridContainer';
this.dataService = dataService;
this.actionProvider = this.instantiationService.createInstance(EditDataGridActionProvider, this.dataService, this.onGridSelectAll(), this.onDeleteRow(), this.onRevertRow()); this.actionProvider = this.instantiationService.createInstance(EditDataGridActionProvider, this.dataService, this.onGridSelectAll(), this.onDeleteRow(), this.onRevertRow());
params.onRestoreViewState(() => this.restoreViewState()); onRestoreViewState(() => this.restoreViewState());
params.onSaveViewState(() => this.saveViewState()); onSaveViewState(() => this.saveViewState());
this.onInit();
} }
/** /**
* Called by Angular when the object is initialized * Called when the object is initialized
*/ */
ngOnInit(): void { onInit(): void {
const self = this; const self = this;
this.baseInit(); this.baseInit();
@@ -132,27 +134,29 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
this.logService.error('Unexpected query event type "' + event.type + '" sent'); this.logService.error('Unexpected query event type "' + event.type + '" sent');
break; break;
} }
self._cd.detectChanges();
}); });
this.dataService.onLoaded();
}
this.dataService.onAngularLoaded(); public render(container: HTMLElement): void {
container.appendChild(this.nativeElement);
} }
protected initShortcuts(shortcuts: { [name: string]: Function }): void { protected initShortcuts(shortcuts: { [name: string]: Function }): void {
// TODO add any Edit Data-specific shortcuts here // TODO add any Edit Data-specific shortcuts here
} }
public ngOnDestroy(): void { public onDestroy(): void {
this.baseDestroy(); this.baseDestroy();
} }
handleStart(self: EditDataComponent, event: any): void { handleStart(self: EditDataGridPanel, event: any): void {
self.dataSet = undefined; self.dataSet = undefined;
self.oldDataRows = undefined;
self.placeHolderDataSets = []; self.placeHolderDataSets = [];
self.renderedDataSets = self.placeHolderDataSets; self.renderedDataSets = self.placeHolderDataSets;
this._cd.detectChanges();
// Hooking up edit functions // Hooking up edit functions handle
this.onIsCellEditValid = (row, column, value): boolean => { this.onIsCellEditValid = (row, column, value): boolean => {
// TODO can only run sync code // TODO can only run sync code
return true; return true;
@@ -322,14 +326,14 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
cellSelectTasks.catch(() => { }); cellSelectTasks.catch(() => { });
} }
handleComplete(self: EditDataComponent, event: any): void { handleComplete(self: EditDataGridPanel, event: any): void {
} }
handleEditSessionReady(self, event): void { handleEditSessionReady(self, event): void {
// TODO: update when edit session is ready // TODO: update when edit session is ready
} }
handleMessage(self: EditDataComponent, event: any): void { handleMessage(self: EditDataGridPanel, event: any): void {
if (event.data && event.data.isError) { if (event.data && event.data.isError) {
self.notificationService.notify({ self.notificationService.notify({
severity: Severity.Error, severity: Severity.Error,
@@ -338,7 +342,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
} }
} }
handleResultSet(self: EditDataComponent, event: any): void { handleResultSet(self: EditDataGridPanel, event: any): void {
// Clone the data before altering it to avoid impacting other subscribers // Clone the data before altering it to avoid impacting other subscribers
let resultSet = assign({}, event.data); let resultSet = assign({}, event.data);
if (!resultSet.complete) { if (!resultSet.complete) {
@@ -363,9 +367,9 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
minHeight: minHeight, minHeight: minHeight,
dataRows: new VirtualizedCollection( dataRows: new VirtualizedCollection(
self.windowSize, self.windowSize,
index => { return {}; },
resultSet.rowCount, resultSet.rowCount,
this.loadDataFunction, this.loadDataFunction,
index => { return {}; }
), ),
columnDefinitions: [rowNumberColumn.getColumnDefinition()].concat(resultSet.columnInfo.map((c, i) => { columnDefinitions: [rowNumberColumn.getColumnDefinition()].concat(resultSet.columnInfo.map((c, i) => {
let columnIndex = (i + 1).toString(); let columnIndex = (i + 1).toString();
@@ -378,14 +382,15 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
}; };
})) }))
}; };
self.plugins.push([rowNumberColumn, new AutoColumnSize({ maxWidth: this.configurationService.getValue<number>('resultsGrid.maxColumnWidth') }), new AdditionalKeyBindings()]); self.plugins.push(rowNumberColumn, new AutoColumnSize({ maxWidth: this.configurationService.getValue<number>('resultsGrid.maxColumnWidth') }), new AdditionalKeyBindings());
self.dataSet = dataSet; self.dataSet = dataSet;
self.gridDataProvider = new AsyncDataProvider(dataSet.dataRows);
// Create a dataSet to render without rows to reduce DOM size // Create a dataSet to render without rows to reduce DOM size
let undefinedDataSet = deepClone(dataSet); let undefinedDataSet = deepClone(dataSet);
undefinedDataSet.columnDefinitions = dataSet.columnDefinitions; undefinedDataSet.columnDefinitions = dataSet.columnDefinitions;
undefinedDataSet.dataRows = undefined; undefinedDataSet.dataRows = undefined;
undefinedDataSet.resized = new EventEmitter(); undefinedDataSet.resized = new Emitter();
self.placeHolderDataSets.push(undefinedDataSet); self.placeHolderDataSets.push(undefinedDataSet);
self.refreshGrid(); self.refreshGrid();
@@ -395,6 +400,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
this.removingNewRow = false; this.removingNewRow = false;
this.newRowVisible = false; this.newRowVisible = false;
this.dirtyCells = []; this.dirtyCells = [];
} }
/** /**
@@ -414,34 +420,54 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
} }
private refreshGrid(): Thenable<void> { private refreshGrid(): Thenable<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>(async (resolve, reject) => {
const self = this; const self = this;
clearTimeout(self.refreshGridTimeoutHandle); clearTimeout(self.refreshGridTimeoutHandle);
this.refreshGridTimeoutHandle = setTimeout(() => { this.refreshGridTimeoutHandle = setTimeout(() => {
for (let i = 0; i < self.placeHolderDataSets.length; i++) {
self.placeHolderDataSets[i].dataRows = self.dataSet.dataRows; if (self.dataSet && self.placeHolderDataSets[0].resized) {
self.placeHolderDataSets[i].resized.emit(); self.placeHolderDataSets[0].dataRows = self.dataSet.dataRows;
self.placeHolderDataSets[0].resized.fire();
} }
self._cd.detectChanges();
if (self.oldDataRows !== self.placeHolderDataSets[0].dataRows) {
self.detectChange();
self.oldDataRows = self.placeHolderDataSets[0].dataRows;
}
if (self.firstRender) { if (self.firstRender) {
let setActive = function () { let setActive = function () {
if (self.firstRender && self.slickgrids.toArray().length > 0) { if (self.firstRender && self.table) {
self.slickgrids.toArray()[0].setActive(); self.table.setActive();
self.firstRender = false; self.firstRender = false;
} }
}; };
setTimeout(() => setActive());
setTimeout(() => {
setActive();
});
} }
resolve(); resolve();
}, self.refreshGridTimeoutInMs); }, self.refreshGridTimeoutInMs);
}); });
} }
protected detectChange(): void {
if (this.firstLoad) {
this.handleChanges({
['dataRows']: { currentValue: this.dataSet.dataRows, firstChange: this.firstLoad, previousValue: undefined },
['columnDefinitions']: { currentValue: this.dataSet.columnDefinitions, firstChange: this.firstLoad, previousValue: undefined }
});
this.handleInitializeTable();
this.firstLoad = false;
}
else {
this.table.setData(this.gridDataProvider);
this.handleChanges({
['dataRows']: { currentValue: this.dataSet.dataRows, firstChange: this.firstLoad, previousValue: this.oldDataRows }
});
}
}
protected tryHandleKeyEvent(e: StandardKeyboardEvent): boolean { protected tryHandleKeyEvent(e: StandardKeyboardEvent): boolean {
let handled: boolean = false; let handled: boolean = false;
@@ -452,6 +478,24 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
return handled; return handled;
} }
/**
* Force re-rendering of the results grids. Calling this upon unhide (upon focus) fixes UI
* glitches that occur when a QueryResultsEditor is hidden then unhidden while it is running a query.
*/
refreshDatasets(): void {
let tempRenderedDataSets = this.renderedDataSets;
this.renderedDataSets = [];
this.handleChanges({
['dataRows']: { currentValue: undefined, firstChange: this.firstLoad, previousValue: this.dataSet.dataRows },
['columnDefinitions']: { currentValue: undefined, firstChange: this.firstLoad, previousValue: this.dataSet.columnDefinitions }
});
this.renderedDataSets = tempRenderedDataSets;
this.handleChanges({
['dataRows']: { currentValue: this.renderedDataSets[0].dataRows, firstChange: this.firstLoad, previousValue: undefined },
['columnDefinitions']: { currentValue: this.renderedDataSets[0].columnDefinitions, firstChange: this.firstLoad, previousValue: undefined }
});
}
// Private Helper Functions //////////////////////////////////////////////////////////////////////////// // Private Helper Functions ////////////////////////////////////////////////////////////////////////////
private async revertCurrentRow(): Promise<void> { private async revertCurrentRow(): Promise<void> {
@@ -537,7 +581,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
// Adds CSS classes to slickgrid cells to indicate a dirty state // Adds CSS classes to slickgrid cells to indicate a dirty state
private setCellDirtyState(row: number, column: number, dirtyState: boolean): void { private setCellDirtyState(row: number, column: number, dirtyState: boolean): void {
let slick: any = this.slickgrids.toArray()[0]; let slick: any = this.table;
let grid = slick._grid; let grid = slick._grid;
if (dirtyState) { if (dirtyState) {
// Change cell color // Change cell color
@@ -555,7 +599,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
// Adds CSS classes to slickgrid rows to indicate a dirty state // Adds CSS classes to slickgrid rows to indicate a dirty state
private setRowDirtyState(row: number, dirtyState: boolean): void { private setRowDirtyState(row: number, dirtyState: boolean): void {
let slick: any = this.slickgrids.toArray()[0]; let slick: any = this.table;
let grid = slick._grid; let grid = slick._grid;
if (dirtyState) { if (dirtyState) {
// Change row header color // Change row header color
@@ -593,10 +637,11 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
self.dataSet.minHeight = self.getMinHeight(self.dataSet.totalRows); self.dataSet.minHeight = self.getMinHeight(self.dataSet.totalRows);
self.dataSet.dataRows = new VirtualizedCollection( self.dataSet.dataRows = new VirtualizedCollection(
self.windowSize, self.windowSize,
index => { return {}; },
self.dataSet.totalRows, self.dataSet.totalRows,
self.loadDataFunction, self.loadDataFunction,
index => { return {}; }
); );
self.gridDataProvider = new AsyncDataProvider(self.dataSet.dataRows);
}); });
} }
@@ -608,11 +653,11 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
this.dataSet.totalRows--; this.dataSet.totalRows--;
this.dataSet.dataRows = new VirtualizedCollection( this.dataSet.dataRows = new VirtualizedCollection(
this.windowSize, this.windowSize,
index => { return {}; },
this.dataSet.totalRows, this.dataSet.totalRows,
this.loadDataFunction, this.loadDataFunction,
index => { return {}; }
); );
this.gridDataProvider = new AsyncDataProvider(this.dataSet.dataRows);
// refresh results view // refresh results view
return this.refreshGrid().then(() => { return this.refreshGrid().then(() => {
// Set focus to the row index column of the removed row if the current selection is in the removed row // Set focus to the row index column of the removed row if the current selection is in the removed row
@@ -624,25 +669,25 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
} }
private focusCell(row: number, column: number, forceEdit: boolean = true): void { private focusCell(row: number, column: number, forceEdit: boolean = true): void {
let slick: any = this.slickgrids.toArray()[0]; let slick: any = this.table;
let grid = slick._grid; let grid = slick._grid;
grid.gotoCell(row, column, forceEdit); grid.gotoCell(row, column, forceEdit);
} }
private getMaxHeight(rowCount: number): any { private getMaxHeight(rowCount: number): any {
return rowCount < this._defaultNumShowingRows return rowCount < this.defaultNumShowingRows
? ((rowCount + 1) * this._rowHeight) + 10 ? ((rowCount + 1) * this.rowHeight) + 10
: 'inherit'; : 'inherit';
} }
private getMinHeight(rowCount: number): any { private getMinHeight(rowCount: number): any {
return rowCount > this._defaultNumShowingRows return rowCount > this.defaultNumShowingRows
? (this._defaultNumShowingRows + 1) * this._rowHeight + 10 ? (this.defaultNumShowingRows + 1) * this.rowHeight + 10
: this.getMaxHeight(rowCount); : this.getMaxHeight(rowCount);
} }
private saveViewState(): void { private saveViewState(): void {
let grid = this.slickgrids.toArray()[0]; let grid = this.table;
let self = this; let self = this;
if (grid) { if (grid) {
let gridSelections = grid.getSelectedRanges(); let gridSelections = grid.getSelectedRanges();
@@ -671,8 +716,8 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
private restoreViewState(): void { private restoreViewState(): void {
if (this.savedViewState) { if (this.savedViewState) {
this.slickgrids.toArray()[0].selection = this.savedViewState.gridSelections; // Row selections are undefined in original slickgrid, removed for no purpose
let viewport = ((this.slickgrids.toArray()[0] as any)._grid.getCanvasNode() as HTMLElement).parentElement; let viewport = ((this.table as any)._grid.getCanvasNode() as HTMLElement).parentElement;
viewport.scrollLeft = this.savedViewState.scrollLeft; viewport.scrollLeft = this.savedViewState.scrollLeft;
viewport.scrollTop = this.savedViewState.scrollTop; viewport.scrollTop = this.savedViewState.scrollTop;
this.savedViewState = undefined; this.savedViewState = undefined;
@@ -700,7 +745,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
} }
private isCellOnScreen(row: number, column: number): boolean { private isCellOnScreen(row: number, column: number): boolean {
let slick: any = this.slickgrids.toArray()[0]; let slick: any = this.table;
let grid = slick._grid; let grid = slick._grid;
let viewport = grid.getViewport(); let viewport = grid.getViewport();
let cellBox = grid.getCellNodeBox(row, column); let cellBox = grid.getCellNodeBox(row, column);
@@ -733,6 +778,283 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
} }
private createNewTable(): void {
let newGridContainer = document.createElement('div');
newGridContainer.className = 'grid';
if (this.placeHolderDataSets) {
let dataSet = this.placeHolderDataSets[0];
let options = {
enableCellNavigation: true,
enableColumnReorder: false,
renderRowWithRange: true,
showHeader: true,
rowHeight: this.rowHeight,
defaultColumnWidth: 120,
defaultFormatter: undefined,
editable: this.enableEditing,
autoEdit: this.enableEditing,
enableAddRow: false,
enableAsyncPostRender: false,
editorFactory: {
getEditor: (column: ISlickColumn<any>) => this.getColumnEditor(column)
}
};
if (dataSet.columnDefinitions) {
this.table = new Table(this.nativeElement.appendChild(newGridContainer), { dataProvider: this.gridDataProvider, columns: dataSet.columnDefinitions }, options);
for (let plugin of this.plugins) {
this.table.registerPlugin(plugin);
}
for (let i = 0; i < dataSet.columnDefinitions.length; i++) {
this.columnNameToIndex[this.dataSet.columnDefinitions[i].name] = i;
}
}
}
else {
this.table = new Table(this.nativeElement.appendChild(newGridContainer));
}
}
getOverridableTextEditorClass(): any {
let self = this;
class OverridableTextEditor {
private _textEditor: any;
public keyCaptureList: number[];
constructor(private _args: any) {
this._textEditor = new Slick.Editors.Text(_args);
const END = 35;
const HOME = 36;
// These are the special keys the text editor should capture instead of letting
// the grid handle them
this.keyCaptureList = [END, HOME];
}
destroy(): void {
this._textEditor.destroy();
}
focus(): void {
this._textEditor.focus();
}
getValue(): string {
return this._textEditor.getValue();
}
setValue(val): void {
this._textEditor.setValue(val);
}
loadValue(item, rowNumber): void {
if (self.overrideCellFn) {
let overrideValue = self.overrideCellFn(rowNumber, this._args.column.id, item[this._args.column.id]);
if (overrideValue !== undefined) {
item[this._args.column.id] = overrideValue;
}
}
this._textEditor.loadValue(item);
}
serializeValue(): string {
return this._textEditor.serializeValue();
}
applyValue(item, state): void {
let activeRow = self.currentCell.row;
let currentRow = self.dataSet.dataRows.at(activeRow);
let colIndex = self.getColumnIndex(this._args.column.name);
let dataLength: number = self.dataSet.dataRows.getLength();
// If this is not the "new row" at the very bottom
if (activeRow !== dataLength) {
currentRow[colIndex] = state;
this._textEditor.applyValue(item, state);
}
}
isValueChanged(): boolean {
return this._textEditor.isValueChanged();
}
validate(): any {
let activeRow = self.currentCell.row;
let result: any = { valid: true, msg: undefined };
let colIndex: number = self.getColumnIndex(this._args.column.name);
let newValue: any = this._textEditor.getValue();
if (self.onIsCellEditValid && !self.onIsCellEditValid(activeRow, colIndex, newValue)) {
result.valid = false;
}
return result;
}
}
return OverridableTextEditor;
}
private getColumnEditor(column: ISlickColumn<any>): any {
if (column.isEditable === false || typeof column.isEditable === 'undefined') {
return undefined;
}
let columnId = column.id;
let canEditColumn = columnId !== undefined;
if (canEditColumn) {
return this.getOverridableTextEditorClass();
}
return undefined;
}
public getColumnIndex(name: string): number {
return this.columnNameToIndex[name];
}
handleChanges(changes: { [propName: string]: any }): void {
let columnDefinitionChanges = changes['columnDefinitions'];
let activeCell = this.table ? this.table.grid.getActiveCell() : undefined;
let hasGridStructureChanges = false;
let wasEditing = this.table ? !!this.table.grid.getCellEditor() : false;
if (columnDefinitionChanges && !equals(columnDefinitionChanges.previousValue, columnDefinitionChanges.currentValue)) {
if (!this.table) {
this.createNewTable();
} else {
this.table.grid.resetActiveCell();
this.table.grid.setColumns(this.dataSet.columnDefinitions);
}
hasGridStructureChanges = true;
if (!columnDefinitionChanges.currentValue || columnDefinitionChanges.currentValue.length === 0) {
activeCell = undefined;
}
if (activeCell) {
let columnThatContainedActiveCell = columnDefinitionChanges.previousValue[Math.max(activeCell.cell - 1, 0)];
let newActiveColumnIndex = columnThatContainedActiveCell
? columnDefinitionChanges.currentValue.findIndex(c => c.id === columnThatContainedActiveCell.id)
: -1;
activeCell.cell = newActiveColumnIndex !== -1 ? newActiveColumnIndex + 1 : 0;
}
}
if (changes['dataRows']
|| (changes['highlightedCells'] && !equals(changes['highlightedCells'].currentValue, changes['highlightedCells'].previousValue))
|| (changes['blurredColumns'] && !equals(changes['blurredColumns'].currentValue, changes['blurredColumns'].previousValue))
|| (changes['columnsLoading'] && !equals(changes['columnsLoading'].currentValue, changes['columnsLoading'].previousValue))) {
this.setCallbackOnDataRowsChanged();
this.table.rerenderGrid(0, this.dataSet.dataRows.getLength());
hasGridStructureChanges = true;
}
if (hasGridStructureChanges) {
if (activeCell) {
this.table.grid.setActiveCell(activeCell.row, activeCell.cell);
} else {
this.table.grid.resetActiveCell();
}
}
if (wasEditing && hasGridStructureChanges) {
this.table.grid.editActiveCell(this.table.grid.getCellEditor());
}
}
private setCallbackOnDataRowsChanged(): void {
//check if dataRows exist before we enable editing or slickgrid will complain
if (this.dataSet.dataRows) {
this.changeEditSession(true);
this.dataSet.dataRows.setCollectionChangedCallback((startIndex: number, count: number) => {
this.renderGridDataRowsRange(startIndex, count);
});
}
}
private changeEditSession(enabled: boolean): void {
this.enableEditing = enabled;
let options: any = this.table.grid.getOptions();
options.editable = enabled;
options.enableAddRow = false;
this.table.grid.setOptions(options);
}
private renderGridDataRowsRange(startIndex: number, count: number): void {
let editor = <Slick.Editors.Text<any>>this.table.grid.getCellEditor();
let oldValue = editor ? editor.getValue() : undefined;
let wasValueChanged = editor ? editor.isValueChanged() : false;
this.invalidateRange(startIndex, startIndex + count);
let activeCell = this.currentCell;
if (editor && activeCell.row >= startIndex && activeCell.row < startIndex + count) {
if (oldValue && wasValueChanged) {
editor.setValue(oldValue);
}
}
}
private invalidateRange(start: number, end: number): void {
let refreshedRows = Array.from({ length: (end - start) }, (v, k) => k + start);
this.table.grid.invalidateRows(refreshedRows, true);
this.table.grid.render();
}
private setupEvents(): void {
this.table.grid.onScroll.subscribe((e, args) => {
this.onScroll(args);
});
this.table.grid.onCellChange.subscribe((e, args) => {
this.onCellEditEnd(args);
});
this.table.grid.onBeforeEditCell.subscribe((e, args) => {
this.onBeforeEditCell(args);
});
// Subscribe to all active cell changes to be able to catch when we tab to the header on the next row
this.table.grid.onActiveCellChanged.subscribe((e, args) => {
// Emit that we've changed active cells
this.onActiveCellChanged(args);
});
this.table.grid.onContextMenu.subscribe((e, args) => {
this.openContextMenu(e, this.dataSet.batchId, this.dataSet.resultId, 0);
});
this.table.grid.onBeforeAppendCell.subscribe((e, args) => {
// Since we need to return a string here, we are using calling a function instead of event emitter like other events handlers
return this.onBeforeAppendCell ? this.onBeforeAppendCell(args.row, args.cell) : undefined;
});
this.table.grid.onRendered.subscribe((e, args) => {
this.onGridRendered(args);
});
}
onBeforeEditCell(event: Slick.OnBeforeEditCellEventArgs<any>): void {
this.logService.debug('onBeforeEditCell called with grid: ' + event.grid + ' row: ' + event.row
+ ' cell: ' + event.cell + ' item: ' + event.item + ' column: ' + event.column);
}
handleInitializeTable(): void {
// handleInitializeTable() will be called *after* the first time handleChanges() is called
// so, grid must be there already
if (this.dataSet.dataRows && this.dataSet.dataRows.getLength() > 0) {
this.table.grid.scrollRowToTop(0);
}
if (this.dataSet.resized) {
// Re-rendering the grid is expensive. Throttle so we only do so every 100ms.
this.dataSet.resized.throttleTime(100)
.subscribe(() => this.onResize());
}
// subscribe to slick events
// https://github.com/mleibman/SlickGrid/wiki/Grid-Events
this.setupEvents();
}
private onResize(): void {
if (this.table.grid !== undefined) {
// this will make sure the grid header and body to be re-rendered
this.table.grid.resizeCanvas();
}
}
/*Formatter for Column*/ /*Formatter for Column*/
private getColumnFormatter(row: number | undefined, cell: any | undefined, value: any, columnDef: any | undefined, dataContext: any | undefined): string { private getColumnFormatter(row: number | undefined, cell: any | undefined, value: any, columnDef: any | undefined, dataContext: any | undefined): string {
let valueToDisplay = ''; let valueToDisplay = '';

View File

@@ -15,11 +15,8 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import * as types from 'vs/base/common/types'; import * as types from 'vs/base/common/types';
import { IQueryModelService } from 'sql/platform/query/common/queryModel'; import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import { bootstrapAngular } from 'sql/workbench/services/bootstrap/browser/bootstrapService'; import { BareResultsGridInfo, getBareResultsGridInfoStyles } from 'sql/workbench/contrib/query/browser/queryResultsEditor';
import { BareResultsGridInfo } from 'sql/workbench/contrib/query/browser/queryResultsEditor'; import { EditDataGridPanel } from 'sql/workbench/contrib/editData/browser/editDataGridPanel';
import { IEditDataComponentParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
import { EditDataModule } from 'sql/workbench/contrib/editData/browser/editData.module';
import { EDITDATA_SELECTOR } from 'sql/workbench/contrib/editData/browser/editData.component';
import { EditDataResultsInput } from 'sql/workbench/contrib/editData/browser/editDataResultsInput'; import { EditDataResultsInput } from 'sql/workbench/contrib/editData/browser/editDataResultsInput';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStorageService } from 'vs/platform/storage/common/storage';
@@ -31,6 +28,8 @@ export class EditDataResultsEditor extends BaseEditor {
protected _input: EditDataResultsInput; protected _input: EditDataResultsInput;
protected _rawOptions: BareResultsGridInfo; protected _rawOptions: BareResultsGridInfo;
private styleSheet = DOM.createStyleSheet();
constructor( constructor(
@ITelemetryService telemetryService: ITelemetryService, @ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService, @IThemeService themeService: IThemeService,
@@ -54,9 +53,11 @@ export class EditDataResultsEditor extends BaseEditor {
} }
public createEditor(parent: HTMLElement): void { public createEditor(parent: HTMLElement): void {
parent.appendChild(this.styleSheet);
} }
public dispose(): void { public dispose(): void {
this.styleSheet = undefined;
super.dispose(); super.dispose();
} }
@@ -67,7 +68,7 @@ export class EditDataResultsEditor extends BaseEditor {
super.setInput(input, options, CancellationToken.None); super.setInput(input, options, CancellationToken.None);
this._applySettings(); this._applySettings();
if (!input.hasBootstrapped) { if (!input.hasBootstrapped) {
this._bootstrapAngular(); this.createGridPanel();
} }
return Promise.resolve<void>(null); return Promise.resolve<void>(null);
} }
@@ -85,41 +86,26 @@ export class EditDataResultsEditor extends BaseEditor {
cssRuleText = this._rawOptions.cellPadding.join('px ') + 'px;'; cssRuleText = this._rawOptions.cellPadding.join('px ') + 'px;';
} }
let content = `.grid .slick-cell { padding: ${cssRuleText}; }`; let content = `.grid .slick-cell { padding: ${cssRuleText}; }`;
content += `.grid-panel .monaco-table, .message-tree { ${getBareResultsGridInfoStyles(this._rawOptions)} }`;
this.input.css.innerHTML = content; this.input.css.innerHTML = content;
} }
} }
/** private createGridPanel(): void {
* Load the angular components and record for this input that we have done so
*/
private _bootstrapAngular(): void {
let input = <EditDataResultsInput>this.input; let input = <EditDataResultsInput>this.input;
let uri = input.uri; let uri = input.uri;
// Pass the correct DataService to the new angular component // Pass the correct DataService to the new angular component
let dataService = this._queryModelService.getDataService(uri); let dataService = this._queryModelService.getDataService(uri);
if (!dataService) { if (!dataService) {
throw new Error('DataService not found for URI: ' + uri); throw new Error('DataService not found for URI: ' + uri);
} }
// Mark that we have bootstrapped // Mark that we have bootstrapped
input.setBootstrappedTrue(); input.setBootstrappedTrue();
// Get the bootstrap params and perform the bootstrap
// Note: pass in input so on disposal this is cleaned up. // Note: pass in input so on disposal this is cleaned up.
// Otherwise many components will be left around and be subscribed // Otherwise many components will be left around and be subscribed
// to events from the backing data service // to events from the backing data service
const parent = input.container; this._applySettings();
let params: IEditDataComponentParams = { let editGridPanel = this._register(this._instantiationService.createInstance(EditDataGridPanel, dataService, input.onSaveViewStateEmitter.event, input.onRestoreViewStateEmitter.event));
dataService: dataService, editGridPanel.render(this.getContainer());
onSaveViewState: input.onSaveViewStateEmitter.event,
onRestoreViewState: input.onRestoreViewStateEmitter.event
};
bootstrapAngular(this._instantiationService,
EditDataModule,
parent,
EDITDATA_SELECTOR,
params,
input);
} }
} }

View File

@@ -6,9 +6,8 @@
import 'vs/css!./media/flexbox'; import 'vs/css!./media/flexbox';
import 'vs/css!./media/styles'; import 'vs/css!./media/styles';
import { Table } from 'sql/base/browser/ui/table/table';
import { Subscription, Subject } from 'rxjs/Rx'; import { Subscription, Subject } from 'rxjs/Rx';
import { ElementRef, QueryList, ChangeDetectorRef, ViewChildren } from '@angular/core';
import { SlickGrid } from 'angular2-slickgrid';
import * as Constants from 'sql/workbench/contrib/query/common/constants'; import * as Constants from 'sql/workbench/contrib/query/common/constants';
import * as LocalizedConstants from 'sql/workbench/contrib/query/common/localizedConstants'; import * as LocalizedConstants from 'sql/workbench/contrib/query/common/localizedConstants';
import { IGridInfo, IGridDataSet, SaveFormat } from 'sql/workbench/contrib/grid/common/interfaces'; import { IGridInfo, IGridDataSet, SaveFormat } from 'sql/workbench/contrib/grid/common/interfaces';
@@ -25,20 +24,21 @@ import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { DisposableStore } from 'vs/base/common/lifecycle'; import { DisposableStore, Disposable } from 'vs/base/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { subscriptionToDisposable } from 'sql/base/browser/lifecycle'; import { subscriptionToDisposable } from 'sql/base/browser/lifecycle';
export abstract class GridParentComponent {
export abstract class GridParentComponent extends Disposable {
// CONSTANTS // CONSTANTS
// tslint:disable:no-unused-variable // tslint:disable:no-unused-variable
protected get selectionModel() { return new CellSelectionModel(); } protected get selectionModel() { return new CellSelectionModel(); }
protected _rowHeight = 29; protected rowHeight = 29;
protected _defaultNumShowingRows = 8; protected defaultNumShowingRows = 8;
protected Constants = Constants; protected Constants = Constants;
protected LocalizedConstants = LocalizedConstants; protected LocalizedConstants = LocalizedConstants;
protected Utils = Utils; protected Utils = Utils;
@@ -56,7 +56,6 @@ export abstract class GridParentComponent {
protected toDispose = new DisposableStore(); protected toDispose = new DisposableStore();
// Context keys to set when keybindings are available // Context keys to set when keybindings are available
private resultsVisibleContextKey: IContextKey<boolean>; private resultsVisibleContextKey: IContextKey<boolean>;
private gridFocussedContextKey: IContextKey<boolean>; private gridFocussedContextKey: IContextKey<boolean>;
@@ -69,34 +68,32 @@ export abstract class GridParentComponent {
// Datasets currently being rendered on the DOM // Datasets currently being rendered on the DOM
protected renderedDataSets: IGridDataSet[] = this.placeHolderDataSets; protected renderedDataSets: IGridDataSet[] = this.placeHolderDataSets;
protected resultActive = true; protected resultActive = true;
protected _messageActive = true; protected messageActiveBool = true;
protected activeGrid = 0; protected activeGrid = 0;
protected nativeElement: HTMLElement;
@ViewChildren('slickgrid') slickgrids: QueryList<SlickGrid>; protected table: Table<any>;
set messageActive(input: boolean) { set messageActive(input: boolean) {
this._messageActive = input; this.messageActiveBool = input;
if (this.resultActive) { if (this.resultActive) {
this.resizeGrids(); this.resizeGrids();
} }
this._cd.detectChanges();
} }
get messageActive(): boolean { get messageActive(): boolean {
return this._messageActive; return this.messageActiveBool;
} }
constructor( constructor(
protected _el: ElementRef, @IContextMenuService protected contextMenuService: IContextMenuService,
protected _cd: ChangeDetectorRef, @IKeybindingService protected keybindingService: IKeybindingService,
protected contextMenuService: IContextMenuService, @IContextKeyService protected contextKeyService: IContextKeyService,
protected keybindingService: IKeybindingService, @IConfigurationService protected configurationService: IConfigurationService,
protected contextKeyService: IContextKeyService, @IClipboardService protected clipboardService: IClipboardService,
protected configurationService: IConfigurationService, @IQueryEditorService protected queryEditorService: IQueryEditorService,
protected clipboardService: IClipboardService, @ILogService protected logService: ILogService
protected queryEditorService: IQueryEditorService,
protected logService: ILogService
) { ) {
super();
} }
protected baseInit(): void { protected baseInit(): void {
@@ -105,13 +102,13 @@ export abstract class GridParentComponent {
if (this.configurationService) { if (this.configurationService) {
let sqlConfig = this.configurationService.getValue('sql'); let sqlConfig = this.configurationService.getValue('sql');
if (sqlConfig) { if (sqlConfig) {
this._messageActive = sqlConfig['messagesDefaultOpen']; this.messageActiveBool = sqlConfig['messagesDefaultOpen'];
} }
} }
this.subscribeWithDispose(this.dataService.gridContentObserver, (type) => { this.subscribeWithDispose(this.dataService.gridContentObserver, (type) => {
switch (type) { switch (type) {
case GridContentEvents.RefreshContents: case GridContentEvents.RefreshContents:
self.refreshResultsets(); self.refreshDatasets();
break; break;
case GridContentEvents.ResizeContents: case GridContentEvents.ResizeContents:
self.resizeGrids(); self.resizeGrids();
@@ -184,7 +181,7 @@ export abstract class GridParentComponent {
this.queryEditorVisible = QueryEditorVisibleContext.bindTo(contextKeyService); this.queryEditorVisible = QueryEditorVisibleContext.bindTo(contextKeyService);
this.queryEditorVisible.set(true); this.queryEditorVisible.set(true);
let gridContextKeyService = this.contextKeyService.createScoped(this._el.nativeElement); let gridContextKeyService = this.contextKeyService.createScoped(this.nativeElement);
this.toDispose.add(gridContextKeyService); this.toDispose.add(gridContextKeyService);
this.resultsVisibleContextKey = ResultsVisibleContext.bindTo(gridContextKeyService); this.resultsVisibleContextKey = ResultsVisibleContext.bindTo(gridContextKeyService);
this.resultsVisibleContextKey.set(true); this.resultsVisibleContextKey.set(true);
@@ -203,7 +200,6 @@ export abstract class GridParentComponent {
if (this.resultActive) { if (this.resultActive) {
this.resizeGrids(); this.resizeGrids();
} }
this._cd.detectChanges();
} }
protected toggleMessagePane(): void { protected toggleMessagePane(): void {
@@ -227,7 +223,7 @@ export abstract class GridParentComponent {
} }
protected getSelection(index?: number): Slick.Range[] { protected getSelection(index?: number): Slick.Range[] {
let selection = this.slickgrids.toArray()[index || this.activeGrid].getSelectedRanges(); let selection = this.table.getSelectedRanges();
if (selection) { if (selection) {
selection = selection.map(c => { return <Slick.Range>{ fromCell: c.fromCell - 1, toCell: c.toCell - 1, toRow: c.toRow, fromRow: c.fromRow }; }); selection = selection.map(c => { return <Slick.Range>{ fromCell: c.fromCell - 1, toCell: c.toCell - 1, toRow: c.toRow, fromRow: c.fromRow }; });
return selection; return selection;
@@ -380,13 +376,11 @@ export abstract class GridParentComponent {
} }
openContextMenu(event, batchId, resultId, index): void { openContextMenu(event, batchId, resultId, index): void {
let slick: any = this.slickgrids.toArray()[index]; let grid = this.table.grid;
let grid = slick._grid;
let selection = this.getSelection(index); let selection = this.getSelection(index);
if (selection && selection.length === 0) { if (selection && selection.length === 0) {
let cell = (grid as Slick.Grid<any>).getCellFromEvent(event); let cell = grid.getCellFromEvent(event);
selection = [new Slick.Range(cell.row, cell.cell - 1)]; selection = [new Slick.Range(cell.row, cell.cell - 1)];
} }
@@ -421,16 +415,14 @@ export abstract class GridParentComponent {
let self = this; let self = this;
return (gridIndex: number) => { return (gridIndex: number) => {
self.activeGrid = gridIndex; self.activeGrid = gridIndex;
let grid = self.slickgrids.toArray()[self.activeGrid]; let grid = self.table;
grid.setActive(); grid.setActive();
grid.selection = true; grid.setSelectedRows(true);
}; };
} }
private onSelectAllForActiveGrid(): void { private onSelectAllForActiveGrid(): void {
if (this.activeGrid >= 0 && this.slickgrids.length > this.activeGrid) { this.table.setSelectedRows(true);
this.slickgrids.toArray()[this.activeGrid].selection = true;
}
} }
/** /**
@@ -447,29 +439,27 @@ export abstract class GridParentComponent {
} }
setTimeout(() => { setTimeout(() => {
self.resizeGrids(); self.resizeGrids();
self.slickgrids.toArray()[0].setActive(); self.table.setActive();
self._cd.detectChanges();
}); });
} }
abstract onScroll(scrollTop): void; abstract onScroll(scrollTop): void;
protected getResultsElement(): any { protected getResultsElement(): any {
return this._el.nativeElement.querySelector('#results'); return this.nativeElement.querySelector('#results');
} }
protected getMessagesElement(): any { protected getMessagesElement(): any {
return this._el.nativeElement.querySelector('#messages'); return this.nativeElement.querySelector('#messages');
} }
/** /**
* Force angular to re-render the results grids. Calling this upon unhide (upon focus) fixes UI * Force re-rendering of the results grids. Calling this upon unhide (upon focus) fixes UI
* glitches that occur when a QueryRestulsEditor is hidden then unhidden while it is running a query. * glitches that occur when a QueryRestulsEditor is hidden then unhidden while it is running a query.
* Detect Changes has been moved to editDataGridPanel version of this.
*/ */
refreshResultsets(): void { refreshDatasets(): void {
let tempRenderedDataSets = this.renderedDataSets; let tempRenderedDataSets = this.renderedDataSets;
this.renderedDataSets = []; this.renderedDataSets = [];
this._cd.detectChanges();
this.renderedDataSets = tempRenderedDataSets; this.renderedDataSets = tempRenderedDataSets;
this._cd.detectChanges();
} }
getSelectedRangeUnderMessages(): Selection { getSelectedRangeUnderMessages(): Selection {
@@ -481,8 +471,8 @@ export abstract class GridParentComponent {
} }
selectAllMessages(): void { selectAllMessages(): void {
let msgEl = this._el.nativeElement.querySelector('#messages'); let msgEl = this.nativeElement.querySelector('#messages');
this.selectElementContents(msgEl); this.selectElementContents(<HTMLElement>msgEl);
} }
selectElementContents(el: HTMLElement): void { selectElementContents(el: HTMLElement): void {
@@ -513,10 +503,15 @@ export abstract class GridParentComponent {
const self = this; const self = this;
setTimeout(() => { setTimeout(() => {
for (let grid of self.renderedDataSets) { for (let grid of self.renderedDataSets) {
grid.resized.emit(); grid.resized.fire();
} }
}); });
} }
// Private Helper Functions //////////////////////////////////////////////////////////////////////////// /**
* used to render the native element into the container.
* */
public render(container: HTMLElement): void {
container.appendChild(this.nativeElement);
}
} }

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
.editdata-component * { .editdatagridpanel * {
box-sizing: border-box; box-sizing: border-box;
} }

View File

@@ -149,7 +149,7 @@ export class DataService {
this._queryModel.copyResults(this._uri, selection, batchId, resultId, includeHeaders); this._queryModel.copyResults(this._uri, selection, batchId, resultId, includeHeaders);
} }
onAngularLoaded(): void { onLoaded(): void {
this._queryModel.onAngularLoaded(this._uri); this._queryModel.onLoaded(this._uri);
} }
} }

View File

@@ -3,8 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { ISlickColumn, VirtualizedCollection } from 'angular2-slickgrid'; import { VirtualizedCollection, ISlickColumn } from 'sql/base/browser/ui/table/asyncDataView';
export interface IGridDataSet { export interface IGridDataSet {
dataRows: VirtualizedCollection<{}>; dataRows: VirtualizedCollection<{}>;
columnDefinitions: ISlickColumn<any>[]; columnDefinitions: ISlickColumn<any>[];

View File

@@ -16,12 +16,6 @@ export interface IQueryComponentParams extends IBootstrapParams {
onRestoreViewState: Event<void>; onRestoreViewState: Event<void>;
} }
export interface IEditDataComponentParams extends IBootstrapParams {
dataService: DataService;
onSaveViewState: Event<void>;
onRestoreViewState: Event<void>;
}
export interface IDefaultComponentParams extends IBootstrapParams { export interface IDefaultComponentParams extends IBootstrapParams {
connection: IConnectionProfile; connection: IConnectionProfile;
ownerUri: string; ownerUri: string;

View File

@@ -1192,6 +1192,7 @@ declare namespace Slick {
public onHeaderContextMenu: Slick.Event<OnHeaderContextMenuEventArgs<T>>; public onHeaderContextMenu: Slick.Event<OnHeaderContextMenuEventArgs<T>>;
public onHeaderClick: Slick.Event<OnHeaderClickEventArgs<T>>; public onHeaderClick: Slick.Event<OnHeaderClickEventArgs<T>>;
public onHeaderCellRendered: Slick.Event<OnHeaderCellRenderedEventArgs<T>>; public onHeaderCellRendered: Slick.Event<OnHeaderCellRenderedEventArgs<T>>;
public onBeforeAppendCell: Slick.Event<OnBeforeAppendCellArgs<T>>;
public onBeforeHeaderCellDestroy: Slick.Event<OnBeforeHeaderCellDestroyEventArgs<T>>; public onBeforeHeaderCellDestroy: Slick.Event<OnBeforeHeaderCellDestroyEventArgs<T>>;
public onHeaderRowCellRendered: Slick.Event<OnHeaderRowCellRenderedEventArgs<T>>; public onHeaderRowCellRendered: Slick.Event<OnHeaderRowCellRenderedEventArgs<T>>;
public onBeforeHeaderRowCellDestroy: Slick.Event<OnBeforeHeaderRowCellDestroyEventArgs<T>>; public onBeforeHeaderRowCellDestroy: Slick.Event<OnBeforeHeaderRowCellDestroyEventArgs<T>>;
@@ -1305,6 +1306,11 @@ declare namespace Slick {
cell: number; cell: number;
} }
export interface OnBeforeAppendCellArgs<T extends SlickData> extends GridEventArgs<T> {
row: number;
cell: number;
}
export interface OnBeforeDestroyEventArgs<T extends SlickData> extends GridEventArgs<T> { export interface OnBeforeDestroyEventArgs<T extends SlickData> extends GridEventArgs<T> {
} }

View File

@@ -726,10 +726,6 @@ angular2-grid@2.0.6:
resolved "https://registry.yarnpkg.com/angular2-grid/-/angular2-grid-2.0.6.tgz#01fe225dc13b2822370b6c61f9a6913b3a26f989" resolved "https://registry.yarnpkg.com/angular2-grid/-/angular2-grid-2.0.6.tgz#01fe225dc13b2822370b6c61f9a6913b3a26f989"
integrity sha1-Af4iXcE7KCI3C2xh+aaROzom+Yk= integrity sha1-Af4iXcE7KCI3C2xh+aaROzom+Yk=
"angular2-slickgrid@github:Microsoft/angular2-slickgrid#1.4.6":
version "1.4.6"
resolved "https://codeload.github.com/Microsoft/angular2-slickgrid/tar.gz/09579fdc90b1ec469578ec1040d1515585ce2a11"
ansi-colors@^1.0.1: ansi-colors@^1.0.1:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9"