Refactor results grid (#2147)

* got a basic ui

* working on message panel

* done with messages moving to grids

* formatting

* working on multiple grids

* it does work

* styling

* formatting

* formatting

* fixed reset methods

* moved for scrollable

* formatting

* fixing scrolling

* making progress

* formatting

* fixed scrolling

* fix horizontal scrolling and size

* fix columns for tables

* integrate view item

* implementing heightmap scrolling

* add context menu and fix scrolling

* formatting

* revert slickgrid

* add actions to message pane

* formatting

* formatting

* bottom padding for tables

* minimized and maximized table actions

* add timestamp

* added batch start message  with selection

* updating

* formatting

* formatting

* fix execution time

* formatting

* fix problems

* fix rendering issues, add icons

* formatting

* formatting

* added commit change

* fix performance, message scrolling, etc

* formatting

* formatting

* fixing performance

* formatting

* update package

* tring to fix bugs

* reworking

* the problem is the 1st sash is always the first sash visible

* remove resizing from grid panels

* add missing files

* trying to get edit to work

* fix editdata

* formatting

* update angular2-slickgrid
This commit is contained in:
Anthony Dresser
2018-08-23 12:32:47 -07:00
committed by GitHub
parent 84da9d289b
commit befa34790f
42 changed files with 3475 additions and 1413 deletions

View File

@@ -0,0 +1,215 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface IObservableCollection<T> {
getLength(): number;
at(index: number): T;
getRange(start: number, end: number): T[];
setCollectionChangedCallback(callback: (change: CollectionChange, startIndex: number, count: number) => void): void;
}
export interface IGridDataRow {
row?: number;
values: any[];
}
export enum CollectionChange {
ItemsReplaced
}
class LoadCancellationToken {
isCancelled: boolean;
}
class DataWindow<TData> {
private _dataSourceLength: number;
private _data: TData[];
private _length: number = 0;
private _offsetFromDataSource: number = -1;
private loadFunction: (offset: number, count: number) => Thenable<TData[]>;
private lastLoadCancellationToken: LoadCancellationToken;
private loadCompleteCallback: (start: number, end: number) => void;
private placeholderItemGenerator: (index: number) => TData;
constructor(dataSourceLength: number,
loadFunction: (offset: number, count: number) => Thenable<TData[]>,
placeholderItemGenerator: (index: number) => TData,
loadCompleteCallback: (start: number, end: number) => void) {
this._dataSourceLength = dataSourceLength;
this.loadFunction = loadFunction;
this.placeholderItemGenerator = placeholderItemGenerator;
this.loadCompleteCallback = loadCompleteCallback;
}
getStartIndex(): number {
return this._offsetFromDataSource;
}
getEndIndex(): number {
return this._offsetFromDataSource + this._length;
}
contains(dataSourceIndex: number): boolean {
return dataSourceIndex >= this.getStartIndex() && dataSourceIndex < this.getEndIndex();
}
getItem(index: number): TData {
if (!this._data) {
return this.placeholderItemGenerator(index);
}
return this._data[index - this._offsetFromDataSource];
}
positionWindow(offset: number, length: number): void {
this._offsetFromDataSource = offset;
this._length = length;
this._data = undefined;
if (this.lastLoadCancellationToken) {
this.lastLoadCancellationToken.isCancelled = true;
}
if (length === 0) {
return;
}
let cancellationToken = new LoadCancellationToken();
this.lastLoadCancellationToken = cancellationToken;
this.loadFunction(offset, length).then(data => {
if (!cancellationToken.isCancelled) {
this._data = data;
this.loadCompleteCallback(this._offsetFromDataSource, this._offsetFromDataSource + this._length);
}
});
}
}
export class VirtualizedCollection<TData> implements IObservableCollection<TData> {
private _length: number;
private _windowSize: number;
private _bufferWindowBefore: DataWindow<TData>;
private _window: DataWindow<TData>;
private _bufferWindowAfter: DataWindow<TData>;
private collectionChangedCallback: (change: CollectionChange, startIndex: number, count: number) => void;
constructor(windowSize: number,
length: number,
loadFn: (offset: number, count: number) => Thenable<TData[]>,
private _placeHolderGenerator: (index: number) => TData) {
this._windowSize = windowSize;
this._length = length;
let loadCompleteCallback = (start: number, end: number) => {
if (this.collectionChangedCallback) {
this.collectionChangedCallback(CollectionChange.ItemsReplaced, start, end - start);
}
};
this._bufferWindowBefore = new DataWindow(length, loadFn, _placeHolderGenerator, loadCompleteCallback);
this._window = new DataWindow(length, loadFn, _placeHolderGenerator, loadCompleteCallback);
this._bufferWindowAfter = new DataWindow(length, loadFn, _placeHolderGenerator, loadCompleteCallback);
}
setCollectionChangedCallback(callback: (change: CollectionChange, startIndex: number, count: number) => void): void {
this.collectionChangedCallback = callback;
}
getLength(): number {
return this._length;
}
at(index: number): TData {
return this.getRange(index, index + 1)[0];
}
getRange(start: number, end: number): TData[] {
// current data may contain placeholders
let currentData = this.getRangeFromCurrent(start, end);
// only shift window and make promise of refreshed data in following condition:
if (start < this._bufferWindowBefore.getStartIndex() || end > this._bufferWindowAfter.getEndIndex()) {
// jump, reset
this.resetWindowsAroundIndex(start);
} else if (end <= this._bufferWindowBefore.getEndIndex()) {
// scroll up, shift up
let windowToRecycle = this._bufferWindowAfter;
this._bufferWindowAfter = this._window;
this._window = this._bufferWindowBefore;
this._bufferWindowBefore = windowToRecycle;
let newWindowOffset = Math.max(0, this._window.getStartIndex() - this._windowSize);
this._bufferWindowBefore.positionWindow(newWindowOffset, this._window.getStartIndex() - newWindowOffset);
} else if (start >= this._bufferWindowAfter.getStartIndex()) {
// scroll down, shift down
let windowToRecycle = this._bufferWindowBefore;
this._bufferWindowBefore = this._window;
this._window = this._bufferWindowAfter;
this._bufferWindowAfter = windowToRecycle;
let newWindowOffset = Math.min(this._window.getStartIndex() + this._windowSize, this._length);
let newWindowLength = Math.min(this._length - newWindowOffset, this._windowSize);
this._bufferWindowAfter.positionWindow(newWindowOffset, newWindowLength);
}
return currentData;
}
private getRangeFromCurrent(start: number, end: number): TData[] {
let currentData = [];
for (let i = 0; i < end - start; i++) {
currentData.push(this.getDataFromCurrent(start + i));
}
return currentData;
}
private getDataFromCurrent(index: number): TData {
if (this._bufferWindowBefore.contains(index)) {
return this._bufferWindowBefore.getItem(index);
} else if (this._bufferWindowAfter.contains(index)) {
return this._bufferWindowAfter.getItem(index);
} else if (this._window.contains(index)) {
return this._window.getItem(index);
}
return this._placeHolderGenerator(index);
}
private resetWindowsAroundIndex(index: number): void {
let bufferWindowBeforeStart = Math.max(0, index - this._windowSize * 1.5);
let bufferWindowBeforeEnd = Math.max(0, index - this._windowSize / 2);
this._bufferWindowBefore.positionWindow(bufferWindowBeforeStart, bufferWindowBeforeEnd - bufferWindowBeforeStart);
let mainWindowStart = bufferWindowBeforeEnd;
let mainWindowEnd = Math.min(mainWindowStart + this._windowSize, this._length);
this._window.positionWindow(mainWindowStart, mainWindowEnd - mainWindowStart);
let bufferWindowAfterStart = mainWindowEnd;
let bufferWindowAfterEnd = Math.min(bufferWindowAfterStart + this._windowSize, this._length);
this._bufferWindowAfter.positionWindow(bufferWindowAfterStart, bufferWindowAfterEnd - bufferWindowAfterStart);
}
}
export class AsyncDataProvider<TData extends IGridDataRow> implements Slick.DataProvider<TData> {
constructor(private dataRows: IObservableCollection<TData>) { }
public getLength(): number {
return this.dataRows ? this.dataRows.getLength() : 0;
}
public getItem(index: number): TData {
return !this.dataRows ? undefined : this.dataRows.at(index);
}
public getRange(start: number, end: number): TData[] {
return !this.dataRows ? undefined : this.dataRows.getRange(start, end);
}
}

View File

@@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as DOM from 'vs/base/browser/dom';
import { StandardMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { mixin } from 'vs/base/common/objects';
const SCROLL_WHEEL_SENSITIVITY = 50;
export interface IMouseWheelSupportOptions {
scrollSpeed?: number;
}
const defaultOptions: IMouseWheelSupportOptions = {
scrollSpeed: SCROLL_WHEEL_SENSITIVITY
};
export class MouseWheelSupport implements Slick.Plugin<any> {
private viewport: HTMLElement;
private canvas: HTMLElement;
private options: IMouseWheelSupportOptions;
private _disposables: IDisposable[] = [];
constructor(options: IMouseWheelSupportOptions = {}) {
this.options = mixin(options, defaultOptions);
}
public init(grid: Slick.Grid<any>): void {
this.canvas = grid.getCanvasNode();
this.viewport = this.canvas.parentElement;
let onMouseWheel = (browserEvent: MouseWheelEvent) => {
let e = new StandardMouseWheelEvent(browserEvent);
this._onMouseWheel(e);
};
this._disposables.push(DOM.addDisposableListener(this.viewport, 'mousewheel', onMouseWheel));
this._disposables.push(DOM.addDisposableListener(this.viewport, 'DOMMouseScroll', onMouseWheel));
}
private _onMouseWheel(event: StandardMouseWheelEvent) {
const scrollHeight = this.canvas.clientHeight;
const height = this.viewport.clientHeight;
const scrollDown = Math.sign(event.deltaY) === -1;
if (scrollDown) {
if ((this.viewport.scrollTop - (event.deltaY * this.options.scrollSpeed)) + height > scrollHeight) {
this.viewport.scrollTop = scrollHeight - height;
this.viewport.dispatchEvent(new Event('scroll'));
} else {
this.viewport.scrollTop = this.viewport.scrollTop - (event.deltaY * this.options.scrollSpeed);
this.viewport.dispatchEvent(new Event('scroll'));
event.stopPropagation();
event.preventDefault();
}
} else {
if ((this.viewport.scrollTop - (event.deltaY * this.options.scrollSpeed)) < 0) {
this.viewport.scrollTop = 0;
this.viewport.dispatchEvent(new Event('scroll'));
} else {
this.viewport.scrollTop = this.viewport.scrollTop - (event.deltaY * this.options.scrollSpeed);
this.viewport.dispatchEvent(new Event('scroll'));
event.stopPropagation();
event.preventDefault();
}
}
}
destroy() {
dispose(this._disposables);
}
}

View File

@@ -11,9 +11,17 @@ import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
import * as DOM from 'vs/base/browser/dom';
import { Color } from 'vs/base/common/color';
import { mixin } from 'vs/base/common/objects';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { Widget } from 'vs/base/browser/ui/widget';
import { isArray, isBoolean } from 'vs/base/common/types';
import { Event, Emitter } from 'vs/base/common/event';
import { range } from 'vs/base/common/arrays';
export interface ITableContextMenuEvent {
anchor: HTMLElement | { x: number, y: number };
cell?: { row: number, cell: number };
}
export interface ITableStyles extends IListStyles {
tableHeaderBackground?: Color;
@@ -27,30 +35,46 @@ function getDefaultOptions<T>(): Slick.GridOptions<T> {
};
}
export class Table<T extends Slick.SlickData> extends Widget implements IThemable {
export interface ITableSorter<T> {
sort(args: Slick.OnSortEventArgs<T>);
}
export interface ITableConfiguration<T> {
dataProvider?: Slick.DataProvider<T> | Array<T>;
columns?: Slick.Column<T>[];
sorter?: ITableSorter<T>;
}
export class Table<T extends Slick.SlickData> extends Widget implements IThemable, IDisposable {
private styleElement: HTMLStyleElement;
private idPrefix: string;
private _grid: Slick.Grid<T>;
private _columns: Slick.Column<T>[];
private _data: TableDataView<T>;
private _data: Slick.DataProvider<T>;
private _sorter: ITableSorter<T>;
private _autoscroll: boolean;
private _onRowCountChangeListener: IDisposable;
private _container: HTMLElement;
private _tableContainer: HTMLElement;
private _classChangeTimeout: number;
constructor(parent: HTMLElement, data?: Array<T> | TableDataView<T>, columns?: Slick.Column<T>[], options?: Slick.GridOptions<T>) {
private _disposables: IDisposable[] = [];
private _onContextMenu = new Emitter<ITableContextMenuEvent>();
public readonly onContextMenu: Event<ITableContextMenuEvent> = this._onContextMenu.event;
constructor(parent: HTMLElement, configuration?: ITableConfiguration<T>, options?: Slick.GridOptions<T>) {
super();
if (data instanceof TableDataView) {
this._data = data;
if (!configuration || isArray(configuration.dataProvider)) {
this._data = new TableDataView<T>(configuration && configuration.dataProvider as Array<T>);
} else {
this._data = new TableDataView<T>(data);
this._data = configuration.dataProvider;
}
if (columns) {
this._columns = columns;
if (configuration.columns) {
this._columns = configuration.columns;
} else {
this._columns = new Array<Slick.Column<T>>();
}
@@ -81,15 +105,33 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
this._grid = new Slick.Grid<T>(this._tableContainer, this._data, this._columns, newOptions);
this.idPrefix = this._tableContainer.classList[0];
DOM.addClass(this._container, this.idPrefix);
this._onRowCountChangeListener = this._data.onRowCountChange(() => this._handleRowCountChange());
this._grid.onSort.subscribe((e, args) => {
this._data.sort(args);
this._grid.invalidate();
this._grid.render();
if (configuration.sorter) {
this._sorter = configuration.sorter;
this._grid.onSort.subscribe((e, args) => {
this._sorter.sort(args);
this._grid.invalidate();
this._grid.render();
});
}
this._grid.onContextMenu.subscribe((e: JQuery.Event) => {
const originalEvent = e.originalEvent;
const cell = this._grid.getCellFromEvent(originalEvent);
const anchor = originalEvent instanceof MouseEvent ? { x: originalEvent.x, y: originalEvent.y } : originalEvent.srcElement as HTMLElement;
this._onContextMenu.fire({ anchor, cell });
});
}
private _handleRowCountChange() {
public dispose() {
dispose(this._disposables);
}
public invalidateRows(rows: number[], keepEditor: boolean) {
this._grid.invalidateRows(rows, keepEditor);
this._grid.render();
}
public updateRowCount() {
this._grid.updateRowCount();
this._grid.render();
if (this._autoscroll) {
@@ -113,20 +155,22 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
} else {
this._data = new TableDataView<T>(data);
}
this._onRowCountChangeListener.dispose();
this._grid.setData(this._data, true);
this._onRowCountChangeListener = this._data.onRowCountChange(() => this._handleRowCountChange());
}
get columns(): Slick.Column<T>[] {
return this._grid.getColumns();
}
setSelectedRows(rows: number[]) {
this._grid.setSelectedRows(rows);
public setSelectedRows(rows: number[] | boolean) {
if (isBoolean(rows)) {
this._grid.setSelectedRows(range(this._grid.getDataLength()));
} else {
this._grid.setSelectedRows(rows);
}
}
getSelectedRows(): number[] {
public getSelectedRows(): number[] {
return this._grid.getSelectedRows();
}
@@ -143,21 +187,6 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
};
}
onContextMenu(fn: (e: Slick.EventData, data: Slick.OnContextMenuEventArgs<T>) => any): IDisposable;
onContextMenu(fn: (e: DOMEvent, data: Slick.OnContextMenuEventArgs<T>) => any): IDisposable;
onContextMenu(fn: any): IDisposable {
this._grid.onContextMenu.subscribe(fn);
return {
dispose: () => {
this._grid.onContextMenu.unsubscribe(fn);
}
};
}
getCellFromEvent(e: DOMEvent): Slick.Cell {
return this._grid.getCellFromEvent(e);
}
setSelectionModel(model: Slick.SelectionModel<T, Array<Slick.Range>>) {
this._grid.setSelectionModel(model);
}
@@ -194,7 +223,7 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
this._tableContainer.style.width = sizing.width + 'px';
this._tableContainer.style.height = sizing.height + 'px';
} else {
if (orientation === Orientation.HORIZONTAL) {
if (orientation === Orientation.VERTICAL) {
this._container.style.width = '100%';
this._container.style.height = sizing + 'px';
this._tableContainer.style.width = '100%';
@@ -207,6 +236,7 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
}
}
this.resizeCanvas();
this.autosizeColumns();
}
autosizeColumns() {

View File

@@ -25,7 +25,7 @@ export class TableBasicView<T> extends View {
super(undefined, viewOpts);
this._container = document.createElement('div');
this._container.className = 'table-view';
this._table = new Table<T>(this._container, data, columns, tableOpts);
this._table = new Table<T>(this._container, { dataProvider: data, columns }, tableOpts);
}
public get table(): Table<T> {
@@ -59,7 +59,7 @@ export class TableHeaderView<T> extends HeaderView {
super(undefined, viewOpts);
this._container = document.createElement('div');
this._container.className = 'table-view';
this._table = new Table<T>(this._container, data, columns, tableOpts);
this._table = new Table<T>(this._container, { dataProvider: data, columns }, tableOpts);
}
public get table(): Table<T> {
@@ -76,7 +76,7 @@ export class TableHeaderView<T> extends HeaderView {
}
protected layoutBody(size: number): void {
this._table.layout(size, Orientation.HORIZONTAL);
this._table.layout(size, Orientation.VERTICAL);
}
focus(): void {
@@ -99,7 +99,7 @@ export class TableCollapsibleView<T> extends AbstractCollapsibleView {
super(undefined, viewOpts);
this._container = document.createElement('div');
this._container.className = 'table-view';
this._table = new Table<T>(this._container, data, columns, tableOpts);
this._table = new Table<T>(this._container, { dataProvider: data, columns }, tableOpts);
}
public render(container: HTMLElement, orientation: Orientation): void {
@@ -143,6 +143,6 @@ export class TableCollapsibleView<T> extends AbstractCollapsibleView {
}
protected layoutBody(size: number): void {
this._table.layout(size, Orientation.HORIZONTAL);
this._table.layout(size, Orientation.VERTICAL);
}
}