mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
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:
4
extensions/insights-default/yarn.lock
Normal file
4
extensions/insights-default/yarn.lock
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
4
extensions/markdown-basics/yarn.lock
Normal file
4
extensions/markdown-basics/yarn.lock
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
"@angular/router": "~4.1.3",
|
"@angular/router": "~4.1.3",
|
||||||
"@angular/upgrade": "~4.1.3",
|
"@angular/upgrade": "~4.1.3",
|
||||||
"angular2-grid": "2.0.6",
|
"angular2-grid": "2.0.6",
|
||||||
"angular2-slickgrid": "github:Microsoft/angular2-slickgrid#1.4.3",
|
"angular2-slickgrid": "github:Microsoft/angular2-slickgrid#1.4.4",
|
||||||
"applicationinsights": "0.18.0",
|
"applicationinsights": "0.18.0",
|
||||||
"chart.js": "^2.6.0",
|
"chart.js": "^2.6.0",
|
||||||
"fast-plist": "0.1.2",
|
"fast-plist": "0.1.2",
|
||||||
|
|||||||
@@ -4,11 +4,9 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { IThemable } from 'vs/platform/theme/common/styler';
|
import { IThemable } from 'vs/platform/theme/common/styler';
|
||||||
import * as objects from 'sql/base/common/objects';
|
|
||||||
import { Event, Emitter } from 'vs/base/common/event';
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
import { Dimension } from 'vs/base/browser/dom';
|
import { Dimension, EventType } from 'vs/base/browser/dom';
|
||||||
import { $, Builder } from 'vs/base/browser/builder';
|
import { $, Builder } from 'vs/base/browser/builder';
|
||||||
import { EventType } from 'vs/base/browser/dom';
|
|
||||||
import { IAction } from 'vs/base/common/actions';
|
import { IAction } from 'vs/base/common/actions';
|
||||||
import { IActionOptions, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
import { IActionOptions, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||||
@@ -17,12 +15,16 @@ import './panelStyles';
|
|||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
|
||||||
export interface IPanelStyles {
|
export interface IPanelStyles {
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPanelOptions {
|
||||||
|
showHeaderWhenSingleView?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPanelView {
|
export interface IPanelView {
|
||||||
render(container: HTMLElement): void;
|
render(container: HTMLElement): void;
|
||||||
layout(dimension: Dimension): void;
|
layout(dimension: Dimension): void;
|
||||||
|
remove?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPanelTab {
|
export interface IPanelTab {
|
||||||
@@ -36,6 +38,10 @@ interface IInternalPanelTab extends IPanelTab {
|
|||||||
label: Builder;
|
label: Builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultOptions: IPanelOptions = {
|
||||||
|
showHeaderWhenSingleView: true
|
||||||
|
};
|
||||||
|
|
||||||
export type PanelTabIdentifier = string;
|
export type PanelTabIdentifier = string;
|
||||||
|
|
||||||
export class TabbedPanel extends Disposable implements IThemable {
|
export class TabbedPanel extends Disposable implements IThemable {
|
||||||
@@ -49,11 +55,12 @@ export class TabbedPanel extends Disposable implements IThemable {
|
|||||||
private _actionbar: ActionBar;
|
private _actionbar: ActionBar;
|
||||||
private _currentDimensions: Dimension;
|
private _currentDimensions: Dimension;
|
||||||
private _collapsed = false;
|
private _collapsed = false;
|
||||||
|
private _headerVisible: boolean;
|
||||||
|
|
||||||
private _onTabChange = new Emitter<PanelTabIdentifier>();
|
private _onTabChange = new Emitter<PanelTabIdentifier>();
|
||||||
public onTabChange: Event<PanelTabIdentifier> = this._onTabChange.event;
|
public onTabChange: Event<PanelTabIdentifier> = this._onTabChange.event;
|
||||||
|
|
||||||
constructor(private container: HTMLElement) {
|
constructor(private container: HTMLElement, private options: IPanelOptions = defaultOptions) {
|
||||||
super();
|
super();
|
||||||
this.$parent = this._register($('.tabbedPanel'));
|
this.$parent = this._register($('.tabbedPanel'));
|
||||||
this.$parent.appendTo(container);
|
this.$parent.appendTo(container);
|
||||||
@@ -65,7 +72,12 @@ export class TabbedPanel extends Disposable implements IThemable {
|
|||||||
let actionbarcontainer = $('.title-actions');
|
let actionbarcontainer = $('.title-actions');
|
||||||
this._actionbar = new ActionBar(actionbarcontainer.getHTMLElement());
|
this._actionbar = new ActionBar(actionbarcontainer.getHTMLElement());
|
||||||
this.$header.append(actionbarcontainer);
|
this.$header.append(actionbarcontainer);
|
||||||
this.$parent.append(this.$header);
|
if (options.showHeaderWhenSingleView) {
|
||||||
|
this._headerVisible = true;
|
||||||
|
this.$parent.append(this.$header);
|
||||||
|
} else {
|
||||||
|
this._headerVisible = false;
|
||||||
|
}
|
||||||
this.$body = $('tabBody');
|
this.$body = $('tabBody');
|
||||||
this.$body.attr('role', 'tabpanel');
|
this.$body.attr('role', 'tabpanel');
|
||||||
this.$body.attr('tabindex', '0');
|
this.$body.attr('tabindex', '0');
|
||||||
@@ -73,12 +85,16 @@ export class TabbedPanel extends Disposable implements IThemable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public pushTab(tab: IPanelTab): PanelTabIdentifier {
|
public pushTab(tab: IPanelTab): PanelTabIdentifier {
|
||||||
let internalTab = objects.clone(tab) as IInternalPanelTab;
|
let internalTab = tab as IInternalPanelTab;
|
||||||
this._tabMap.set(tab.identifier, internalTab);
|
this._tabMap.set(tab.identifier, internalTab);
|
||||||
this._createTab(internalTab);
|
this._createTab(internalTab);
|
||||||
if (!this._shownTab) {
|
if (!this._shownTab) {
|
||||||
this.showTab(tab.identifier);
|
this.showTab(tab.identifier);
|
||||||
}
|
}
|
||||||
|
if (this._tabMap.size > 1 && !this._headerVisible) {
|
||||||
|
this.$parent.append(this.$header, 0);
|
||||||
|
this._headerVisible = true;
|
||||||
|
}
|
||||||
return tab.identifier as PanelTabIdentifier;
|
return tab.identifier as PanelTabIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +155,11 @@ export class TabbedPanel extends Disposable implements IThemable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public removeTab(tab: PanelTabIdentifier) {
|
public removeTab(tab: PanelTabIdentifier) {
|
||||||
|
let actualTab = this._tabMap.get(tab);
|
||||||
|
actualTab.header.destroy();
|
||||||
|
if (actualTab.view.remove) {
|
||||||
|
actualTab.view.remove();
|
||||||
|
}
|
||||||
this._tabMap.get(tab).header.destroy();
|
this._tabMap.get(tab).header.destroy();
|
||||||
this._tabMap.delete(tab);
|
this._tabMap.delete(tab);
|
||||||
}
|
}
|
||||||
@@ -151,8 +172,9 @@ export class TabbedPanel extends Disposable implements IThemable {
|
|||||||
this._currentDimensions = dimension;
|
this._currentDimensions = dimension;
|
||||||
this.$header.style('width', dimension.width + 'px');
|
this.$header.style('width', dimension.width + 'px');
|
||||||
this.$body.style('width', dimension.width + 'px');
|
this.$body.style('width', dimension.width + 'px');
|
||||||
this.$body.style('height', (dimension.height - this.headersize) + 'px');
|
const bodyHeight = dimension.height - (this._headerVisible ? this.headersize : 0);
|
||||||
this._layoutCurrentTab(new Dimension(dimension.width, dimension.height - this.headersize));
|
this.$body.style('height', bodyHeight + 'px');
|
||||||
|
this._layoutCurrentTab(new Dimension(dimension.width, bodyHeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _layoutCurrentTab(dimension: Dimension): void {
|
private _layoutCurrentTab(dimension: Dimension): void {
|
||||||
|
|||||||
212
src/sql/base/browser/ui/scrollableSplitview/heightMap.ts
Normal file
212
src/sql/base/browser/ui/scrollableSplitview/heightMap.ts
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { INextIterator } from 'vs/base/common/iterator';
|
||||||
|
|
||||||
|
export interface IView {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IViewItem {
|
||||||
|
view: IView;
|
||||||
|
top: number;
|
||||||
|
height: number;
|
||||||
|
width: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HeightMap {
|
||||||
|
|
||||||
|
private heightMap: IViewItem[];
|
||||||
|
private indexes: { [item: string]: number; };
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.heightMap = [];
|
||||||
|
this.indexes = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public getContentHeight(): number {
|
||||||
|
let last = this.heightMap[this.heightMap.length - 1];
|
||||||
|
return !last ? 0 : last.top + last.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onInsertItems(iterator: INextIterator<IViewItem>, afterItemId: string = null): number {
|
||||||
|
let viewItem: IViewItem;
|
||||||
|
let i: number, j: number;
|
||||||
|
let totalSize: number;
|
||||||
|
let sizeDiff = 0;
|
||||||
|
|
||||||
|
if (afterItemId === null) {
|
||||||
|
i = 0;
|
||||||
|
totalSize = 0;
|
||||||
|
} else {
|
||||||
|
i = this.indexes[afterItemId] + 1;
|
||||||
|
viewItem = this.heightMap[i - 1];
|
||||||
|
|
||||||
|
if (!viewItem) {
|
||||||
|
console.error('view item doesnt exist');
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalSize = viewItem.top + viewItem.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
let boundSplice = this.heightMap.splice.bind(this.heightMap, i, 0);
|
||||||
|
|
||||||
|
let itemsToInsert: IViewItem[] = [];
|
||||||
|
|
||||||
|
while (viewItem = iterator.next()) {
|
||||||
|
viewItem.top = totalSize + sizeDiff;
|
||||||
|
|
||||||
|
this.indexes[viewItem.view.id] = i++;
|
||||||
|
itemsToInsert.push(viewItem);
|
||||||
|
sizeDiff += viewItem.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
boundSplice.apply(this.heightMap, itemsToInsert);
|
||||||
|
|
||||||
|
for (j = i; j < this.heightMap.length; j++) {
|
||||||
|
viewItem = this.heightMap[j];
|
||||||
|
viewItem.top += sizeDiff;
|
||||||
|
this.indexes[viewItem.view.id] = j;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (j = itemsToInsert.length - 1; j >= 0; j--) {
|
||||||
|
this.onInsertItem(itemsToInsert[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (j = this.heightMap.length - 1; j >= i; j--) {
|
||||||
|
this.onRefreshItem(this.heightMap[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sizeDiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onInsertItem(item: IViewItem): void {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contiguous items
|
||||||
|
public onRemoveItems(iterator: INextIterator<string>): void {
|
||||||
|
let itemId: string;
|
||||||
|
let viewItem: IViewItem;
|
||||||
|
let startIndex: number = null;
|
||||||
|
let i: number;
|
||||||
|
let sizeDiff = 0;
|
||||||
|
|
||||||
|
while (itemId = iterator.next()) {
|
||||||
|
i = this.indexes[itemId];
|
||||||
|
viewItem = this.heightMap[i];
|
||||||
|
|
||||||
|
if (!viewItem) {
|
||||||
|
console.error('view item doesnt exist');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sizeDiff -= viewItem.height;
|
||||||
|
delete this.indexes[itemId];
|
||||||
|
this.onRemoveItem(viewItem);
|
||||||
|
|
||||||
|
if (startIndex === null) {
|
||||||
|
startIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sizeDiff === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.heightMap.splice(startIndex, i - startIndex + 1);
|
||||||
|
|
||||||
|
for (i = startIndex; i < this.heightMap.length; i++) {
|
||||||
|
viewItem = this.heightMap[i];
|
||||||
|
viewItem.top += sizeDiff;
|
||||||
|
this.indexes[viewItem.view.id] = i;
|
||||||
|
this.onRefreshItem(viewItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public onRemoveItem(item: IViewItem): void {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
public onRefreshItem(item: IViewItem, needsRender: boolean = false): void {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updateSize(item: string, size: number): void {
|
||||||
|
let i = this.indexes[item];
|
||||||
|
|
||||||
|
let viewItem = this.heightMap[i];
|
||||||
|
|
||||||
|
viewItem.height = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updateTop(item: string, top: number): void {
|
||||||
|
let i = this.indexes[item];
|
||||||
|
|
||||||
|
let viewItem = this.heightMap[i];
|
||||||
|
|
||||||
|
viewItem.top = top;
|
||||||
|
}
|
||||||
|
|
||||||
|
public itemsCount(): number {
|
||||||
|
return this.heightMap.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public itemAt(position: number): string {
|
||||||
|
return this.heightMap[this.indexAt(position)].view.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public withItemsInRange(start: number, end: number, fn: (item: string) => void): void {
|
||||||
|
start = this.indexAt(start);
|
||||||
|
end = this.indexAt(end);
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
fn(this.heightMap[i].view.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public indexAt(position: number): number {
|
||||||
|
let left = 0;
|
||||||
|
let right = this.heightMap.length;
|
||||||
|
let center: number;
|
||||||
|
let item: IViewItem;
|
||||||
|
|
||||||
|
// Binary search
|
||||||
|
while (left < right) {
|
||||||
|
center = Math.floor((left + right) / 2);
|
||||||
|
item = this.heightMap[center];
|
||||||
|
|
||||||
|
if (position < item.top) {
|
||||||
|
right = center;
|
||||||
|
} else if (position >= item.top + item.height) {
|
||||||
|
if (left === center) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
left = center;
|
||||||
|
} else {
|
||||||
|
return center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.heightMap.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public indexAfter(position: number): number {
|
||||||
|
return Math.min(this.indexAt(position) + 1, this.heightMap.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public itemAtIndex(index: number): IViewItem {
|
||||||
|
return this.heightMap[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public itemAfter(item: IViewItem): IViewItem {
|
||||||
|
return this.heightMap[this.indexes[item.view.id] + 1] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
this.heightMap = null;
|
||||||
|
this.indexes = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.monaco-scroll-split-view {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
@@ -0,0 +1,622 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 'vs/css!./scrollableSplitview';
|
||||||
|
import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { mapEvent, Emitter, Event, debounceEvent } from 'vs/base/common/event';
|
||||||
|
import * as types from 'vs/base/common/types';
|
||||||
|
import * as dom from 'vs/base/browser/dom';
|
||||||
|
import { clamp } from 'vs/base/common/numbers';
|
||||||
|
import { range, firstIndex } from 'vs/base/common/arrays';
|
||||||
|
import { Sash, Orientation, ISashEvent as IBaseSashEvent } from 'vs/base/browser/ui/sash/sash';
|
||||||
|
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||||
|
import { HeightMap, IView as HeightIView, IViewItem as HeightIViewItem } from './heightMap';
|
||||||
|
import { ArrayIterator } from 'vs/base/common/iterator';
|
||||||
|
import { mixin } from 'vs/base/common/objects';
|
||||||
|
export { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||||
|
|
||||||
|
export interface ISplitViewOptions {
|
||||||
|
orientation?: Orientation; // default Orientation.VERTICAL
|
||||||
|
enableResizing?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions: ISplitViewOptions = {
|
||||||
|
enableResizing: true
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IView extends HeightIView {
|
||||||
|
readonly minimumSize: number;
|
||||||
|
readonly maximumSize: number;
|
||||||
|
readonly onDidChange: Event<number | undefined>;
|
||||||
|
render(container: HTMLElement, orientation: Orientation): void;
|
||||||
|
layout(size: number, orientation: Orientation): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISashEvent {
|
||||||
|
sash: Sash;
|
||||||
|
start: number;
|
||||||
|
current: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IViewItem extends HeightIViewItem {
|
||||||
|
view: IView;
|
||||||
|
size: number;
|
||||||
|
container: HTMLElement;
|
||||||
|
disposable: IDisposable;
|
||||||
|
layout(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISashItem {
|
||||||
|
sash: Sash;
|
||||||
|
disposable: IDisposable;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISashDragState {
|
||||||
|
index: number;
|
||||||
|
start: number;
|
||||||
|
sizes: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
Idle,
|
||||||
|
Busy
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushToEnd<T>(arr: T[], value: T): T[] {
|
||||||
|
let didFindValue = false;
|
||||||
|
|
||||||
|
const result = arr.filter(v => {
|
||||||
|
if (v === value) {
|
||||||
|
didFindValue = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (didFindValue) {
|
||||||
|
result.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ScrollableSplitView extends HeightMap implements IDisposable {
|
||||||
|
|
||||||
|
private orientation: Orientation;
|
||||||
|
private el: HTMLElement;
|
||||||
|
private size = 0;
|
||||||
|
private contentSize = 0;
|
||||||
|
private viewItems: IViewItem[] = [];
|
||||||
|
private sashItems: ISashItem[] = [];
|
||||||
|
private sashDragState: ISashDragState;
|
||||||
|
private state: State = State.Idle;
|
||||||
|
private scrollable: ScrollableElement;
|
||||||
|
|
||||||
|
private options: ISplitViewOptions;
|
||||||
|
|
||||||
|
private dirtyState = false;
|
||||||
|
|
||||||
|
private lastRenderTop: number;
|
||||||
|
private lastRenderHeight: number;
|
||||||
|
|
||||||
|
private _onDidSashChange = new Emitter<void>();
|
||||||
|
readonly onDidSashChange = this._onDidSashChange.event;
|
||||||
|
private _onDidSashReset = new Emitter<void>();
|
||||||
|
readonly onDidSashReset = this._onDidSashReset.event;
|
||||||
|
|
||||||
|
get length(): number {
|
||||||
|
return this.viewItems.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(container: HTMLElement, options: ISplitViewOptions = {}) {
|
||||||
|
super();
|
||||||
|
this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
|
||||||
|
|
||||||
|
this.options = mixin(options, defaultOptions, false);
|
||||||
|
|
||||||
|
this.el = document.createElement('div');
|
||||||
|
this.scrollable = new ScrollableElement(this.el, {});
|
||||||
|
debounceEvent(this.scrollable.onScroll, (l, e) => e, 25)(e => {
|
||||||
|
this.render(e.scrollTop, e.height);
|
||||||
|
this.relayout();
|
||||||
|
});
|
||||||
|
let domNode = this.scrollable.getDomNode();
|
||||||
|
dom.addClass(this.el, 'monaco-scroll-split-view');
|
||||||
|
dom.addClass(domNode, 'monaco-split-view2');
|
||||||
|
dom.addClass(domNode, this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal');
|
||||||
|
container.appendChild(domNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
addViews(views: IView[], sizes: number[], index = this.viewItems.length): void {
|
||||||
|
if (this.state !== State.Idle) {
|
||||||
|
throw new Error('Cant modify splitview');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = State.Busy;
|
||||||
|
|
||||||
|
for (let i = 0; i < views.length; i++) {
|
||||||
|
let view = views[i], size = sizes[i];
|
||||||
|
|
||||||
|
// Add view
|
||||||
|
const container = dom.$('.split-view-view');
|
||||||
|
|
||||||
|
const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size));
|
||||||
|
const containerDisposable = toDisposable(() => {
|
||||||
|
if (container.parentElement) {
|
||||||
|
this.el.removeChild(container);
|
||||||
|
}
|
||||||
|
this.onRemoveItems(new ArrayIterator([item.view.id]));
|
||||||
|
});
|
||||||
|
const disposable = combinedDisposable([onChangeDisposable, containerDisposable]);
|
||||||
|
|
||||||
|
const layoutContainer = this.orientation === Orientation.VERTICAL
|
||||||
|
? size => item.container.style.height = `${item.size}px`
|
||||||
|
: size => item.container.style.width = `${item.size}px`;
|
||||||
|
|
||||||
|
const layout = () => {
|
||||||
|
layoutContainer(item.size);
|
||||||
|
item.view.layout(item.size, this.orientation);
|
||||||
|
};
|
||||||
|
|
||||||
|
size = Math.round(size);
|
||||||
|
const item: IViewItem = { view, container, size, layout, disposable, height: size, top: 0, width: 0 };
|
||||||
|
this.viewItems.splice(index, 0, item);
|
||||||
|
|
||||||
|
this.onInsertItems(new ArrayIterator([item]), index > 0 ? this.viewItems[index - 1].view.id : undefined);
|
||||||
|
|
||||||
|
// Add sash
|
||||||
|
if (this.options.enableResizing && this.viewItems.length > 1) {
|
||||||
|
const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
|
||||||
|
const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) };
|
||||||
|
const sash = new Sash(this.el, layoutProvider, { orientation });
|
||||||
|
const sashEventMapper = this.orientation === Orientation.VERTICAL
|
||||||
|
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY })
|
||||||
|
: (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX });
|
||||||
|
|
||||||
|
const onStart = mapEvent(sash.onDidStart, sashEventMapper);
|
||||||
|
const onStartDisposable = onStart(this.onSashStart, this);
|
||||||
|
const onChange = mapEvent(sash.onDidChange, sashEventMapper);
|
||||||
|
const onSashChangeDisposable = onChange(this.onSashChange, this);
|
||||||
|
const onEnd = mapEvent<void, void>(sash.onDidEnd, () => null);
|
||||||
|
const onEndDisposable = onEnd(() => this._onDidSashChange.fire());
|
||||||
|
const onDidReset = mapEvent<void, void>(sash.onDidReset, () => null);
|
||||||
|
const onDidResetDisposable = onDidReset(() => this._onDidSashReset.fire());
|
||||||
|
|
||||||
|
const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, onEndDisposable, onDidResetDisposable, sash]);
|
||||||
|
const sashItem: ISashItem = { sash, disposable };
|
||||||
|
|
||||||
|
this.sashItems.splice(index - 1, 0, sashItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
view.render(container, this.orientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.relayout(index);
|
||||||
|
this.state = State.Idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
addView(view: IView, size: number, index = this.viewItems.length): void {
|
||||||
|
if (this.state !== State.Idle) {
|
||||||
|
throw new Error('Cant modify splitview');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = State.Busy;
|
||||||
|
|
||||||
|
// Add view
|
||||||
|
const container = dom.$('.split-view-view');
|
||||||
|
|
||||||
|
const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size));
|
||||||
|
const containerDisposable = toDisposable(() => {
|
||||||
|
if (container.parentElement) {
|
||||||
|
this.el.removeChild(container);
|
||||||
|
}
|
||||||
|
this.onRemoveItems(new ArrayIterator([item.view.id]));
|
||||||
|
});
|
||||||
|
const disposable = combinedDisposable([onChangeDisposable, containerDisposable]);
|
||||||
|
|
||||||
|
const layoutContainer = this.orientation === Orientation.VERTICAL
|
||||||
|
? size => item.container.style.height = `${item.size}px`
|
||||||
|
: size => item.container.style.width = `${item.size}px`;
|
||||||
|
|
||||||
|
const layout = () => {
|
||||||
|
layoutContainer(item.size);
|
||||||
|
item.view.layout(item.size, this.orientation);
|
||||||
|
};
|
||||||
|
|
||||||
|
size = Math.round(size);
|
||||||
|
const item: IViewItem = { view, container, size, layout, disposable, height: size, top: 0, width: 0 };
|
||||||
|
this.viewItems.splice(index, 0, item);
|
||||||
|
|
||||||
|
this.onInsertItems(new ArrayIterator([item]), index > 0 ? this.viewItems[index - 1].view.id : undefined);
|
||||||
|
|
||||||
|
// Add sash
|
||||||
|
if (this.options.enableResizing && this.viewItems.length > 1) {
|
||||||
|
const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
|
||||||
|
const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) };
|
||||||
|
const sash = new Sash(this.el, layoutProvider, { orientation });
|
||||||
|
const sashEventMapper = this.orientation === Orientation.VERTICAL
|
||||||
|
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY })
|
||||||
|
: (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX });
|
||||||
|
|
||||||
|
const onStart = mapEvent(sash.onDidStart, sashEventMapper);
|
||||||
|
const onStartDisposable = onStart(this.onSashStart, this);
|
||||||
|
const onChange = mapEvent(sash.onDidChange, sashEventMapper);
|
||||||
|
const onSashChangeDisposable = onChange(this.onSashChange, this);
|
||||||
|
const onEnd = mapEvent<void, void>(sash.onDidEnd, () => null);
|
||||||
|
const onEndDisposable = onEnd(() => this._onDidSashChange.fire());
|
||||||
|
const onDidReset = mapEvent<void, void>(sash.onDidReset, () => null);
|
||||||
|
const onDidResetDisposable = onDidReset(() => this._onDidSashReset.fire());
|
||||||
|
|
||||||
|
const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, onEndDisposable, onDidResetDisposable, sash]);
|
||||||
|
const sashItem: ISashItem = { sash, disposable };
|
||||||
|
|
||||||
|
sash.hide();
|
||||||
|
this.sashItems.splice(index - 1, 0, sashItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
view.render(container, this.orientation);
|
||||||
|
this.relayout(index);
|
||||||
|
this.state = State.Idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeView(index: number): void {
|
||||||
|
if (this.state !== State.Idle) {
|
||||||
|
throw new Error('Cant modify splitview');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = State.Busy;
|
||||||
|
|
||||||
|
if (index < 0 || index >= this.viewItems.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove view
|
||||||
|
const viewItem = this.viewItems.splice(index, 1)[0];
|
||||||
|
viewItem.disposable.dispose();
|
||||||
|
|
||||||
|
// Remove sash
|
||||||
|
if (this.options.enableResizing && this.viewItems.length >= 1) {
|
||||||
|
const sashIndex = Math.max(index - 1, 0);
|
||||||
|
const sashItem = this.sashItems.splice(sashIndex, 1)[0];
|
||||||
|
sashItem.disposable.dispose();
|
||||||
|
} else {
|
||||||
|
this.lastRenderHeight = NaN, this.lastRenderTop = NaN;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.relayout();
|
||||||
|
this.state = State.Idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
moveView(from: number, to: number): void {
|
||||||
|
if (this.state !== State.Idle) {
|
||||||
|
throw new Error('Cant modify splitview');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = State.Busy;
|
||||||
|
|
||||||
|
if (from < 0 || from >= this.viewItems.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to < 0 || to >= this.viewItems.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from === to) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewItem = this.viewItems.splice(from, 1)[0];
|
||||||
|
this.viewItems.splice(to, 0, viewItem);
|
||||||
|
|
||||||
|
if (to + 1 < this.viewItems.length) {
|
||||||
|
this.el.insertBefore(viewItem.container, this.viewItems[to + 1].container);
|
||||||
|
} else {
|
||||||
|
this.el.appendChild(viewItem.container);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.layoutViews();
|
||||||
|
this.state = State.Idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private relayout(lowPriorityIndex?: number): void {
|
||||||
|
const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||||
|
this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
layout(size: number): void {
|
||||||
|
const previousSize = Math.max(this.size, this.contentSize);
|
||||||
|
this.size = size;
|
||||||
|
this.resize(this.viewItems.length - 1, size - previousSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private render(scrollTop: number, viewHeight: number): void {
|
||||||
|
let i: number;
|
||||||
|
let stop: number;
|
||||||
|
|
||||||
|
let renderTop = scrollTop;
|
||||||
|
let renderBottom = scrollTop + viewHeight;
|
||||||
|
let thisRenderBottom = this.lastRenderTop + this.lastRenderHeight;
|
||||||
|
|
||||||
|
// when view scrolls down, start rendering from the renderBottom
|
||||||
|
for (i = this.indexAfter(renderBottom) - 1, stop = this.indexAt(Math.max(thisRenderBottom, renderTop)); i >= stop; i--) {
|
||||||
|
if (this.insertItemInDOM(<IViewItem>this.itemAtIndex(i))) {
|
||||||
|
this.dirtyState = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when view scrolls up, start rendering from either this.renderTop or renderBottom
|
||||||
|
for (i = Math.min(this.indexAt(this.lastRenderTop), this.indexAfter(renderBottom)) - 1, stop = this.indexAt(renderTop); i >= stop; i--) {
|
||||||
|
if (this.insertItemInDOM(<IViewItem>this.itemAtIndex(i))) {
|
||||||
|
this.dirtyState = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when view scrolls down, start unrendering from renderTop
|
||||||
|
for (i = this.indexAt(this.lastRenderTop), stop = Math.min(this.indexAt(renderTop), this.indexAfter(thisRenderBottom)); i < stop; i++) {
|
||||||
|
if (this.removeItemFromDOM(<IViewItem>this.itemAtIndex(i))) {
|
||||||
|
this.dirtyState = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when view scrolls up, start unrendering from either renderBottom this.renderTop
|
||||||
|
for (i = Math.max(this.indexAfter(renderBottom), this.indexAt(this.lastRenderTop)), stop = this.indexAfter(thisRenderBottom); i < stop; i++) {
|
||||||
|
if (this.removeItemFromDOM(<IViewItem>this.itemAtIndex(i))) {
|
||||||
|
this.dirtyState = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let topItem = this.itemAtIndex(this.indexAt(renderTop));
|
||||||
|
|
||||||
|
if (topItem) {
|
||||||
|
this.el.style.top = (topItem.top - renderTop) + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastRenderTop = renderTop;
|
||||||
|
this.lastRenderHeight = renderBottom - renderTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
private onSashStart({ sash, start }: ISashEvent): void {
|
||||||
|
const index = firstIndex(this.sashItems, item => item.sash === sash);
|
||||||
|
const sizes = this.viewItems.map(i => i.size);
|
||||||
|
|
||||||
|
// const upIndexes = range(index, -1);
|
||||||
|
// const collapseUp = upIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
|
||||||
|
// const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
|
||||||
|
|
||||||
|
// const downIndexes = range(index + 1, this.viewItems.length);
|
||||||
|
// const collapseDown = downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
|
||||||
|
// const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
|
||||||
|
|
||||||
|
// const minDelta = -Math.min(collapseUp, expandDown);
|
||||||
|
// const maxDelta = Math.min(collapseDown, expandUp);
|
||||||
|
|
||||||
|
this.sashDragState = { start, index, sizes };
|
||||||
|
}
|
||||||
|
|
||||||
|
private onSashChange({ sash, current }: ISashEvent): void {
|
||||||
|
const { index, start, sizes } = this.sashDragState;
|
||||||
|
const delta = current - start;
|
||||||
|
|
||||||
|
this.resize(index, delta, sizes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onViewChange(item: IViewItem, size: number | undefined): void {
|
||||||
|
const index = this.viewItems.indexOf(item);
|
||||||
|
|
||||||
|
if (index < 0 || index >= this.viewItems.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size = typeof size === 'number' ? size : item.size;
|
||||||
|
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
||||||
|
item.size = size;
|
||||||
|
this.relayout(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeView(index: number, size: number): void {
|
||||||
|
if (this.state !== State.Idle) {
|
||||||
|
throw new Error('Cant modify splitview');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = State.Busy;
|
||||||
|
|
||||||
|
if (index < 0 || index >= this.viewItems.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = this.viewItems[index];
|
||||||
|
size = Math.round(size);
|
||||||
|
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
||||||
|
let delta = size - item.size;
|
||||||
|
|
||||||
|
if (delta !== 0 && index < this.viewItems.length - 1) {
|
||||||
|
const downIndexes = range(index + 1, this.viewItems.length);
|
||||||
|
const collapseDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
|
||||||
|
const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
|
||||||
|
const deltaDown = clamp(delta, -expandDown, collapseDown);
|
||||||
|
|
||||||
|
this.resize(index, deltaDown);
|
||||||
|
delta -= deltaDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta !== 0 && index > 0) {
|
||||||
|
const upIndexes = range(index - 1, -1);
|
||||||
|
const collapseUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
|
||||||
|
const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
|
||||||
|
const deltaUp = clamp(-delta, -collapseUp, expandUp);
|
||||||
|
|
||||||
|
this.resize(index - 1, deltaUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = State.Idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOM changes
|
||||||
|
|
||||||
|
private insertItemInDOM(item: IViewItem): boolean {
|
||||||
|
if (item.container.parentElement) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let elementAfter: HTMLElement = null;
|
||||||
|
let itemAfter = <IViewItem>this.itemAfter(item);
|
||||||
|
|
||||||
|
if (itemAfter && itemAfter.container) {
|
||||||
|
elementAfter = itemAfter.container;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elementAfter === null) {
|
||||||
|
this.el.appendChild(item.container);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
this.el.insertBefore(item.container, elementAfter);
|
||||||
|
} catch (e) {
|
||||||
|
// console.warn('Failed to locate previous tree element');
|
||||||
|
this.el.appendChild(item.container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item.layout();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeItemFromDOM(item: IViewItem): boolean {
|
||||||
|
if (!item || !item.container || !item.container.parentElement) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.el.removeChild(item.container);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
getViewSize(index: number): number {
|
||||||
|
if (index < 0 || index >= this.viewItems.length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.viewItems[index].size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private resize(index: number, delta: number, sizes = this.viewItems.map(i => i.size), lowPriorityIndex?: number): void {
|
||||||
|
if (index < 0 || index >= this.viewItems.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta !== 0) {
|
||||||
|
let upIndexes = range(index, -1);
|
||||||
|
let downIndexes = range(index + 1, this.viewItems.length);
|
||||||
|
|
||||||
|
if (typeof lowPriorityIndex === 'number') {
|
||||||
|
upIndexes = pushToEnd(upIndexes, lowPriorityIndex);
|
||||||
|
downIndexes = pushToEnd(downIndexes, lowPriorityIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upItems = upIndexes.map(i => this.viewItems[i]);
|
||||||
|
const upSizes = upIndexes.map(i => sizes[i]);
|
||||||
|
|
||||||
|
const downItems = downIndexes.map(i => this.viewItems[i]);
|
||||||
|
const downSizes = downIndexes.map(i => sizes[i]);
|
||||||
|
|
||||||
|
for (let i = 0, deltaUp = delta; deltaUp !== 0 && i < upItems.length; i++) {
|
||||||
|
const item = upItems[i];
|
||||||
|
const size = clamp(upSizes[i] + deltaUp, item.view.minimumSize, item.view.maximumSize);
|
||||||
|
const viewDelta = size - upSizes[i];
|
||||||
|
|
||||||
|
deltaUp -= viewDelta;
|
||||||
|
item.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0, deltaDown = delta; deltaDown !== 0 && i < downItems.length; i++) {
|
||||||
|
const item = downItems[i];
|
||||||
|
const size = clamp(downSizes[i] - deltaDown, item.view.minimumSize, item.view.maximumSize);
|
||||||
|
const viewDelta = size - downSizes[i];
|
||||||
|
|
||||||
|
deltaDown += viewDelta;
|
||||||
|
item.size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||||
|
let emptyDelta = this.size - contentSize;
|
||||||
|
|
||||||
|
for (let i = this.viewItems.length - 1; emptyDelta > 0 && i >= 0; i--) {
|
||||||
|
const item = this.viewItems[i];
|
||||||
|
const size = clamp(item.size + emptyDelta, item.view.minimumSize, item.view.maximumSize);
|
||||||
|
const viewDelta = size - item.size;
|
||||||
|
|
||||||
|
emptyDelta -= viewDelta;
|
||||||
|
item.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||||
|
|
||||||
|
this.scrollable.setScrollDimensions({
|
||||||
|
scrollHeight: this.contentSize,
|
||||||
|
height: this.size
|
||||||
|
});
|
||||||
|
|
||||||
|
this.layoutViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
private layoutViews(): void {
|
||||||
|
if (this.dirtyState) {
|
||||||
|
for (let i = this.indexAt(this.lastRenderTop); i <= this.indexAfter(this.lastRenderTop + this.lastRenderHeight) - 1; i++) {
|
||||||
|
this.viewItems[i].layout();
|
||||||
|
if (this.options.enableResizing) {
|
||||||
|
this.sashItems[i].sash.layout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.dirtyState = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update sashes enablement
|
||||||
|
// let previous = false;
|
||||||
|
// const collapsesDown = this.viewItems.map(i => previous = (i.size - i.view.minimumSize > 0) || previous);
|
||||||
|
|
||||||
|
// previous = false;
|
||||||
|
// const expandsDown = this.viewItems.map(i => previous = (i.view.maximumSize - i.size > 0) || previous);
|
||||||
|
|
||||||
|
// const reverseViews = [...this.viewItems].reverse();
|
||||||
|
// previous = false;
|
||||||
|
// const collapsesUp = reverseViews.map(i => previous = (i.size - i.view.minimumSize > 0) || previous).reverse();
|
||||||
|
|
||||||
|
// previous = false;
|
||||||
|
// const expandsUp = reverseViews.map(i => previous = (i.view.maximumSize - i.size > 0) || previous).reverse();
|
||||||
|
|
||||||
|
// this.sashItems.forEach((s, i) => {
|
||||||
|
// if ((collapsesDown[i] && expandsUp[i + 1]) || (expandsDown[i] && collapsesUp[i + 1])) {
|
||||||
|
// s.sash.enable();
|
||||||
|
// } else {
|
||||||
|
// s.sash.disable();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSashPosition(sash: Sash): number {
|
||||||
|
let position = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < this.sashItems.length; i++) {
|
||||||
|
position += this.viewItems[i].size;
|
||||||
|
|
||||||
|
if (this.sashItems[i].sash === sash) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this.viewItems.forEach(i => i.disposable.dispose());
|
||||||
|
this.viewItems = [];
|
||||||
|
|
||||||
|
this.sashItems.forEach(i => i.disposable.dispose());
|
||||||
|
this.sashItems = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
215
src/sql/base/browser/ui/table/asyncDataView.ts
Normal file
215
src/sql/base/browser/ui/table/asyncDataView.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,9 +11,17 @@ import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
|
|||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
import { Color } from 'vs/base/common/color';
|
import { Color } from 'vs/base/common/color';
|
||||||
import { mixin } from 'vs/base/common/objects';
|
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 { Orientation } from 'vs/base/browser/ui/splitview/splitview';
|
||||||
import { Widget } from 'vs/base/browser/ui/widget';
|
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 {
|
export interface ITableStyles extends IListStyles {
|
||||||
tableHeaderBackground?: Color;
|
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 styleElement: HTMLStyleElement;
|
||||||
private idPrefix: string;
|
private idPrefix: string;
|
||||||
|
|
||||||
private _grid: Slick.Grid<T>;
|
private _grid: Slick.Grid<T>;
|
||||||
private _columns: Slick.Column<T>[];
|
private _columns: Slick.Column<T>[];
|
||||||
private _data: TableDataView<T>;
|
private _data: Slick.DataProvider<T>;
|
||||||
|
private _sorter: ITableSorter<T>;
|
||||||
|
|
||||||
private _autoscroll: boolean;
|
private _autoscroll: boolean;
|
||||||
private _onRowCountChangeListener: IDisposable;
|
|
||||||
private _container: HTMLElement;
|
private _container: HTMLElement;
|
||||||
private _tableContainer: HTMLElement;
|
private _tableContainer: HTMLElement;
|
||||||
|
|
||||||
private _classChangeTimeout: number;
|
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();
|
super();
|
||||||
if (data instanceof TableDataView) {
|
if (!configuration || isArray(configuration.dataProvider)) {
|
||||||
this._data = data;
|
this._data = new TableDataView<T>(configuration && configuration.dataProvider as Array<T>);
|
||||||
} else {
|
} else {
|
||||||
this._data = new TableDataView<T>(data);
|
this._data = configuration.dataProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (columns) {
|
if (configuration.columns) {
|
||||||
this._columns = columns;
|
this._columns = configuration.columns;
|
||||||
} else {
|
} else {
|
||||||
this._columns = new Array<Slick.Column<T>>();
|
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._grid = new Slick.Grid<T>(this._tableContainer, this._data, this._columns, newOptions);
|
||||||
this.idPrefix = this._tableContainer.classList[0];
|
this.idPrefix = this._tableContainer.classList[0];
|
||||||
DOM.addClass(this._container, this.idPrefix);
|
DOM.addClass(this._container, this.idPrefix);
|
||||||
this._onRowCountChangeListener = this._data.onRowCountChange(() => this._handleRowCountChange());
|
if (configuration.sorter) {
|
||||||
this._grid.onSort.subscribe((e, args) => {
|
this._sorter = configuration.sorter;
|
||||||
this._data.sort(args);
|
this._grid.onSort.subscribe((e, args) => {
|
||||||
this._grid.invalidate();
|
this._sorter.sort(args);
|
||||||
this._grid.render();
|
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.updateRowCount();
|
||||||
this._grid.render();
|
this._grid.render();
|
||||||
if (this._autoscroll) {
|
if (this._autoscroll) {
|
||||||
@@ -113,20 +155,22 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
|
|||||||
} else {
|
} else {
|
||||||
this._data = new TableDataView<T>(data);
|
this._data = new TableDataView<T>(data);
|
||||||
}
|
}
|
||||||
this._onRowCountChangeListener.dispose();
|
|
||||||
this._grid.setData(this._data, true);
|
this._grid.setData(this._data, true);
|
||||||
this._onRowCountChangeListener = this._data.onRowCountChange(() => this._handleRowCountChange());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get columns(): Slick.Column<T>[] {
|
get columns(): Slick.Column<T>[] {
|
||||||
return this._grid.getColumns();
|
return this._grid.getColumns();
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedRows(rows: number[]) {
|
public setSelectedRows(rows: number[] | boolean) {
|
||||||
this._grid.setSelectedRows(rows);
|
if (isBoolean(rows)) {
|
||||||
|
this._grid.setSelectedRows(range(this._grid.getDataLength()));
|
||||||
|
} else {
|
||||||
|
this._grid.setSelectedRows(rows);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedRows(): number[] {
|
public getSelectedRows(): number[] {
|
||||||
return this._grid.getSelectedRows();
|
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>>) {
|
setSelectionModel(model: Slick.SelectionModel<T, Array<Slick.Range>>) {
|
||||||
this._grid.setSelectionModel(model);
|
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.width = sizing.width + 'px';
|
||||||
this._tableContainer.style.height = sizing.height + 'px';
|
this._tableContainer.style.height = sizing.height + 'px';
|
||||||
} else {
|
} else {
|
||||||
if (orientation === Orientation.HORIZONTAL) {
|
if (orientation === Orientation.VERTICAL) {
|
||||||
this._container.style.width = '100%';
|
this._container.style.width = '100%';
|
||||||
this._container.style.height = sizing + 'px';
|
this._container.style.height = sizing + 'px';
|
||||||
this._tableContainer.style.width = '100%';
|
this._tableContainer.style.width = '100%';
|
||||||
@@ -207,6 +236,7 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.resizeCanvas();
|
this.resizeCanvas();
|
||||||
|
this.autosizeColumns();
|
||||||
}
|
}
|
||||||
|
|
||||||
autosizeColumns() {
|
autosizeColumns() {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export class TableBasicView<T> extends View {
|
|||||||
super(undefined, viewOpts);
|
super(undefined, viewOpts);
|
||||||
this._container = document.createElement('div');
|
this._container = document.createElement('div');
|
||||||
this._container.className = 'table-view';
|
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> {
|
public get table(): Table<T> {
|
||||||
@@ -59,7 +59,7 @@ export class TableHeaderView<T> extends HeaderView {
|
|||||||
super(undefined, viewOpts);
|
super(undefined, viewOpts);
|
||||||
this._container = document.createElement('div');
|
this._container = document.createElement('div');
|
||||||
this._container.className = 'table-view';
|
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> {
|
public get table(): Table<T> {
|
||||||
@@ -76,7 +76,7 @@ export class TableHeaderView<T> extends HeaderView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected layoutBody(size: number): void {
|
protected layoutBody(size: number): void {
|
||||||
this._table.layout(size, Orientation.HORIZONTAL);
|
this._table.layout(size, Orientation.VERTICAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
focus(): void {
|
focus(): void {
|
||||||
@@ -99,7 +99,7 @@ export class TableCollapsibleView<T> extends AbstractCollapsibleView {
|
|||||||
super(undefined, viewOpts);
|
super(undefined, viewOpts);
|
||||||
this._container = document.createElement('div');
|
this._container = document.createElement('div');
|
||||||
this._container.className = 'table-view';
|
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 {
|
public render(container: HTMLElement, orientation: Orientation): void {
|
||||||
@@ -143,6 +143,6 @@ export class TableCollapsibleView<T> extends AbstractCollapsibleView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected layoutBody(size: number): void {
|
protected layoutBody(size: number): void {
|
||||||
this._table.layout(size, Orientation.HORIZONTAL);
|
this._table.layout(size, Orientation.VERTICAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export default class TableInsight extends Disposable implements IInsightsView, O
|
|||||||
|
|
||||||
private createTable() {
|
private createTable() {
|
||||||
if (!this.table) {
|
if (!this.table) {
|
||||||
this.table = new Table(this._elementRef.nativeElement, this.dataView, this.columns, { showRowNumber: true });
|
this.table = new Table(this._elementRef.nativeElement, { dataProvider: this.dataView, columns: this.columns }, { showRowNumber: true });
|
||||||
this.table.setSelectionModel(new CellSelectionModel());
|
this.table.setSelectionModel(new CellSelectionModel());
|
||||||
this._register(attachTableStyler(this.table, this.themeService));
|
this._register(attachTableStyler(this.table, this.themeService));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,7 +272,8 @@ export class RestoreDialog extends Modal {
|
|||||||
this._restorePlanTableContainer = labelContainer.getHTMLElement();
|
this._restorePlanTableContainer = labelContainer.getHTMLElement();
|
||||||
labelContainer.hide();
|
labelContainer.hide();
|
||||||
this._restorePlanData = new TableDataView<Slick.SlickData>();
|
this._restorePlanData = new TableDataView<Slick.SlickData>();
|
||||||
this._restorePlanTable = new Table<Slick.SlickData>(labelContainer.getHTMLElement(), this._restorePlanData, this._restorePlanColumn, { enableColumnReorder: false });
|
this._restorePlanTable = new Table<Slick.SlickData>(labelContainer.getHTMLElement(),
|
||||||
|
{ dataProvider: this._restorePlanData, columns: this._restorePlanColumn }, { enableColumnReorder: false });
|
||||||
this._restorePlanTable.setSelectionModel(new RowSelectionModel({ selectActiveRow: false }));
|
this._restorePlanTable.setSelectionModel(new RowSelectionModel({ selectActiveRow: false }));
|
||||||
this._restorePlanTable.onSelectedRowsChanged((e, data) => this.backupFileCheckboxChanged(e, data));
|
this._restorePlanTable.onSelectedRowsChanged((e, data) => this.backupFileCheckboxChanged(e, data));
|
||||||
});
|
});
|
||||||
@@ -328,7 +329,8 @@ export class RestoreDialog extends Modal {
|
|||||||
field: 'restoreAs'
|
field: 'restoreAs'
|
||||||
}];
|
}];
|
||||||
this._fileListData = new TableDataView<FileListElement>();
|
this._fileListData = new TableDataView<FileListElement>();
|
||||||
this._fileListTable = new Table<FileListElement>(fileNameContainer.getHTMLElement(), this._fileListData, columns, { enableColumnReorder: false });
|
this._fileListTable = new Table<FileListElement>(fileNameContainer.getHTMLElement(),
|
||||||
|
{ dataProvider : this._fileListData, columns } , { enableColumnReorder: false });
|
||||||
this._fileListTable.setSelectionModel(new RowSelectionModel());
|
this._fileListTable.setSelectionModel(new RowSelectionModel());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,14 +4,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, IObservableCollection, IGridDataRow } from 'angular2-slickgrid';
|
import { ISlickColumn, VirtualizedCollection } from 'angular2-slickgrid';
|
||||||
|
|
||||||
export interface ISlickRange {
|
|
||||||
fromCell: number;
|
|
||||||
fromRow: number;
|
|
||||||
toCell: number;
|
|
||||||
toRow: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IGridIcon {
|
export interface IGridIcon {
|
||||||
showCondition: () => boolean;
|
showCondition: () => boolean;
|
||||||
@@ -41,7 +34,7 @@ export interface IGridIcon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IGridDataSet {
|
export interface IGridDataSet {
|
||||||
dataRows: IObservableCollection<IGridDataRow>;
|
dataRows: VirtualizedCollection<{}>;
|
||||||
columnDefinitions: ISlickColumn<any>[];
|
columnDefinitions: ISlickColumn<any>[];
|
||||||
resized: any; // EventEmitter<any>;
|
resized: any; // EventEmitter<any>;
|
||||||
totalRows: number;
|
totalRows: number;
|
||||||
@@ -61,7 +54,7 @@ export enum SaveFormat {
|
|||||||
export interface IGridInfo {
|
export interface IGridInfo {
|
||||||
batchIndex: number;
|
batchIndex: number;
|
||||||
resultSetNumber: number;
|
resultSetNumber: number;
|
||||||
selection: ISlickRange[];
|
selection: Slick.Range[];
|
||||||
gridIndex: number;
|
gridIndex: number;
|
||||||
rowIndex?: number;
|
rowIndex?: number;
|
||||||
}
|
}
|
||||||
@@ -69,5 +62,5 @@ export interface ISaveRequest {
|
|||||||
format: SaveFormat;
|
format: SaveFormat;
|
||||||
batchIndex: number;
|
batchIndex: number;
|
||||||
resultSetNumber: number;
|
resultSetNumber: number;
|
||||||
selection: ISlickRange[];
|
selection: Slick.Range[];
|
||||||
}
|
}
|
||||||
@@ -9,66 +9,64 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.errorMessage {
|
.errorMessage {
|
||||||
color: var(--color-error);
|
color: var(--color-error);
|
||||||
}
|
}
|
||||||
|
|
||||||
.batchMessage {
|
.batchMessage {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slick-cell a, a:link {
|
.slick-cell a, a:link {
|
||||||
color: var(--color-grid-link);
|
color: var(--color-grid-link);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slick-cell a:hover {
|
.slick-cell a:hover {
|
||||||
color: var(--color-grid-link-hover);
|
color: var(--color-grid-link-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.resultsMessageValue a, a:link {
|
.resultsMessageValue a, a:link {
|
||||||
color: var(--color-grid-link);
|
color: var(--color-grid-link);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resultsMessageValue a:hover {
|
.resultsMessageValue a:hover {
|
||||||
color: var(--color-grid-link-hover);
|
color: var(--color-grid-link-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid .slick-cell.dirtyCell {
|
.grid .slick-cell.dirtyCell {
|
||||||
color: var(--color-grid-dirty-text);
|
color: var(--color-grid-dirty-text);
|
||||||
background-color: var(--color-grid-dirty-background);
|
background-color: var(--color-grid-dirty-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid .slick-cell.dirtyRowHeader {
|
.grid .slick-cell.dirtyRowHeader {
|
||||||
background-color: var(--color-grid-dirty-background);
|
background-color: var(--color-grid-dirty-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.slick-cell.dirtyRowHeader > .row-number {
|
.slick-cell.dirtyRowHeader > .row-number {
|
||||||
color: var(--color-grid-dirty-text);
|
color: var(--color-grid-dirty-text);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* vs theme
|
* vs theme
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.vs .slickgridContainer {
|
.vs .slickgridContainer {
|
||||||
--color-content: #101010;
|
--color-content: #101010;
|
||||||
--color-content-disabled: #a9a9a9;
|
--color-content-disabled: #a9a9a9;
|
||||||
--color-error: #E81123;
|
--color-error: #E81123;
|
||||||
--color-success: #7CD300;
|
--color-success: #7CD300;
|
||||||
--color-bg-header: hsla(0,0%,50%,.2);
|
--color-bg-header: hsla(0,0%,50%,.2);
|
||||||
--color-resize-handle: grey;
|
--color-resize-handle: grey;
|
||||||
--color-bg-content-header: #F5F5F5; /* used for color of grid headers */
|
--color-bg-content-header: #F5F5F5; /* used for color of grid headers */
|
||||||
--color-cell-border-active: grey;
|
--color-cell-border-active: grey;
|
||||||
--color-cell-bg-grid-selected: rgb(173, 214, 255);
|
--color-cell-bg-grid-selected: rgb(173, 214, 255);
|
||||||
--color-grid-link: #0078D7;
|
--color-grid-link: #0078D7;
|
||||||
--color-grid-link-hover: #0b93ff;
|
--color-grid-link-hover: #0b93ff;
|
||||||
--color-grid-dirty-background: #CCC;
|
--color-grid-dirty-background: #CCC;
|
||||||
--color-grid-dirty-text: #101010;
|
--color-grid-dirty-text: #101010;
|
||||||
}
|
}
|
||||||
/* grid styling */
|
/* grid styling */
|
||||||
|
|
||||||
@@ -77,102 +75,102 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vs slick-grid.active .grid .slick-cell.selected {
|
.vs slick-grid.active .grid .slick-cell.selected {
|
||||||
background-color: var(--color-cell-bg-grid-selected);
|
background-color: var(--color-cell-bg-grid-selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs .grid .slick-cell.selected .grid-cell-value-container.missing-value {
|
.vs .grid .slick-cell.selected .grid-cell-value-container.missing-value {
|
||||||
color: var(--color-content) !important;
|
color: var(--color-content) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs .boxRow.content.horzBox.slickgrid {
|
.vs .boxRow.content.horzBox.slickgrid {
|
||||||
border: solid 1px #EEEEF2;
|
border: solid 1px #EEEEF2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* icons */
|
/* icons */
|
||||||
.vs .gridIcon.extendFullScreen {
|
.vs .icon.extendFullScreen {
|
||||||
/* ExtendToFullScreen_16x_vscode */
|
/* ExtendToFullScreen_16x_vscode */
|
||||||
background-image: url("extendFullScreen.svg");
|
background-image: url("extendFullScreen.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs .gridIcon.exitFullScreen {
|
.vs .icon.exitFullScreen {
|
||||||
/* ExitFullScreen_16x_vscode */
|
/* ExitFullScreen_16x_vscode */
|
||||||
background-image: url("exitFullScreen.svg");
|
background-image: url("exitFullScreen.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs .gridIcon.saveJson {
|
.vs .icon.saveJson {
|
||||||
/* ResultToJSON_16x_vscode */
|
/* ResultToJSON_16x_vscode */
|
||||||
background-image: url("saveJson.svg");
|
background-image: url("saveJson.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs .gridIcon.saveCsv {
|
.vs .icon.saveCsv {
|
||||||
/* ResultToCSV_16x_vscode */
|
/* ResultToCSV_16x_vscode */
|
||||||
background-image: url("saveCsv.svg");
|
background-image: url("saveCsv.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs .gridIcon.saveExcel {
|
.vs .icon.saveExcel {
|
||||||
/* ResultToXlsx_16x_vscode */
|
/* ResultToXlsx_16x_vscode */
|
||||||
background-image: url("saveExcel.svg");
|
background-image: url("saveExcel.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs .gridIcon.viewChart {
|
.vs .icon.viewChart {
|
||||||
/* ResultToXlsx_16x_vscode */
|
/* ResultToXlsx_16x_vscode */
|
||||||
background-image: url("viewChart.svg");
|
background-image: url("viewChart.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* headers */
|
/* headers */
|
||||||
.vs .resultsMessageHeader {
|
.vs .resultsMessageHeader {
|
||||||
background: var(--color-bg-header);
|
background: var(--color-bg-header);
|
||||||
color: var(--color-content);
|
color: var(--color-content);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs .resultsViewCollapsible:not(.collapsed) {
|
.vs .resultsViewCollapsible:not(.collapsed) {
|
||||||
background-image: url("uncollapsedArrow.svg");
|
background-image: url("uncollapsedArrow.svg");
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: 2px;
|
background-position: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs .resultsViewCollapsible {
|
.vs .resultsViewCollapsible {
|
||||||
background-image: url("collapsedArrow.svg");
|
background-image: url("collapsedArrow.svg");
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: 2px;
|
background-position: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs .queryResultsShortCut {
|
.vs .queryResultsShortCut {
|
||||||
color: grey;
|
color: grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* scroll bar */
|
/* scroll bar */
|
||||||
|
|
||||||
.vs ::-webkit-scrollbar {
|
.vs ::-webkit-scrollbar {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
.vs ::-webkit-scrollbar-thumb {
|
.vs ::-webkit-scrollbar-thumb {
|
||||||
background: hsla(0,0%,47%,.4);
|
background: hsla(0,0%,47%,.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs ::-webkit-scrollbar-thumb:hover {
|
.vs ::-webkit-scrollbar-thumb:hover {
|
||||||
background: hsla(0,0%,39%,.7);
|
background: hsla(0,0%,39%,.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs ::-webkit-scrollbar-thumb:active {
|
.vs ::-webkit-scrollbar-thumb:active {
|
||||||
background: rgba(85,85,85,0.8);
|
background: rgba(85,85,85,0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs ::-webkit-scrollbar-track {
|
.vs ::-webkit-scrollbar-track {
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs ::-webkit-scrollbar-corner {
|
.vs ::-webkit-scrollbar-corner {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs .monaco-workbench input {
|
.vs .monaco-workbench input {
|
||||||
color: var(--color-content);
|
color: var(--color-content);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs .monaco-workbench .input {
|
.vs .monaco-workbench .input {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -181,19 +179,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.vs-dark .slickgridContainer {
|
.vs-dark .slickgridContainer {
|
||||||
--color-content: #E5E5E5;
|
--color-content: #E5E5E5;
|
||||||
--color-content-disabled: grey;
|
--color-content-disabled: grey;
|
||||||
--color-error: #E81123;
|
--color-error: #E81123;
|
||||||
--color-success: #7CD300;
|
--color-success: #7CD300;
|
||||||
--color-bg-header: hsla(0,0%,50%,.2); /* used for pane toolbars */
|
--color-bg-header: hsla(0,0%,50%,.2); /* used for pane toolbars */
|
||||||
--color-resize-handle: #4d4d4d;
|
--color-resize-handle: #4d4d4d;
|
||||||
--color-bg-content-header: #333334; /* used for color of grid headers */
|
--color-bg-content-header: #333334; /* used for color of grid headers */
|
||||||
--color-cell-border-active: white;
|
--color-cell-border-active: white;
|
||||||
--color-cell-bg-grid-selected: rgb(38, 79, 120);
|
--color-cell-bg-grid-selected: rgb(38, 79, 120);
|
||||||
--color-grid-link: #FF6000;
|
--color-grid-link: #FF6000;
|
||||||
--color-grid-link-hover: #ff8033;
|
--color-grid-link-hover: #ff8033;
|
||||||
--color-grid-dirty-background: #4d4d4d;
|
--color-grid-dirty-background: #4d4d4d;
|
||||||
--color-grid-dirty-text: #E5E5E5;
|
--color-grid-dirty-text: #E5E5E5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* grid styling */
|
/* grid styling */
|
||||||
@@ -203,109 +201,117 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark slick-grid.active .grid .slick-cell.selected {
|
.vs-dark slick-grid.active .grid .slick-cell.selected {
|
||||||
background-color: var(--color-cell-bg-grid-selected);
|
background-color: var(--color-cell-bg-grid-selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .grid .slick-cell.selected .grid-cell-value-container.missing-value {
|
.vs-dark .grid .slick-cell.selected .grid-cell-value-container.missing-value {
|
||||||
color: var(--color-content) !important;
|
color: var(--color-content) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .boxRow.content.horzBox.slickgrid {
|
.vs-dark .boxRow.content.horzBox.slickgrid {
|
||||||
border: solid 1px #2D2D30;
|
border: solid 1px #2D2D30;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* icons */
|
/* icons */
|
||||||
.vs-dark .gridIcon.extendFullScreen,
|
.vs-dark .icon.extendFullScreen,
|
||||||
.hc-black .gridIcon.extendFullScreen {
|
.hc-black .icon.extendFullScreen {
|
||||||
/* ExtendToFullScreen_16x_vscode_inverse.svg */
|
/* ExtendToFullScreen_16x_vscode_inverse.svg */
|
||||||
background-image: url("extendFullScreen_inverse.svg");
|
background-image: url("extendFullScreen_inverse.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .gridIcon.exitFullScreen,
|
.vs-dark .icon.exitFullScreen,
|
||||||
.hc-black .gridIcon.exitFullScreen {
|
.hc-black .icon.exitFullScreen {
|
||||||
/* ExitFullScreen_16x_vscode_inverse.svg */
|
/* ExitFullScreen_16x_vscode_inverse.svg */
|
||||||
background-image: url("exitFullScreen_inverse.svg");
|
background-image: url("exitFullScreen_inverse.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .gridIcon.saveJson,
|
.vs-dark .icon.saveJson,
|
||||||
.hc-black .gridIcon.saveJson {
|
.hc-black .icon.saveJson {
|
||||||
/* ResultToJSON_16x_vscode_inverse.svg */
|
/* ResultToJSON_16x_vscode_inverse.svg */
|
||||||
background-image: url("saveJson_inverse.svg");
|
background-image: url("saveJson_inverse.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .gridIcon.saveCsv,
|
.vs-dark .icon.saveCsv,
|
||||||
.hc-black .gridIcon.saveCsv {
|
.hc-black .icon.saveCsv {
|
||||||
/* ResultToCSV_16x_vscode_inverse.svg */
|
/* ResultToCSV_16x_vscode_inverse.svg */
|
||||||
background-image: url("saveCsv_inverse.svg");
|
background-image: url("saveCsv_inverse.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .gridIcon.saveExcel,
|
.vs-dark .icon.saveExcel,
|
||||||
.hc-black .gridIcon.saveExcel {
|
.hc-black .icon.saveExcel {
|
||||||
/* ResultToXlsx_16x_vscode_inverse.svg */
|
/* ResultToXlsx_16x_vscode_inverse.svg */
|
||||||
background-image: url("saveExcel_inverse.svg");
|
background-image: url("saveExcel_inverse.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .gridIcon.viewChart,
|
.vs-dark .icon.viewChart,
|
||||||
.hc-black .gridIcon.viewChart {
|
.hc-black .icon.viewChart {
|
||||||
/* ResultToXlsx_16x_vscode */
|
/* ResultToXlsx_16x_vscode */
|
||||||
background-image: url("viewChart_inverse.svg");
|
background-image: url("viewChart_inverse.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grid-panel .action-label.icon {
|
||||||
|
height: 35px;
|
||||||
|
line-height: 35px;
|
||||||
|
min-width: 28px;
|
||||||
|
background-size: 16px;
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
/* headers */
|
/* headers */
|
||||||
.vs-dark .resultsMessageHeader {
|
.vs-dark .resultsMessageHeader {
|
||||||
background: var(--color-bg-header);
|
background: var(--color-bg-header);
|
||||||
color: var(--color-content);
|
color: var(--color-content);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .resultsViewCollapsible:not(.collapsed),
|
.vs-dark .resultsViewCollapsible:not(.collapsed),
|
||||||
.hc-black .resultsViewCollapsible:not(.collapsed) {
|
.hc-black .resultsViewCollapsible:not(.collapsed) {
|
||||||
background-image:url("uncollapsedArrow_inverse.svg");
|
background-image:url("uncollapsedArrow_inverse.svg");
|
||||||
background-repeat:no-repeat;
|
background-repeat:no-repeat;
|
||||||
background-position: 2px;
|
background-position: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .resultsViewCollapsible,
|
.vs-dark .resultsViewCollapsible,
|
||||||
.hc-black .resultsViewCollapsible {
|
.hc-black .resultsViewCollapsible {
|
||||||
background-image: url("collapsedArrow_inverse.svg");
|
background-image: url("collapsedArrow_inverse.svg");
|
||||||
background-repeat:no-repeat;
|
background-repeat:no-repeat;
|
||||||
background-position: 2px;
|
background-position: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .queryResultsShortCut {
|
.vs-dark .queryResultsShortCut {
|
||||||
color: grey;
|
color: grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* scroll bar */
|
/* scroll bar */
|
||||||
|
|
||||||
.vs-dark ::-webkit-scrollbar {
|
.vs-dark ::-webkit-scrollbar {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark ::-webkit-scrollbar-thumb {
|
.vs-dark ::-webkit-scrollbar-thumb {
|
||||||
background: hsla(0,0%,47%,.4);
|
background: hsla(0,0%,47%,.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark ::-webkit-scrollbar-thumb:hover {
|
.vs-dark ::-webkit-scrollbar-thumb:hover {
|
||||||
background: hsla(0,0%,39%,.7);
|
background: hsla(0,0%,39%,.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark ::-webkit-scrollbar-thumb:active {
|
.vs-dark ::-webkit-scrollbar-thumb:active {
|
||||||
background: rgba(85,85,85,0.8);
|
background: rgba(85,85,85,0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark ::-webkit-scrollbar-track {
|
.vs-dark ::-webkit-scrollbar-track {
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark ::-webkit-scrollbar-corner {
|
.vs-dark ::-webkit-scrollbar-corner {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .monaco-workbench input, .vs-dark .monaco-workbench .input {
|
.vs-dark .monaco-workbench input, .vs-dark .monaco-workbench .input {
|
||||||
color: var(--color-content);
|
color: var(--color-content);
|
||||||
background-color: #3C3C3C;
|
background-color: #3C3C3C;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -313,20 +319,20 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.hc-black .slickgridContainer {
|
.hc-black .slickgridContainer {
|
||||||
--color-content: #E5E5E5;
|
--color-content: #E5E5E5;
|
||||||
--color-content-disabled: grey;
|
--color-content-disabled: grey;
|
||||||
--color-error: #E81123;
|
--color-error: #E81123;
|
||||||
--color-success: #7CD300;
|
--color-success: #7CD300;
|
||||||
--color-bg-header: hsla(0,0%,50%,.2); /* used for pane toolbars */
|
--color-bg-header: hsla(0,0%,50%,.2); /* used for pane toolbars */
|
||||||
--color-resize-handle: #4d4d4d;
|
--color-resize-handle: #4d4d4d;
|
||||||
--color-bg-content-header: #333334; /* used for color of grid headers */
|
--color-bg-content-header: #333334; /* used for color of grid headers */
|
||||||
--color-cell-border-active: orange;
|
--color-cell-border-active: orange;
|
||||||
--color-cell-bg-grid-selected: rgb(38, 79, 120);
|
--color-cell-bg-grid-selected: rgb(38, 79, 120);
|
||||||
--color-grid-link: #FF6000;
|
--color-grid-link: #FF6000;
|
||||||
--color-grid-link-hover: #ff8033;
|
--color-grid-link-hover: #ff8033;
|
||||||
--color-grid-dirty-background: #FFF;
|
--color-grid-dirty-background: #FFF;
|
||||||
--color-grid-dirty-text: #000;
|
--color-grid-dirty-text: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* grid styling */
|
/* grid styling */
|
||||||
@@ -336,32 +342,32 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hc-black slick-grid.active .grid .slick-cell.selected {
|
.hc-black slick-grid.active .grid .slick-cell.selected {
|
||||||
background-color: var(--color-cell-bg-grid-selected);
|
background-color: var(--color-cell-bg-grid-selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hc-black .grid .slick-cell.selected .grid-cell-value-container.missing-value {
|
.hc-black .grid .slick-cell.selected .grid-cell-value-container.missing-value {
|
||||||
color: var(--color-content) !important;
|
color: var(--color-content) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hc-black .boxRow.content.horzBox.slickgrid {
|
.hc-black .boxRow.content.horzBox.slickgrid {
|
||||||
border: solid 1px #2D2D30;
|
border: solid 1px #2D2D30;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* headers */
|
/* headers */
|
||||||
.hc-black .resultsMessageHeader {
|
.hc-black .resultsMessageHeader {
|
||||||
background: var(--color-bg-header);
|
background: var(--color-bg-header);
|
||||||
color: var(--color-content);
|
color: var(--color-content);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hc-black .queryResultsShortCut {
|
.hc-black .queryResultsShortCut {
|
||||||
color: grey;
|
color: grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* scroll bar */
|
/* scroll bar */
|
||||||
|
|
||||||
.hc-black ::-webkit-scrollbar {
|
.hc-black ::-webkit-scrollbar {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hc-black ::-webkit-scrollbar-thumb {
|
.hc-black ::-webkit-scrollbar-thumb {
|
||||||
@@ -377,14 +383,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hc-black ::-webkit-scrollbar-track {
|
.hc-black ::-webkit-scrollbar-track {
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hc-black ::-webkit-scrollbar-corner {
|
.hc-black ::-webkit-scrollbar-corner {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hc-black .monaco-workbench input {
|
.hc-black .monaco-workbench input {
|
||||||
color: #000;
|
color: #000;
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
|||||||
import { ResultSerializer } from 'sql/parts/query/common/resultSerializer';
|
import { ResultSerializer } from 'sql/parts/query/common/resultSerializer';
|
||||||
import { ISaveRequest } from 'sql/parts/grid/common/interfaces';
|
import { ISaveRequest } from 'sql/parts/grid/common/interfaces';
|
||||||
|
|
||||||
import { ISlickRange } from 'angular2-slickgrid';
|
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
|
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
|
||||||
|
|
||||||
@@ -184,7 +183,7 @@ export class DataService {
|
|||||||
* @param resultId The result id of the result to copy from
|
* @param resultId The result id of the result to copy from
|
||||||
* @param includeHeaders [Optional]: Should column headers be included in the copy selection
|
* @param includeHeaders [Optional]: Should column headers be included in the copy selection
|
||||||
*/
|
*/
|
||||||
copyResults(selection: ISlickRange[], batchId: number, resultId: number, includeHeaders?: boolean): void {
|
copyResults(selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): void {
|
||||||
this._queryModel.copyResults(this._uri, selection, batchId, resultId, includeHeaders);
|
this._queryModel.copyResults(this._uri, selection, batchId, resultId, includeHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import 'vs/css!sql/parts/grid/media/slickGrid';
|
|||||||
import 'vs/css!./media/editData';
|
import 'vs/css!./media/editData';
|
||||||
|
|
||||||
import { ElementRef, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject, forwardRef, EventEmitter } from '@angular/core';
|
import { ElementRef, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject, forwardRef, EventEmitter } from '@angular/core';
|
||||||
import { IGridDataRow, VirtualizedCollection, ISlickRange } from 'angular2-slickgrid';
|
import { VirtualizedCollection } from 'angular2-slickgrid';
|
||||||
|
|
||||||
import { IGridDataSet } from 'sql/parts/grid/common/interfaces';
|
import { IGridDataSet } from 'sql/parts/grid/common/interfaces';
|
||||||
import * as Services from 'sql/parts/grid/services/sharedServices';
|
import * as Services from 'sql/parts/grid/services/sharedServices';
|
||||||
@@ -76,10 +76,10 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
|||||||
public onIsCellEditValid: (row: number, column: number, newValue: any) => boolean;
|
public onIsCellEditValid: (row: number, column: number, newValue: any) => boolean;
|
||||||
public onIsColumnEditable: (column: number) => boolean;
|
public onIsColumnEditable: (column: number) => boolean;
|
||||||
public overrideCellFn: (rowNumber, columnId, value?, data?) => string;
|
public overrideCellFn: (rowNumber, columnId, value?, data?) => string;
|
||||||
public loadDataFunction: (offset: number, count: number) => Promise<IGridDataRow[]>;
|
public loadDataFunction: (offset: number, count: number) => Promise<{}[]>;
|
||||||
|
|
||||||
private savedViewState: {
|
private savedViewState: {
|
||||||
gridSelections: ISlickRange[];
|
gridSelections: Slick.Range[];
|
||||||
scrollTop;
|
scrollTop;
|
||||||
scrollLeft;
|
scrollLeft;
|
||||||
};
|
};
|
||||||
@@ -153,6 +153,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
|||||||
self.dataSet = undefined;
|
self.dataSet = undefined;
|
||||||
self.placeHolderDataSets = [];
|
self.placeHolderDataSets = [];
|
||||||
self.renderedDataSets = self.placeHolderDataSets;
|
self.renderedDataSets = self.placeHolderDataSets;
|
||||||
|
this._cd.detectChanges();
|
||||||
self.totalElapsedTimeSpan = undefined;
|
self.totalElapsedTimeSpan = undefined;
|
||||||
self.complete = false;
|
self.complete = false;
|
||||||
|
|
||||||
@@ -180,23 +181,31 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Setup a function for generating a promise to lookup result subsets
|
// Setup a function for generating a promise to lookup result subsets
|
||||||
this.loadDataFunction = (offset: number, count: number): Promise<IGridDataRow[]> => {
|
this.loadDataFunction = (offset: number, count: number): Promise<{}[]> => {
|
||||||
return new Promise<IGridDataRow[]>((resolve, reject) => {
|
return new Promise<{}[]>((resolve, reject) => {
|
||||||
self.dataService.getEditRows(offset, count).subscribe(result => {
|
self.dataService.getEditRows(offset, count).subscribe(result => {
|
||||||
let rowIndex = offset;
|
let gridData = result.subset.map(r => {
|
||||||
let gridData: IGridDataRow[] = result.subset.map(row => {
|
let dataWithSchema = {};
|
||||||
self.idMapping[rowIndex] = row.id;
|
// skip the first column since its a number column
|
||||||
rowIndex++;
|
for (let i = 1; i < this.dataSet.columnDefinitions.length; i++) {
|
||||||
return {
|
dataWithSchema[this.dataSet.columnDefinitions[i].field] = r.cells[i - 1].displayValue;
|
||||||
values: [{}].concat(row.cells.map(c => {
|
}
|
||||||
return mixin({ ariaLabel: escape(c.displayValue) }, c);
|
return dataWithSchema;
|
||||||
})), row: row.id
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
// let rowIndex = offset;
|
||||||
|
// let gridData: IGridDataRow[] = result.subset.map(row => {
|
||||||
|
// self.idMapping[rowIndex] = row.id;
|
||||||
|
// rowIndex++;
|
||||||
|
// return {
|
||||||
|
// values: [{}].concat(row.cells.map(c => {
|
||||||
|
// return mixin({ ariaLabel: escape(c.displayValue) }, c);
|
||||||
|
// })), row: row.id
|
||||||
|
// };
|
||||||
|
// });
|
||||||
|
|
||||||
// Append a NULL row to the end of gridData
|
// Append a NULL row to the end of gridData
|
||||||
let newLastRow = gridData.length === 0 ? 0 : (gridData[gridData.length - 1].row + 1);
|
// let newLastRow = gridData.length === 0 ? 0 : (gridData[gridData.length - 1].row + 1);
|
||||||
gridData.push({ values: self.dataSet.columnDefinitions.map(cell => { return { displayValue: 'NULL', isNull: false }; }), row: newLastRow });
|
// gridData.push({ values: self.dataSet.columnDefinitions.map(cell => { return { displayValue: 'NULL', isNull: false }; }), row: newLastRow });
|
||||||
resolve(gridData);
|
resolve(gridData);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -356,7 +365,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
|||||||
self.windowSize,
|
self.windowSize,
|
||||||
resultSet.rowCount,
|
resultSet.rowCount,
|
||||||
this.loadDataFunction,
|
this.loadDataFunction,
|
||||||
index => { return { values: [] }; }
|
index => { return {}; }
|
||||||
),
|
),
|
||||||
columnDefinitions: [rowNumberColumn.getColumnDefinition()].concat(resultSet.columnInfo.map((c, i) => {
|
columnDefinitions: [rowNumberColumn.getColumnDefinition()].concat(resultSet.columnInfo.map((c, i) => {
|
||||||
let isLinked = c.isXml || c.isJson;
|
let isLinked = c.isXml || c.isJson;
|
||||||
@@ -581,14 +590,17 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
|||||||
}
|
}
|
||||||
|
|
||||||
private saveViewState(): void {
|
private saveViewState(): void {
|
||||||
let gridSelections = this.slickgrids.toArray()[0].getSelectedRanges();
|
let grid = this.slickgrids.toArray()[0]
|
||||||
let viewport = ((this.slickgrids.toArray()[0] as any)._grid.getCanvasNode() as HTMLElement).parentElement;
|
if (grid) {
|
||||||
|
let gridSelections = grid.getSelectedRanges();
|
||||||
|
let viewport = ((grid as any)._grid.getCanvasNode() as HTMLElement).parentElement;
|
||||||
|
|
||||||
this.savedViewState = {
|
this.savedViewState = {
|
||||||
gridSelections,
|
gridSelections,
|
||||||
scrollTop: viewport.scrollTop,
|
scrollTop: viewport.scrollTop,
|
||||||
scrollLeft: viewport.scrollLeft
|
scrollLeft: viewport.scrollLeft
|
||||||
};
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private restoreViewState(): void {
|
private restoreViewState(): void {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import 'vs/css!sql/parts/grid/media/slickGrid';
|
|||||||
|
|
||||||
import { Subscription, Subject } from 'rxjs/Rx';
|
import { Subscription, Subject } from 'rxjs/Rx';
|
||||||
import { ElementRef, QueryList, ChangeDetectorRef, ViewChildren } from '@angular/core';
|
import { ElementRef, QueryList, ChangeDetectorRef, ViewChildren } from '@angular/core';
|
||||||
import { IGridDataRow, ISlickRange, SlickGrid, FieldType } from 'angular2-slickgrid';
|
import { SlickGrid } from 'angular2-slickgrid';
|
||||||
import { toDisposableSubscription } from 'sql/parts/common/rxjsUtils';
|
import { toDisposableSubscription } from 'sql/parts/common/rxjsUtils';
|
||||||
import * as Constants from 'sql/parts/query/common/constants';
|
import * as Constants from 'sql/parts/query/common/constants';
|
||||||
import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants';
|
import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants';
|
||||||
@@ -226,11 +226,11 @@ export abstract class GridParentComponent {
|
|||||||
this.messagesFocussedContextKey.set(false);
|
this.messagesFocussedContextKey.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getSelection(index?: number): ISlickRange[] {
|
protected getSelection(index?: number): Slick.Range[] {
|
||||||
let selection = this.slickgrids.toArray()[index || this.activeGrid].getSelectedRanges();
|
let selection = this.slickgrids.toArray()[index || this.activeGrid].getSelectedRanges();
|
||||||
if (selection) {
|
if (selection) {
|
||||||
selection = selection.map(c => { return <ISlickRange>{ 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;
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -332,7 +332,7 @@ export abstract class GridParentComponent {
|
|||||||
/**
|
/**
|
||||||
* Send save result set request to service
|
* Send save result set request to service
|
||||||
*/
|
*/
|
||||||
handleContextClick(event: { type: string, batchId: number, resultId: number, index: number, selection: ISlickRange[] }): void {
|
handleContextClick(event: { type: string, batchId: number, resultId: number, index: number, selection: Slick.Range[] }): void {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case 'savecsv':
|
case 'savecsv':
|
||||||
this.dataService.sendSaveRequest({ batchIndex: event.batchId, resultSetNumber: event.resultId, format: SaveFormat.CSV, selection: event.selection });
|
this.dataService.sendSaveRequest({ batchIndex: event.batchId, resultSetNumber: event.resultId, format: SaveFormat.CSV, selection: event.selection });
|
||||||
|
|||||||
@@ -328,11 +328,15 @@ export class ChartViewerComponent implements OnInit, OnDestroy, IChartViewAction
|
|||||||
|
|
||||||
// Remove first column and its value since this is the row number column
|
// Remove first column and its value since this is the row number column
|
||||||
this._executeResult.columns = dataSet.columnDefinitions.slice(1).map(def => def.name);
|
this._executeResult.columns = dataSet.columnDefinitions.slice(1).map(def => def.name);
|
||||||
this._executeResult.rows = dataSet.dataRows.getRange(0, dataSet.dataRows.getLength()).map(gridRow => {
|
this._executeResult.rows = dataSet.dataRows.getRange(0, dataSet.dataRows.getLength()).map(v => {
|
||||||
return gridRow.values.slice(1).map(cell => (cell.invariantCultureDisplayValue === null || cell.invariantCultureDisplayValue === undefined) ? cell.displayValue : cell.invariantCultureDisplayValue);
|
return this._executeResult.columns.reduce((p, c) => {
|
||||||
|
p.push(v[c]);
|
||||||
|
return p;
|
||||||
|
}, []);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public initChart() {
|
public initChart() {
|
||||||
this._cd.detectChanges();
|
this._cd.detectChanges();
|
||||||
if (this._executeResult) {
|
if (this._executeResult) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
ElementRef, QueryList, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject,
|
ElementRef, QueryList, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject,
|
||||||
ViewChildren, forwardRef, EventEmitter, Input, ViewChild
|
ViewChildren, forwardRef, EventEmitter, Input, ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { IGridDataRow, SlickGrid, VirtualizedCollection, ISlickRange } from 'angular2-slickgrid';
|
import { IGridDataRow, SlickGrid, VirtualizedCollection } from 'angular2-slickgrid';
|
||||||
|
|
||||||
import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants';
|
import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants';
|
||||||
import * as Services from 'sql/parts/grid/services/sharedServices';
|
import * as Services from 'sql/parts/grid/services/sharedServices';
|
||||||
@@ -163,7 +163,7 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
|
|||||||
public onActiveCellChanged: (gridIndex: number) => void;
|
public onActiveCellChanged: (gridIndex: number) => void;
|
||||||
|
|
||||||
private savedViewState: {
|
private savedViewState: {
|
||||||
gridSelections: ISlickRange[][];
|
gridSelections: Slick.Range[][];
|
||||||
resultsScroll: number;
|
resultsScroll: number;
|
||||||
messagePaneScroll: number;
|
messagePaneScroll: number;
|
||||||
slickGridScrolls: { vertical: number; horizontal: number }[];
|
slickGridScrolls: { vertical: number; horizontal: number }[];
|
||||||
|
|||||||
@@ -196,21 +196,21 @@ export class InsightsDialogView extends Modal {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._register(this._topTable.onContextMenu((e: DOMEvent, data: Slick.OnContextMenuEventArgs<any>) => {
|
this._register(this._topTable.onContextMenu(e => {
|
||||||
if (this.hasActions()) {
|
if (this.hasActions()) {
|
||||||
this._contextMenuService.showContextMenu({
|
this._contextMenuService.showContextMenu({
|
||||||
getAnchor: () => e.target as HTMLElement,
|
getAnchor: () => e.anchor,
|
||||||
getActions: () => this.insightActions,
|
getActions: () => this.insightActions,
|
||||||
getActionsContext: () => this.topInsightContext(this._topTableData.getItem(this._topTable.getCellFromEvent(e).row))
|
getActionsContext: () => this.topInsightContext(this._topTableData.getItem(e.cell.row))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._register(this._bottomTable.onContextMenu((e: DOMEvent, data: Slick.OnContextMenuEventArgs<any>) => {
|
this._register(this._bottomTable.onContextMenu(e => {
|
||||||
this._contextMenuService.showContextMenu({
|
this._contextMenuService.showContextMenu({
|
||||||
getAnchor: () => e.target as HTMLElement,
|
getAnchor: () => e.anchor,
|
||||||
getActions: () => TPromise.as([this._instantiationService.createInstance(CopyInsightDialogSelectionAction, CopyInsightDialogSelectionAction.ID, CopyInsightDialogSelectionAction.LABEL)]),
|
getActions: () => TPromise.as([this._instantiationService.createInstance(CopyInsightDialogSelectionAction, CopyInsightDialogSelectionAction.ID, CopyInsightDialogSelectionAction.LABEL)]),
|
||||||
getActionsContext: () => this.bottomInsightContext(this._bottomTableData.getItem(this._bottomTable.getCellFromEvent(e).row), this._bottomTable.getCellFromEvent(e))
|
getActionsContext: () => this.bottomInsightContext(this._bottomTableData.getItem(e.cell.row), e.cell)
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -109,10 +109,10 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
|
|||||||
$(this._gridEl.nativeElement).empty();
|
$(this._gridEl.nativeElement).empty();
|
||||||
$(this.actionBarContainer.nativeElement).empty();
|
$(this.actionBarContainer.nativeElement).empty();
|
||||||
this.initActionBar();
|
this.initActionBar();
|
||||||
this._table = new Table(this._gridEl.nativeElement, undefined, columns, this.options);
|
this._table = new Table(this._gridEl.nativeElement, {columns}, this.options);
|
||||||
this._table.grid.setData(this.dataView, true);
|
this._table.grid.setData(this.dataView, true);
|
||||||
|
|
||||||
this._register(this._table.onContextMenu((e: DOMEvent, data: Slick.OnContextMenuEventArgs<any>) => {
|
this._register(this._table.onContextMenu(e => {
|
||||||
self.openContextMenu(e);
|
self.openContextMenu(e);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
|
|||||||
$(this._gridEl.nativeElement).empty();
|
$(this._gridEl.nativeElement).empty();
|
||||||
$(this.actionBarContainer.nativeElement).empty();
|
$(this.actionBarContainer.nativeElement).empty();
|
||||||
this.initActionBar();
|
this.initActionBar();
|
||||||
this._table = new Table(this._gridEl.nativeElement, undefined, columns, options);
|
this._table = new Table(this._gridEl.nativeElement, {columns}, options);
|
||||||
this._table.grid.setData(this.dataView, true);
|
this._table.grid.setData(this.dataView, true);
|
||||||
this._table.grid.onClick.subscribe((e, args) => {
|
this._table.grid.onClick.subscribe((e, args) => {
|
||||||
let job = self.getJob(args);
|
let job = self.getJob(args);
|
||||||
@@ -186,7 +186,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
|
|||||||
self._agentViewComponent.showHistory = true;
|
self._agentViewComponent.showHistory = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
this._register(this._table.onContextMenu((e: DOMEvent, data: Slick.OnContextMenuEventArgs<any>) => {
|
this._register(this._table.onContextMenu(e => {
|
||||||
self.openContextMenu(e);
|
self.openContextMenu(e);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -110,10 +110,10 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
|
|||||||
$(this._gridEl.nativeElement).empty();
|
$(this._gridEl.nativeElement).empty();
|
||||||
$(this.actionBarContainer.nativeElement).empty();
|
$(this.actionBarContainer.nativeElement).empty();
|
||||||
this.initActionBar();
|
this.initActionBar();
|
||||||
this._table = new Table(this._gridEl.nativeElement, undefined, columns, this.options);
|
this._table = new Table(this._gridEl.nativeElement, {columns}, this.options);
|
||||||
this._table.grid.setData(this.dataView, true);
|
this._table.grid.setData(this.dataView, true);
|
||||||
|
|
||||||
this._register(this._table.onContextMenu((e: DOMEvent, data: Slick.OnContextMenuEventArgs<any>) => {
|
this._register(this._table.onContextMenu(e => {
|
||||||
self.openContextMenu(e);
|
self.openContextMenu(e);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -112,10 +112,10 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
|
|||||||
$(this._gridEl.nativeElement).empty();
|
$(this._gridEl.nativeElement).empty();
|
||||||
$(this.actionBarContainer.nativeElement).empty();
|
$(this.actionBarContainer.nativeElement).empty();
|
||||||
this.initActionBar();
|
this.initActionBar();
|
||||||
this._table = new Table(this._gridEl.nativeElement, undefined, columns, this.options);
|
this._table = new Table(this._gridEl.nativeElement, {columns}, this.options);
|
||||||
this._table.grid.setData(this.dataView, true);
|
this._table.grid.setData(this.dataView, true);
|
||||||
|
|
||||||
this._register(this._table.onContextMenu((e: DOMEvent, data: Slick.OnContextMenuEventArgs<any>) => {
|
this._register(this._table.onContextMenu(e => {
|
||||||
self.openContextMenu(e);
|
self.openContextMenu(e);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export default class TableComponent extends ComponentBase implements IComponent,
|
|||||||
forceFitColumns: true
|
forceFitColumns: true
|
||||||
};
|
};
|
||||||
|
|
||||||
this._table = new Table<Slick.SlickData>(this._inputContainer.nativeElement, this._tableData, this._tableColumns, options);
|
this._table = new Table<Slick.SlickData>(this._inputContainer.nativeElement, { dataProvider: this._tableData, columns: this._tableColumns }, options);
|
||||||
this._table.setData(this._tableData);
|
this._table.setData(this._tableData);
|
||||||
this._table.setSelectionModel(new RowSelectionModel({ selectActiveRow: true }));
|
this._table.setSelectionModel(new RowSelectionModel({ selectActiveRow: true }));
|
||||||
|
|
||||||
|
|||||||
@@ -327,7 +327,7 @@ export class ProfilerEditor extends BaseEditor {
|
|||||||
detailTableContainer.style.width = '100%';
|
detailTableContainer.style.width = '100%';
|
||||||
detailTableContainer.style.height = '100%';
|
detailTableContainer.style.height = '100%';
|
||||||
this._detailTableData = new TableDataView<IDetailData>();
|
this._detailTableData = new TableDataView<IDetailData>();
|
||||||
this._detailTable = new Table(detailTableContainer, this._detailTableData, [
|
this._detailTable = new Table(detailTableContainer, { dataProvider: this._detailTableData, columns: [
|
||||||
{
|
{
|
||||||
id: 'label',
|
id: 'label',
|
||||||
name: nls.localize('label', "Label"),
|
name: nls.localize('label', "Label"),
|
||||||
@@ -340,7 +340,7 @@ export class ProfilerEditor extends BaseEditor {
|
|||||||
field: 'value',
|
field: 'value',
|
||||||
formatter: textFormatter
|
formatter: textFormatter
|
||||||
}
|
}
|
||||||
], { forceFitColumns: true });
|
]}, { forceFitColumns: true });
|
||||||
|
|
||||||
this._tabbedPanel.pushTab({
|
this._tabbedPanel.pushTab({
|
||||||
identifier: 'detailTable',
|
identifier: 'detailTable',
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import * as paths from 'vs/base/common/paths';
|
|||||||
import * as nls from 'vs/nls';
|
import * as nls from 'vs/nls';
|
||||||
import * as pretty from 'pretty-data';
|
import * as pretty from 'pretty-data';
|
||||||
|
|
||||||
import { ISlickRange } from 'angular2-slickgrid';
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import Severity from 'vs/base/common/severity';
|
import Severity from 'vs/base/common/severity';
|
||||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
@@ -255,7 +254,7 @@ export class ResultSerializer {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getParameters(filePath: string, batchIndex: number, resultSetNo: number, format: string, selection: ISlickRange): SaveResultsRequestParams {
|
private getParameters(filePath: string, batchIndex: number, resultSetNo: number, format: string, selection: Slick.Range): SaveResultsRequestParams {
|
||||||
let saveResultsParams: SaveResultsRequestParams;
|
let saveResultsParams: SaveResultsRequestParams;
|
||||||
if (!path.isAbsolute(filePath)) {
|
if (!path.isAbsolute(filePath)) {
|
||||||
this._filePath = PathUtilities.resolveFilePath(this._uri, filePath, this.rootPath);
|
this._filePath = PathUtilities.resolveFilePath(this._uri, filePath, this.rootPath);
|
||||||
@@ -287,7 +286,7 @@ export class ResultSerializer {
|
|||||||
/**
|
/**
|
||||||
* Check if a range of cells were selected.
|
* Check if a range of cells were selected.
|
||||||
*/
|
*/
|
||||||
private isSelected(selection: ISlickRange): boolean {
|
private isSelected(selection: Slick.Range): boolean {
|
||||||
return (selection && !((selection.fromCell === selection.toCell) && (selection.fromRow === selection.toRow)));
|
return (selection && !((selection.fromCell === selection.toCell) && (selection.fromRow === selection.toRow)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,7 +318,7 @@ export class ResultSerializer {
|
|||||||
/**
|
/**
|
||||||
* Send request to sql tools service to save a result set
|
* Send request to sql tools service to save a result set
|
||||||
*/
|
*/
|
||||||
private sendRequestToService(filePath: string, batchIndex: number, resultSetNo: number, format: string, selection: ISlickRange): Thenable<void> {
|
private sendRequestToService(filePath: string, batchIndex: number, resultSetNo: number, format: string, selection: Slick.Range): Thenable<void> {
|
||||||
let saveResultsParams = this.getParameters(filePath, batchIndex, resultSetNo, format, selection);
|
let saveResultsParams = this.getParameters(filePath, batchIndex, resultSetNo, format, selection);
|
||||||
|
|
||||||
this.logToOutputChannel(LocalizedConstants.msgSaveStarted + this._filePath);
|
this.logToOutputChannel(LocalizedConstants.msgSaveStarted + this._filePath);
|
||||||
|
|||||||
160
src/sql/parts/query/editor/actions.ts
Normal file
160
src/sql/parts/query/editor/actions.ts
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 { Action } from 'vs/base/common/actions';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
|
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||||
|
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
||||||
|
|
||||||
|
import QueryRunner from 'sql/parts/query/execution/queryRunner';
|
||||||
|
import { SaveFormat } from 'sql/parts/grid/common/interfaces';
|
||||||
|
import { Table } from 'sql/base/browser/ui/table/table';
|
||||||
|
import { GridTableState } from 'sql/parts/query/editor/gridPanel';
|
||||||
|
|
||||||
|
export interface IGridActionContext {
|
||||||
|
cell: { row: number; cell: number; };
|
||||||
|
selection: Slick.Range[];
|
||||||
|
runner: QueryRunner;
|
||||||
|
batchId: number;
|
||||||
|
resultId: number;
|
||||||
|
table: Table<any>;
|
||||||
|
tableState: GridTableState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMessagesActionContext {
|
||||||
|
selection: Selection;
|
||||||
|
tree: ITree;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SaveResultAction extends Action {
|
||||||
|
public static SAVECSV_ID = 'grid.saveAsCsv';
|
||||||
|
public static SAVECSV_LABEL = localize('saveAsCsv', 'Save As CSV');
|
||||||
|
public static SAVECSV_ICON = 'saveCsv';
|
||||||
|
|
||||||
|
public static SAVEJSON_ID = 'grid.saveAsJson';
|
||||||
|
public static SAVEJSON_LABEL = localize('saveAsJson', 'Save As JSON');
|
||||||
|
public static SAVEJSON_ICON = 'saveJson';
|
||||||
|
|
||||||
|
public static SAVEEXCEL_ID = 'grid.saveAsExcel';
|
||||||
|
public static SAVEEXCEL_LABEL = localize('saveAsExcel', 'Save As Excel');
|
||||||
|
public static SAVEEXCEL_ICON = 'saveExcel';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
id: string,
|
||||||
|
label: string,
|
||||||
|
icon: string,
|
||||||
|
private format: SaveFormat
|
||||||
|
) {
|
||||||
|
super(id, label, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public run(context: IGridActionContext): TPromise<boolean> {
|
||||||
|
context.runner.serializeResults(context.batchId, context.resultId, this.format, context.selection);
|
||||||
|
return TPromise.as(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CopyResultAction extends Action {
|
||||||
|
public static COPY_ID = 'grid.copySelection';
|
||||||
|
public static COPY_LABEL = localize('copySelection', 'Copy');
|
||||||
|
|
||||||
|
public static COPYWITHHEADERS_ID = 'grid.copyWithHeaders';
|
||||||
|
public static COPYWITHHEADERS_LABEL = localize('copyWithHeaders', 'Copy With Headers');
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
id: string,
|
||||||
|
label: string,
|
||||||
|
private copyHeader: boolean,
|
||||||
|
) {
|
||||||
|
super(id, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public run(context: IGridActionContext): TPromise<boolean> {
|
||||||
|
context.runner.copyResults(context.selection, context.batchId, context.resultId, this.copyHeader);
|
||||||
|
return TPromise.as(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SelectAllGridAction extends Action {
|
||||||
|
public static ID = 'grid.selectAll';
|
||||||
|
public static LABEL = localize('selectAll', 'Select All');
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(SelectAllGridAction.ID, SelectAllGridAction.LABEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public run(context: IGridActionContext): TPromise<boolean> {
|
||||||
|
context.table.setSelectedRows(true);
|
||||||
|
return TPromise.as(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CopyMessagesAction extends Action {
|
||||||
|
public static ID = 'grid.messages.copy';
|
||||||
|
public static LABEL = localize('copyMessages', 'Copy');
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IClipboardService private clipboardService: IClipboardService
|
||||||
|
) {
|
||||||
|
super(CopyMessagesAction.ID, CopyMessagesAction.LABEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public run(context: IMessagesActionContext): TPromise<boolean> {
|
||||||
|
this.clipboardService.writeText(context.selection.toString());
|
||||||
|
return TPromise.as(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SelectAllMessagesAction extends Action {
|
||||||
|
public static ID = 'grid.messages.selectAll';
|
||||||
|
public static LABEL = localize('selectAll', 'Select All');
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(SelectAllMessagesAction.ID, SelectAllMessagesAction.LABEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public run(context: IMessagesActionContext): TPromise<boolean> {
|
||||||
|
let range = document.createRange();
|
||||||
|
range.selectNodeContents(context.tree.getHTMLElement());
|
||||||
|
let sel = document.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
return TPromise.as(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MaximizeTableAction extends Action {
|
||||||
|
public static ID = 'grid.maximize';
|
||||||
|
public static LABEL = localize('maximize', 'Maximize');
|
||||||
|
public static ICON = 'extendFullScreen';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(MaximizeTableAction.ID, MaximizeTableAction.LABEL, MaximizeTableAction.ICON);
|
||||||
|
}
|
||||||
|
|
||||||
|
public run(context: IGridActionContext): TPromise<boolean> {
|
||||||
|
context.tableState.maximized = true;
|
||||||
|
return TPromise.as(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MinimizeTableAction extends Action {
|
||||||
|
public static ID = 'grid.minimize';
|
||||||
|
public static LABEL = localize('minimize', 'Minimize');
|
||||||
|
public static ICON = 'exitFullScreen';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(MinimizeTableAction.ID, MinimizeTableAction.LABEL, MinimizeTableAction.ICON);
|
||||||
|
}
|
||||||
|
|
||||||
|
public run(context: IGridActionContext): TPromise<boolean> {
|
||||||
|
context.tableState.maximized = false;
|
||||||
|
return TPromise.as(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
414
src/sql/parts/query/editor/gridPanel.ts
Normal file
414
src/sql/parts/query/editor/gridPanel.ts
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 { attachTableStyler } from 'sql/common/theme/styler';
|
||||||
|
import QueryRunner from 'sql/parts/query/execution/queryRunner';
|
||||||
|
import { VirtualizedCollection, AsyncDataProvider } from 'sql/base/browser/ui/table/asyncDataView';
|
||||||
|
import { Table, ITableStyles, ITableContextMenuEvent } from 'sql/base/browser/ui/table/table';
|
||||||
|
import { ScrollableSplitView } from 'sql/base/browser/ui/scrollableSplitview/scrollableSplitview';
|
||||||
|
import { MouseWheelSupport } from 'sql/base/browser/ui/table/plugins/mousewheelTableScroll.plugin';
|
||||||
|
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
|
||||||
|
import { SaveFormat } from 'sql/parts/grid/common/interfaces';
|
||||||
|
import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, MinimizeTableAction } from 'sql/parts/query/editor/actions';
|
||||||
|
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
|
||||||
|
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
|
||||||
|
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
|
||||||
|
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||||
|
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||||
|
import { range } from 'vs/base/common/arrays';
|
||||||
|
import { Orientation, IView } from 'vs/base/browser/ui/splitview/splitview';
|
||||||
|
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||||
|
import { $ } from 'vs/base/browser/builder';
|
||||||
|
import { generateUuid } from 'vs/base/common/uuid';
|
||||||
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
|
import { Separator, ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||||
|
import { Dimension, getContentWidth } from 'vs/base/browser/dom';
|
||||||
|
|
||||||
|
const rowHeight = 29;
|
||||||
|
const columnHeight = 26;
|
||||||
|
const minGridHeightInRows = 8;
|
||||||
|
const estimatedScrollBarHeight = 10;
|
||||||
|
|
||||||
|
export interface IGridTableState {
|
||||||
|
canBeMaximized: boolean;
|
||||||
|
maximized: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GridTableState {
|
||||||
|
|
||||||
|
private _maximized: boolean;
|
||||||
|
|
||||||
|
private _onMaximizedChange = new Emitter<boolean>();
|
||||||
|
public onMaximizedChange: Event<boolean> = this._onMaximizedChange.event;
|
||||||
|
|
||||||
|
public canBeMaximized: boolean;
|
||||||
|
|
||||||
|
constructor(state?: IGridTableState) {
|
||||||
|
if (state) {
|
||||||
|
this._maximized = state.maximized;
|
||||||
|
this.canBeMaximized = state.canBeMaximized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get maximized(): boolean {
|
||||||
|
return this._maximized;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set maximized(val: boolean) {
|
||||||
|
if (val === this._maximized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._maximized = val;
|
||||||
|
this._onMaximizedChange.fire(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clone(): GridTableState {
|
||||||
|
return new GridTableState({ canBeMaximized: this.canBeMaximized, maximized: this.maximized });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GridPanel extends ViewletPanel {
|
||||||
|
private container = document.createElement('div');
|
||||||
|
private splitView: ScrollableSplitView;
|
||||||
|
private tables: GridTable<any>[] = [];
|
||||||
|
private tableDisposable: IDisposable[] = [];
|
||||||
|
private queryRunnerDisposables: IDisposable[] = [];
|
||||||
|
|
||||||
|
private runner: QueryRunner;
|
||||||
|
|
||||||
|
private maximizedGrid: GridTable<any>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
title: string, options: IViewletPanelOptions,
|
||||||
|
@IKeybindingService keybindingService: IKeybindingService,
|
||||||
|
@IContextMenuService contextMenuService: IContextMenuService,
|
||||||
|
@IConfigurationService configurationService: IConfigurationService,
|
||||||
|
@IThemeService private themeService: IThemeService
|
||||||
|
) {
|
||||||
|
super(title, options, keybindingService, contextMenuService, configurationService);
|
||||||
|
this.splitView = new ScrollableSplitView(this.container, { enableResizing: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderBody(container: HTMLElement): void {
|
||||||
|
this.container.style.width = '100%';
|
||||||
|
this.container.style.height = '100%';
|
||||||
|
|
||||||
|
container.appendChild(this.container);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected layoutBody(size: number): void {
|
||||||
|
this.splitView.layout(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public set queryRunner(runner: QueryRunner) {
|
||||||
|
dispose(this.queryRunnerDisposables);
|
||||||
|
this.reset();
|
||||||
|
this.queryRunnerDisposables = [];
|
||||||
|
this.runner = runner;
|
||||||
|
this.queryRunnerDisposables.push(this.runner.onResultSet(e => this.onResultSet(e)));
|
||||||
|
this.queryRunnerDisposables.push(this.runner.onQueryStart(() => this.reset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onResultSet(resultSet: sqlops.ResultSetSummary | sqlops.ResultSetSummary[]) {
|
||||||
|
this.addResultSet(resultSet);
|
||||||
|
|
||||||
|
this.tables.map(t => {
|
||||||
|
t.state.canBeMaximized = this.tables.length > 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.maximumBodySize = this.tables.reduce((p, c) => {
|
||||||
|
return p + c.maximumSize;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private addResultSet(resultSet: sqlops.ResultSetSummary | sqlops.ResultSetSummary[]) {
|
||||||
|
let resultsToAdd: sqlops.ResultSetSummary[];
|
||||||
|
if (!Array.isArray(resultSet)) {
|
||||||
|
resultsToAdd = [resultSet];
|
||||||
|
} else {
|
||||||
|
resultsToAdd = resultSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tables: GridTable<any>[] = [];
|
||||||
|
|
||||||
|
for (let set of resultsToAdd) {
|
||||||
|
let tableState = new GridTableState();
|
||||||
|
let table = new GridTable(this.runner, tableState, set, this.contextMenuService);
|
||||||
|
tableState.onMaximizedChange(e => {
|
||||||
|
if (e) {
|
||||||
|
this.maximizeTable(table.id);
|
||||||
|
} else {
|
||||||
|
this.minimizeTables();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.tableDisposable.push(attachTableStyler(table, this.themeService));
|
||||||
|
|
||||||
|
tables.push(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isUndefinedOrNull(this.maximizedGrid)) {
|
||||||
|
this.splitView.addViews(tables, tables.map(i => i.minimumSize), this.splitView.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tables = this.tables.concat(tables);
|
||||||
|
}
|
||||||
|
|
||||||
|
private reset() {
|
||||||
|
for (let i = this.splitView.length - 1; i >= 0; i--) {
|
||||||
|
this.splitView.removeView(i);
|
||||||
|
}
|
||||||
|
dispose(this.tables);
|
||||||
|
this.tables = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private maximizeTable(tableid: string): void {
|
||||||
|
if (!this.tables.find(t => t.id === tableid)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = this.tables.length - 1; i >= 0; i--) {
|
||||||
|
if (this.tables[i].id === tableid) {
|
||||||
|
this.tables[i].state.maximized = true;
|
||||||
|
this.maximizedGrid = this.tables[i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.splitView.removeView(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private minimizeTables(): void {
|
||||||
|
if (this.maximizedGrid) {
|
||||||
|
this.maximizedGrid.state.maximized = false;
|
||||||
|
this.maximizedGrid = undefined;
|
||||||
|
this.splitView.removeView(0);
|
||||||
|
this.splitView.addViews(this.tables, this.tables.map(i => i.minimumSize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridTable<T> extends Disposable implements IView {
|
||||||
|
private static BOTTOMPADDING = 5;
|
||||||
|
private static ACTIONBAR_WIDTH = 26;
|
||||||
|
private table: Table<T>;
|
||||||
|
private actionBar: ActionBar;
|
||||||
|
private container = document.createElement('div');
|
||||||
|
private selectionModel = new CellSelectionModel();
|
||||||
|
private styles: ITableStyles;
|
||||||
|
|
||||||
|
private columns: Slick.Column<T>[];
|
||||||
|
|
||||||
|
private _onDidChange = new Emitter<number>();
|
||||||
|
public readonly onDidChange: Event<number> = this._onDidChange.event;
|
||||||
|
|
||||||
|
public id = generateUuid();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private runner: QueryRunner,
|
||||||
|
public state: GridTableState,
|
||||||
|
private resultSet: sqlops.ResultSetSummary,
|
||||||
|
private contextMenuService: IContextMenuService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.container.style.width = '100%';
|
||||||
|
this.container.style.height = '100%';
|
||||||
|
this.container.style.marginBottom = GridTable.BOTTOMPADDING + 'px';
|
||||||
|
this.container.className = 'grid-panel';
|
||||||
|
|
||||||
|
this.columns = this.resultSet.columnInfo.map((c, i) => {
|
||||||
|
return <Slick.Column<T>>{
|
||||||
|
id: i.toString(),
|
||||||
|
name: c.columnName,
|
||||||
|
field: i.toString(),
|
||||||
|
width: 100
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(container: HTMLElement, orientation: Orientation): void {
|
||||||
|
container.appendChild(this.container);
|
||||||
|
}
|
||||||
|
|
||||||
|
private build(): void {
|
||||||
|
let tableContainer = document.createElement('div');
|
||||||
|
tableContainer.style.display = 'inline-block';
|
||||||
|
|
||||||
|
this.container.appendChild(tableContainer);
|
||||||
|
|
||||||
|
let collection = new VirtualizedCollection(50, this.resultSet.rowCount,
|
||||||
|
(offset, count) => this.loadData(offset, count),
|
||||||
|
index => this.placeholdGenerator(index)
|
||||||
|
);
|
||||||
|
collection.setCollectionChangedCallback((change, startIndex, count) => {
|
||||||
|
this.renderGridDataRowsRange(startIndex, count);
|
||||||
|
});
|
||||||
|
let numberColumn = new RowNumberColumn({ numberOfRows: this.resultSet.rowCount });
|
||||||
|
this.columns.unshift(numberColumn.getColumnDefinition());
|
||||||
|
this.table = this._register(new Table(tableContainer, { dataProvider: new AsyncDataProvider(collection), columns: this.columns }, { rowHeight, showRowNumber: true }));
|
||||||
|
this.table.setSelectionModel(this.selectionModel);
|
||||||
|
this.table.registerPlugin(new MouseWheelSupport());
|
||||||
|
this.table.registerPlugin(new AutoColumnSize());
|
||||||
|
this.table.registerPlugin(numberColumn);
|
||||||
|
this._register(this.table.onContextMenu(this.contextMenu, this));
|
||||||
|
|
||||||
|
if (this.styles) {
|
||||||
|
this.table.style(this.styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
let actions = [];
|
||||||
|
|
||||||
|
if (this.state.canBeMaximized) {
|
||||||
|
if (this.state.maximized) {
|
||||||
|
actions.splice(1, 0, new MinimizeTableAction());
|
||||||
|
} else {
|
||||||
|
actions.splice(1, 0, new MaximizeTableAction());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.push(
|
||||||
|
new SaveResultAction(SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
|
||||||
|
new SaveResultAction(SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
|
||||||
|
new SaveResultAction(SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON)
|
||||||
|
);
|
||||||
|
|
||||||
|
let actionBarContainer = document.createElement('div');
|
||||||
|
actionBarContainer.style.width = GridTable.ACTIONBAR_WIDTH + 'px';
|
||||||
|
actionBarContainer.style.display = 'inline-block';
|
||||||
|
actionBarContainer.style.height = '100%';
|
||||||
|
actionBarContainer.style.verticalAlign = 'top';
|
||||||
|
this.container.appendChild(actionBarContainer);
|
||||||
|
this.actionBar = new ActionBar(actionBarContainer, {
|
||||||
|
orientation: ActionsOrientation.VERTICAL, context: {
|
||||||
|
runner: this.runner,
|
||||||
|
batchId: this.resultSet.batchId,
|
||||||
|
resultId: this.resultSet.id,
|
||||||
|
table: this.table,
|
||||||
|
tableState: this.state
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.actionBar.push(actions, { icon: true, label: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
public layout(size: number): void {
|
||||||
|
if (!this.table) {
|
||||||
|
this.build();
|
||||||
|
}
|
||||||
|
this.table.layout(
|
||||||
|
new Dimension(
|
||||||
|
getContentWidth(this.container) - GridTable.ACTIONBAR_WIDTH,
|
||||||
|
size - GridTable.BOTTOMPADDING
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get minimumSize(): number {
|
||||||
|
let smallestRows = ((this.resultSet.rowCount) * rowHeight) + columnHeight + estimatedScrollBarHeight + GridTable.BOTTOMPADDING;
|
||||||
|
let smallestSize = (minGridHeightInRows * rowHeight) + columnHeight + estimatedScrollBarHeight + GridTable.BOTTOMPADDING;
|
||||||
|
return Math.min(smallestRows, smallestSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get maximumSize(): number {
|
||||||
|
return ((this.resultSet.rowCount) * rowHeight) + columnHeight + estimatedScrollBarHeight + GridTable.BOTTOMPADDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadData(offset: number, count: number): Thenable<T[]> {
|
||||||
|
return this.runner.getQueryRows(offset, count, this.resultSet.batchId, this.resultSet.id).then(response => {
|
||||||
|
return response.resultSubset.rows.map(r => {
|
||||||
|
let dataWithSchema = {};
|
||||||
|
// skip the first column since its a number column
|
||||||
|
for (let i = 1; i < this.columns.length; i++) {
|
||||||
|
dataWithSchema[this.columns[i].field] = r[i - 1].displayValue;
|
||||||
|
}
|
||||||
|
return dataWithSchema as T;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private contextMenu(e: ITableContextMenuEvent): void {
|
||||||
|
const selection = this.selectionModel.getSelectedRanges();
|
||||||
|
const { cell } = e;
|
||||||
|
this.contextMenuService.showContextMenu({
|
||||||
|
getAnchor: () => e.anchor,
|
||||||
|
getActions: () => {
|
||||||
|
let actions = [
|
||||||
|
new SelectAllGridAction(),
|
||||||
|
new Separator(),
|
||||||
|
new SaveResultAction(SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
|
||||||
|
new SaveResultAction(SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
|
||||||
|
new SaveResultAction(SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON),
|
||||||
|
new Separator(),
|
||||||
|
new CopyResultAction(CopyResultAction.COPY_ID, CopyResultAction.COPY_LABEL, false),
|
||||||
|
new CopyResultAction(CopyResultAction.COPYWITHHEADERS_ID, CopyResultAction.COPYWITHHEADERS_LABEL, true)
|
||||||
|
];
|
||||||
|
|
||||||
|
if (this.state.canBeMaximized) {
|
||||||
|
if (this.state.maximized) {
|
||||||
|
actions.splice(1, 0, new MinimizeTableAction());
|
||||||
|
} else {
|
||||||
|
actions.splice(1, 0, new MaximizeTableAction());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TPromise.as(actions);
|
||||||
|
},
|
||||||
|
getActionsContext: () => {
|
||||||
|
return <IGridActionContext>{
|
||||||
|
cell,
|
||||||
|
selection,
|
||||||
|
runner: this.runner,
|
||||||
|
batchId: this.resultSet.batchId,
|
||||||
|
resultId: this.resultSet.id,
|
||||||
|
table: this.table,
|
||||||
|
tableState: this.state
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private placeholdGenerator(index: number): any {
|
||||||
|
return { values: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderGridDataRowsRange(startIndex: number, count: number): void {
|
||||||
|
// let editor = this.table.getCellEditor();
|
||||||
|
// let oldValue = editor ? editor.getValue() : undefined;
|
||||||
|
// let wasValueChanged = editor ? editor.isValueChanged() : false;
|
||||||
|
this.invalidateRange(startIndex, startIndex + count);
|
||||||
|
// let activeCell = this._grid.getActiveCell();
|
||||||
|
// if (editor && activeCell.row >= startIndex && activeCell.row < startIndex + count) {
|
||||||
|
// if (oldValue && wasValueChanged) {
|
||||||
|
// editor.setValue(oldValue);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private invalidateRange(start: number, end: number): void {
|
||||||
|
let refreshedRows = range(start, end);
|
||||||
|
if (this.table) {
|
||||||
|
this.table.invalidateRows(refreshedRows, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public style(styles: ITableStyles) {
|
||||||
|
if (this.table) {
|
||||||
|
this.table.style(styles);
|
||||||
|
} else {
|
||||||
|
this.styles = styles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() {
|
||||||
|
$(this.container).destroy();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/sql/parts/query/editor/media/messagePanel.css
Normal file
32
src/sql/parts/query/editor/media/messagePanel.css
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
/* Disable repl hover highlight in tree. */
|
||||||
|
.monaco-workbench .message-tree .monaco-tree .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) {
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disable repl hover highlight in tree. */
|
||||||
|
.monaco-workbench .message-tree .monaco-tree .monaco-tree-row > .content {
|
||||||
|
line-height: 18px;
|
||||||
|
user-select: text;
|
||||||
|
word-wrap: break-word;
|
||||||
|
/* white-space: pre-wrap; */
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-tree .time-stamp {
|
||||||
|
width: 100px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-tree .message,
|
||||||
|
.message-tree .batch-start {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-tree .batch-start {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-tree .batch-start:hover {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
343
src/sql/parts/query/editor/messagePanel.ts
Normal file
343
src/sql/parts/query/editor/messagePanel.ts
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 'vs/css!./media/messagePanel';
|
||||||
|
import { IMessagesActionContext, SelectAllMessagesAction, CopyMessagesAction } from './actions';
|
||||||
|
import QueryRunner from 'sql/parts/query/execution/queryRunner';
|
||||||
|
|
||||||
|
import { IResultMessage, BatchSummary, ISelectionData } from 'sqlops';
|
||||||
|
|
||||||
|
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||||
|
import { IDataSource, ITree, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
|
||||||
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
|
import { generateUuid } from 'vs/base/common/uuid';
|
||||||
|
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||||
|
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||||
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { OpenMode, ClickBehavior, ICancelableEvent, IControllerOptions } from 'vs/base/parts/tree/browser/treeDefaults';
|
||||||
|
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
|
||||||
|
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||||
|
import { $ } from 'vs/base/browser/builder';
|
||||||
|
import { isArray } from 'vs/base/common/types';
|
||||||
|
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||||
|
import { IEditor } from 'vs/editor/common/editorCommon';
|
||||||
|
|
||||||
|
export interface IResultMessageIntern extends IResultMessage {
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMessagePanelMessage {
|
||||||
|
message: string;
|
||||||
|
isError: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMessagePanelBatchMessage extends IMessagePanelMessage {
|
||||||
|
selection: ISelectionData;
|
||||||
|
time: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IMessageTemplate {
|
||||||
|
message: HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IBatchTemplate extends IMessageTemplate {
|
||||||
|
timeStamp: HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TemplateIds = {
|
||||||
|
MESSAGE: 'message',
|
||||||
|
BATCH: 'batch',
|
||||||
|
MODEL: 'model'
|
||||||
|
};
|
||||||
|
|
||||||
|
export class MessagePanel extends ViewletPanel {
|
||||||
|
private ds = new MessageDataSource();
|
||||||
|
private renderer = new MessageRenderer();
|
||||||
|
private model = new Model();
|
||||||
|
private controller: MessageController;
|
||||||
|
private container = $('div message-tree').getHTMLElement();
|
||||||
|
|
||||||
|
private queryRunnerDisposables: IDisposable[] = [];
|
||||||
|
|
||||||
|
private tree: ITree;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
title: string, options: IViewletPanelOptions,
|
||||||
|
@IKeybindingService keybindingService: IKeybindingService,
|
||||||
|
@IContextMenuService contextMenuService: IContextMenuService,
|
||||||
|
@IConfigurationService configurationService: IConfigurationService,
|
||||||
|
@IThemeService private themeService: IThemeService,
|
||||||
|
@IInstantiationService instantiationService: IInstantiationService
|
||||||
|
) {
|
||||||
|
super(title, options, keybindingService, contextMenuService, configurationService);
|
||||||
|
this.controller = instantiationService.createInstance(MessageController, { openMode: OpenMode.SINGLE_CLICK, clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change, to preserve focus behaviour in input field */ });
|
||||||
|
this.controller.toFocusOnClick = this.model;
|
||||||
|
this.tree = new Tree(this.container, {
|
||||||
|
dataSource: this.ds,
|
||||||
|
renderer: this.renderer,
|
||||||
|
controller: this.controller
|
||||||
|
}, { keyboardSupport: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderBody(container: HTMLElement): void {
|
||||||
|
this.container.style.width = '100%';
|
||||||
|
this.container.style.height = '100%';
|
||||||
|
attachListStyler(this.tree, this.themeService);
|
||||||
|
container.appendChild(this.container);
|
||||||
|
this.tree.setInput(this.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected layoutBody(size: number): void {
|
||||||
|
const previousScrollPosition = this.tree.getScrollPosition();
|
||||||
|
this.tree.layout(size);
|
||||||
|
if (previousScrollPosition === 1) {
|
||||||
|
this.tree.setScrollPosition(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public set queryRunner(runner: QueryRunner) {
|
||||||
|
dispose(this.queryRunnerDisposables);
|
||||||
|
this.queryRunnerDisposables = [];
|
||||||
|
this.reset();
|
||||||
|
this.queryRunnerDisposables.push(runner.onQueryStart(() => this.reset()));
|
||||||
|
this.queryRunnerDisposables.push(runner.onBatchStart(e => this.onBatchStart(e)));
|
||||||
|
this.queryRunnerDisposables.push(runner.onMessage(e => this.onMessage(e)));
|
||||||
|
this.queryRunnerDisposables.push(runner.onQueryEnd(e => this.onQueryEnd(e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onMessage(message: IResultMessage | IResultMessage[]) {
|
||||||
|
if (isArray(message)) {
|
||||||
|
this.model.messages.push(...message.map(c => {
|
||||||
|
return <IMessagePanelMessage>{
|
||||||
|
isError: c.isError,
|
||||||
|
message: c.message
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
this.model.messages.push({
|
||||||
|
message: message.message,
|
||||||
|
isError: message.isError
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const previousScrollPosition = this.tree.getScrollPosition();
|
||||||
|
this.tree.refresh(this.model).then(() => {
|
||||||
|
if (previousScrollPosition === 1) {
|
||||||
|
this.tree.setScrollPosition(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onBatchStart(batch: BatchSummary) {
|
||||||
|
this.model.messages.push({
|
||||||
|
message: localize('query.message.startQuery', 'Started executing query at Line {0}', batch.selection.startLine),
|
||||||
|
time: new Date(batch.executionStart).toLocaleTimeString(),
|
||||||
|
selection: batch.selection,
|
||||||
|
isError: false
|
||||||
|
});
|
||||||
|
const previousScrollPosition = this.tree.getScrollPosition();
|
||||||
|
this.tree.refresh(this.model).then(() => {
|
||||||
|
if (previousScrollPosition === 1) {
|
||||||
|
this.tree.setScrollPosition(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onQueryEnd(elapsedTime: string) {
|
||||||
|
this.model.totalExecuteMessage = {
|
||||||
|
message: localize('query.message.executionTime', 'Total execution time: {0}', elapsedTime),
|
||||||
|
isError: false
|
||||||
|
};
|
||||||
|
const previousScrollPosition = this.tree.getScrollPosition();
|
||||||
|
this.tree.refresh(this.model).then(() => {
|
||||||
|
if (previousScrollPosition === 1) {
|
||||||
|
this.tree.setScrollPosition(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private reset() {
|
||||||
|
this.model.messages = [];
|
||||||
|
this.model.totalExecuteMessage = undefined;
|
||||||
|
this.tree.refresh(this.model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageDataSource implements IDataSource {
|
||||||
|
getId(tree: ITree, element: Model | IResultMessageIntern): string {
|
||||||
|
if (element instanceof Model) {
|
||||||
|
return element.uuid;
|
||||||
|
} else {
|
||||||
|
if (!element.id) {
|
||||||
|
element.id = generateUuid();
|
||||||
|
}
|
||||||
|
return element.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasChildren(tree: ITree, element: any): boolean {
|
||||||
|
return element instanceof Model;
|
||||||
|
}
|
||||||
|
|
||||||
|
getChildren(tree: ITree, element: any): TPromise {
|
||||||
|
if (element instanceof Model) {
|
||||||
|
let messages = element.messages;
|
||||||
|
if (element.totalExecuteMessage) {
|
||||||
|
messages = messages.concat(element.totalExecuteMessage);
|
||||||
|
}
|
||||||
|
return TPromise.as(messages);
|
||||||
|
} else {
|
||||||
|
return TPromise.as(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getParent(tree: ITree, element: any): TPromise {
|
||||||
|
return TPromise.as(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageRenderer implements IRenderer {
|
||||||
|
getHeight(tree: ITree, element: any): number {
|
||||||
|
return 22;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTemplateId(tree: ITree, element: any): string {
|
||||||
|
if (element instanceof Model) {
|
||||||
|
return TemplateIds.MODEL;
|
||||||
|
} else if (element.selection) {
|
||||||
|
return TemplateIds.BATCH;
|
||||||
|
} else {
|
||||||
|
return TemplateIds.MESSAGE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTemplate(tree: ITree, templateId: string, container: HTMLElement): IMessageTemplate | IBatchTemplate {
|
||||||
|
|
||||||
|
if (templateId === TemplateIds.MESSAGE) {
|
||||||
|
$('div.time-stamp').appendTo(container);
|
||||||
|
const message = $('div.message').appendTo(container).getHTMLElement();
|
||||||
|
return { message };
|
||||||
|
} else if (templateId === TemplateIds.BATCH) {
|
||||||
|
const timeStamp = $('div.time-stamp').appendTo(container).getHTMLElement();
|
||||||
|
const message = $('div.batch-start').appendTo(container).getHTMLElement();
|
||||||
|
return { message, timeStamp };
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderElement(tree: ITree, element: IResultMessage, templateId: string, templateData: IMessageTemplate | IBatchTemplate): void {
|
||||||
|
if (templateId === TemplateIds.MESSAGE) {
|
||||||
|
let data: IMessageTemplate = templateData;
|
||||||
|
data.message.innerText = element.message;
|
||||||
|
} else if (templateId === TemplateIds.BATCH) {
|
||||||
|
let data = templateData as IBatchTemplate;
|
||||||
|
data.timeStamp.innerText = element.time;
|
||||||
|
data.message.innerText = element.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MessageController extends WorkbenchTreeController {
|
||||||
|
|
||||||
|
private lastSelectedString: string = null;
|
||||||
|
public toFocusOnClick: { focus(): void };
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
options: IControllerOptions,
|
||||||
|
@IConfigurationService configurationService: IConfigurationService,
|
||||||
|
@IWorkbenchEditorService private workbenchEditorService: IWorkbenchEditorService,
|
||||||
|
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||||
|
@IInstantiationService private instantiationService: IInstantiationService
|
||||||
|
) {
|
||||||
|
super(options, configurationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onLeftClick(tree: ITree, element: any, eventish: ICancelableEvent, origin: string = 'mouse'): boolean {
|
||||||
|
const mouseEvent = <IMouseEvent>eventish;
|
||||||
|
// input and output are one element in the tree => we only expand if the user clicked on the output.
|
||||||
|
// if ((element.reference > 0 || (element instanceof RawObjectReplElement && element.hasChildren)) && mouseEvent.target.className.indexOf('input expression') === -1) {
|
||||||
|
super.onLeftClick(tree, element, eventish, origin);
|
||||||
|
tree.clearFocus();
|
||||||
|
tree.deselect(element);
|
||||||
|
// }
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (selection.type !== 'Range' || this.lastSelectedString === selection.toString()) {
|
||||||
|
// only focus the input if the user is not currently selecting.
|
||||||
|
this.toFocusOnClick.focus();
|
||||||
|
}
|
||||||
|
this.lastSelectedString = selection.toString();
|
||||||
|
|
||||||
|
if (element.selection) {
|
||||||
|
let selection: ISelectionData = element.selection;
|
||||||
|
// this is a batch statement
|
||||||
|
let control = this.workbenchEditorService.getActiveEditor().getControl() as IEditor;
|
||||||
|
control.setSelection({
|
||||||
|
startColumn: selection.startColumn + 1,
|
||||||
|
endColumn: selection.endColumn + 1,
|
||||||
|
endLineNumber: selection.endLine + 1,
|
||||||
|
startLineNumber: selection.startLine + 1
|
||||||
|
});
|
||||||
|
control.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onContextMenu(tree: ITree, element: any, event: ContextMenuEvent): boolean {
|
||||||
|
if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
|
||||||
|
return false; // allow context menu on input fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent native context menu from showing up
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection = document.getSelection();
|
||||||
|
|
||||||
|
this.contextMenuService.showContextMenu({
|
||||||
|
getAnchor: () => {
|
||||||
|
return { x: event.posx, y: event.posy };
|
||||||
|
},
|
||||||
|
getActions: () => {
|
||||||
|
return TPromise.as([
|
||||||
|
this.instantiationService.createInstance(CopyMessagesAction),
|
||||||
|
new SelectAllMessagesAction()
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
getActionsContext: () => {
|
||||||
|
return <IMessagesActionContext>{
|
||||||
|
selection,
|
||||||
|
tree
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Model {
|
||||||
|
public messages: Array<IMessagePanelMessage | IMessagePanelBatchMessage> = [];
|
||||||
|
public totalExecuteMessage: IMessagePanelMessage;
|
||||||
|
|
||||||
|
public uuid = generateUuid();
|
||||||
|
|
||||||
|
public focus() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { TPromise } from 'vs/base/common/winjs.base';
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
import { Builder } from 'vs/base/browser/builder';
|
|
||||||
import { EditorOptions } from 'vs/workbench/common/editor';
|
import { EditorOptions } from 'vs/workbench/common/editor';
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||||
@@ -14,10 +13,10 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
|||||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
|
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
|
||||||
import { getZoomLevel } from 'vs/base/browser/browser';
|
import { getZoomLevel } from 'vs/base/browser/browser';
|
||||||
import { Configuration } from 'vs/editor/browser/config/configuration';
|
|
||||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
import * as types from 'vs/base/common/types';
|
import * as types from 'vs/base/common/types';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
|
||||||
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
|
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
|
||||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||||
@@ -25,8 +24,7 @@ import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService';
|
|||||||
import { IQueryComponentParams } from 'sql/services/bootstrap/bootstrapParams';
|
import { IQueryComponentParams } from 'sql/services/bootstrap/bootstrapParams';
|
||||||
import { QueryOutputModule } from 'sql/parts/query/views/queryOutput.module';
|
import { QueryOutputModule } from 'sql/parts/query/views/queryOutput.module';
|
||||||
import { QUERY_OUTPUT_SELECTOR } from 'sql/parts/query/views/queryOutput.component';
|
import { QUERY_OUTPUT_SELECTOR } from 'sql/parts/query/views/queryOutput.component';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { QueryResultsView } from 'sql/parts/query/editor/queryResultsView';
|
||||||
import { Event } from 'vs/base/common/event';
|
|
||||||
|
|
||||||
export const RESULTS_GRID_DEFAULTS = {
|
export const RESULTS_GRID_DEFAULTS = {
|
||||||
cellPadding: [6, 10, 5],
|
cellPadding: [6, 10, 5],
|
||||||
@@ -97,6 +95,8 @@ export class QueryResultsEditor extends BaseEditor {
|
|||||||
protected _rawOptions: BareResultsGridInfo;
|
protected _rawOptions: BareResultsGridInfo;
|
||||||
protected _input: QueryResultsInput;
|
protected _input: QueryResultsInput;
|
||||||
|
|
||||||
|
private resultsView: QueryResultsView;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ITelemetryService telemetryService: ITelemetryService,
|
@ITelemetryService telemetryService: ITelemetryService,
|
||||||
@IThemeService themeService: IThemeService,
|
@IThemeService themeService: IThemeService,
|
||||||
@@ -106,12 +106,12 @@ export class QueryResultsEditor extends BaseEditor {
|
|||||||
) {
|
) {
|
||||||
super(QueryResultsEditor.ID, telemetryService, themeService);
|
super(QueryResultsEditor.ID, telemetryService, themeService);
|
||||||
this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel());
|
this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel());
|
||||||
this._configurationService.onDidChangeConfiguration(e => {
|
// this._configurationService.onDidChangeConfiguration(e => {
|
||||||
if (e.affectsConfiguration('resultsGrid')) {
|
// if (e.affectsConfiguration('resultsGrid')) {
|
||||||
this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel());
|
// this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel());
|
||||||
this.applySettings();
|
// this.applySettings();
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
public get input(): QueryResultsInput {
|
public get input(): QueryResultsInput {
|
||||||
@@ -136,54 +136,22 @@ export class QueryResultsEditor extends BaseEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createEditor(parent: HTMLElement): void {
|
createEditor(parent: HTMLElement): void {
|
||||||
|
if (!this.resultsView) {
|
||||||
|
this.resultsView = new QueryResultsView(parent, this._instantiationService, this._queryModelService);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layout(dimension: DOM.Dimension): void {
|
layout(dimension: DOM.Dimension): void {
|
||||||
|
this.resultsView.layout(dimension);
|
||||||
}
|
}
|
||||||
|
|
||||||
setInput(input: QueryResultsInput, options: EditorOptions): TPromise<void> {
|
setInput(input: QueryResultsInput, options: EditorOptions): TPromise<void> {
|
||||||
super.setInput(input, options);
|
super.setInput(input, options);
|
||||||
this.applySettings();
|
this.resultsView.input = input;
|
||||||
if (!input.hasBootstrapped) {
|
|
||||||
this._bootstrapAngular();
|
|
||||||
}
|
|
||||||
return TPromise.wrap<void>(null);
|
return TPromise.wrap<void>(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the angular components and record for this input that we have done so
|
|
||||||
*/
|
|
||||||
private _bootstrapAngular(): void {
|
|
||||||
let input = <QueryResultsInput>this.input;
|
|
||||||
let uri = input.uri;
|
|
||||||
|
|
||||||
// Pass the correct DataService to the new angular component
|
|
||||||
let dataService = this._queryModelService.getDataService(uri);
|
|
||||||
if (!dataService) {
|
|
||||||
throw new Error('DataService not found for URI: ' + uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark that we have bootstrapped
|
|
||||||
input.setBootstrappedTrue();
|
|
||||||
|
|
||||||
// Get the bootstrap params and perform the bootstrap
|
|
||||||
// Note: pass in input so on disposal this is cleaned up.
|
|
||||||
// Otherwise many components will be left around and be subscribed
|
|
||||||
// to events from the backing data service
|
|
||||||
let params: IQueryComponentParams = {
|
|
||||||
dataService: dataService,
|
|
||||||
onSaveViewState: this.input.onSaveViewStateEmitter.event,
|
|
||||||
onRestoreViewState: this.input.onRestoreViewStateEmitter.event
|
|
||||||
};
|
|
||||||
bootstrapAngular(this._instantiationService,
|
|
||||||
QueryOutputModule,
|
|
||||||
this.getContainer(),
|
|
||||||
QUERY_OUTPUT_SELECTOR,
|
|
||||||
params,
|
|
||||||
input);
|
|
||||||
}
|
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
122
src/sql/parts/query/editor/queryResultsView.ts
Normal file
122
src/sql/parts/query/editor/queryResultsView.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
|
||||||
|
import { TabbedPanel, IPanelTab, IPanelView } from 'sql/base/browser/ui/panel/panel';
|
||||||
|
import { IQueryModelService } from '../execution/queryModel';
|
||||||
|
import QueryRunner from 'sql/parts/query/execution/queryRunner';
|
||||||
|
import { MessagePanel } from './messagePanel';
|
||||||
|
import { GridPanel } from './gridPanel';
|
||||||
|
|
||||||
|
import * as nls from 'vs/nls';
|
||||||
|
import * as UUID from 'vs/base/common/uuid';
|
||||||
|
import { PanelViewlet } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
|
import { Emitter } from 'vs/base/common/event';
|
||||||
|
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||||
|
|
||||||
|
class ResultsView implements IPanelView {
|
||||||
|
private panelViewlet: PanelViewlet;
|
||||||
|
private gridPanel: GridPanel;
|
||||||
|
private messagePanel: MessagePanel;
|
||||||
|
private container = document.createElement('div');
|
||||||
|
|
||||||
|
private _onRemove = new Emitter<void>();
|
||||||
|
public readonly onRemove = this._onRemove.event;
|
||||||
|
|
||||||
|
private _onLayout = new Emitter<void>();
|
||||||
|
public readonly onLayout = this._onLayout.event;
|
||||||
|
|
||||||
|
private queryRunnerDisposable: IDisposable[] = [];
|
||||||
|
|
||||||
|
constructor(instantiationService: IInstantiationService) {
|
||||||
|
this.panelViewlet = instantiationService.createInstance(PanelViewlet, 'resultsView', { showHeaderInTitleWhenSingleView: false });
|
||||||
|
this.gridPanel = instantiationService.createInstance(GridPanel, nls.localize('gridPanel', 'Results'), {});
|
||||||
|
this.messagePanel = instantiationService.createInstance(MessagePanel, nls.localize('messagePanel', 'Messages'), {});
|
||||||
|
this.panelViewlet.create(this.container).then(() => {
|
||||||
|
this.panelViewlet.addPanels([
|
||||||
|
{ panel: this.gridPanel, size: 1000, index: 0 },
|
||||||
|
{ panel: this.messagePanel, size: this.messagePanel.minimumSize, index: 1 }
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render(container: HTMLElement): void {
|
||||||
|
container.appendChild(this.container);
|
||||||
|
}
|
||||||
|
|
||||||
|
layout(dimension: DOM.Dimension): void {
|
||||||
|
this.panelViewlet.layout(dimension);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(): void {
|
||||||
|
this.container.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
public set queryRunner(runner: QueryRunner) {
|
||||||
|
this.gridPanel.queryRunner = runner;
|
||||||
|
this.messagePanel.queryRunner = runner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResultsTab implements IPanelTab {
|
||||||
|
public readonly title = nls.localize('resultsTabTitle', 'Results');
|
||||||
|
public readonly identifier = UUID.generateUuid();
|
||||||
|
public readonly view: ResultsView;
|
||||||
|
|
||||||
|
private _isAttached = false;
|
||||||
|
|
||||||
|
constructor(instantiationService: IInstantiationService) {
|
||||||
|
this.view = new ResultsView(instantiationService);
|
||||||
|
|
||||||
|
this.view.onLayout(() => this._isAttached = true, this);
|
||||||
|
this.view.onRemove(() => this._isAttached = false, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public isAttached(): boolean {
|
||||||
|
return this._isAttached;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set queryRunner(runner: QueryRunner) {
|
||||||
|
this.view.queryRunner = runner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class QueryResultsView {
|
||||||
|
private _panelView: TabbedPanel;
|
||||||
|
private _input: QueryResultsInput;
|
||||||
|
private resultsTab: ResultsTab;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
container: HTMLElement,
|
||||||
|
@IInstantiationService instantiationService: IInstantiationService,
|
||||||
|
@IQueryModelService private queryModelService: IQueryModelService
|
||||||
|
) {
|
||||||
|
this.resultsTab = new ResultsTab(instantiationService);
|
||||||
|
this._panelView = new TabbedPanel(container, { showHeaderWhenSingleView: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
public style() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public set input(input: QueryResultsInput) {
|
||||||
|
this._input = input;
|
||||||
|
this.resultsTab.queryRunner = this.queryModelService._getQueryInfo(input.uri).queryRunner;
|
||||||
|
// if (!this.resultsTab.isAttached) {
|
||||||
|
this._panelView.pushTab(this.resultsTab);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
public get input(): QueryResultsInput {
|
||||||
|
return this._input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public layout(dimension: DOM.Dimension) {
|
||||||
|
this._panelView.layout(dimension);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
import QueryRunner from 'sql/parts/query/execution/queryRunner';
|
import QueryRunner from 'sql/parts/query/execution/queryRunner';
|
||||||
import { DataService } from 'sql/parts/grid/services/dataService';
|
import { DataService } from 'sql/parts/grid/services/dataService';
|
||||||
import { ISlickRange } from 'angular2-slickgrid';
|
|
||||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { Event } from 'vs/base/common/event';
|
import { Event } from 'vs/base/common/event';
|
||||||
import { QueryInput } from 'sql/parts/query/common/queryInput';
|
import { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||||
@@ -19,6 +18,7 @@ import {
|
|||||||
EditRevertCellResult,
|
EditRevertCellResult,
|
||||||
ExecutionPlanOptions
|
ExecutionPlanOptions
|
||||||
} from 'sqlops';
|
} from 'sqlops';
|
||||||
|
import { QueryInfo } from 'sql/parts/query/execution/queryModelService';
|
||||||
|
|
||||||
export const SERVICE_ID = 'queryModelService';
|
export const SERVICE_ID = 'queryModelService';
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ export interface IQueryModelService {
|
|||||||
resizeResultsets(uri: string): void;
|
resizeResultsets(uri: string): void;
|
||||||
onAngularLoaded(uri: string): void;
|
onAngularLoaded(uri: string): void;
|
||||||
|
|
||||||
copyResults(uri: string, selection: ISlickRange[], batchId: number, resultId: number, includeHeaders?: boolean): void;
|
copyResults(uri: string, selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): void;
|
||||||
setEditorSelection(uri: string, index: number): void;
|
setEditorSelection(uri: string, index: number): void;
|
||||||
showWarning(uri: string, message: string): void;
|
showWarning(uri: string, message: string): void;
|
||||||
showError(uri: string, message: string): void;
|
showError(uri: string, message: string): void;
|
||||||
@@ -69,6 +69,7 @@ export interface IQueryModelService {
|
|||||||
revertRow(ownerUri: string, rowId: number): Thenable<void>;
|
revertRow(ownerUri: string, rowId: number): Thenable<void>;
|
||||||
getEditRows(ownerUri: string, rowStart: number, numberOfRows: number): Thenable<EditSubsetResult>;
|
getEditRows(ownerUri: string, rowStart: number, numberOfRows: number): Thenable<EditSubsetResult>;
|
||||||
|
|
||||||
|
_getQueryInfo(uri: string): QueryInfo;
|
||||||
// Edit Data Callbacks
|
// Edit Data Callbacks
|
||||||
onEditSessionReady: Event<EditSessionReadyParams>;
|
onEditSessionReady: Event<EditSessionReadyParams>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import { SqlFlavorStatusbarItem } from 'sql/parts/query/common/flavorStatus';
|
|||||||
import { RowCountStatusBarItem } from 'sql/parts/query/common/rowCountStatus';
|
import { RowCountStatusBarItem } from 'sql/parts/query/common/rowCountStatus';
|
||||||
|
|
||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
import { ISlickRange } from 'angular2-slickgrid';
|
|
||||||
|
|
||||||
import * as nls from 'vs/nls';
|
import * as nls from 'vs/nls';
|
||||||
import * as statusbar from 'vs/workbench/browser/parts/statusbar/statusbar';
|
import * as statusbar from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||||
@@ -31,7 +30,7 @@ import Severity from 'vs/base/common/severity';
|
|||||||
|
|
||||||
const selectionSnippetMaxLen = 100;
|
const selectionSnippetMaxLen = 100;
|
||||||
|
|
||||||
interface QueryEvent {
|
export interface QueryEvent {
|
||||||
type: string;
|
type: string;
|
||||||
data: any;
|
data: any;
|
||||||
}
|
}
|
||||||
@@ -39,7 +38,7 @@ interface QueryEvent {
|
|||||||
/**
|
/**
|
||||||
* Holds information about the state of a query runner
|
* Holds information about the state of a query runner
|
||||||
*/
|
*/
|
||||||
class QueryInfo {
|
export class QueryInfo {
|
||||||
public queryRunner: QueryRunner;
|
public queryRunner: QueryRunner;
|
||||||
public dataService: DataService;
|
public dataService: DataService;
|
||||||
public queryEventQueue: QueryEvent[];
|
public queryEventQueue: QueryEvent[];
|
||||||
@@ -170,7 +169,7 @@ export class QueryModelService implements IQueryModelService {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public copyResults(uri: string, selection: ISlickRange[], batchId: number, resultId: number, includeHeaders?: boolean): void {
|
public copyResults(uri: string, selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): void {
|
||||||
this._queryInfoMap.get(uri).queryRunner.copyResults(selection, batchId, resultId, includeHeaders);
|
this._queryInfoMap.get(uri).queryRunner.copyResults(selection, batchId, resultId, includeHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,7 +568,7 @@ export class QueryModelService implements IQueryModelService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getQueryInfo(uri: string): QueryInfo {
|
public _getQueryInfo(uri: string): QueryInfo {
|
||||||
return this._queryInfoMap.get(uri);
|
return this._queryInfoMap.get(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import * as sqlops from 'sqlops';
|
|||||||
import * as Constants from 'sql/parts/query/common/constants';
|
import * as Constants from 'sql/parts/query/common/constants';
|
||||||
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
||||||
import { IQueryManagementService } from 'sql/parts/query/common/queryManagement';
|
import { IQueryManagementService } from 'sql/parts/query/common/queryManagement';
|
||||||
import { ISlickRange } from 'angular2-slickgrid';
|
|
||||||
import * as Utils from 'sql/parts/connection/common/utils';
|
import * as Utils from 'sql/parts/connection/common/utils';
|
||||||
|
import { SaveFormat } from 'sql/parts/grid/common/interfaces';
|
||||||
|
|
||||||
import Severity from 'vs/base/common/severity';
|
import Severity from 'vs/base/common/severity';
|
||||||
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||||
@@ -21,6 +21,9 @@ import * as types from 'vs/base/common/types';
|
|||||||
import { EventEmitter } from 'sql/base/common/eventEmitter';
|
import { EventEmitter } from 'sql/base/common/eventEmitter';
|
||||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
|
import { Emitter, echo, debounceEvent, Event } from 'vs/base/common/event';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { ResultSerializer } from 'sql/parts/query/common/resultSerializer';
|
||||||
|
|
||||||
export interface IEditSessionReadyEvent {
|
export interface IEditSessionReadyEvent {
|
||||||
ownerUri: string;
|
ownerUri: string;
|
||||||
@@ -61,6 +64,38 @@ export default class QueryRunner {
|
|||||||
private _batchSets: sqlops.BatchSummary[] = [];
|
private _batchSets: sqlops.BatchSummary[] = [];
|
||||||
private _eventEmitter = new EventEmitter();
|
private _eventEmitter = new EventEmitter();
|
||||||
|
|
||||||
|
private _onMessage = new Emitter<sqlops.IResultMessage>();
|
||||||
|
public readonly onMessage = debounceEvent<sqlops.IResultMessage, sqlops.IResultMessage[]>(echo(this._onMessage.event), (l, e) => {
|
||||||
|
// on first run
|
||||||
|
if (types.isUndefinedOrNull(l)) {
|
||||||
|
return [e];
|
||||||
|
} else {
|
||||||
|
return l.concat(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private _onResultSet = new Emitter<sqlops.ResultSetSummary>();
|
||||||
|
public readonly onResultSet = debounceEvent<sqlops.ResultSetSummary, sqlops.ResultSetSummary[]>(echo(this._onResultSet.event), (l, e) => {
|
||||||
|
// on first run
|
||||||
|
if (types.isUndefinedOrNull(l)) {
|
||||||
|
return [e];
|
||||||
|
} else {
|
||||||
|
return l.concat(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private _onQueryStart = new Emitter<void>();
|
||||||
|
public readonly onQueryStart: Event<void> = echo(this._onQueryStart.event);
|
||||||
|
|
||||||
|
private _onQueryEnd = new Emitter<string>();
|
||||||
|
public readonly onQueryEnd: Event<string> = echo(this._onQueryEnd.event);
|
||||||
|
|
||||||
|
private _onBatchStart = new Emitter<sqlops.BatchSummary>();
|
||||||
|
public readonly onBatchStart: Event<sqlops.BatchSummary> = echo(this._onBatchStart.event);
|
||||||
|
|
||||||
|
private _onBatchEnd = new Emitter<sqlops.BatchSummary>();
|
||||||
|
public readonly onBatchEnd: Event<sqlops.BatchSummary> = echo(this._onBatchEnd.event);
|
||||||
|
|
||||||
// CONSTRUCTOR /////////////////////////////////////////////////////////
|
// CONSTRUCTOR /////////////////////////////////////////////////////////
|
||||||
constructor(
|
constructor(
|
||||||
public uri: string,
|
public uri: string,
|
||||||
@@ -68,7 +103,8 @@ export default class QueryRunner {
|
|||||||
@IQueryManagementService private _queryManagementService: IQueryManagementService,
|
@IQueryManagementService private _queryManagementService: IQueryManagementService,
|
||||||
@INotificationService private _notificationService: INotificationService,
|
@INotificationService private _notificationService: INotificationService,
|
||||||
@IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService,
|
@IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService,
|
||||||
@IClipboardService private _clipboardService: IClipboardService
|
@IClipboardService private _clipboardService: IClipboardService,
|
||||||
|
@IInstantiationService private instantiationService: IInstantiationService
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
get isExecuting(): boolean {
|
get isExecuting(): boolean {
|
||||||
@@ -152,6 +188,7 @@ export default class QueryRunner {
|
|||||||
|
|
||||||
private handleSuccessRunQueryResult() {
|
private handleSuccessRunQueryResult() {
|
||||||
// The query has started, so lets fire up the result pane
|
// The query has started, so lets fire up the result pane
|
||||||
|
this._onQueryStart.fire();
|
||||||
this._eventEmitter.emit(EventType.START);
|
this._eventEmitter.emit(EventType.START);
|
||||||
this._queryManagementService.registerRunner(this, this.uri);
|
this._queryManagementService.registerRunner(this, this.uri);
|
||||||
}
|
}
|
||||||
@@ -187,8 +224,9 @@ export default class QueryRunner {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// We're done with this query so shut down any waiting mechanisms
|
|
||||||
this._eventEmitter.emit(EventType.COMPLETE, Utils.parseNumAsTimeString(this._totalElapsedMilliseconds));
|
this._eventEmitter.emit(EventType.COMPLETE, Utils.parseNumAsTimeString(this._totalElapsedMilliseconds));
|
||||||
|
// We're done with this query so shut down any waiting mechanisms
|
||||||
|
this._onQueryEnd.fire(Utils.parseNumAsTimeString(this._totalElapsedMilliseconds));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -209,6 +247,7 @@ export default class QueryRunner {
|
|||||||
// Store the batch
|
// Store the batch
|
||||||
this.batchSets[batch.id] = batch;
|
this.batchSets[batch.id] = batch;
|
||||||
this._eventEmitter.emit(EventType.BATCH_START, batch);
|
this._eventEmitter.emit(EventType.BATCH_START, batch);
|
||||||
|
this._onBatchStart.fire(batch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -225,7 +264,9 @@ export default class QueryRunner {
|
|||||||
// send a time message in the format used for query complete
|
// send a time message in the format used for query complete
|
||||||
this.sendBatchTimeMessage(batch.id, Utils.parseNumAsTimeString(executionTime));
|
this.sendBatchTimeMessage(batch.id, Utils.parseNumAsTimeString(executionTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
this._eventEmitter.emit(EventType.BATCH_COMPLETE, batch);
|
this._eventEmitter.emit(EventType.BATCH_COMPLETE, batch);
|
||||||
|
this._onBatchEnd.fire(batch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -256,6 +297,7 @@ export default class QueryRunner {
|
|||||||
// Store the result set in the batch and emit that a result set has completed
|
// Store the result set in the batch and emit that a result set has completed
|
||||||
batchSet.resultSetSummaries[resultSet.id] = resultSet;
|
batchSet.resultSetSummaries[resultSet.id] = resultSet;
|
||||||
this._eventEmitter.emit(EventType.RESULT_SET, resultSet);
|
this._eventEmitter.emit(EventType.RESULT_SET, resultSet);
|
||||||
|
this._onResultSet.fire(resultSet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,13 +311,13 @@ export default class QueryRunner {
|
|||||||
|
|
||||||
// Send the message to the results pane
|
// Send the message to the results pane
|
||||||
this._eventEmitter.emit(EventType.MESSAGE, message);
|
this._eventEmitter.emit(EventType.MESSAGE, message);
|
||||||
|
this._onMessage.fire(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get more data rows from the current resultSets from the service layer
|
* Get more data rows from the current resultSets from the service layer
|
||||||
*/
|
*/
|
||||||
public getQueryRows(rowStart: number, numberOfRows: number, batchIndex: number, resultSetIndex: number): Thenable<sqlops.QueryExecuteSubsetResult> {
|
public getQueryRows(rowStart: number, numberOfRows: number, batchIndex: number, resultSetIndex: number): Thenable<sqlops.QueryExecuteSubsetResult> {
|
||||||
const self = this;
|
|
||||||
let rowData: sqlops.QueryExecuteSubsetParams = <sqlops.QueryExecuteSubsetParams>{
|
let rowData: sqlops.QueryExecuteSubsetParams = <sqlops.QueryExecuteSubsetParams>{
|
||||||
ownerUri: this.uri,
|
ownerUri: this.uri,
|
||||||
resultSetIndex: resultSetIndex,
|
resultSetIndex: resultSetIndex,
|
||||||
@@ -284,16 +326,12 @@ export default class QueryRunner {
|
|||||||
batchIndex: batchIndex
|
batchIndex: batchIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Promise<sqlops.QueryExecuteSubsetResult>((resolve, reject) => {
|
return this._queryManagementService.getQueryRows(rowData).then(r => r, error => {
|
||||||
self._queryManagementService.getQueryRows(rowData).then(result => {
|
this._notificationService.notify({
|
||||||
resolve(result);
|
severity: Severity.Error,
|
||||||
}, error => {
|
message: nls.localize('query.gettingRowsFailedError', 'Something went wrong getting more rows: {0}', error)
|
||||||
self._notificationService.notify({
|
|
||||||
severity: Severity.Error,
|
|
||||||
message: nls.localize('query.gettingRowsFailedError', 'Something went wrong getting more rows: {0}', error)
|
|
||||||
});
|
|
||||||
reject(error);
|
|
||||||
});
|
});
|
||||||
|
return error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,7 +355,7 @@ export default class QueryRunner {
|
|||||||
this._isExecuting = false;
|
this._isExecuting = false;
|
||||||
this._notificationService.notify({
|
this._notificationService.notify({
|
||||||
severity: Severity.Error,
|
severity: Severity.Error,
|
||||||
message: nls.localize('query.initEditExecutionFailed', 'Init Edit Execution failed: ') + error
|
message: nls.localize('query.initEditExecutionFailed', 'Init Edit Execution failed: ') + error
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -341,7 +379,7 @@ export default class QueryRunner {
|
|||||||
let error = `Nothing returned from subset query`;
|
let error = `Nothing returned from subset query`;
|
||||||
self._notificationService.notify({
|
self._notificationService.notify({
|
||||||
severity: Severity.Error,
|
severity: Severity.Error,
|
||||||
message: error
|
message: error
|
||||||
});
|
});
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
@@ -350,7 +388,7 @@ export default class QueryRunner {
|
|||||||
let errorMessage = nls.localize('query.moreRowsFailedError', 'Something went wrong getting more rows:');
|
let errorMessage = nls.localize('query.moreRowsFailedError', 'Something went wrong getting more rows:');
|
||||||
self._notificationService.notify({
|
self._notificationService.notify({
|
||||||
severity: Severity.Error,
|
severity: Severity.Error,
|
||||||
message: `${errorMessage} ${error}`
|
message: `${errorMessage} ${error}`
|
||||||
});
|
});
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
@@ -412,7 +450,7 @@ export default class QueryRunner {
|
|||||||
* @param resultId The result id of the result to copy from
|
* @param resultId The result id of the result to copy from
|
||||||
* @param includeHeaders [Optional]: Should column headers be included in the copy selection
|
* @param includeHeaders [Optional]: Should column headers be included in the copy selection
|
||||||
*/
|
*/
|
||||||
copyResults(selection: ISlickRange[], batchId: number, resultId: number, includeHeaders?: boolean): void {
|
copyResults(selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): void {
|
||||||
const self = this;
|
const self = this;
|
||||||
let copyString = '';
|
let copyString = '';
|
||||||
const eol = this.getEolString();
|
const eol = this.getEolString();
|
||||||
@@ -483,7 +521,7 @@ export default class QueryRunner {
|
|||||||
return !!removeNewLines;
|
return !!removeNewLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getColumnHeaders(batchId: number, resultId: number, range: ISlickRange): string[] {
|
private getColumnHeaders(batchId: number, resultId: number, range: Slick.Range): string[] {
|
||||||
let headers: string[] = undefined;
|
let headers: string[] = undefined;
|
||||||
let batchSummary: sqlops.BatchSummary = this.batchSets[batchId];
|
let batchSummary: sqlops.BatchSummary = this.batchSets[batchId];
|
||||||
if (batchSummary !== undefined) {
|
if (batchSummary !== undefined) {
|
||||||
@@ -519,7 +557,11 @@ export default class QueryRunner {
|
|||||||
isError: false
|
isError: false
|
||||||
};
|
};
|
||||||
// Send the message to the results pane
|
// Send the message to the results pane
|
||||||
this._eventEmitter.emit(EventType.MESSAGE, message);
|
this._onMessage.fire(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public serializeResults(batchId: number, resultSetId: number, format: SaveFormat, selection: Slick.Range[]) {
|
||||||
|
return this.instantiationService.createInstance(ResultSerializer).saveResults(this.uri, { selection, format, batchIndex: batchId, resultSetNumber: resultSetId });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export class TopOperationsComponent extends TabChild implements OnDestroy, OnIni
|
|||||||
column.rerenderOnResize = true;
|
column.rerenderOnResize = true;
|
||||||
return column;
|
return column;
|
||||||
});
|
});
|
||||||
this._table = new Table(this._el.nativeElement, data, columns);
|
this._table = new Table(this._el.nativeElement, { dataProvider: data, columns });
|
||||||
this._disposables.push(attachTableStyler(this._table, this.themeService));
|
this._disposables.push(attachTableStyler(this._table, this.themeService));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/typings/globals/slickgrid/index.d.ts
vendored
2
src/typings/globals/slickgrid/index.d.ts
vendored
@@ -1212,7 +1212,7 @@ declare namespace Slick {
|
|||||||
public render(): void;
|
public render(): void;
|
||||||
public invalidate(): void;
|
public invalidate(): void;
|
||||||
public invalidateRow(row: number): void;
|
public invalidateRow(row: number): void;
|
||||||
public invalidateRows(rows: number[]): void;
|
public invalidateRows(rows: number[], keepEditor: boolean): void;
|
||||||
public invalidateAllRows(): void;
|
public invalidateAllRows(): void;
|
||||||
public updateCell(row: number, cell: number): void;
|
public updateCell(row: number, cell: number): void;
|
||||||
public updateRow(row: number): void;
|
public updateRow(row: number): void;
|
||||||
|
|||||||
145
src/typings/modules/angular2-slickgrid/index.d.ts
vendored
145
src/typings/modules/angular2-slickgrid/index.d.ts
vendored
@@ -1,39 +1,3 @@
|
|||||||
// Generated by typings
|
|
||||||
// Source: node_modules/angular2-slickgrid/out/js/gridsync.service.d.ts
|
|
||||||
declare module '~angular2-slickgrid/out/js/gridsync.service' {
|
|
||||||
import { Observable } from 'rxjs/Rx';
|
|
||||||
import { SelectionModel } from '~angular2-slickgrid/out/js/selectionModel';
|
|
||||||
export class GridSyncService {
|
|
||||||
columnMinWidthPX: number;
|
|
||||||
private _scrollLeftPX;
|
|
||||||
private _scrollBarWidthPX;
|
|
||||||
private _columnWidthPXs;
|
|
||||||
private _rowNumberColumnWidthPX;
|
|
||||||
private _updated;
|
|
||||||
private _typeDropdownOffset;
|
|
||||||
private _selectionModel;
|
|
||||||
private _initialColumnWidthPXsOnResize;
|
|
||||||
private _isGridReadOnly;
|
|
||||||
initialColumnResize(): void;
|
|
||||||
resizeColumn(index: number, deltaWidthPX: number): void;
|
|
||||||
openTypeDropdown(columnIndex: number): void;
|
|
||||||
private setColumnWidthPX(index, widthPX);
|
|
||||||
underlyingSelectionModel: any;
|
|
||||||
readonly updated: Observable<string>;
|
|
||||||
readonly typeDropdownOffset: Observable<[number, number]>;
|
|
||||||
scrollLeftPX: number;
|
|
||||||
scrollBarWidthPX: number;
|
|
||||||
columnWidthPXs: number[];
|
|
||||||
rowNumberColumnWidthPX: number;
|
|
||||||
readonly selectionModel: SelectionModel;
|
|
||||||
isGridReadOnly: boolean;
|
|
||||||
private notifyUpdates(propertyName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
declare module 'angular2-slickgrid/out/js/gridsync.service' {
|
|
||||||
export * from '~angular2-slickgrid/out/js/gridsync.service';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generated by typings
|
// Generated by typings
|
||||||
// Source: node_modules/angular2-slickgrid/out/js/interfaces.d.ts
|
// Source: node_modules/angular2-slickgrid/out/js/interfaces.d.ts
|
||||||
declare module '~angular2-slickgrid/out/js/interfaces' {
|
declare module '~angular2-slickgrid/out/js/interfaces' {
|
||||||
@@ -57,7 +21,6 @@ export interface IObservableCollection<T> {
|
|||||||
at(index: number): T;
|
at(index: number): T;
|
||||||
getRange(start: number, end: number): T[];
|
getRange(start: number, end: number): T[];
|
||||||
setCollectionChangedCallback(callback: (change: CollectionChange, startIndex: number, count: number) => void): void;
|
setCollectionChangedCallback(callback: (change: CollectionChange, startIndex: number, count: number) => void): void;
|
||||||
resetWindowsAroundIndex(index: number): void;
|
|
||||||
}
|
}
|
||||||
export class CancellationToken {
|
export class CancellationToken {
|
||||||
private _isCanceled;
|
private _isCanceled;
|
||||||
@@ -66,27 +29,6 @@ export class CancellationToken {
|
|||||||
readonly isCanceled: boolean;
|
readonly isCanceled: boolean;
|
||||||
readonly canceled: Observable<any>;
|
readonly canceled: Observable<any>;
|
||||||
}
|
}
|
||||||
export enum FieldType {
|
|
||||||
String = 0,
|
|
||||||
Boolean = 1,
|
|
||||||
Integer = 2,
|
|
||||||
Decimal = 3,
|
|
||||||
Date = 4,
|
|
||||||
Unknown = 5,
|
|
||||||
}
|
|
||||||
export interface IColumnDefinition {
|
|
||||||
id?: string;
|
|
||||||
name: string;
|
|
||||||
type: FieldType;
|
|
||||||
asyncPostRender?: (cellRef: string, row: number, dataContext: JSON, colDef: any) => void;
|
|
||||||
formatter?: (row: number, cell: any, value: any, columnDef: any, dataContext: any) => string;
|
|
||||||
isEditable?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISlickColumn<T> extends Slick.Column<T> {
|
|
||||||
isEditable?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IGridColumnDefinition {
|
export interface IGridColumnDefinition {
|
||||||
id: string;
|
id: string;
|
||||||
type: number;
|
type: number;
|
||||||
@@ -95,6 +37,9 @@ export interface IGridDataRow {
|
|||||||
row?: number;
|
row?: number;
|
||||||
values: any[];
|
values: any[];
|
||||||
}
|
}
|
||||||
|
export interface ISlickColumn<T> extends Slick.Column<T> {
|
||||||
|
isEditable?: boolean;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
declare module 'angular2-slickgrid/out/js/interfaces' {
|
declare module 'angular2-slickgrid/out/js/interfaces' {
|
||||||
export * from '~angular2-slickgrid/out/js/interfaces';
|
export * from '~angular2-slickgrid/out/js/interfaces';
|
||||||
@@ -109,13 +54,13 @@ export class SelectionModel implements ISlickSelectionModel {
|
|||||||
private _handler;
|
private _handler;
|
||||||
private _onSelectedRangesChanged;
|
private _onSelectedRangesChanged;
|
||||||
private _slickRangeFactory;
|
private _slickRangeFactory;
|
||||||
constructor(_rowSelectionModel: ISlickSelectionModel, _handler: ISlickEventHandler, _onSelectedRangesChanged: ISlickEvent, _slickRangeFactory: (fromRow: number, fromCell: number, toRow: number, toCell: number) => ISlickRange);
|
constructor(_rowSelectionModel: ISlickSelectionModel, _handler: ISlickEventHandler, _onSelectedRangesChanged: ISlickEvent, _slickRangeFactory: (fromRow: number, fromCell: number, toRow: number, toCell: number) => Slick.Range);
|
||||||
readonly range: ISlickRange[];
|
readonly range: Slick.Range[];
|
||||||
readonly onSelectedRangesChanged: ISlickEvent;
|
readonly onSelectedRangesChanged: ISlickEvent;
|
||||||
init(grid: ISlickGrid): void;
|
init(grid: ISlickGrid): void;
|
||||||
destroy(): void;
|
destroy(): void;
|
||||||
setSelectedRanges(ranges: ISlickRange[]): void;
|
setSelectedRanges(ranges: Slick.Range[]): void;
|
||||||
getSelectedRanges(): ISlickRange[];
|
getSelectedRanges(): Slick.Range[];
|
||||||
changeSelectedRanges(selections: ISelectionRange[]): void;
|
changeSelectedRanges(selections: ISelectionRange[]): void;
|
||||||
toggleSingleColumnSelection(columnId: string): void;
|
toggleSingleColumnSelection(columnId: string): void;
|
||||||
setSingleColumnSelection(columnId: string): void;
|
setSingleColumnSelection(columnId: string): void;
|
||||||
@@ -132,27 +77,21 @@ export class SelectionModel implements ISlickSelectionModel {
|
|||||||
private updateSelectedRanges(ranges);
|
private updateSelectedRanges(ranges);
|
||||||
}
|
}
|
||||||
export interface ISlickSelectionModel {
|
export interface ISlickSelectionModel {
|
||||||
range: ISlickRange[];
|
range: Slick.Range[];
|
||||||
onSelectedRangesChanged: any;
|
onSelectedRangesChanged: any;
|
||||||
init(grid: any): void;
|
init(grid: any): void;
|
||||||
destroy(): void;
|
destroy(): void;
|
||||||
setSelectedRanges(ranges: ISlickRange[]): void;
|
setSelectedRanges(ranges: Slick.Range[]): void;
|
||||||
getSelectedRanges(): ISlickRange[];
|
getSelectedRanges(): Slick.Range[];
|
||||||
}
|
}
|
||||||
export interface ISlickEventHandler {
|
export interface ISlickEventHandler {
|
||||||
subscribe(event: any, handler: any): void;
|
subscribe(event: any, handler: any): void;
|
||||||
unsubscribeAll(): void;
|
unsubscribeAll(): void;
|
||||||
}
|
}
|
||||||
export interface ISlickEvent {
|
export interface ISlickEvent {
|
||||||
notify(eventData: ISlickRange[]): void;
|
notify(eventData: Slick.Range[]): void;
|
||||||
subscribe(handler: (e: any, args: any) => void): void;
|
subscribe(handler: (e: any, args: any) => void): void;
|
||||||
}
|
}
|
||||||
export interface ISlickRange {
|
|
||||||
fromCell: number;
|
|
||||||
fromRow: number;
|
|
||||||
toCell: number;
|
|
||||||
toRow: number;
|
|
||||||
}
|
|
||||||
export interface ISlickGrid {
|
export interface ISlickGrid {
|
||||||
getActiveCellNode(): any;
|
getActiveCellNode(): any;
|
||||||
getCanvasNode(): any;
|
getCanvasNode(): any;
|
||||||
@@ -171,12 +110,11 @@ export * from '~angular2-slickgrid/out/js/selectionModel';
|
|||||||
declare module '~angular2-slickgrid/out/js/slickGrid' {
|
declare module '~angular2-slickgrid/out/js/slickGrid' {
|
||||||
import { OnChanges, OnInit, OnDestroy, SimpleChange, EventEmitter, AfterViewInit } from '@angular/core';
|
import { OnChanges, OnInit, OnDestroy, SimpleChange, EventEmitter, AfterViewInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/Rx';
|
import { Observable } from 'rxjs/Rx';
|
||||||
import { IObservableCollection, IGridDataRow, IColumnDefinition } from '~angular2-slickgrid/out/js/interfaces';
|
import { IObservableCollection, IGridDataRow, ISlickColumn } from '~angular2-slickgrid/out/js/interfaces';
|
||||||
import { ISlickRange } from '~angular2-slickgrid/out/js/selectionModel';
|
export function getOverridableTextEditorClass(grid: SlickGrid): any;
|
||||||
export class SlickGrid implements OnChanges, OnInit, OnDestroy, AfterViewInit {
|
export class SlickGrid implements OnChanges, OnInit, OnDestroy, AfterViewInit {
|
||||||
private _el;
|
private _el;
|
||||||
private _gridSyncService;
|
columnDefinitions: ISlickColumn<any>[];
|
||||||
columnDefinitions: IColumnDefinition[];
|
|
||||||
dataRows: IObservableCollection<IGridDataRow>;
|
dataRows: IObservableCollection<IGridDataRow>;
|
||||||
resized: Observable<any>;
|
resized: Observable<any>;
|
||||||
highlightedCells: {
|
highlightedCells: {
|
||||||
@@ -187,7 +125,6 @@ export class SlickGrid implements OnChanges, OnInit, OnDestroy, AfterViewInit {
|
|||||||
contextColumns: string[];
|
contextColumns: string[];
|
||||||
columnsLoading: string[];
|
columnsLoading: string[];
|
||||||
showHeader: boolean;
|
showHeader: boolean;
|
||||||
showDataTypeIcon: boolean;
|
|
||||||
enableColumnReorder: boolean;
|
enableColumnReorder: boolean;
|
||||||
enableAsyncPostRender: boolean;
|
enableAsyncPostRender: boolean;
|
||||||
selectionModel: string | Slick.SelectionModel<any, any>;
|
selectionModel: string | Slick.SelectionModel<any, any>;
|
||||||
@@ -195,45 +132,23 @@ export class SlickGrid implements OnChanges, OnInit, OnDestroy, AfterViewInit {
|
|||||||
enableEditing: boolean;
|
enableEditing: boolean;
|
||||||
topRowNumber: number;
|
topRowNumber: number;
|
||||||
overrideCellFn: (rowNumber, columnId, value?, data?) => string;
|
overrideCellFn: (rowNumber, columnId, value?, data?) => string;
|
||||||
isColumnEditable: (column: number) => boolean;
|
|
||||||
isCellEditValid: (row: number, column: number, newValue: any) => boolean;
|
isCellEditValid: (row: number, column: number, newValue: any) => boolean;
|
||||||
private _rowHeight;
|
|
||||||
loadFinished: EventEmitter<void>;
|
loadFinished: EventEmitter<void>;
|
||||||
editingFinished: EventEmitter<any>;
|
onContextMenu: EventEmitter<Slick.OnContextMenuEventArgs<any>>;
|
||||||
contextMenu: EventEmitter<any>;
|
onScroll: EventEmitter<Slick.OnScrollEventArgs<any>>;
|
||||||
topRowNumberChange: EventEmitter<number>;
|
onActiveCellChanged: EventEmitter<Slick.OnActiveCellChangedEventArgs<any>>;
|
||||||
activeCellChanged: EventEmitter<{
|
onBeforeEditCell: EventEmitter<Slick.OnBeforeEditCellEventArgs<any>>;
|
||||||
row: number;
|
onCellChange: EventEmitter<Slick.OnCellChangeEventArgs<any>>;
|
||||||
column: number;
|
|
||||||
}>;
|
|
||||||
cellEditBegin: EventEmitter<{
|
|
||||||
row: number;
|
|
||||||
column: number;
|
|
||||||
}>;
|
|
||||||
cellEditExit: EventEmitter<{
|
|
||||||
row: number;
|
|
||||||
column: number;
|
|
||||||
newValue: any;
|
|
||||||
}>;
|
|
||||||
rowEditBegin: EventEmitter<{
|
|
||||||
row: number;
|
|
||||||
}>;
|
|
||||||
rowEditExit: EventEmitter<{
|
|
||||||
row: number;
|
|
||||||
}>;
|
|
||||||
onFocus(): void;
|
onFocus(): void;
|
||||||
rowHeight: number;
|
rowHeight: number;
|
||||||
|
private _rowHeight;
|
||||||
private _grid;
|
private _grid;
|
||||||
private _gridColumns;
|
private _gridColumns;
|
||||||
private _columnNameToIndex;
|
private _columnNameToIndex;
|
||||||
private _gridData;
|
private _gridData;
|
||||||
private _resizeSubscription;
|
private _resizeSubscription;
|
||||||
private _gridSyncSubscription;
|
private _gridSyncSubscription;
|
||||||
private _topRow;
|
constructor(_el: any);
|
||||||
private _leftPx;
|
|
||||||
private _activeEditingRow;
|
|
||||||
private _activeEditingRowHasChanges;
|
|
||||||
constructor(_el: any, _gridSyncService: any);
|
|
||||||
ngOnChanges(changes: {
|
ngOnChanges(changes: {
|
||||||
[propName: string]: SimpleChange;
|
[propName: string]: SimpleChange;
|
||||||
}): void;
|
}): void;
|
||||||
@@ -245,27 +160,20 @@ export class SlickGrid implements OnChanges, OnInit, OnDestroy, AfterViewInit {
|
|||||||
readonly onSelectedRowsChanged: Slick.Event<Slick.OnSelectedRowsChangedEventArgs<any>>;
|
readonly onSelectedRowsChanged: Slick.Event<Slick.OnSelectedRowsChangedEventArgs<any>>;
|
||||||
getSelectedRows(): number[];
|
getSelectedRows(): number[];
|
||||||
getColumnIndex(name: string): number;
|
getColumnIndex(name: string): number;
|
||||||
getSelectedRanges(): ISlickRange[];
|
getSelectedRanges(): Slick.Range[];
|
||||||
registerPlugin(plugin: Slick.Plugin<any> | string): void;
|
registerPlugin(plugin: Slick.Plugin<any> | string): void;
|
||||||
setActive(): void;
|
setActive(): void;
|
||||||
selection: ISlickRange[] | boolean;
|
selection: Slick.Range[] | boolean;
|
||||||
subscribeToContextMenu(): void;
|
|
||||||
private initGrid();
|
private initGrid();
|
||||||
private changeEditSession(enabled);
|
private changeEditSession(enabled);
|
||||||
private handleEditorCellChange(rowNumber);
|
|
||||||
private static getDataWithSchema(data, columns);
|
|
||||||
private onResize();
|
private onResize();
|
||||||
private invalidateRange(start, end);
|
private invalidateRange(start, end);
|
||||||
private getColumnEditor;
|
private getColumnEditor(column);
|
||||||
private getFormatter;
|
private getFormatter;
|
||||||
private subscribeToScroll();
|
private setupEvents();
|
||||||
private subscribeToCellChanged();
|
|
||||||
private subscribeToBeforeEditCell();
|
|
||||||
private subscribeToActiveCellChanged();
|
|
||||||
private updateColumnWidths();
|
|
||||||
private updateSchema();
|
private updateSchema();
|
||||||
private getImagePathForDataType(type);
|
|
||||||
private setCallbackOnDataRowsChanged();
|
private setCallbackOnDataRowsChanged();
|
||||||
|
readonly activeCell: Slick.Cell;
|
||||||
private renderGridDataRowsRange(startIndex, count);
|
private renderGridDataRowsRange(startIndex, count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -302,7 +210,6 @@ export * from '~angular2-slickgrid/out/js/virtualizedCollection';
|
|||||||
// Generated by typings
|
// Generated by typings
|
||||||
// Source: node_modules/angular2-slickgrid/out/index.d.ts
|
// Source: node_modules/angular2-slickgrid/out/index.d.ts
|
||||||
declare module '~angular2-slickgrid/out/index' {
|
declare module '~angular2-slickgrid/out/index' {
|
||||||
export * from '~angular2-slickgrid/out/js/gridsync.service';
|
|
||||||
export * from '~angular2-slickgrid/out/js/interfaces';
|
export * from '~angular2-slickgrid/out/js/interfaces';
|
||||||
export * from '~angular2-slickgrid/out/js/selectionModel';
|
export * from '~angular2-slickgrid/out/js/selectionModel';
|
||||||
export * from '~angular2-slickgrid/out/js/slickGrid';
|
export * from '~angular2-slickgrid/out/js/slickGrid';
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"resolution": "main",
|
"resolution": "main",
|
||||||
"tree": {
|
"tree": {
|
||||||
"raw": "npm:angular2-slickgrid",
|
"src": "D:\\code\\sqlopsstudio\\node_modules\\angular2-slickgrid\\package.json",
|
||||||
"main": "out/index.js",
|
"raw": "npm:angular2-slickgrid",
|
||||||
"version": "1.3.9",
|
"main": "out/index.js",
|
||||||
"name": "angular2-slickgrid"
|
"version": "1.4.1",
|
||||||
|
"name": "angular2-slickgrid"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user