mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-13 17:22:15 -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/upgrade": "~4.1.3",
|
||||
"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",
|
||||
"chart.js": "^2.6.0",
|
||||
"fast-plist": "0.1.2",
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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 { Dimension } from 'vs/base/browser/dom';
|
||||
import { Dimension, EventType } from 'vs/base/browser/dom';
|
||||
import { $, Builder } from 'vs/base/browser/builder';
|
||||
import { EventType } from 'vs/base/browser/dom';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IActionOptions, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
@@ -17,12 +15,16 @@ import './panelStyles';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IPanelStyles {
|
||||
}
|
||||
|
||||
export interface IPanelOptions {
|
||||
showHeaderWhenSingleView?: boolean;
|
||||
}
|
||||
|
||||
export interface IPanelView {
|
||||
render(container: HTMLElement): void;
|
||||
layout(dimension: Dimension): void;
|
||||
remove?(): void;
|
||||
}
|
||||
|
||||
export interface IPanelTab {
|
||||
@@ -36,6 +38,10 @@ interface IInternalPanelTab extends IPanelTab {
|
||||
label: Builder;
|
||||
}
|
||||
|
||||
const defaultOptions: IPanelOptions = {
|
||||
showHeaderWhenSingleView: true
|
||||
};
|
||||
|
||||
export type PanelTabIdentifier = string;
|
||||
|
||||
export class TabbedPanel extends Disposable implements IThemable {
|
||||
@@ -49,11 +55,12 @@ export class TabbedPanel extends Disposable implements IThemable {
|
||||
private _actionbar: ActionBar;
|
||||
private _currentDimensions: Dimension;
|
||||
private _collapsed = false;
|
||||
private _headerVisible: boolean;
|
||||
|
||||
private _onTabChange = new Emitter<PanelTabIdentifier>();
|
||||
public onTabChange: Event<PanelTabIdentifier> = this._onTabChange.event;
|
||||
|
||||
constructor(private container: HTMLElement) {
|
||||
constructor(private container: HTMLElement, private options: IPanelOptions = defaultOptions) {
|
||||
super();
|
||||
this.$parent = this._register($('.tabbedPanel'));
|
||||
this.$parent.appendTo(container);
|
||||
@@ -65,7 +72,12 @@ export class TabbedPanel extends Disposable implements IThemable {
|
||||
let actionbarcontainer = $('.title-actions');
|
||||
this._actionbar = new ActionBar(actionbarcontainer.getHTMLElement());
|
||||
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.attr('role', 'tabpanel');
|
||||
this.$body.attr('tabindex', '0');
|
||||
@@ -73,12 +85,16 @@ export class TabbedPanel extends Disposable implements IThemable {
|
||||
}
|
||||
|
||||
public pushTab(tab: IPanelTab): PanelTabIdentifier {
|
||||
let internalTab = objects.clone(tab) as IInternalPanelTab;
|
||||
let internalTab = tab as IInternalPanelTab;
|
||||
this._tabMap.set(tab.identifier, internalTab);
|
||||
this._createTab(internalTab);
|
||||
if (!this._shownTab) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -139,6 +155,11 @@ export class TabbedPanel extends Disposable implements IThemable {
|
||||
}
|
||||
|
||||
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.delete(tab);
|
||||
}
|
||||
@@ -151,8 +172,9 @@ export class TabbedPanel extends Disposable implements IThemable {
|
||||
this._currentDimensions = dimension;
|
||||
this.$header.style('width', dimension.width + 'px');
|
||||
this.$body.style('width', dimension.width + 'px');
|
||||
this.$body.style('height', (dimension.height - this.headersize) + 'px');
|
||||
this._layoutCurrentTab(new Dimension(dimension.width, dimension.height - this.headersize));
|
||||
const bodyHeight = dimension.height - (this._headerVisible ? this.headersize : 0);
|
||||
this.$body.style('height', bodyHeight + 'px');
|
||||
this._layoutCurrentTab(new Dimension(dimension.width, bodyHeight));
|
||||
}
|
||||
|
||||
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 { Color } from 'vs/base/common/color';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { isArray, isBoolean } from 'vs/base/common/types';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { range } from 'vs/base/common/arrays';
|
||||
|
||||
export interface ITableContextMenuEvent {
|
||||
anchor: HTMLElement | { x: number, y: number };
|
||||
cell?: { row: number, cell: number };
|
||||
}
|
||||
|
||||
export interface ITableStyles extends IListStyles {
|
||||
tableHeaderBackground?: Color;
|
||||
@@ -27,30 +35,46 @@ function getDefaultOptions<T>(): Slick.GridOptions<T> {
|
||||
};
|
||||
}
|
||||
|
||||
export class Table<T extends Slick.SlickData> extends Widget implements IThemable {
|
||||
export interface ITableSorter<T> {
|
||||
sort(args: Slick.OnSortEventArgs<T>);
|
||||
}
|
||||
|
||||
export interface ITableConfiguration<T> {
|
||||
dataProvider?: Slick.DataProvider<T> | Array<T>;
|
||||
columns?: Slick.Column<T>[];
|
||||
sorter?: ITableSorter<T>;
|
||||
}
|
||||
|
||||
export class Table<T extends Slick.SlickData> extends Widget implements IThemable, IDisposable {
|
||||
private styleElement: HTMLStyleElement;
|
||||
private idPrefix: string;
|
||||
|
||||
private _grid: Slick.Grid<T>;
|
||||
private _columns: Slick.Column<T>[];
|
||||
private _data: TableDataView<T>;
|
||||
private _data: Slick.DataProvider<T>;
|
||||
private _sorter: ITableSorter<T>;
|
||||
|
||||
private _autoscroll: boolean;
|
||||
private _onRowCountChangeListener: IDisposable;
|
||||
private _container: HTMLElement;
|
||||
private _tableContainer: HTMLElement;
|
||||
|
||||
private _classChangeTimeout: number;
|
||||
|
||||
constructor(parent: HTMLElement, data?: Array<T> | TableDataView<T>, columns?: Slick.Column<T>[], options?: Slick.GridOptions<T>) {
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
private _onContextMenu = new Emitter<ITableContextMenuEvent>();
|
||||
public readonly onContextMenu: Event<ITableContextMenuEvent> = this._onContextMenu.event;
|
||||
|
||||
constructor(parent: HTMLElement, configuration?: ITableConfiguration<T>, options?: Slick.GridOptions<T>) {
|
||||
super();
|
||||
if (data instanceof TableDataView) {
|
||||
this._data = data;
|
||||
if (!configuration || isArray(configuration.dataProvider)) {
|
||||
this._data = new TableDataView<T>(configuration && configuration.dataProvider as Array<T>);
|
||||
} else {
|
||||
this._data = new TableDataView<T>(data);
|
||||
this._data = configuration.dataProvider;
|
||||
}
|
||||
|
||||
if (columns) {
|
||||
this._columns = columns;
|
||||
if (configuration.columns) {
|
||||
this._columns = configuration.columns;
|
||||
} else {
|
||||
this._columns = new Array<Slick.Column<T>>();
|
||||
}
|
||||
@@ -81,15 +105,33 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
|
||||
this._grid = new Slick.Grid<T>(this._tableContainer, this._data, this._columns, newOptions);
|
||||
this.idPrefix = this._tableContainer.classList[0];
|
||||
DOM.addClass(this._container, this.idPrefix);
|
||||
this._onRowCountChangeListener = this._data.onRowCountChange(() => this._handleRowCountChange());
|
||||
this._grid.onSort.subscribe((e, args) => {
|
||||
this._data.sort(args);
|
||||
this._grid.invalidate();
|
||||
this._grid.render();
|
||||
if (configuration.sorter) {
|
||||
this._sorter = configuration.sorter;
|
||||
this._grid.onSort.subscribe((e, args) => {
|
||||
this._sorter.sort(args);
|
||||
this._grid.invalidate();
|
||||
this._grid.render();
|
||||
});
|
||||
}
|
||||
|
||||
this._grid.onContextMenu.subscribe((e: JQuery.Event) => {
|
||||
const originalEvent = e.originalEvent;
|
||||
const cell = this._grid.getCellFromEvent(originalEvent);
|
||||
const anchor = originalEvent instanceof MouseEvent ? { x: originalEvent.x, y: originalEvent.y } : originalEvent.srcElement as HTMLElement;
|
||||
this._onContextMenu.fire({ anchor, cell });
|
||||
});
|
||||
}
|
||||
|
||||
private _handleRowCountChange() {
|
||||
public dispose() {
|
||||
dispose(this._disposables);
|
||||
}
|
||||
|
||||
public invalidateRows(rows: number[], keepEditor: boolean) {
|
||||
this._grid.invalidateRows(rows, keepEditor);
|
||||
this._grid.render();
|
||||
}
|
||||
|
||||
public updateRowCount() {
|
||||
this._grid.updateRowCount();
|
||||
this._grid.render();
|
||||
if (this._autoscroll) {
|
||||
@@ -113,20 +155,22 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
|
||||
} else {
|
||||
this._data = new TableDataView<T>(data);
|
||||
}
|
||||
this._onRowCountChangeListener.dispose();
|
||||
this._grid.setData(this._data, true);
|
||||
this._onRowCountChangeListener = this._data.onRowCountChange(() => this._handleRowCountChange());
|
||||
}
|
||||
|
||||
get columns(): Slick.Column<T>[] {
|
||||
return this._grid.getColumns();
|
||||
}
|
||||
|
||||
setSelectedRows(rows: number[]) {
|
||||
this._grid.setSelectedRows(rows);
|
||||
public setSelectedRows(rows: number[] | boolean) {
|
||||
if (isBoolean(rows)) {
|
||||
this._grid.setSelectedRows(range(this._grid.getDataLength()));
|
||||
} else {
|
||||
this._grid.setSelectedRows(rows);
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedRows(): number[] {
|
||||
public getSelectedRows(): number[] {
|
||||
return this._grid.getSelectedRows();
|
||||
}
|
||||
|
||||
@@ -143,21 +187,6 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
|
||||
};
|
||||
}
|
||||
|
||||
onContextMenu(fn: (e: Slick.EventData, data: Slick.OnContextMenuEventArgs<T>) => any): IDisposable;
|
||||
onContextMenu(fn: (e: DOMEvent, data: Slick.OnContextMenuEventArgs<T>) => any): IDisposable;
|
||||
onContextMenu(fn: any): IDisposable {
|
||||
this._grid.onContextMenu.subscribe(fn);
|
||||
return {
|
||||
dispose: () => {
|
||||
this._grid.onContextMenu.unsubscribe(fn);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getCellFromEvent(e: DOMEvent): Slick.Cell {
|
||||
return this._grid.getCellFromEvent(e);
|
||||
}
|
||||
|
||||
setSelectionModel(model: Slick.SelectionModel<T, Array<Slick.Range>>) {
|
||||
this._grid.setSelectionModel(model);
|
||||
}
|
||||
@@ -194,7 +223,7 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
|
||||
this._tableContainer.style.width = sizing.width + 'px';
|
||||
this._tableContainer.style.height = sizing.height + 'px';
|
||||
} else {
|
||||
if (orientation === Orientation.HORIZONTAL) {
|
||||
if (orientation === Orientation.VERTICAL) {
|
||||
this._container.style.width = '100%';
|
||||
this._container.style.height = sizing + 'px';
|
||||
this._tableContainer.style.width = '100%';
|
||||
@@ -207,6 +236,7 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
|
||||
}
|
||||
}
|
||||
this.resizeCanvas();
|
||||
this.autosizeColumns();
|
||||
}
|
||||
|
||||
autosizeColumns() {
|
||||
|
||||
@@ -25,7 +25,7 @@ export class TableBasicView<T> extends View {
|
||||
super(undefined, viewOpts);
|
||||
this._container = document.createElement('div');
|
||||
this._container.className = 'table-view';
|
||||
this._table = new Table<T>(this._container, data, columns, tableOpts);
|
||||
this._table = new Table<T>(this._container, { dataProvider: data, columns }, tableOpts);
|
||||
}
|
||||
|
||||
public get table(): Table<T> {
|
||||
@@ -59,7 +59,7 @@ export class TableHeaderView<T> extends HeaderView {
|
||||
super(undefined, viewOpts);
|
||||
this._container = document.createElement('div');
|
||||
this._container.className = 'table-view';
|
||||
this._table = new Table<T>(this._container, data, columns, tableOpts);
|
||||
this._table = new Table<T>(this._container, { dataProvider: data, columns }, tableOpts);
|
||||
}
|
||||
|
||||
public get table(): Table<T> {
|
||||
@@ -76,7 +76,7 @@ export class TableHeaderView<T> extends HeaderView {
|
||||
}
|
||||
|
||||
protected layoutBody(size: number): void {
|
||||
this._table.layout(size, Orientation.HORIZONTAL);
|
||||
this._table.layout(size, Orientation.VERTICAL);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
@@ -99,7 +99,7 @@ export class TableCollapsibleView<T> extends AbstractCollapsibleView {
|
||||
super(undefined, viewOpts);
|
||||
this._container = document.createElement('div');
|
||||
this._container.className = 'table-view';
|
||||
this._table = new Table<T>(this._container, data, columns, tableOpts);
|
||||
this._table = new Table<T>(this._container, { dataProvider: data, columns }, tableOpts);
|
||||
}
|
||||
|
||||
public render(container: HTMLElement, orientation: Orientation): void {
|
||||
@@ -143,6 +143,6 @@ export class TableCollapsibleView<T> extends AbstractCollapsibleView {
|
||||
}
|
||||
|
||||
protected layoutBody(size: number): void {
|
||||
this._table.layout(size, Orientation.HORIZONTAL);
|
||||
this._table.layout(size, Orientation.VERTICAL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export default class TableInsight extends Disposable implements IInsightsView, O
|
||||
|
||||
private createTable() {
|
||||
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._register(attachTableStyler(this.table, this.themeService));
|
||||
}
|
||||
|
||||
@@ -272,7 +272,8 @@ export class RestoreDialog extends Modal {
|
||||
this._restorePlanTableContainer = labelContainer.getHTMLElement();
|
||||
labelContainer.hide();
|
||||
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.onSelectedRowsChanged((e, data) => this.backupFileCheckboxChanged(e, data));
|
||||
});
|
||||
@@ -328,7 +329,8 @@ export class RestoreDialog extends Modal {
|
||||
field: 'restoreAs'
|
||||
}];
|
||||
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());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,14 +4,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ISlickColumn, IObservableCollection, IGridDataRow } from 'angular2-slickgrid';
|
||||
|
||||
export interface ISlickRange {
|
||||
fromCell: number;
|
||||
fromRow: number;
|
||||
toCell: number;
|
||||
toRow: number;
|
||||
}
|
||||
import { ISlickColumn, VirtualizedCollection } from 'angular2-slickgrid';
|
||||
|
||||
export interface IGridIcon {
|
||||
showCondition: () => boolean;
|
||||
@@ -41,7 +34,7 @@ export interface IGridIcon {
|
||||
}
|
||||
|
||||
export interface IGridDataSet {
|
||||
dataRows: IObservableCollection<IGridDataRow>;
|
||||
dataRows: VirtualizedCollection<{}>;
|
||||
columnDefinitions: ISlickColumn<any>[];
|
||||
resized: any; // EventEmitter<any>;
|
||||
totalRows: number;
|
||||
@@ -61,7 +54,7 @@ export enum SaveFormat {
|
||||
export interface IGridInfo {
|
||||
batchIndex: number;
|
||||
resultSetNumber: number;
|
||||
selection: ISlickRange[];
|
||||
selection: Slick.Range[];
|
||||
gridIndex: number;
|
||||
rowIndex?: number;
|
||||
}
|
||||
@@ -69,5 +62,5 @@ export interface ISaveRequest {
|
||||
format: SaveFormat;
|
||||
batchIndex: number;
|
||||
resultSetNumber: number;
|
||||
selection: ISlickRange[];
|
||||
selection: Slick.Range[];
|
||||
}
|
||||
@@ -9,66 +9,64 @@
|
||||
*/
|
||||
|
||||
.errorMessage {
|
||||
color: var(--color-error);
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.batchMessage {
|
||||
padding-left: 20px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.slick-cell a, a:link {
|
||||
color: var(--color-grid-link);
|
||||
text-decoration: underline;
|
||||
color: var(--color-grid-link);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.slick-cell a:hover {
|
||||
color: var(--color-grid-link-hover);
|
||||
color: var(--color-grid-link-hover);
|
||||
}
|
||||
|
||||
.resultsMessageValue a, a:link {
|
||||
color: var(--color-grid-link);
|
||||
text-decoration: underline;
|
||||
color: var(--color-grid-link);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.resultsMessageValue a:hover {
|
||||
color: var(--color-grid-link-hover);
|
||||
color: var(--color-grid-link-hover);
|
||||
}
|
||||
|
||||
.grid .slick-cell.dirtyCell {
|
||||
color: var(--color-grid-dirty-text);
|
||||
background-color: var(--color-grid-dirty-background);
|
||||
color: var(--color-grid-dirty-text);
|
||||
background-color: var(--color-grid-dirty-background);
|
||||
}
|
||||
|
||||
.grid .slick-cell.dirtyRowHeader {
|
||||
background-color: var(--color-grid-dirty-background);
|
||||
background-color: var(--color-grid-dirty-background);
|
||||
}
|
||||
|
||||
|
||||
.slick-cell.dirtyRowHeader > .row-number {
|
||||
color: var(--color-grid-dirty-text);
|
||||
font-weight: 500;
|
||||
color: var(--color-grid-dirty-text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* vs theme
|
||||
*
|
||||
*/
|
||||
|
||||
.vs .slickgridContainer {
|
||||
--color-content: #101010;
|
||||
--color-content-disabled: #a9a9a9;
|
||||
--color-error: #E81123;
|
||||
--color-success: #7CD300;
|
||||
--color-bg-header: hsla(0,0%,50%,.2);
|
||||
--color-resize-handle: grey;
|
||||
--color-bg-content-header: #F5F5F5; /* used for color of grid headers */
|
||||
--color-cell-border-active: grey;
|
||||
--color-cell-bg-grid-selected: rgb(173, 214, 255);
|
||||
--color-grid-link: #0078D7;
|
||||
--color-grid-link-hover: #0b93ff;
|
||||
--color-grid-dirty-background: #CCC;
|
||||
--color-grid-dirty-text: #101010;
|
||||
--color-content: #101010;
|
||||
--color-content-disabled: #a9a9a9;
|
||||
--color-error: #E81123;
|
||||
--color-success: #7CD300;
|
||||
--color-bg-header: hsla(0,0%,50%,.2);
|
||||
--color-resize-handle: grey;
|
||||
--color-bg-content-header: #F5F5F5; /* used for color of grid headers */
|
||||
--color-cell-border-active: grey;
|
||||
--color-cell-bg-grid-selected: rgb(173, 214, 255);
|
||||
--color-grid-link: #0078D7;
|
||||
--color-grid-link-hover: #0b93ff;
|
||||
--color-grid-dirty-background: #CCC;
|
||||
--color-grid-dirty-text: #101010;
|
||||
}
|
||||
/* grid styling */
|
||||
|
||||
@@ -77,102 +75,102 @@
|
||||
}
|
||||
|
||||
.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 {
|
||||
color: var(--color-content) !important;
|
||||
color: var(--color-content) !important;
|
||||
}
|
||||
|
||||
.vs .boxRow.content.horzBox.slickgrid {
|
||||
border: solid 1px #EEEEF2;
|
||||
border: solid 1px #EEEEF2;
|
||||
}
|
||||
|
||||
/* icons */
|
||||
.vs .gridIcon.extendFullScreen {
|
||||
/* ExtendToFullScreen_16x_vscode */
|
||||
background-image: url("extendFullScreen.svg");
|
||||
.vs .icon.extendFullScreen {
|
||||
/* ExtendToFullScreen_16x_vscode */
|
||||
background-image: url("extendFullScreen.svg");
|
||||
}
|
||||
|
||||
.vs .gridIcon.exitFullScreen {
|
||||
/* ExitFullScreen_16x_vscode */
|
||||
background-image: url("exitFullScreen.svg");
|
||||
.vs .icon.exitFullScreen {
|
||||
/* ExitFullScreen_16x_vscode */
|
||||
background-image: url("exitFullScreen.svg");
|
||||
}
|
||||
|
||||
.vs .gridIcon.saveJson {
|
||||
/* ResultToJSON_16x_vscode */
|
||||
background-image: url("saveJson.svg");
|
||||
.vs .icon.saveJson {
|
||||
/* ResultToJSON_16x_vscode */
|
||||
background-image: url("saveJson.svg");
|
||||
}
|
||||
|
||||
.vs .gridIcon.saveCsv {
|
||||
/* ResultToCSV_16x_vscode */
|
||||
background-image: url("saveCsv.svg");
|
||||
.vs .icon.saveCsv {
|
||||
/* ResultToCSV_16x_vscode */
|
||||
background-image: url("saveCsv.svg");
|
||||
}
|
||||
|
||||
.vs .gridIcon.saveExcel {
|
||||
/* ResultToXlsx_16x_vscode */
|
||||
background-image: url("saveExcel.svg");
|
||||
.vs .icon.saveExcel {
|
||||
/* ResultToXlsx_16x_vscode */
|
||||
background-image: url("saveExcel.svg");
|
||||
}
|
||||
|
||||
.vs .gridIcon.viewChart {
|
||||
/* ResultToXlsx_16x_vscode */
|
||||
background-image: url("viewChart.svg");
|
||||
.vs .icon.viewChart {
|
||||
/* ResultToXlsx_16x_vscode */
|
||||
background-image: url("viewChart.svg");
|
||||
}
|
||||
|
||||
/* headers */
|
||||
.vs .resultsMessageHeader {
|
||||
background: var(--color-bg-header);
|
||||
color: var(--color-content);
|
||||
background: var(--color-bg-header);
|
||||
color: var(--color-content);
|
||||
}
|
||||
|
||||
.vs .resultsViewCollapsible:not(.collapsed) {
|
||||
background-image: url("uncollapsedArrow.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 2px;
|
||||
background-image: url("uncollapsedArrow.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 2px;
|
||||
}
|
||||
|
||||
.vs .resultsViewCollapsible {
|
||||
background-image: url("collapsedArrow.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 2px;
|
||||
background-image: url("collapsedArrow.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 2px;
|
||||
}
|
||||
|
||||
.vs .queryResultsShortCut {
|
||||
color: grey;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
/* scroll bar */
|
||||
|
||||
.vs ::-webkit-scrollbar {
|
||||
width: 14px;
|
||||
height: 10px;
|
||||
width: 14px;
|
||||
height: 10px;
|
||||
}
|
||||
.vs ::-webkit-scrollbar-thumb {
|
||||
background: hsla(0,0%,47%,.4);
|
||||
background: hsla(0,0%,47%,.4);
|
||||
}
|
||||
|
||||
.vs ::-webkit-scrollbar-thumb:hover {
|
||||
background: hsla(0,0%,39%,.7);
|
||||
background: hsla(0,0%,39%,.7);
|
||||
}
|
||||
|
||||
.vs ::-webkit-scrollbar-thumb:active {
|
||||
background: rgba(85,85,85,0.8);
|
||||
background: rgba(85,85,85,0.8);
|
||||
}
|
||||
|
||||
.vs ::-webkit-scrollbar-track {
|
||||
background: var(--background-color);
|
||||
background: var(--background-color);
|
||||
}
|
||||
|
||||
.vs ::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench input {
|
||||
color: var(--color-content);
|
||||
color: var(--color-content);
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .input {
|
||||
background-color: white;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -181,19 +179,19 @@
|
||||
*/
|
||||
|
||||
.vs-dark .slickgridContainer {
|
||||
--color-content: #E5E5E5;
|
||||
--color-content-disabled: grey;
|
||||
--color-error: #E81123;
|
||||
--color-success: #7CD300;
|
||||
--color-bg-header: hsla(0,0%,50%,.2); /* used for pane toolbars */
|
||||
--color-resize-handle: #4d4d4d;
|
||||
--color-bg-content-header: #333334; /* used for color of grid headers */
|
||||
--color-cell-border-active: white;
|
||||
--color-cell-bg-grid-selected: rgb(38, 79, 120);
|
||||
--color-grid-link: #FF6000;
|
||||
--color-grid-link-hover: #ff8033;
|
||||
--color-grid-dirty-background: #4d4d4d;
|
||||
--color-grid-dirty-text: #E5E5E5;
|
||||
--color-content: #E5E5E5;
|
||||
--color-content-disabled: grey;
|
||||
--color-error: #E81123;
|
||||
--color-success: #7CD300;
|
||||
--color-bg-header: hsla(0,0%,50%,.2); /* used for pane toolbars */
|
||||
--color-resize-handle: #4d4d4d;
|
||||
--color-bg-content-header: #333334; /* used for color of grid headers */
|
||||
--color-cell-border-active: white;
|
||||
--color-cell-bg-grid-selected: rgb(38, 79, 120);
|
||||
--color-grid-link: #FF6000;
|
||||
--color-grid-link-hover: #ff8033;
|
||||
--color-grid-dirty-background: #4d4d4d;
|
||||
--color-grid-dirty-text: #E5E5E5;
|
||||
}
|
||||
|
||||
/* grid styling */
|
||||
@@ -203,109 +201,117 @@
|
||||
}
|
||||
|
||||
.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 {
|
||||
color: var(--color-content) !important;
|
||||
color: var(--color-content) !important;
|
||||
}
|
||||
|
||||
.vs-dark .boxRow.content.horzBox.slickgrid {
|
||||
border: solid 1px #2D2D30;
|
||||
border: solid 1px #2D2D30;
|
||||
}
|
||||
|
||||
/* icons */
|
||||
.vs-dark .gridIcon.extendFullScreen,
|
||||
.hc-black .gridIcon.extendFullScreen {
|
||||
/* ExtendToFullScreen_16x_vscode_inverse.svg */
|
||||
background-image: url("extendFullScreen_inverse.svg");
|
||||
.vs-dark .icon.extendFullScreen,
|
||||
.hc-black .icon.extendFullScreen {
|
||||
/* ExtendToFullScreen_16x_vscode_inverse.svg */
|
||||
background-image: url("extendFullScreen_inverse.svg");
|
||||
}
|
||||
|
||||
.vs-dark .gridIcon.exitFullScreen,
|
||||
.hc-black .gridIcon.exitFullScreen {
|
||||
/* ExitFullScreen_16x_vscode_inverse.svg */
|
||||
background-image: url("exitFullScreen_inverse.svg");
|
||||
.vs-dark .icon.exitFullScreen,
|
||||
.hc-black .icon.exitFullScreen {
|
||||
/* ExitFullScreen_16x_vscode_inverse.svg */
|
||||
background-image: url("exitFullScreen_inverse.svg");
|
||||
}
|
||||
|
||||
.vs-dark .gridIcon.saveJson,
|
||||
.hc-black .gridIcon.saveJson {
|
||||
/* ResultToJSON_16x_vscode_inverse.svg */
|
||||
background-image: url("saveJson_inverse.svg");
|
||||
.vs-dark .icon.saveJson,
|
||||
.hc-black .icon.saveJson {
|
||||
/* ResultToJSON_16x_vscode_inverse.svg */
|
||||
background-image: url("saveJson_inverse.svg");
|
||||
}
|
||||
|
||||
.vs-dark .gridIcon.saveCsv,
|
||||
.hc-black .gridIcon.saveCsv {
|
||||
/* ResultToCSV_16x_vscode_inverse.svg */
|
||||
background-image: url("saveCsv_inverse.svg");
|
||||
.vs-dark .icon.saveCsv,
|
||||
.hc-black .icon.saveCsv {
|
||||
/* ResultToCSV_16x_vscode_inverse.svg */
|
||||
background-image: url("saveCsv_inverse.svg");
|
||||
}
|
||||
|
||||
.vs-dark .gridIcon.saveExcel,
|
||||
.hc-black .gridIcon.saveExcel {
|
||||
/* ResultToXlsx_16x_vscode_inverse.svg */
|
||||
background-image: url("saveExcel_inverse.svg");
|
||||
.vs-dark .icon.saveExcel,
|
||||
.hc-black .icon.saveExcel {
|
||||
/* ResultToXlsx_16x_vscode_inverse.svg */
|
||||
background-image: url("saveExcel_inverse.svg");
|
||||
}
|
||||
|
||||
.vs-dark .gridIcon.viewChart,
|
||||
.hc-black .gridIcon.viewChart {
|
||||
/* ResultToXlsx_16x_vscode */
|
||||
background-image: url("viewChart_inverse.svg");
|
||||
.vs-dark .icon.viewChart,
|
||||
.hc-black .icon.viewChart {
|
||||
/* ResultToXlsx_16x_vscode */
|
||||
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 */
|
||||
.vs-dark .resultsMessageHeader {
|
||||
background: var(--color-bg-header);
|
||||
color: var(--color-content);
|
||||
background: var(--color-bg-header);
|
||||
color: var(--color-content);
|
||||
}
|
||||
|
||||
.vs-dark .resultsViewCollapsible:not(.collapsed),
|
||||
.hc-black .resultsViewCollapsible:not(.collapsed) {
|
||||
background-image:url("uncollapsedArrow_inverse.svg");
|
||||
background-repeat:no-repeat;
|
||||
background-position: 2px;
|
||||
background-image:url("uncollapsedArrow_inverse.svg");
|
||||
background-repeat:no-repeat;
|
||||
background-position: 2px;
|
||||
}
|
||||
|
||||
.vs-dark .resultsViewCollapsible,
|
||||
.hc-black .resultsViewCollapsible {
|
||||
background-image: url("collapsedArrow_inverse.svg");
|
||||
background-repeat:no-repeat;
|
||||
background-position: 2px;
|
||||
background-image: url("collapsedArrow_inverse.svg");
|
||||
background-repeat:no-repeat;
|
||||
background-position: 2px;
|
||||
}
|
||||
|
||||
.vs-dark .queryResultsShortCut {
|
||||
color: grey;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
/* scroll bar */
|
||||
|
||||
.vs-dark ::-webkit-scrollbar {
|
||||
width: 14px;
|
||||
height: 10px;
|
||||
width: 14px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.vs-dark ::-webkit-scrollbar-thumb {
|
||||
background: hsla(0,0%,47%,.4);
|
||||
background: hsla(0,0%,47%,.4);
|
||||
}
|
||||
|
||||
.vs-dark ::-webkit-scrollbar-thumb:hover {
|
||||
background: hsla(0,0%,39%,.7);
|
||||
background: hsla(0,0%,39%,.7);
|
||||
}
|
||||
|
||||
.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 {
|
||||
background: var(--background-color);
|
||||
background: var(--background-color);
|
||||
}
|
||||
|
||||
.vs-dark ::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench input, .vs-dark .monaco-workbench .input {
|
||||
color: var(--color-content);
|
||||
background-color: #3C3C3C;
|
||||
color: var(--color-content);
|
||||
background-color: #3C3C3C;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -313,20 +319,20 @@
|
||||
*
|
||||
*/
|
||||
|
||||
.hc-black .slickgridContainer {
|
||||
--color-content: #E5E5E5;
|
||||
--color-content-disabled: grey;
|
||||
--color-error: #E81123;
|
||||
--color-success: #7CD300;
|
||||
--color-bg-header: hsla(0,0%,50%,.2); /* used for pane toolbars */
|
||||
--color-resize-handle: #4d4d4d;
|
||||
--color-bg-content-header: #333334; /* used for color of grid headers */
|
||||
--color-cell-border-active: orange;
|
||||
--color-cell-bg-grid-selected: rgb(38, 79, 120);
|
||||
--color-grid-link: #FF6000;
|
||||
--color-grid-link-hover: #ff8033;
|
||||
--color-grid-dirty-background: #FFF;
|
||||
--color-grid-dirty-text: #000;
|
||||
.hc-black .slickgridContainer {
|
||||
--color-content: #E5E5E5;
|
||||
--color-content-disabled: grey;
|
||||
--color-error: #E81123;
|
||||
--color-success: #7CD300;
|
||||
--color-bg-header: hsla(0,0%,50%,.2); /* used for pane toolbars */
|
||||
--color-resize-handle: #4d4d4d;
|
||||
--color-bg-content-header: #333334; /* used for color of grid headers */
|
||||
--color-cell-border-active: orange;
|
||||
--color-cell-bg-grid-selected: rgb(38, 79, 120);
|
||||
--color-grid-link: #FF6000;
|
||||
--color-grid-link-hover: #ff8033;
|
||||
--color-grid-dirty-background: #FFF;
|
||||
--color-grid-dirty-text: #000;
|
||||
}
|
||||
|
||||
/* grid styling */
|
||||
@@ -336,32 +342,32 @@
|
||||
}
|
||||
|
||||
.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 {
|
||||
color: var(--color-content) !important;
|
||||
color: var(--color-content) !important;
|
||||
}
|
||||
|
||||
.hc-black .boxRow.content.horzBox.slickgrid {
|
||||
border: solid 1px #2D2D30;
|
||||
border: solid 1px #2D2D30;
|
||||
}
|
||||
|
||||
/* headers */
|
||||
.hc-black .resultsMessageHeader {
|
||||
background: var(--color-bg-header);
|
||||
color: var(--color-content);
|
||||
background: var(--color-bg-header);
|
||||
color: var(--color-content);
|
||||
}
|
||||
|
||||
.hc-black .queryResultsShortCut {
|
||||
color: grey;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
/* scroll bar */
|
||||
|
||||
.hc-black ::-webkit-scrollbar {
|
||||
width: 14px;
|
||||
height: 10px;
|
||||
width: 14px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.hc-black ::-webkit-scrollbar-thumb {
|
||||
@@ -377,14 +383,14 @@
|
||||
}
|
||||
|
||||
.hc-black ::-webkit-scrollbar-track {
|
||||
background: var(--background-color);
|
||||
background: var(--background-color);
|
||||
}
|
||||
|
||||
.hc-black ::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench input {
|
||||
color: #000;
|
||||
background-color: #FFF;
|
||||
color: #000;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import { ResultSerializer } from 'sql/parts/query/common/resultSerializer';
|
||||
import { ISaveRequest } from 'sql/parts/grid/common/interfaces';
|
||||
|
||||
import { ISlickRange } from 'angular2-slickgrid';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
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 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import 'vs/css!sql/parts/grid/media/slickGrid';
|
||||
import 'vs/css!./media/editData';
|
||||
|
||||
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 * 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 onIsColumnEditable: (column: number) => boolean;
|
||||
public overrideCellFn: (rowNumber, columnId, value?, data?) => string;
|
||||
public loadDataFunction: (offset: number, count: number) => Promise<IGridDataRow[]>;
|
||||
public loadDataFunction: (offset: number, count: number) => Promise<{}[]>;
|
||||
|
||||
private savedViewState: {
|
||||
gridSelections: ISlickRange[];
|
||||
gridSelections: Slick.Range[];
|
||||
scrollTop;
|
||||
scrollLeft;
|
||||
};
|
||||
@@ -153,6 +153,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
self.dataSet = undefined;
|
||||
self.placeHolderDataSets = [];
|
||||
self.renderedDataSets = self.placeHolderDataSets;
|
||||
this._cd.detectChanges();
|
||||
self.totalElapsedTimeSpan = undefined;
|
||||
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
|
||||
this.loadDataFunction = (offset: number, count: number): Promise<IGridDataRow[]> => {
|
||||
return new Promise<IGridDataRow[]>((resolve, reject) => {
|
||||
this.loadDataFunction = (offset: number, count: number): Promise<{}[]> => {
|
||||
return new Promise<{}[]>((resolve, reject) => {
|
||||
self.dataService.getEditRows(offset, count).subscribe(result => {
|
||||
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
|
||||
};
|
||||
let gridData = result.subset.map(r => {
|
||||
let dataWithSchema = {};
|
||||
// skip the first column since its a number column
|
||||
for (let i = 1; i < this.dataSet.columnDefinitions.length; i++) {
|
||||
dataWithSchema[this.dataSet.columnDefinitions[i].field] = r.cells[i - 1].displayValue;
|
||||
}
|
||||
return dataWithSchema;
|
||||
});
|
||||
// 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
|
||||
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 });
|
||||
// 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 });
|
||||
resolve(gridData);
|
||||
});
|
||||
});
|
||||
@@ -356,7 +365,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
self.windowSize,
|
||||
resultSet.rowCount,
|
||||
this.loadDataFunction,
|
||||
index => { return { values: [] }; }
|
||||
index => { return {}; }
|
||||
),
|
||||
columnDefinitions: [rowNumberColumn.getColumnDefinition()].concat(resultSet.columnInfo.map((c, i) => {
|
||||
let isLinked = c.isXml || c.isJson;
|
||||
@@ -581,14 +590,17 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
}
|
||||
|
||||
private saveViewState(): void {
|
||||
let gridSelections = this.slickgrids.toArray()[0].getSelectedRanges();
|
||||
let viewport = ((this.slickgrids.toArray()[0] as any)._grid.getCanvasNode() as HTMLElement).parentElement;
|
||||
let grid = this.slickgrids.toArray()[0]
|
||||
if (grid) {
|
||||
let gridSelections = grid.getSelectedRanges();
|
||||
let viewport = ((grid as any)._grid.getCanvasNode() as HTMLElement).parentElement;
|
||||
|
||||
this.savedViewState = {
|
||||
gridSelections,
|
||||
scrollTop: viewport.scrollTop,
|
||||
scrollLeft: viewport.scrollLeft
|
||||
};
|
||||
this.savedViewState = {
|
||||
gridSelections,
|
||||
scrollTop: viewport.scrollTop,
|
||||
scrollLeft: viewport.scrollLeft
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private restoreViewState(): void {
|
||||
|
||||
@@ -13,7 +13,7 @@ import 'vs/css!sql/parts/grid/media/slickGrid';
|
||||
|
||||
import { Subscription, Subject } from 'rxjs/Rx';
|
||||
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 * as Constants from 'sql/parts/query/common/constants';
|
||||
import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants';
|
||||
@@ -226,11 +226,11 @@ export abstract class GridParentComponent {
|
||||
this.messagesFocussedContextKey.set(false);
|
||||
}
|
||||
|
||||
protected getSelection(index?: number): ISlickRange[] {
|
||||
protected getSelection(index?: number): Slick.Range[] {
|
||||
let selection = this.slickgrids.toArray()[index || this.activeGrid].getSelectedRanges();
|
||||
if (selection) {
|
||||
selection = selection.map(c => { return <ISlickRange>{ fromCell: c.fromCell - 1, toCell: c.toCell - 1, toRow: c.toRow, fromRow: c.fromRow }; });
|
||||
return selection;
|
||||
selection = selection.map(c => { return <Slick.Range>{ fromCell: c.fromCell - 1, toCell: c.toCell - 1, toRow: c.toRow, fromRow: c.fromRow }; });
|
||||
return selection;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
@@ -332,7 +332,7 @@ export abstract class GridParentComponent {
|
||||
/**
|
||||
* 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) {
|
||||
case 'savecsv':
|
||||
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
|
||||
this._executeResult.columns = dataSet.columnDefinitions.slice(1).map(def => def.name);
|
||||
this._executeResult.rows = dataSet.dataRows.getRange(0, dataSet.dataRows.getLength()).map(gridRow => {
|
||||
return gridRow.values.slice(1).map(cell => (cell.invariantCultureDisplayValue === null || cell.invariantCultureDisplayValue === undefined) ? cell.displayValue : cell.invariantCultureDisplayValue);
|
||||
this._executeResult.rows = dataSet.dataRows.getRange(0, dataSet.dataRows.getLength()).map(v => {
|
||||
return this._executeResult.columns.reduce((p, c) => {
|
||||
p.push(v[c]);
|
||||
return p;
|
||||
}, []);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public initChart() {
|
||||
this._cd.detectChanges();
|
||||
if (this._executeResult) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
ElementRef, QueryList, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject,
|
||||
ViewChildren, forwardRef, EventEmitter, Input, ViewChild
|
||||
} 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 Services from 'sql/parts/grid/services/sharedServices';
|
||||
@@ -163,7 +163,7 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
|
||||
public onActiveCellChanged: (gridIndex: number) => void;
|
||||
|
||||
private savedViewState: {
|
||||
gridSelections: ISlickRange[][];
|
||||
gridSelections: Slick.Range[][];
|
||||
resultsScroll: number;
|
||||
messagePaneScroll: 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()) {
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.target as HTMLElement,
|
||||
getAnchor: () => e.anchor,
|
||||
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({
|
||||
getAnchor: () => e.target as HTMLElement,
|
||||
getAnchor: () => e.anchor,
|
||||
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.actionBarContainer.nativeElement).empty();
|
||||
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._register(this._table.onContextMenu((e: DOMEvent, data: Slick.OnContextMenuEventArgs<any>) => {
|
||||
this._register(this._table.onContextMenu(e => {
|
||||
self.openContextMenu(e);
|
||||
}));
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
|
||||
$(this._gridEl.nativeElement).empty();
|
||||
$(this.actionBarContainer.nativeElement).empty();
|
||||
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.onClick.subscribe((e, args) => {
|
||||
let job = self.getJob(args);
|
||||
@@ -186,7 +186,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
|
||||
self._agentViewComponent.showHistory = true;
|
||||
});
|
||||
|
||||
this._register(this._table.onContextMenu((e: DOMEvent, data: Slick.OnContextMenuEventArgs<any>) => {
|
||||
this._register(this._table.onContextMenu(e => {
|
||||
self.openContextMenu(e);
|
||||
}));
|
||||
|
||||
|
||||
@@ -110,10 +110,10 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
|
||||
$(this._gridEl.nativeElement).empty();
|
||||
$(this.actionBarContainer.nativeElement).empty();
|
||||
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._register(this._table.onContextMenu((e: DOMEvent, data: Slick.OnContextMenuEventArgs<any>) => {
|
||||
this._register(this._table.onContextMenu(e => {
|
||||
self.openContextMenu(e);
|
||||
}));
|
||||
|
||||
|
||||
@@ -112,10 +112,10 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
|
||||
$(this._gridEl.nativeElement).empty();
|
||||
$(this.actionBarContainer.nativeElement).empty();
|
||||
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._register(this._table.onContextMenu((e: DOMEvent, data: Slick.OnContextMenuEventArgs<any>) => {
|
||||
this._register(this._table.onContextMenu(e => {
|
||||
self.openContextMenu(e);
|
||||
}));
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ export default class TableComponent extends ComponentBase implements IComponent,
|
||||
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.setSelectionModel(new RowSelectionModel({ selectActiveRow: true }));
|
||||
|
||||
|
||||
@@ -327,7 +327,7 @@ export class ProfilerEditor extends BaseEditor {
|
||||
detailTableContainer.style.width = '100%';
|
||||
detailTableContainer.style.height = '100%';
|
||||
this._detailTableData = new TableDataView<IDetailData>();
|
||||
this._detailTable = new Table(detailTableContainer, this._detailTableData, [
|
||||
this._detailTable = new Table(detailTableContainer, { dataProvider: this._detailTableData, columns: [
|
||||
{
|
||||
id: 'label',
|
||||
name: nls.localize('label', "Label"),
|
||||
@@ -340,7 +340,7 @@ export class ProfilerEditor extends BaseEditor {
|
||||
field: 'value',
|
||||
formatter: textFormatter
|
||||
}
|
||||
], { forceFitColumns: true });
|
||||
]}, { forceFitColumns: true });
|
||||
|
||||
this._tabbedPanel.pushTab({
|
||||
identifier: 'detailTable',
|
||||
|
||||
@@ -26,7 +26,6 @@ import * as paths from 'vs/base/common/paths';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as pretty from 'pretty-data';
|
||||
|
||||
import { ISlickRange } from 'angular2-slickgrid';
|
||||
import * as path from 'path';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
@@ -255,7 +254,7 @@ export class ResultSerializer {
|
||||
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;
|
||||
if (!path.isAbsolute(filePath)) {
|
||||
this._filePath = PathUtilities.resolveFilePath(this._uri, filePath, this.rootPath);
|
||||
@@ -287,7 +286,7 @@ export class ResultSerializer {
|
||||
/**
|
||||
* 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)));
|
||||
}
|
||||
|
||||
@@ -319,7 +318,7 @@ export class ResultSerializer {
|
||||
/**
|
||||
* 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);
|
||||
|
||||
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';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Builder } from 'vs/base/browser/builder';
|
||||
import { EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
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 { BareFontInfo } from 'vs/editor/common/config/fontInfo';
|
||||
import { getZoomLevel } from 'vs/base/browser/browser';
|
||||
import { Configuration } from 'vs/editor/browser/config/configuration';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
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 { 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 { QueryOutputModule } from 'sql/parts/query/views/queryOutput.module';
|
||||
import { QUERY_OUTPUT_SELECTOR } from 'sql/parts/query/views/queryOutput.component';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { QueryResultsView } from 'sql/parts/query/editor/queryResultsView';
|
||||
|
||||
export const RESULTS_GRID_DEFAULTS = {
|
||||
cellPadding: [6, 10, 5],
|
||||
@@ -97,6 +95,8 @@ export class QueryResultsEditor extends BaseEditor {
|
||||
protected _rawOptions: BareResultsGridInfo;
|
||||
protected _input: QueryResultsInput;
|
||||
|
||||
private resultsView: QueryResultsView;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@@ -106,12 +106,12 @@ export class QueryResultsEditor extends BaseEditor {
|
||||
) {
|
||||
super(QueryResultsEditor.ID, telemetryService, themeService);
|
||||
this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel());
|
||||
this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('resultsGrid')) {
|
||||
this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel());
|
||||
this.applySettings();
|
||||
}
|
||||
});
|
||||
// this._configurationService.onDidChangeConfiguration(e => {
|
||||
// if (e.affectsConfiguration('resultsGrid')) {
|
||||
// this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel());
|
||||
// this.applySettings();
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
public get input(): QueryResultsInput {
|
||||
@@ -136,54 +136,22 @@ export class QueryResultsEditor extends BaseEditor {
|
||||
}
|
||||
|
||||
createEditor(parent: HTMLElement): void {
|
||||
if (!this.resultsView) {
|
||||
this.resultsView = new QueryResultsView(parent, this._instantiationService, this._queryModelService);
|
||||
}
|
||||
}
|
||||
|
||||
layout(dimension: DOM.Dimension): void {
|
||||
this.resultsView.layout(dimension);
|
||||
}
|
||||
|
||||
setInput(input: QueryResultsInput, options: EditorOptions): TPromise<void> {
|
||||
super.setInput(input, options);
|
||||
this.applySettings();
|
||||
if (!input.hasBootstrapped) {
|
||||
this._bootstrapAngular();
|
||||
}
|
||||
this.resultsView.input = input;
|
||||
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 {
|
||||
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 { DataService } from 'sql/parts/grid/services/dataService';
|
||||
import { ISlickRange } from 'angular2-slickgrid';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||
@@ -19,6 +18,7 @@ import {
|
||||
EditRevertCellResult,
|
||||
ExecutionPlanOptions
|
||||
} from 'sqlops';
|
||||
import { QueryInfo } from 'sql/parts/query/execution/queryModelService';
|
||||
|
||||
export const SERVICE_ID = 'queryModelService';
|
||||
|
||||
@@ -48,7 +48,7 @@ export interface IQueryModelService {
|
||||
resizeResultsets(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;
|
||||
showWarning(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>;
|
||||
getEditRows(ownerUri: string, rowStart: number, numberOfRows: number): Thenable<EditSubsetResult>;
|
||||
|
||||
_getQueryInfo(uri: string): QueryInfo;
|
||||
// Edit Data Callbacks
|
||||
onEditSessionReady: Event<EditSessionReadyParams>;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import { SqlFlavorStatusbarItem } from 'sql/parts/query/common/flavorStatus';
|
||||
import { RowCountStatusBarItem } from 'sql/parts/query/common/rowCountStatus';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { ISlickRange } from 'angular2-slickgrid';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as statusbar from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
@@ -31,7 +30,7 @@ import Severity from 'vs/base/common/severity';
|
||||
|
||||
const selectionSnippetMaxLen = 100;
|
||||
|
||||
interface QueryEvent {
|
||||
export interface QueryEvent {
|
||||
type: string;
|
||||
data: any;
|
||||
}
|
||||
@@ -39,7 +38,7 @@ interface QueryEvent {
|
||||
/**
|
||||
* Holds information about the state of a query runner
|
||||
*/
|
||||
class QueryInfo {
|
||||
export class QueryInfo {
|
||||
public queryRunner: QueryRunner;
|
||||
public dataService: DataService;
|
||||
public queryEventQueue: QueryEvent[];
|
||||
@@ -170,7 +169,7 @@ export class QueryModelService implements IQueryModelService {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -569,7 +568,7 @@ export class QueryModelService implements IQueryModelService {
|
||||
}
|
||||
}
|
||||
|
||||
private _getQueryInfo(uri: string): QueryInfo {
|
||||
public _getQueryInfo(uri: string): QueryInfo {
|
||||
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 WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
||||
import { IQueryManagementService } from 'sql/parts/query/common/queryManagement';
|
||||
import { ISlickRange } from 'angular2-slickgrid';
|
||||
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 { 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 { IDisposable } from 'vs/base/common/lifecycle';
|
||||
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 {
|
||||
ownerUri: string;
|
||||
@@ -61,6 +64,38 @@ export default class QueryRunner {
|
||||
private _batchSets: sqlops.BatchSummary[] = [];
|
||||
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(
|
||||
public uri: string,
|
||||
@@ -68,7 +103,8 @@ export default class QueryRunner {
|
||||
@IQueryManagementService private _queryManagementService: IQueryManagementService,
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
@IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService,
|
||||
@IClipboardService private _clipboardService: IClipboardService
|
||||
@IClipboardService private _clipboardService: IClipboardService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService
|
||||
) { }
|
||||
|
||||
get isExecuting(): boolean {
|
||||
@@ -152,6 +188,7 @@ export default class QueryRunner {
|
||||
|
||||
private handleSuccessRunQueryResult() {
|
||||
// The query has started, so lets fire up the result pane
|
||||
this._onQueryStart.fire();
|
||||
this._eventEmitter.emit(EventType.START);
|
||||
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));
|
||||
// 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
|
||||
this.batchSets[batch.id] = 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
|
||||
this.sendBatchTimeMessage(batch.id, Utils.parseNumAsTimeString(executionTime));
|
||||
}
|
||||
|
||||
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
|
||||
batchSet.resultSetSummaries[resultSet.id] = 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
|
||||
this._eventEmitter.emit(EventType.MESSAGE, message);
|
||||
this._onMessage.fire(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
const self = this;
|
||||
let rowData: sqlops.QueryExecuteSubsetParams = <sqlops.QueryExecuteSubsetParams>{
|
||||
ownerUri: this.uri,
|
||||
resultSetIndex: resultSetIndex,
|
||||
@@ -284,16 +326,12 @@ export default class QueryRunner {
|
||||
batchIndex: batchIndex
|
||||
};
|
||||
|
||||
return new Promise<sqlops.QueryExecuteSubsetResult>((resolve, reject) => {
|
||||
self._queryManagementService.getQueryRows(rowData).then(result => {
|
||||
resolve(result);
|
||||
}, error => {
|
||||
self._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: nls.localize('query.gettingRowsFailedError', 'Something went wrong getting more rows: {0}', error)
|
||||
});
|
||||
reject(error);
|
||||
return this._queryManagementService.getQueryRows(rowData).then(r => r, error => {
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: nls.localize('query.gettingRowsFailedError', 'Something went wrong getting more rows: {0}', error)
|
||||
});
|
||||
return error;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -317,7 +355,7 @@ export default class QueryRunner {
|
||||
this._isExecuting = false;
|
||||
this._notificationService.notify({
|
||||
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`;
|
||||
self._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: error
|
||||
message: error
|
||||
});
|
||||
reject(error);
|
||||
}
|
||||
@@ -350,7 +388,7 @@ export default class QueryRunner {
|
||||
let errorMessage = nls.localize('query.moreRowsFailedError', 'Something went wrong getting more rows:');
|
||||
self._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: `${errorMessage} ${error}`
|
||||
message: `${errorMessage} ${error}`
|
||||
});
|
||||
reject(error);
|
||||
});
|
||||
@@ -412,7 +450,7 @@ export default class QueryRunner {
|
||||
* @param resultId The result id of the result to copy from
|
||||
* @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;
|
||||
let copyString = '';
|
||||
const eol = this.getEolString();
|
||||
@@ -483,7 +521,7 @@ export default class QueryRunner {
|
||||
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 batchSummary: sqlops.BatchSummary = this.batchSets[batchId];
|
||||
if (batchSummary !== undefined) {
|
||||
@@ -519,7 +557,11 @@ export default class QueryRunner {
|
||||
isError: false
|
||||
};
|
||||
// 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;
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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 invalidate(): void;
|
||||
public invalidateRow(row: number): void;
|
||||
public invalidateRows(rows: number[]): void;
|
||||
public invalidateRows(rows: number[], keepEditor: boolean): void;
|
||||
public invalidateAllRows(): void;
|
||||
public updateCell(row: number, cell: 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
|
||||
// Source: node_modules/angular2-slickgrid/out/js/interfaces.d.ts
|
||||
declare module '~angular2-slickgrid/out/js/interfaces' {
|
||||
@@ -57,7 +21,6 @@ export interface IObservableCollection<T> {
|
||||
at(index: number): T;
|
||||
getRange(start: number, end: number): T[];
|
||||
setCollectionChangedCallback(callback: (change: CollectionChange, startIndex: number, count: number) => void): void;
|
||||
resetWindowsAroundIndex(index: number): void;
|
||||
}
|
||||
export class CancellationToken {
|
||||
private _isCanceled;
|
||||
@@ -66,27 +29,6 @@ export class CancellationToken {
|
||||
readonly isCanceled: boolean;
|
||||
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 {
|
||||
id: string;
|
||||
type: number;
|
||||
@@ -95,6 +37,9 @@ export interface IGridDataRow {
|
||||
row?: number;
|
||||
values: any[];
|
||||
}
|
||||
export interface ISlickColumn<T> extends Slick.Column<T> {
|
||||
isEditable?: boolean;
|
||||
}
|
||||
}
|
||||
declare module 'angular2-slickgrid/out/js/interfaces' {
|
||||
export * from '~angular2-slickgrid/out/js/interfaces';
|
||||
@@ -109,13 +54,13 @@ export class SelectionModel implements ISlickSelectionModel {
|
||||
private _handler;
|
||||
private _onSelectedRangesChanged;
|
||||
private _slickRangeFactory;
|
||||
constructor(_rowSelectionModel: ISlickSelectionModel, _handler: ISlickEventHandler, _onSelectedRangesChanged: ISlickEvent, _slickRangeFactory: (fromRow: number, fromCell: number, toRow: number, toCell: number) => ISlickRange);
|
||||
readonly range: ISlickRange[];
|
||||
constructor(_rowSelectionModel: ISlickSelectionModel, _handler: ISlickEventHandler, _onSelectedRangesChanged: ISlickEvent, _slickRangeFactory: (fromRow: number, fromCell: number, toRow: number, toCell: number) => Slick.Range);
|
||||
readonly range: Slick.Range[];
|
||||
readonly onSelectedRangesChanged: ISlickEvent;
|
||||
init(grid: ISlickGrid): void;
|
||||
destroy(): void;
|
||||
setSelectedRanges(ranges: ISlickRange[]): void;
|
||||
getSelectedRanges(): ISlickRange[];
|
||||
setSelectedRanges(ranges: Slick.Range[]): void;
|
||||
getSelectedRanges(): Slick.Range[];
|
||||
changeSelectedRanges(selections: ISelectionRange[]): void;
|
||||
toggleSingleColumnSelection(columnId: string): void;
|
||||
setSingleColumnSelection(columnId: string): void;
|
||||
@@ -132,27 +77,21 @@ export class SelectionModel implements ISlickSelectionModel {
|
||||
private updateSelectedRanges(ranges);
|
||||
}
|
||||
export interface ISlickSelectionModel {
|
||||
range: ISlickRange[];
|
||||
range: Slick.Range[];
|
||||
onSelectedRangesChanged: any;
|
||||
init(grid: any): void;
|
||||
destroy(): void;
|
||||
setSelectedRanges(ranges: ISlickRange[]): void;
|
||||
getSelectedRanges(): ISlickRange[];
|
||||
setSelectedRanges(ranges: Slick.Range[]): void;
|
||||
getSelectedRanges(): Slick.Range[];
|
||||
}
|
||||
export interface ISlickEventHandler {
|
||||
subscribe(event: any, handler: any): void;
|
||||
unsubscribeAll(): void;
|
||||
}
|
||||
export interface ISlickEvent {
|
||||
notify(eventData: ISlickRange[]): void;
|
||||
notify(eventData: Slick.Range[]): void;
|
||||
subscribe(handler: (e: any, args: any) => void): void;
|
||||
}
|
||||
export interface ISlickRange {
|
||||
fromCell: number;
|
||||
fromRow: number;
|
||||
toCell: number;
|
||||
toRow: number;
|
||||
}
|
||||
export interface ISlickGrid {
|
||||
getActiveCellNode(): any;
|
||||
getCanvasNode(): any;
|
||||
@@ -171,12 +110,11 @@ export * from '~angular2-slickgrid/out/js/selectionModel';
|
||||
declare module '~angular2-slickgrid/out/js/slickGrid' {
|
||||
import { OnChanges, OnInit, OnDestroy, SimpleChange, EventEmitter, AfterViewInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { IObservableCollection, IGridDataRow, IColumnDefinition } from '~angular2-slickgrid/out/js/interfaces';
|
||||
import { ISlickRange } from '~angular2-slickgrid/out/js/selectionModel';
|
||||
import { IObservableCollection, IGridDataRow, ISlickColumn } from '~angular2-slickgrid/out/js/interfaces';
|
||||
export function getOverridableTextEditorClass(grid: SlickGrid): any;
|
||||
export class SlickGrid implements OnChanges, OnInit, OnDestroy, AfterViewInit {
|
||||
private _el;
|
||||
private _gridSyncService;
|
||||
columnDefinitions: IColumnDefinition[];
|
||||
columnDefinitions: ISlickColumn<any>[];
|
||||
dataRows: IObservableCollection<IGridDataRow>;
|
||||
resized: Observable<any>;
|
||||
highlightedCells: {
|
||||
@@ -187,7 +125,6 @@ export class SlickGrid implements OnChanges, OnInit, OnDestroy, AfterViewInit {
|
||||
contextColumns: string[];
|
||||
columnsLoading: string[];
|
||||
showHeader: boolean;
|
||||
showDataTypeIcon: boolean;
|
||||
enableColumnReorder: boolean;
|
||||
enableAsyncPostRender: boolean;
|
||||
selectionModel: string | Slick.SelectionModel<any, any>;
|
||||
@@ -195,45 +132,23 @@ export class SlickGrid implements OnChanges, OnInit, OnDestroy, AfterViewInit {
|
||||
enableEditing: boolean;
|
||||
topRowNumber: number;
|
||||
overrideCellFn: (rowNumber, columnId, value?, data?) => string;
|
||||
isColumnEditable: (column: number) => boolean;
|
||||
isCellEditValid: (row: number, column: number, newValue: any) => boolean;
|
||||
private _rowHeight;
|
||||
loadFinished: EventEmitter<void>;
|
||||
editingFinished: EventEmitter<any>;
|
||||
contextMenu: EventEmitter<any>;
|
||||
topRowNumberChange: EventEmitter<number>;
|
||||
activeCellChanged: EventEmitter<{
|
||||
row: number;
|
||||
column: number;
|
||||
}>;
|
||||
cellEditBegin: EventEmitter<{
|
||||
row: number;
|
||||
column: number;
|
||||
}>;
|
||||
cellEditExit: EventEmitter<{
|
||||
row: number;
|
||||
column: number;
|
||||
newValue: any;
|
||||
}>;
|
||||
rowEditBegin: EventEmitter<{
|
||||
row: number;
|
||||
}>;
|
||||
rowEditExit: EventEmitter<{
|
||||
row: number;
|
||||
}>;
|
||||
onContextMenu: EventEmitter<Slick.OnContextMenuEventArgs<any>>;
|
||||
onScroll: EventEmitter<Slick.OnScrollEventArgs<any>>;
|
||||
onActiveCellChanged: EventEmitter<Slick.OnActiveCellChangedEventArgs<any>>;
|
||||
onBeforeEditCell: EventEmitter<Slick.OnBeforeEditCellEventArgs<any>>;
|
||||
onCellChange: EventEmitter<Slick.OnCellChangeEventArgs<any>>;
|
||||
onFocus(): void;
|
||||
rowHeight: number;
|
||||
private _rowHeight;
|
||||
private _grid;
|
||||
private _gridColumns;
|
||||
private _columnNameToIndex;
|
||||
private _gridData;
|
||||
private _resizeSubscription;
|
||||
private _gridSyncSubscription;
|
||||
private _topRow;
|
||||
private _leftPx;
|
||||
private _activeEditingRow;
|
||||
private _activeEditingRowHasChanges;
|
||||
constructor(_el: any, _gridSyncService: any);
|
||||
constructor(_el: any);
|
||||
ngOnChanges(changes: {
|
||||
[propName: string]: SimpleChange;
|
||||
}): void;
|
||||
@@ -245,27 +160,20 @@ export class SlickGrid implements OnChanges, OnInit, OnDestroy, AfterViewInit {
|
||||
readonly onSelectedRowsChanged: Slick.Event<Slick.OnSelectedRowsChangedEventArgs<any>>;
|
||||
getSelectedRows(): number[];
|
||||
getColumnIndex(name: string): number;
|
||||
getSelectedRanges(): ISlickRange[];
|
||||
getSelectedRanges(): Slick.Range[];
|
||||
registerPlugin(plugin: Slick.Plugin<any> | string): void;
|
||||
setActive(): void;
|
||||
selection: ISlickRange[] | boolean;
|
||||
subscribeToContextMenu(): void;
|
||||
selection: Slick.Range[] | boolean;
|
||||
private initGrid();
|
||||
private changeEditSession(enabled);
|
||||
private handleEditorCellChange(rowNumber);
|
||||
private static getDataWithSchema(data, columns);
|
||||
private onResize();
|
||||
private invalidateRange(start, end);
|
||||
private getColumnEditor;
|
||||
private getColumnEditor(column);
|
||||
private getFormatter;
|
||||
private subscribeToScroll();
|
||||
private subscribeToCellChanged();
|
||||
private subscribeToBeforeEditCell();
|
||||
private subscribeToActiveCellChanged();
|
||||
private updateColumnWidths();
|
||||
private setupEvents();
|
||||
private updateSchema();
|
||||
private getImagePathForDataType(type);
|
||||
private setCallbackOnDataRowsChanged();
|
||||
readonly activeCell: Slick.Cell;
|
||||
private renderGridDataRowsRange(startIndex, count);
|
||||
}
|
||||
}
|
||||
@@ -302,7 +210,6 @@ export * from '~angular2-slickgrid/out/js/virtualizedCollection';
|
||||
// Generated by typings
|
||||
// Source: node_modules/angular2-slickgrid/out/index.d.ts
|
||||
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/selectionModel';
|
||||
export * from '~angular2-slickgrid/out/js/slickGrid';
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"resolution": "main",
|
||||
"tree": {
|
||||
"raw": "npm:angular2-slickgrid",
|
||||
"main": "out/index.js",
|
||||
"version": "1.3.9",
|
||||
"name": "angular2-slickgrid"
|
||||
"src": "D:\\code\\sqlopsstudio\\node_modules\\angular2-slickgrid\\package.json",
|
||||
"raw": "npm:angular2-slickgrid",
|
||||
"main": "out/index.js",
|
||||
"version": "1.4.1",
|
||||
"name": "angular2-slickgrid"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user