Layer grid code (#5029)
* layer grid * errors; edit data still not showing up * fix edit data * fix tab spaces
21
src/sql/workbench/parts/grid/common/gridContentEvents.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export const ResizeContents = 'ResizeContents';
|
||||
export const RefreshContents = 'RefreshContents';
|
||||
export const ToggleResultPane = 'ToggleResultPane';
|
||||
export const ToggleMessagePane = 'ToggleMessagePane';
|
||||
export const CopySelection = 'CopySelection';
|
||||
export const CopyWithHeaders = 'CopyWithHeaders';
|
||||
export const CopyMessagesSelection = 'CopyMessagesSelection';
|
||||
export const SelectAll = 'SelectAll';
|
||||
export const SelectAllMessages = 'SelectAllMessages';
|
||||
export const SaveAsCsv = 'SaveAsCSV';
|
||||
export const SaveAsJSON = 'SaveAsJSON';
|
||||
export const SaveAsExcel = 'SaveAsExcel';
|
||||
export const SaveAsXML = 'SaveAsXML';
|
||||
export const ViewAsChart = 'ViewAsChart';
|
||||
export const GoToNextQueryOutputTab = 'GoToNextQueryOutputTab';
|
||||
export const GoToNextGrid = 'GoToNextGrid';
|
||||
39
src/sql/workbench/parts/grid/common/interfaces.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ISlickColumn, VirtualizedCollection } from 'angular2-slickgrid';
|
||||
|
||||
export interface IGridDataSet {
|
||||
dataRows: VirtualizedCollection<{}>;
|
||||
columnDefinitions: ISlickColumn<any>[];
|
||||
resized: any; // EventEmitter<any>;
|
||||
totalRows: number;
|
||||
batchId: number;
|
||||
resultId: number;
|
||||
maxHeight: number | string;
|
||||
minHeight: number | string;
|
||||
}
|
||||
|
||||
export enum SaveFormat {
|
||||
CSV = 'csv',
|
||||
JSON = 'json',
|
||||
EXCEL = 'excel',
|
||||
XML = 'xml'
|
||||
}
|
||||
|
||||
export interface IGridInfo {
|
||||
batchIndex: number;
|
||||
resultSetNumber: number;
|
||||
selection: Slick.Range[];
|
||||
gridIndex: number;
|
||||
rowIndex?: number;
|
||||
}
|
||||
export interface ISaveRequest {
|
||||
format: SaveFormat;
|
||||
batchIndex: number;
|
||||
resultSetNumber: number;
|
||||
selection: Slick.Range[];
|
||||
}
|
||||
1
src/sql/workbench/parts/grid/media/collapsedArrow.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16'><path fill='#646465' d='M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z'/></svg>
|
||||
|
After Width: | Height: | Size: 142 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16'><path fill='#e8e8e8' d='M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z'/></svg>
|
||||
|
After Width: | Height: | Size: 142 B |
1
src/sql/workbench/parts/grid/media/exitFullScreen.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 16H0V0h16v16z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M12 7H9V4l1-1v2l2-2 1 1-2 2h2l-1 1zm-2 1H9v4H4V8h4V6H3v7h7V8zm5-7v14H1V1h14zm-1 1H2v12h12V2z" id="iconBg"/><path class="icon-vs-fg" d="M12.414 8L14 6.414V14H2V2h7.586L8 3.586V6H3v7h7V8h2.414zM4 8v4h5V8H4z" id="iconFg" style="display: none;"/></svg>
|
||||
|
After Width: | Height: | Size: 633 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 16H0V0h16v16z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M12 7H9V4l1-1v2l2-2 1 1-2 2h2l-1 1zm-2 1H9v4H4V8h4V6H3v7h7V8zm5-7v14H1V1h14zm-1 1H2v12h12V2z" id="iconBg"/><path class="icon-vs-fg" d="M12.414 8L14 6.414V14H2V2h7.586L8 3.586V6H3v7h7V8h2.414zM4 8v4h5V8H4z" id="iconFg" style="display: none;"/></svg>
|
||||
|
After Width: | Height: | Size: 633 B |
1
src/sql/workbench/parts/grid/media/extendFullScreen.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"> <style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1} </style> <path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas" /> <path class="icon-vs-out" d="M16 16H0V0h16v16z" id="outline" style="display: none;" /> <path class="icon-vs-fg" d="M11 9.414l3-3V14H2V2h7.586l-3 3h2l-1 1H3v7h7V8.414l1-1v2zM4 12h5V8H4v4z" id="iconFg" style="display: none;" /> <path class="icon-vs-bg" d="M7.586 6H3v7h7V8.414L7.586 6zM9 12H4V8h5v4zM1 1v14h14V1H1zm13 13H2V2h12v12zM9 6l2-2H9l1-1h3v3l-1 1V5l-2 2-1-1z" id="iconBg" /> </svg >
|
||||
|
After Width: | Height: | Size: 665 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 16H0V0h16v16z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M11 9.414l3-3V14H2V2h7.586l-3 3h2l-1 1H3v7h7V8.414l1-1v2zM4 12h5V8H4v4z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M7.586 6H3v7h7V8.414L7.586 6zM9 12H4V8h5v4zM1 1v14h14V1H1zm13 13H2V2h12v12zM9 6l2-2H9l1-1h3v3l-1 1V5l-2 2-1-1z" id="iconBg"/></svg>
|
||||
|
After Width: | Height: | Size: 653 B |
71
src/sql/workbench/parts/grid/media/flexbox.css
Normal file
@@ -0,0 +1,71 @@
|
||||
.fullsize {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* vertical box styles */
|
||||
.vertBox {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
.vertBox .boxRow.header {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.vertBox .boxRow.content {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
.edgesPadding {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.messagesTopSpacing {
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
.results {
|
||||
flex: 100 1 0;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.maxHeight {
|
||||
max-height: fit-content;
|
||||
}
|
||||
|
||||
.minHeight {
|
||||
min-height: fit-content;
|
||||
}
|
||||
|
||||
/* horizontal box style */
|
||||
.horzBox {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
}
|
||||
|
||||
.horzBox .boxCol.content {
|
||||
flex: 1 1 1%;
|
||||
overflow: auto;
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* testing
|
||||
|
||||
.vertBox {
|
||||
border: 1px solid green;
|
||||
}
|
||||
|
||||
.horzBox {
|
||||
border: 1px solid red;
|
||||
}
|
||||
*/
|
||||
1
src/sql/workbench/parts/grid/media/saveCsv.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}.icon-vs-action-blue{fill:#00539c}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M14 4.552V13c-.028.825-.593 2-2.035 2h-8C3.012 15 2 14.299 2 13V8H.586l2-2H0V2h2.586l-2-2h4.828l1 1h3.608L14 4.552z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M9 3.586L8.414 3H9v.586zM12 6h-2v7h2V6zm-6 7V7.414L5.414 8H4v5h2zm1 0h2V4.414l-1 1V6h-.586L7 6.414V13z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M8 5.414V6h-.586L8 5.414zM13 5v8s-.035 1-1.035 1h-8S3 14 3 13V8h1v5h2V7.414l1-1V13h2V4.414L9.414 4 9 3.586V3h-.586l-1-1h2.227L13 5zm-1 1h-2v7h2V6z" id="iconBg"/><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z" id="colorAction"/></svg>
|
||||
|
After Width: | Height: | Size: 940 B |
1
src/sql/workbench/parts/grid/media/saveCsv_inverse.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}.icon-vs-action-blue{fill:#75beff}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M14 4.552V13c-.028.825-.593 2-2.035 2h-8C3.012 15 2 14.299 2 13V8H.586l2-2H0V2h2.586l-2-2h4.828l1 1h3.608L14 4.552z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M9 3.586L8.414 3H9v.586zM12 6h-2v7h2V6zm-6 7V7.414L5.414 8H4v5h2zm1 0h2V4.414l-1 1V6h-.586L7 6.414V13z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M8 5.414V6h-.586L8 5.414zM13 5v8s-.035 1-1.035 1h-8S3 14 3 13V8h1v5h2V7.414l1-1V13h2V4.414L9.414 4 9 3.586V3h-.586l-1-1h2.227L13 5zm-1 1h-2v7h2V6z" id="iconBg"/><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z" id="colorAction"/></svg>
|
||||
|
After Width: | Height: | Size: 940 B |
1
src/sql/workbench/parts/grid/media/saveExcel.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}.icon-vs-action-blue{fill:#00539c}</style><path class="icon-canvas-transparent" d="M16 0v16H0V0h16z" id="canvas"/><path class="icon-vs-out" d="M16 7v8H3.964C3.012 15 2 14.299 2 13V8H.586l2-2H0V2h2.586l-2-2h4.828l1 1h3.608L14 4.552V7h2z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M9 6h3v1H7v6H4V8h1.414L9 4.414V6zm0-3h-.586L9 3.586V3zm0 10h1v-1H9v1zm0-2h1v-1H9v1zm2 2h1v-1h-1v1zm2-3v1h1v-1h-1zm-2 1h1v-1h-1v1zm2 2h1v-1h-1v1z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M8.414 3l-1-1h2.227L13 5v2h-1V6H9V4.414L9.414 4 9 3.586V3h-.586zM4 8H3v5c0 1 .964 1 .964 1H7v-1H4V8zm11 0v6H8V8h7zm-5 4H9v1h1v-1zm0-2H9v1h1v-1zm2 2h-1v1h1v-1zm0-2h-1v1h1v-1zm2 2h-1v1h1v-1zm0-2h-1v1h1v-1z" id="iconBg"/><g id="colorAction"><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1022 B |
1
src/sql/workbench/parts/grid/media/saveExcel_inverse.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}.icon-vs-action-blue{fill:#75beff}</style><path class="icon-canvas-transparent" d="M16 0v16H0V0h16z" id="canvas"/><path class="icon-vs-out" d="M16 7v8H3.964C3.012 15 2 14.299 2 13V8H.586l2-2H0V2h2.586l-2-2h4.828l1 1h3.608L14 4.552V7h2z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M9 6h3v1H7v6H4V8h1.414L9 4.414V6zm0-3h-.586L9 3.586V3zm0 10h1v-1H9v1zm0-2h1v-1H9v1zm2 2h1v-1h-1v1zm2-3v1h1v-1h-1zm-2 1h1v-1h-1v1zm2 2h1v-1h-1v1z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M8.414 3l-1-1h2.227L13 5v2h-1V6H9V4.414L9.414 4 9 3.586V3h-.586zM4 8H3v5c0 1 .964 1 .964 1H7v-1H4V8zm11 0v6H8V8h7zm-5 4H9v1h1v-1zm0-2H9v1h1v-1zm2 2h-1v1h1v-1zm0-2h-1v1h1v-1zm2 2h-1v1h1v-1zm0-2h-1v1h1v-1z" id="iconBg"/><g id="colorAction"><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1022 B |
1
src/sql/workbench/parts/grid/media/saveJson.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-action-blue{fill:#00539c}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 8.38v2.258s-.992-.001-.997-.003c-.012.052-.003.14-.003.281v1.579c0 1.271-.37 2.185-1.054 2.746-.638.522-1.576.759-2.822.759H10v-3.247s1.014-.001 1.037-.003c.004-.046-.037-.117-.037-.214V11.08c0-.943.222-1.606.539-2.072C11.223 8.527 11 7.843 11 6.869V5.468c0-.087.102-.286.094-.325-.02-.002.063-.143.03-.143H10V2h1.124c1.251 0 2.193.265 2.832.81C14.633 3.387 15 4.3 15 5.522V7h.919L16 8.38zM9.414 4l-4-4H.586l2 2H0v4h2v.586L1.586 7H1v.586L.586 8H1v1.638L1.329 11H2v1.536c0 1.247.495 2.149 1.19 2.711.641.517 1.697.753 2.937.753H7v-3.247s-1.011-.001-1.033-.003c-.008-.053.033-.127.033-.228v-1.401c0-.962-.224-1.637-.542-2.111.256-.378.444-.89.511-1.564L9.414 4z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M15 8.38v1.258c-.697 0-1.046.426-1.046 1.278v1.579c0 .961-.223 1.625-.666 1.989-.445.364-1.166.547-2.164.547v-1.278c.383 0 .661-.092.834-.277s.26-.498.26-.94V11.08c0-1.089.349-1.771 1.046-2.044v-.027c-.697-.287-1.046-1-1.046-2.14V5.468c0-.793-.364-1.189-1.094-1.189V3c.993 0 1.714.19 2.16.571s.67 1.031.67 1.952v1.565c0 .861.349 1.292 1.046 1.292zm-9.967 4.142v-1.401c0-1.117-.351-1.816-1.053-2.099v-.027c.429-.175.71-.519.877-.995H3.049c-.173.247-.436.38-.805.38v1.258c.692 0 1.039.419 1.039 1.258v1.641c0 .934.226 1.584.677 1.948s1.174.547 2.167.547v-1.278c-.388 0-.666-.093-.838-.28-.17-.188-.256-.505-.256-.952z" id="iconBg"/><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z" id="colorAction"/></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
1
src/sql/workbench/parts/grid/media/saveJson_inverse.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-action-blue{fill:#75beff}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 8.38v2.258s-.992-.001-.997-.003c-.012.052-.003.14-.003.281v1.579c0 1.271-.37 2.185-1.054 2.746-.638.522-1.576.759-2.822.759H10v-3.247s1.014-.001 1.037-.003c.004-.046-.037-.117-.037-.214V11.08c0-.943.222-1.606.539-2.072C11.223 8.527 11 7.843 11 6.869V5.468c0-.087.102-.286.094-.325-.02-.002.063-.143.03-.143H10V2h1.124c1.251 0 2.193.265 2.832.81C14.633 3.387 15 4.3 15 5.522V7h.919L16 8.38zM9.414 4l-4-4H.586l2 2H0v4h2v.586L1.586 7H1v.586L.586 8H1v1.638L1.329 11H2v1.536c0 1.247.495 2.149 1.19 2.711.641.517 1.697.753 2.937.753H7v-3.247s-1.011-.001-1.033-.003c-.008-.053.033-.127.033-.228v-1.401c0-.962-.224-1.637-.542-2.111.256-.378.444-.89.511-1.564L9.414 4z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M15 8.38v1.258c-.697 0-1.046.426-1.046 1.278v1.579c0 .961-.223 1.625-.666 1.989-.445.364-1.166.547-2.164.547v-1.278c.383 0 .661-.092.834-.277s.26-.498.26-.94V11.08c0-1.089.349-1.771 1.046-2.044v-.027c-.697-.287-1.046-1-1.046-2.14V5.468c0-.793-.364-1.189-1.094-1.189V3c.993 0 1.714.19 2.16.571s.67 1.031.67 1.952v1.565c0 .861.349 1.292 1.046 1.292zm-9.967 4.142v-1.401c0-1.117-.351-1.816-1.053-2.099v-.027c.429-.175.71-.519.877-.995H3.049c-.173.247-.436.38-.805.38v1.258c.692 0 1.039.419 1.039 1.258v1.641c0 .934.226 1.584.677 1.948s1.174.547 2.167.547v-1.278c-.388 0-.666-.093-.838-.28-.17-.188-.256-.505-.256-.952z" id="iconBg"/><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z" id="colorAction"/></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
9
src/sql/workbench/parts/grid/media/saveXml.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="saveXml" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<polygon id="canvas" fill="#F6F6F6" fill-rule="nonzero" opacity="0" points="16 16 0 16 0 0 16 0"></polygon>
|
||||
<polygon id="<->" fill="#424242" points="14.3701172 10.7222222 9.41870773 15 9.41870773 12.3454861 12.9578306 9.5 9.41870773 6.6640625 9.41870773 4 14.3701172 8.296875"></polygon>
|
||||
<polygon id="colorAction" fill="#00539C" fill-rule="nonzero" points="8 4 5 7 3 7 5 5 1 5 1 3 5 3 3 1 5 1"></polygon>
|
||||
<path d="M2.32152009,8 L5.28619938,8 L3.4164898,9.5 L6.95140946,12.3454861 L6.95140946,15 L2,10.7222222 L2,8.27777778 L2.32152009,8 Z" id="Combined-Shape" fill="#424242"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 877 B |
9
src/sql/workbench/parts/grid/media/saveXml_inverse.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="saveXmlInverse" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<polygon id="canvas" fill="#F6F6F6" fill-rule="nonzero" opacity="0" points="16 16 0 16 0 0 16 0"></polygon>
|
||||
<polygon id="<->" fill="#C5C5C5" points="14.3701172 10.7222222 9.41870773 15 9.41870773 12.3454861 12.9578306 9.5 9.41870773 6.6640625 9.41870773 4 14.3701172 8.296875"></polygon>
|
||||
<polygon id="colorAction" fill="#75BEFF" fill-rule="nonzero" points="8 4 5 7 3 7 5 5 1 5 1 3 5 3 3 1 5 1"></polygon>
|
||||
<path d="M2.32152009,8 L5.28619938,8 L3.4164898,9.5 L6.95140946,12.3454861 L6.95140946,15 L2,10.7222222 L2,8.27777778 L2.32152009,8 Z" id="Combined-Shape" fill="#C5C5C5"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 884 B |
109
src/sql/workbench/parts/grid/media/styles.css
Normal file
@@ -0,0 +1,109 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* slick grid */
|
||||
|
||||
.boxRow.content.horzBox.slickgrid {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.boxRow.content.horzBox.slickgrid:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.boxRow.content.padded {
|
||||
padding: 15px
|
||||
}
|
||||
|
||||
/* icon */
|
||||
.gridIcon {
|
||||
padding: 8px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0px;
|
||||
}
|
||||
|
||||
.gridIconContainer {
|
||||
margin: 5px 5px;
|
||||
}
|
||||
|
||||
/* messages */
|
||||
.resultsMessageTable {
|
||||
font-weight: inherit;
|
||||
font-size: inherit;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
user-select: initial;
|
||||
-webkit-user-select: initial;
|
||||
}
|
||||
|
||||
.wideResultsMessage {
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
#messageTable td {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
#messageTable tbody tr:last-child > td {
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
#messageTable {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.resultsMessageValue {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
/* headers */
|
||||
.resultsMessageHeader {
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
height: 22px;
|
||||
padding-left: 20px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.resultsMessageHeader > span {
|
||||
float: left;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.resizableHandle {
|
||||
font-size: 0.1px;
|
||||
display: block;
|
||||
float: right;
|
||||
position: absolute;
|
||||
cursor: row-resize;
|
||||
top: 0;
|
||||
height: 4px;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#resizeHandle {
|
||||
font-size: 0.1px;
|
||||
position: absolute;
|
||||
cursor: row-resize;
|
||||
height: 4px;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
background-color: var(--color-resize-handle);
|
||||
}
|
||||
|
||||
.header > span.shortCut {
|
||||
float: right;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.queryLink {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
1
src/sql/workbench/parts/grid/media/uncollapsedArrow.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16'><path fill='#646465' d='M11 10.07H5.344L11 4.414v5.656z'/></svg>
|
||||
|
After Width: | Height: | Size: 127 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16'><path fill='#e8e8e8' d='M11 10.07H5.344L11 4.414v5.656z'/></svg>
|
||||
|
After Width: | Height: | Size: 127 B |
1
src/sql/workbench/parts/grid/media/viewChart.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#c91f1f;}</style></defs><title>view_as_chart_16x16</title><rect x="0.5" y="14.22" width="14.99" height="1"/><rect x="13.52" y="0.2" width="1" height="13.03"/><path d="M2,12.34a.71.71,0,0,0,.44-.15v1h-1V12.1A.74.74,0,0,0,2,12.34Z"/><path d="M5.52,6.22v.29a2.58,2.58,0,0,1-.14-.29Z"/><polygon points="4.52 10.16 5.52 9.16 5.52 13.22 4.52 13.22 4.52 10.16"/><path d="M7.54,8.74a3.68,3.68,0,0,0,1,.18v4.3h-1Z"/><path d="M10.93,8.36a3.07,3.07,0,0,0,.59-.43v5.31h-1V8.57A3.18,3.18,0,0,0,10.93,8.36Z"/><path class="cls-1" d="M5.38,6.22a2.58,2.58,0,0,0,.14.29V6.22Z"/><path class="cls-1" d="M5.38,6.22a2.58,2.58,0,0,0,.14.29V6.22Z"/><path d="M12.42,3.76a3.71,3.71,0,0,0-.37-.89A3.85,3.85,0,0,0,10.7,1.52a3.35,3.35,0,0,0-.89-.37,3.78,3.78,0,0,0-2.88.37,4.3,4.3,0,0,0-.76.59,3.92,3.92,0,0,0-.58.76,3.73,3.73,0,0,0-.38.89,3.63,3.63,0,0,0-.13,1A3.61,3.61,0,0,0,5.3,6a1.19,1.19,0,0,0,.08.2h.14v.29a4.32,4.32,0,0,0,.42.63,4,4,0,0,1-.3.3L5.1,8l-.58.56-.11.11-.76.75-.73.74-.47.48-.16.18q-.29.29-.45.48a.67.67,0,0,0-.16.24.41.41,0,0,0,.09.26.31.31,0,0,0,.25.11.33.33,0,0,0,.24-.1l.2-.2,2.06-2,1-1,.91-.91a3.87,3.87,0,0,0,1.11.64,3.37,3.37,0,0,0,1,.21h.28a3.64,3.64,0,0,0,1-.13,3.41,3.41,0,0,0,.71-.29L10.7,8a3.61,3.61,0,0,0,.76-.59l.06-.07a3.46,3.46,0,0,0,.53-.68,4.08,4.08,0,0,0,.37-.89,3.61,3.61,0,0,0,0-2Zm-.79,2.18a1.63,1.63,0,0,1-.11.23,3.13,3.13,0,0,1-.54.74,3.32,3.32,0,0,1-.46.38,3.15,3.15,0,0,1-.51.28,3,3,0,0,1-1.19.24H8.54a3.12,3.12,0,0,1-.91-.23l-.09,0a3,3,0,0,1-.88-.62,3,3,0,0,1-.9-2.16A3,3,0,0,1,6,3.56a3.09,3.09,0,0,1,.66-1,3,3,0,0,1,1-.65,2.82,2.82,0,0,1,1.19-.25A2.86,2.86,0,0,1,10,1.94a4.12,4.12,0,0,1,.51.27,3.31,3.31,0,0,1,.46.38,3.6,3.6,0,0,1,.54.74,1.63,1.63,0,0,1,.11.23,2.82,2.82,0,0,1,.25,1.19A2.82,2.82,0,0,1,11.63,5.94Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
1
src/sql/workbench/parts/grid/media/viewChart_inverse.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>view_as_chart_inverse_16x16</title><rect class="cls-1" x="0.5" y="14.43" width="14.99" height="1"/><rect class="cls-1" x="13.52" y="0.41" width="1" height="13.03"/><path class="cls-1" d="M2,12.55a.71.71,0,0,0,.44-.15v1h-1V12.31A.74.74,0,0,0,2,12.55Z"/><path class="cls-1" d="M5.52,6.43v.29a2.58,2.58,0,0,1-.14-.29Z"/><polygon class="cls-1" points="4.52 10.37 5.52 9.37 5.52 13.43 4.52 13.43 4.52 10.37"/><path class="cls-1" d="M7.54,8.95a3.68,3.68,0,0,0,1,.18v4.3h-1Z"/><path class="cls-1" d="M10.93,8.57a3.07,3.07,0,0,0,.59-.43v5.31h-1V8.78A3.18,3.18,0,0,0,10.93,8.57Z"/><path class="cls-1" d="M5.38,6.43a2.58,2.58,0,0,0,.14.29V6.43Z"/><path class="cls-1" d="M5.38,6.43a2.58,2.58,0,0,0,.14.29V6.43Z"/><path class="cls-1" d="M12.42,4a3.71,3.71,0,0,0-.37-.89A3.85,3.85,0,0,0,10.7,1.73a3.35,3.35,0,0,0-.89-.37,3.78,3.78,0,0,0-2.88.37,4.3,4.3,0,0,0-.76.59,3.92,3.92,0,0,0-.58.76A3.73,3.73,0,0,0,5.21,4a3.63,3.63,0,0,0-.13,1A3.61,3.61,0,0,0,5.3,6.23a1.19,1.19,0,0,0,.08.2h.14v.29a4.32,4.32,0,0,0,.42.63,4,4,0,0,1-.3.3l-.54.54-.58.56-.11.11-.76.75-.73.74-.47.48L2.29,11q-.29.29-.45.48a.67.67,0,0,0-.16.24.41.41,0,0,0,.09.26A.31.31,0,0,0,2,12.1a.33.33,0,0,0,.24-.1l.2-.2,2.06-2,1-1,.91-.91a3.87,3.87,0,0,0,1.11.64,3.37,3.37,0,0,0,1,.21h.28a3.64,3.64,0,0,0,1-.13,3.41,3.41,0,0,0,.71-.29l.18-.09a3.61,3.61,0,0,0,.76-.59l.06-.07a3.46,3.46,0,0,0,.53-.68A4.08,4.08,0,0,0,12.42,6a3.61,3.61,0,0,0,0-2Zm-.79,2.18a1.63,1.63,0,0,1-.11.23,3.13,3.13,0,0,1-.54.74,3.32,3.32,0,0,1-.46.38,3.15,3.15,0,0,1-.51.28A3,3,0,0,1,8.82,8H8.54a3.12,3.12,0,0,1-.91-.23l-.09,0a3,3,0,0,1-.88-.62A3,3,0,0,1,5.76,5,3,3,0,0,1,6,3.77a3.09,3.09,0,0,1,.66-1,3,3,0,0,1,1-.65A2.82,2.82,0,0,1,8.82,1.9,2.86,2.86,0,0,1,10,2.15a4.12,4.12,0,0,1,.51.27A3.31,3.31,0,0,1,11,2.8a3.6,3.6,0,0,1,.54.74,1.63,1.63,0,0,1,.11.23A2.82,2.82,0,0,1,11.88,5,2.82,2.82,0,0,1,11.63,6.15Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
172
src/sql/workbench/parts/grid/services/dataService.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { Observer } from 'rxjs/Observer';
|
||||
|
||||
import { EditUpdateCellResult, EditSubsetResult, EditCreateRowResult } from 'azdata';
|
||||
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
|
||||
import { ResultSerializer } from 'sql/platform/node/resultSerializer';
|
||||
import { ISaveRequest } from 'sql/workbench/parts/grid/common/interfaces';
|
||||
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
/**
|
||||
* DataService handles the interactions between QueryModel and app.component. Thus, it handles
|
||||
* query running and grid interaction communication for a single URI.
|
||||
*/
|
||||
export class DataService {
|
||||
|
||||
public queryEventObserver: Subject<any>;
|
||||
public gridContentObserver: Subject<any>;
|
||||
private editQueue: Promise<any>;
|
||||
|
||||
constructor(
|
||||
private _uri: string,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IQueryModelService private _queryModel: IQueryModelService
|
||||
) {
|
||||
this.queryEventObserver = new Subject();
|
||||
this.gridContentObserver = new Subject();
|
||||
this.editQueue = Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specified number of rows starting at a specified row. Should only
|
||||
* be used for edit sessions.
|
||||
* @param rowStart The row to start retrieving from (inclusive)
|
||||
* @param numberOfRows The maximum number of rows to return
|
||||
*/
|
||||
getEditRows(rowStart: number, numberOfRows: number): Observable<EditSubsetResult> {
|
||||
const self = this;
|
||||
return Observable.create(function (observer: Observer<EditSubsetResult>) {
|
||||
self._queryModel.getEditRows(self._uri, rowStart, numberOfRows).then(results => {
|
||||
observer.next(results);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateCell(rowId: number, columnId: number, newValue: string): Thenable<EditUpdateCellResult> {
|
||||
const self = this;
|
||||
self.editQueue = self.editQueue.then(() => {
|
||||
return self._queryModel.updateCell(self._uri, rowId, columnId, newValue).then(result => {
|
||||
return result;
|
||||
}, error => {
|
||||
// Start our editQueue over due to the rejected promise
|
||||
self.editQueue = Promise.resolve();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
return self.editQueue;
|
||||
}
|
||||
|
||||
commitEdit(): Thenable<void> {
|
||||
const self = this;
|
||||
self.editQueue = self.editQueue.then(() => {
|
||||
return self._queryModel.commitEdit(self._uri).then(result => {
|
||||
return result;
|
||||
}, error => {
|
||||
// Start our editQueue over due to the rejected promise
|
||||
self.editQueue = Promise.resolve();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
return self.editQueue;
|
||||
}
|
||||
|
||||
createRow(): Thenable<EditCreateRowResult> {
|
||||
const self = this;
|
||||
self.editQueue = self.editQueue.then(() => {
|
||||
return self._queryModel.createRow(self._uri).then(result => {
|
||||
return result;
|
||||
}, error => {
|
||||
// Start our editQueue over due to the rejected promise
|
||||
self.editQueue = Promise.resolve();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
return self.editQueue;
|
||||
}
|
||||
|
||||
deleteRow(rowId: number): Thenable<void> {
|
||||
const self = this;
|
||||
self.editQueue = self.editQueue.then(() => {
|
||||
return self._queryModel.deleteRow(self._uri, rowId).then(result => {
|
||||
return result;
|
||||
}, error => {
|
||||
// Start our editQueue over due to the rejected promise
|
||||
self.editQueue = Promise.resolve();
|
||||
self._queryModel.showCommitError(error.message);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
return self.editQueue;
|
||||
}
|
||||
|
||||
revertCell(rowId: number, columnId: number): Thenable<void> {
|
||||
const self = this;
|
||||
self.editQueue = self.editQueue.then(() => {
|
||||
return self._queryModel.revertCell(self._uri, rowId, columnId).then(result => {
|
||||
return result;
|
||||
}, error => {
|
||||
// Start our editQueue over due to the rejected promise
|
||||
self.editQueue = Promise.resolve();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
return self.editQueue;
|
||||
}
|
||||
|
||||
revertRow(rowId: number): Thenable<void> {
|
||||
const self = this;
|
||||
self.editQueue = self.editQueue.then(() => {
|
||||
return self._queryModel.revertRow(self._uri, rowId).then(result => {
|
||||
return result;
|
||||
}, error => {
|
||||
// Start our editQueue over due to the rejected promise
|
||||
self.editQueue = Promise.resolve();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
return self.editQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* send request to save the selected result set as csv
|
||||
* @param uri of the calling document
|
||||
* @param batchId The batch id of the batch with the result to save
|
||||
* @param resultId The id of the result to save as csv
|
||||
*/
|
||||
sendSaveRequest(saveRequest: ISaveRequest): void {
|
||||
let serializer = this._instantiationService.createInstance(ResultSerializer);
|
||||
serializer.saveResults(this._uri, saveRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* send request to open content in new editor
|
||||
* @param content The content to be opened
|
||||
* @param columnName The column name of the content
|
||||
*/
|
||||
openLink(content: string, columnName: string, linkType: string): void {
|
||||
let serializer = this._instantiationService.createInstance(ResultSerializer);
|
||||
serializer.openLink(content, columnName, linkType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a copy request
|
||||
* @param selection The selection range to copy
|
||||
* @param batchId The batch id of the result to copy from
|
||||
* @param resultId The result id of the result to copy from
|
||||
* @param includeHeaders [Optional]: Should column headers be included in the copy selection
|
||||
*/
|
||||
copyResults(selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): void {
|
||||
this._queryModel.copyResults(this._uri, selection, batchId, resultId, includeHeaders);
|
||||
}
|
||||
|
||||
onAngularLoaded(): void {
|
||||
this._queryModel.onAngularLoaded(this._uri);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<!--
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
|
||||
<div class="fullsize vertBox editdata-component">
|
||||
<div id="results" *ngIf="renderedDataSets.length > 0" class="results vertBox scrollable"
|
||||
(onScroll)="onScroll($event)" [class.hidden]="!resultActive">
|
||||
<div class="boxRow content horzBox slickgrid editable" *ngFor="let dataSet of renderedDataSets; let i = index"
|
||||
[style.max-height]="dataSet.maxHeight" [style.min-height]="dataSet.minHeight">
|
||||
<slick-grid #slickgrid id="slickgrid_{{i}}" [columnDefinitions]="dataSet.columnDefinitions"
|
||||
[ngClass]="i === activeGrid ? 'active' : ''"
|
||||
[dataRows]="dataSet.dataRows"
|
||||
enableAsyncPostRender="true"
|
||||
showDataTypeIcon="false"
|
||||
showHeader="true"
|
||||
[resized]="dataSet.resized"
|
||||
[plugins]="plugins[i]"
|
||||
(onActiveCellChanged)="onActiveCellChanged($event)"
|
||||
(onCellChange)="onCellEditEnd($event)"
|
||||
(onContextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)"
|
||||
(onRendered)="onGridRendered($event)"
|
||||
[isCellEditValid]="onIsCellEditValid"
|
||||
[overrideCellFn]="overrideCellFn"
|
||||
[onBeforeAppendCell]="onBeforeAppendCell"
|
||||
[enableEditing]="true"
|
||||
class="boxCol content vertBox slickgrid">
|
||||
</slick-grid>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,729 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!sql/workbench/parts/grid/media/flexbox';
|
||||
import 'vs/css!sql/workbench/parts/grid/media/styles';
|
||||
import 'vs/css!./media/editData';
|
||||
|
||||
import { ElementRef, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject, forwardRef, EventEmitter } from '@angular/core';
|
||||
import { VirtualizedCollection } from 'angular2-slickgrid';
|
||||
|
||||
import { IGridDataSet } from 'sql/workbench/parts/grid/common/interfaces';
|
||||
import * as Services from 'sql/base/browser/ui/table/formatters';
|
||||
import { IEditDataComponentParams } from 'sql/platform/bootstrap/node/bootstrapParams';
|
||||
import { GridParentComponent } from 'sql/workbench/parts/grid/views/gridParentComponent';
|
||||
import { EditDataGridActionProvider } from 'sql/workbench/parts/grid/views/editData/editDataGridActions';
|
||||
import { error } from 'sql/base/common/log';
|
||||
import { clone } from 'sql/base/common/objects';
|
||||
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
|
||||
import { IBootstrapParams } from 'sql/platform/bootstrap/node/bootstrapService';
|
||||
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
|
||||
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
|
||||
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
|
||||
import { escape } from 'sql/base/common/strings';
|
||||
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { EditUpdateCellResult } from 'azdata';
|
||||
export const EDITDATA_SELECTOR: string = 'editdata-component';
|
||||
|
||||
@Component({
|
||||
selector: EDITDATA_SELECTOR,
|
||||
host: { '(window:keydown)': 'keyEvent($event)', '(window:gridnav)': 'keyEvent($event)' },
|
||||
templateUrl: decodeURI(require.toUrl('sql/workbench/parts/grid/views/editData/editData.component.html'))
|
||||
})
|
||||
export class EditDataComponent extends GridParentComponent implements OnInit, OnDestroy {
|
||||
// The time(in milliseconds) we wait before refreshing the grid.
|
||||
// We use clearTimeout and setTimeout pair to avoid unnecessary refreshes.
|
||||
private refreshGridTimeoutInMs = 200;
|
||||
|
||||
// The timeout handle for the refresh grid task
|
||||
private refreshGridTimeoutHandle: NodeJS.Timer;
|
||||
|
||||
// Optimized for the edit top 200 rows scenario, only need to retrieve the data once
|
||||
// to make the scroll experience smoother
|
||||
private windowSize = 200;
|
||||
|
||||
// FIELDS
|
||||
// All datasets
|
||||
private dataSet: IGridDataSet;
|
||||
private firstRender = true;
|
||||
private totalElapsedTimeSpan: number;
|
||||
private complete = false;
|
||||
// Current selected cell state
|
||||
private currentCell: { row: number, column: number, isEditable: boolean, isDirty: boolean };
|
||||
private currentEditCellValue: string;
|
||||
private newRowVisible: boolean;
|
||||
private removingNewRow: boolean;
|
||||
private rowIdMappings: { [gridRowId: number]: number } = {};
|
||||
private dirtyCells: number[] = [];
|
||||
protected plugins = new Array<Array<Slick.Plugin<any>>>();
|
||||
|
||||
// Edit Data functions
|
||||
public onActiveCellChanged: (event: Slick.OnActiveCellChangedEventArgs<any>) => void;
|
||||
public onCellEditEnd: (event: Slick.OnCellChangeEventArgs<any>) => void;
|
||||
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<{}[]>;
|
||||
public onBeforeAppendCell: (row: number, column: number) => string;
|
||||
public onGridRendered: (event: Slick.OnRenderedEventArgs<any>) => void;
|
||||
|
||||
private savedViewState: {
|
||||
gridSelections: Slick.Range[];
|
||||
scrollTop;
|
||||
scrollLeft;
|
||||
};
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) cd: ChangeDetectorRef,
|
||||
@Inject(IBootstrapParams) params: IEditDataComponentParams,
|
||||
@Inject(IInstantiationService) private instantiationService: IInstantiationService,
|
||||
@Inject(INotificationService) private notificationService: INotificationService,
|
||||
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
|
||||
@Inject(IKeybindingService) keybindingService: IKeybindingService,
|
||||
@Inject(IContextKeyService) contextKeyService: IContextKeyService,
|
||||
@Inject(IConfigurationService) configurationService: IConfigurationService,
|
||||
@Inject(IClipboardService) clipboardService: IClipboardService,
|
||||
@Inject(IQueryEditorService) queryEditorService: IQueryEditorService
|
||||
) {
|
||||
super(el, cd, contextMenuService, keybindingService, contextKeyService, configurationService, clipboardService, queryEditorService);
|
||||
this._el.nativeElement.className = 'slickgridContainer';
|
||||
this.dataService = params.dataService;
|
||||
this.actionProvider = this.instantiationService.createInstance(EditDataGridActionProvider, this.dataService, this.onGridSelectAll(), this.onDeleteRow(), this.onRevertRow());
|
||||
params.onRestoreViewState(() => this.restoreViewState());
|
||||
params.onSaveViewState(() => this.saveViewState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by Angular when the object is initialized
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
const self = this;
|
||||
this.baseInit();
|
||||
|
||||
// Add the subscription to the list of things to be disposed on destroy, or else on a new component init
|
||||
// may get the "destroyed" object still getting called back.
|
||||
this.subscribeWithDispose(this.dataService.queryEventObserver, (event) => {
|
||||
switch (event.type) {
|
||||
case 'start':
|
||||
self.handleStart(self, event);
|
||||
break;
|
||||
case 'complete':
|
||||
self.handleComplete(self, event);
|
||||
break;
|
||||
case 'message':
|
||||
self.handleMessage(self, event);
|
||||
break;
|
||||
case 'resultSet':
|
||||
self.handleResultSet(self, event);
|
||||
break;
|
||||
case 'editSessionReady':
|
||||
self.handleEditSessionReady(self, event);
|
||||
break;
|
||||
default:
|
||||
error('Unexpected query event type "' + event.type + '" sent');
|
||||
break;
|
||||
}
|
||||
self._cd.detectChanges();
|
||||
});
|
||||
|
||||
this.dataService.onAngularLoaded();
|
||||
}
|
||||
|
||||
protected initShortcuts(shortcuts: { [name: string]: Function }): void {
|
||||
// TODO add any Edit Data-specific shortcuts here
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this.baseDestroy();
|
||||
}
|
||||
|
||||
handleStart(self: EditDataComponent, event: any): void {
|
||||
self.dataSet = undefined;
|
||||
self.placeHolderDataSets = [];
|
||||
self.renderedDataSets = self.placeHolderDataSets;
|
||||
this._cd.detectChanges();
|
||||
self.totalElapsedTimeSpan = undefined;
|
||||
self.complete = false;
|
||||
|
||||
// Hooking up edit functions
|
||||
this.onIsCellEditValid = (row, column, value): boolean => {
|
||||
// TODO can only run sync code
|
||||
return true;
|
||||
};
|
||||
|
||||
this.onActiveCellChanged = this.onCellSelect;
|
||||
|
||||
this.onCellEditEnd = (event: Slick.OnCellChangeEventArgs<any>): void => {
|
||||
if (self.currentEditCellValue !== event.item[event.cell]) {
|
||||
self.currentCell.isDirty = true;
|
||||
}
|
||||
// Store the value that was set
|
||||
self.currentEditCellValue = event.item[event.cell];
|
||||
};
|
||||
|
||||
this.overrideCellFn = (rowNumber, columnId, value?, data?): string => {
|
||||
let returnVal = '';
|
||||
if (Services.DBCellValue.isDBCellValue(value)) {
|
||||
returnVal = value.displayValue;
|
||||
} else if (typeof value === 'string') {
|
||||
returnVal = value;
|
||||
}
|
||||
return returnVal;
|
||||
};
|
||||
|
||||
// This is the event slickgrid will raise in order to get the additional cell CSS classes for the cell
|
||||
// Due to performance advantage we are using this event instead of the onViewportChanged event.
|
||||
this.onBeforeAppendCell = (row: number, column: number): string => {
|
||||
let cellClass = undefined;
|
||||
if (this.isRowDirty(row) && column === 0) {
|
||||
cellClass = ' dirtyRowHeader ';
|
||||
} else if (this.isCellDirty(row, column)) {
|
||||
cellClass = ' dirtyCell ';
|
||||
}
|
||||
|
||||
return cellClass;
|
||||
};
|
||||
|
||||
this.onGridRendered = (args: Slick.OnRenderedEventArgs<any>): void => {
|
||||
// After rendering move the focus back to the previous active cell
|
||||
if (this.currentCell.column !== undefined && this.currentCell.row !== undefined
|
||||
&& this.isCellOnScreen(this.currentCell.row, this.currentCell.column)) {
|
||||
this.focusCell(this.currentCell.row, this.currentCell.column, false);
|
||||
}
|
||||
};
|
||||
|
||||
// Setup a function for generating a promise to lookup result subsets
|
||||
this.loadDataFunction = (offset: number, count: number): Promise<{}[]> => {
|
||||
return new Promise<{}[]>((resolve, reject) => {
|
||||
self.dataService.getEditRows(offset, count).subscribe(result => {
|
||||
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] = {
|
||||
displayValue: r.cells[i - 1].displayValue,
|
||||
ariaLabel: escape(r.cells[i - 1].displayValue),
|
||||
isNull: r.cells[i - 1].isNull
|
||||
};
|
||||
}
|
||||
return dataWithSchema;
|
||||
});
|
||||
|
||||
// should add null row?
|
||||
if (offset + count > this.dataSet.totalRows - 1) {
|
||||
gridData.push(this.dataSet.columnDefinitions.reduce((p, c) => {
|
||||
p[c.field] = 'NULL';
|
||||
return p;
|
||||
}, {}));
|
||||
}
|
||||
|
||||
resolve(gridData);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
onDeleteRow(): (index: number) => void {
|
||||
const self = this;
|
||||
return (index: number): void => {
|
||||
// If the user is deleting a new row that hasn't been committed yet then use the revert code
|
||||
if (self.newRowVisible && index === self.dataSet.dataRows.getLength() - 2) {
|
||||
self.revertCurrentRow();
|
||||
}
|
||||
else if (self.isNullRow(index)) {
|
||||
// Don't try to delete NULL (new) row since it doesn't actually exist and will throw an error
|
||||
// TODO #478 : We should really just stop the context menu from showing up for this row, but that's a bit more involved
|
||||
// so until then at least make it not display an error
|
||||
return;
|
||||
}
|
||||
else {
|
||||
self.dataService.deleteRow(index)
|
||||
.then(() => self.dataService.commitEdit())
|
||||
.then(() => self.removeRow(index));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onRevertRow(): () => void {
|
||||
const self = this;
|
||||
return (): void => {
|
||||
self.revertCurrentRow();
|
||||
};
|
||||
}
|
||||
|
||||
onCellSelect(event: Slick.OnActiveCellChangedEventArgs<any>): void {
|
||||
let self = this;
|
||||
let row = event.row;
|
||||
let column = event.cell;
|
||||
|
||||
// Skip processing if the newly selected cell is undefined or we don't have column
|
||||
// definition for the column (ie, the selection was reset)
|
||||
if (row === undefined || column === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip processing if the cell hasn't moved (eg, we reset focus to the previous cell after a failed update)
|
||||
if (this.currentCell.row === row && this.currentCell.column === column && this.currentCell.isDirty === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cellSelectTasks: Promise<void> = this.submitCurrentCellChange(
|
||||
(result: EditUpdateCellResult) => {
|
||||
// Cell update was successful, update the flags
|
||||
self.setCellDirtyState(self.currentCell.row, self.currentCell.column, result.cell.isDirty);
|
||||
self.setRowDirtyState(self.currentCell.row, result.isRowDirty);
|
||||
return Promise.resolve();
|
||||
},
|
||||
(error) => {
|
||||
// Cell update failed, jump back to the last cell we were on
|
||||
self.focusCell(self.currentCell.row, self.currentCell.column, true);
|
||||
return Promise.reject(null);
|
||||
});
|
||||
|
||||
if (this.currentCell.row !== row) {
|
||||
// We're changing row, commit the changes
|
||||
cellSelectTasks = cellSelectTasks.then(() => {
|
||||
return self.dataService.commitEdit().then(result => {
|
||||
// Committing was successful, clean the grid
|
||||
self.setGridClean();
|
||||
self.rowIdMappings = {};
|
||||
self.newRowVisible = false;
|
||||
return Promise.resolve();
|
||||
}, error => {
|
||||
// Committing failed, jump back to the last selected cell
|
||||
self.focusCell(self.currentCell.row, self.currentCell.column);
|
||||
return Promise.reject(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// At the end of a successful cell select, update the currently selected cell
|
||||
cellSelectTasks = cellSelectTasks.then(() => {
|
||||
self.setCurrentCell(row, column);
|
||||
});
|
||||
|
||||
// Cap off any failed promises, since they'll be handled
|
||||
cellSelectTasks.catch(() => { });
|
||||
}
|
||||
|
||||
handleComplete(self: EditDataComponent, event: any): void {
|
||||
self.totalElapsedTimeSpan = event.data;
|
||||
self.complete = true;
|
||||
}
|
||||
|
||||
handleEditSessionReady(self, event): void {
|
||||
// TODO: update when edit session is ready
|
||||
}
|
||||
|
||||
handleMessage(self: EditDataComponent, event: any): void {
|
||||
if (event.data && event.data.isError) {
|
||||
self.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: event.data.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleResultSet(self: EditDataComponent, event: any): void {
|
||||
// Clone the data before altering it to avoid impacting other subscribers
|
||||
let resultSet = Object.assign({}, event.data);
|
||||
if (!resultSet.complete) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add an extra 'new row'
|
||||
resultSet.rowCount++;
|
||||
// Precalculate the max height and min height
|
||||
let maxHeight = this.getMaxHeight(resultSet.rowCount);
|
||||
let minHeight = this.getMinHeight(resultSet.rowCount);
|
||||
|
||||
let rowNumberColumn = new RowNumberColumn({ numberOfRows: resultSet.rowCount });
|
||||
|
||||
// Store the result set from the event
|
||||
let dataSet: IGridDataSet = {
|
||||
resized: undefined,
|
||||
batchId: resultSet.batchId,
|
||||
resultId: resultSet.id,
|
||||
totalRows: resultSet.rowCount,
|
||||
maxHeight: maxHeight,
|
||||
minHeight: minHeight,
|
||||
dataRows: new VirtualizedCollection(
|
||||
self.windowSize,
|
||||
resultSet.rowCount,
|
||||
this.loadDataFunction,
|
||||
index => { return {}; }
|
||||
),
|
||||
columnDefinitions: [rowNumberColumn.getColumnDefinition()].concat(resultSet.columnInfo.map((c, i) => {
|
||||
let columnIndex = (i + 1).toString();
|
||||
return {
|
||||
id: columnIndex,
|
||||
name: escape(c.columnName),
|
||||
field: columnIndex,
|
||||
formatter: Services.textFormatter,
|
||||
isEditable: c.isUpdatable
|
||||
};
|
||||
}))
|
||||
};
|
||||
self.plugins.push([rowNumberColumn, new AutoColumnSize({ maxWidth: this.configurationService.getValue<number>('resultsGrid.maxColumnWidth') }), new AdditionalKeyBindings()]);
|
||||
self.dataSet = dataSet;
|
||||
|
||||
// Create a dataSet to render without rows to reduce DOM size
|
||||
let undefinedDataSet = clone(dataSet);
|
||||
undefinedDataSet.columnDefinitions = dataSet.columnDefinitions;
|
||||
undefinedDataSet.dataRows = undefined;
|
||||
undefinedDataSet.resized = new EventEmitter();
|
||||
self.placeHolderDataSets.push(undefinedDataSet);
|
||||
self.refreshGrid();
|
||||
|
||||
// Setup the state of the selected cell
|
||||
this.resetCurrentCell();
|
||||
this.currentEditCellValue = undefined;
|
||||
this.removingNewRow = false;
|
||||
this.newRowVisible = false;
|
||||
this.dirtyCells = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles rendering the results to the DOM that are currently being shown
|
||||
* and destroying any results that have moved out of view
|
||||
* @param scrollTop The scrolltop value, if not called by the scroll event should be 0
|
||||
*/
|
||||
onScroll(scrollTop): void {
|
||||
this.refreshGrid();
|
||||
}
|
||||
|
||||
private refreshGrid(): Thenable<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const self = this;
|
||||
clearTimeout(self.refreshGridTimeoutHandle);
|
||||
this.refreshGridTimeoutHandle = setTimeout(() => {
|
||||
for (let i = 0; i < self.placeHolderDataSets.length; i++) {
|
||||
self.placeHolderDataSets[i].dataRows = self.dataSet.dataRows;
|
||||
self.placeHolderDataSets[i].resized.emit();
|
||||
}
|
||||
|
||||
self._cd.detectChanges();
|
||||
|
||||
if (self.firstRender) {
|
||||
let setActive = function () {
|
||||
if (self.firstRender && self.slickgrids.toArray().length > 0) {
|
||||
self.slickgrids.toArray()[0].setActive();
|
||||
self.firstRender = false;
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
setActive();
|
||||
});
|
||||
}
|
||||
resolve();
|
||||
}, self.refreshGridTimeoutInMs);
|
||||
});
|
||||
}
|
||||
|
||||
protected tryHandleKeyEvent(e: StandardKeyboardEvent): boolean {
|
||||
let handled: boolean = false;
|
||||
// If the esc key was pressed while in a create session
|
||||
let currentNewRowIndex = this.dataSet.totalRows - 2;
|
||||
|
||||
if (e.keyCode === KeyCode.Escape) {
|
||||
this.revertCurrentRow();
|
||||
handled = true;
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
// Private Helper Functions ////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private async revertCurrentRow(): Promise<void> {
|
||||
let currentNewRowIndex = this.dataSet.totalRows - 2;
|
||||
if (this.newRowVisible && this.currentCell.row === currentNewRowIndex) {
|
||||
// revert our last new row
|
||||
this.removingNewRow = true;
|
||||
|
||||
this.dataService.revertRow(this.rowIdMappings[currentNewRowIndex])
|
||||
.then(() => {
|
||||
return this.removeRow(currentNewRowIndex);
|
||||
}).then(() => {
|
||||
this.newRowVisible = false;
|
||||
this.resetCurrentCell();
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
// Perform a revert row operation
|
||||
if (this.currentCell && this.currentCell.row !== undefined) {
|
||||
await this.dataService.revertRow(this.currentCell.row);
|
||||
}
|
||||
} finally {
|
||||
// The operation may fail if there were no changes sent to the service to revert,
|
||||
// so clear any existing client-side edit and refresh on-screen data
|
||||
// do not refresh the whole dataset as it will move the focus away to the first row.
|
||||
//
|
||||
this.currentEditCellValue = undefined;
|
||||
this.dirtyCells = [];
|
||||
let row = this.currentCell.row;
|
||||
this.resetCurrentCell();
|
||||
|
||||
if (row !== undefined) {
|
||||
this.dataSet.dataRows.resetWindowsAroundIndex(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private submitCurrentCellChange(resultHandler, errorHandler): Promise<void> {
|
||||
let self = this;
|
||||
let updateCellPromise: Promise<void> = Promise.resolve();
|
||||
let refreshGrid = false;
|
||||
if (this.currentCell && this.currentCell.isEditable && this.currentEditCellValue !== undefined && !this.removingNewRow) {
|
||||
if (this.isNullRow(this.currentCell.row)) {
|
||||
refreshGrid = true;
|
||||
// We've entered the "new row", so we need to add a row and jump to it
|
||||
updateCellPromise = updateCellPromise.then(() => {
|
||||
return self.addRow(this.currentCell.row);
|
||||
});
|
||||
}
|
||||
// We're exiting a read/write cell after having changed the value, update the cell value in the service
|
||||
updateCellPromise = updateCellPromise.then(() => {
|
||||
// Use the mapped row ID if we're on that row
|
||||
let sessionRowId = self.rowIdMappings[self.currentCell.row] !== undefined
|
||||
? self.rowIdMappings[self.currentCell.row]
|
||||
: self.currentCell.row;
|
||||
|
||||
return self.dataService.updateCell(sessionRowId, self.currentCell.column - 1, self.currentEditCellValue);
|
||||
}).then(
|
||||
result => {
|
||||
self.currentEditCellValue = undefined;
|
||||
let refreshPromise: Thenable<void> = Promise.resolve();
|
||||
if (refreshGrid) {
|
||||
refreshPromise = self.refreshGrid();
|
||||
}
|
||||
return refreshPromise.then(() => {
|
||||
return resultHandler(result);
|
||||
});
|
||||
},
|
||||
error => {
|
||||
return errorHandler(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
return updateCellPromise;
|
||||
}
|
||||
|
||||
// Checks if input row is our NULL new row
|
||||
private isNullRow(row: number): boolean {
|
||||
// Null row is always at index (totalRows - 1)
|
||||
return (row === this.dataSet.totalRows - 1);
|
||||
}
|
||||
|
||||
// Adds CSS classes to slickgrid cells to indicate a dirty state
|
||||
private setCellDirtyState(row: number, column: number, dirtyState: boolean): void {
|
||||
let slick: any = this.slickgrids.toArray()[0];
|
||||
let grid = slick._grid;
|
||||
if (dirtyState) {
|
||||
// Change cell color
|
||||
jQuery(grid.getCellNode(row, column)).addClass('dirtyCell').removeClass('selected');
|
||||
if (this.dirtyCells.indexOf(column) === -1) {
|
||||
this.dirtyCells.push(column);
|
||||
}
|
||||
} else {
|
||||
jQuery(grid.getCellNode(row, column)).removeClass('dirtyCell');
|
||||
if (this.dirtyCells.indexOf(column) !== -1) {
|
||||
this.dirtyCells.splice(this.dirtyCells.indexOf(column), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adds CSS classes to slickgrid rows to indicate a dirty state
|
||||
private setRowDirtyState(row: number, dirtyState: boolean): void {
|
||||
let slick: any = this.slickgrids.toArray()[0];
|
||||
let grid = slick._grid;
|
||||
if (dirtyState) {
|
||||
// Change row header color
|
||||
jQuery(grid.getCellNode(row, 0)).addClass('dirtyRowHeader');
|
||||
} else {
|
||||
jQuery(grid.getCellNode(row, 0)).removeClass('dirtyRowHeader');
|
||||
}
|
||||
}
|
||||
|
||||
// Sets CSS to clean the entire grid of dirty state cells and rows
|
||||
private setGridClean(): void {
|
||||
// Remove dirty classes from the entire table
|
||||
let allRows = jQuery(jQuery('.grid-canvas').children());
|
||||
let allCells = jQuery(allRows.children());
|
||||
allCells.removeClass('dirtyCell').removeClass('dirtyRowHeader');
|
||||
this.dirtyCells = [];
|
||||
}
|
||||
|
||||
// Adds an extra row to the end of slickgrid (just for rendering purposes)
|
||||
// Then sets the focused call afterwards
|
||||
private addRow(row: number): Thenable<void> {
|
||||
let self = this;
|
||||
|
||||
// Add a new row to the edit session in the tools service
|
||||
return this.dataService.createRow()
|
||||
.then(result => {
|
||||
// Map the new row ID to the row ID we have
|
||||
self.rowIdMappings[row] = result.newRowId;
|
||||
self.newRowVisible = true;
|
||||
|
||||
// Add a new "new row" to the end of the results
|
||||
// Adding an extra row for 'new row' functionality
|
||||
self.dataSet.totalRows++;
|
||||
self.dataSet.maxHeight = self.getMaxHeight(self.dataSet.totalRows);
|
||||
self.dataSet.minHeight = self.getMinHeight(self.dataSet.totalRows);
|
||||
self.dataSet.dataRows = new VirtualizedCollection(
|
||||
self.windowSize,
|
||||
self.dataSet.totalRows,
|
||||
self.loadDataFunction,
|
||||
index => { return {}; }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// removes a row from the end of slickgrid (just for rendering purposes)
|
||||
// Then sets the focused call afterwards
|
||||
private removeRow(row: number): Thenable<void> {
|
||||
// Removing the new row
|
||||
this.dataSet.totalRows--;
|
||||
this.dataSet.dataRows = new VirtualizedCollection(
|
||||
this.windowSize,
|
||||
this.dataSet.totalRows,
|
||||
this.loadDataFunction,
|
||||
index => { return {}; }
|
||||
);
|
||||
|
||||
// refresh results view
|
||||
return this.refreshGrid().then(() => {
|
||||
// Set focus to the row index column of the removed row if the current selection is in the removed row
|
||||
if (this.currentCell.row === row && !this.removingNewRow) {
|
||||
this.focusCell(row, 1);
|
||||
}
|
||||
this.removingNewRow = false;
|
||||
});
|
||||
}
|
||||
|
||||
private focusCell(row: number, column: number, forceEdit: boolean = true): void {
|
||||
let slick: any = this.slickgrids.toArray()[0];
|
||||
let grid = slick._grid;
|
||||
grid.gotoCell(row, column, forceEdit);
|
||||
}
|
||||
|
||||
private getMaxHeight(rowCount: number): any {
|
||||
return rowCount < this._defaultNumShowingRows
|
||||
? ((rowCount + 1) * this._rowHeight) + 10
|
||||
: 'inherit';
|
||||
}
|
||||
|
||||
private getMinHeight(rowCount: number): any {
|
||||
return rowCount > this._defaultNumShowingRows
|
||||
? (this._defaultNumShowingRows + 1) * this._rowHeight + 10
|
||||
: this.getMaxHeight(rowCount);
|
||||
}
|
||||
|
||||
private saveViewState(): void {
|
||||
let grid = this.slickgrids.toArray()[0];
|
||||
let self = this;
|
||||
if (grid) {
|
||||
let gridSelections = grid.getSelectedRanges();
|
||||
let gridObject = grid as any;
|
||||
let viewport = (gridObject._grid.getCanvasNode() as HTMLElement).parentElement;
|
||||
this.savedViewState = {
|
||||
gridSelections,
|
||||
scrollTop: viewport.scrollTop,
|
||||
scrollLeft: viewport.scrollLeft
|
||||
};
|
||||
|
||||
// Save the cell that is currently being edited.
|
||||
// Note: This is only updating the data in tools service, not saving the change to database.
|
||||
// This is added to fix the data inconsistency: the updated value is displayed but won't be saved to the database
|
||||
// when committing the changes for the row.
|
||||
if (this.currentCell.row !== undefined && this.currentCell.column !== undefined && this.currentCell.isEditable) {
|
||||
gridObject._grid.getEditorLock().commitCurrentEdit();
|
||||
this.submitCurrentCellChange((result: EditUpdateCellResult) => {
|
||||
self.setCellDirtyState(self.currentCell.row, self.currentCell.column, result.cell.isDirty);
|
||||
}, (error: any) => {
|
||||
self.notificationService.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private restoreViewState(): void {
|
||||
if (this.savedViewState) {
|
||||
this.slickgrids.toArray()[0].selection = this.savedViewState.gridSelections;
|
||||
let viewport = ((this.slickgrids.toArray()[0] as any)._grid.getCanvasNode() as HTMLElement).parentElement;
|
||||
viewport.scrollLeft = this.savedViewState.scrollLeft;
|
||||
viewport.scrollTop = this.savedViewState.scrollTop;
|
||||
this.savedViewState = undefined;
|
||||
|
||||
// This block of code is responsible for restoring the dirty state indicators if slickgrid decides not to re-render the dirty row
|
||||
// Other scenarios will be taken care of by getAdditionalCssClassesForCell method when slickgrid needs to re-render the rows.
|
||||
if (this.currentCell.row !== undefined) {
|
||||
if (this.isRowDirty(this.currentCell.row)) {
|
||||
this.setRowDirtyState(this.currentCell.row, true);
|
||||
|
||||
this.dirtyCells.forEach(cell => {
|
||||
this.setCellDirtyState(this.currentCell.row, cell, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isRowDirty(row: number): boolean {
|
||||
return this.currentCell.row === row && this.dirtyCells.length > 0;
|
||||
}
|
||||
|
||||
private isCellDirty(row: number, column: number): boolean {
|
||||
return this.currentCell.row === row && this.dirtyCells.indexOf(column) !== -1;
|
||||
}
|
||||
|
||||
private isCellOnScreen(row: number, column: number): boolean {
|
||||
let slick: any = this.slickgrids.toArray()[0];
|
||||
let grid = slick._grid;
|
||||
let viewport = grid.getViewport();
|
||||
let cellBox = grid.getCellNodeBox(row, column);
|
||||
return viewport && cellBox
|
||||
&& viewport.leftPx <= cellBox.left && viewport.rightPx >= cellBox.right
|
||||
&& viewport.top <= row && viewport.bottom >= row;
|
||||
}
|
||||
|
||||
private resetCurrentCell() {
|
||||
this.currentCell = {
|
||||
row: undefined,
|
||||
column: undefined,
|
||||
isEditable: false,
|
||||
isDirty: false
|
||||
};
|
||||
}
|
||||
|
||||
private setCurrentCell(row: number, column: number) {
|
||||
// Only update if we're actually changing cells
|
||||
if (this.currentCell && (row !== this.currentCell.row || column !== this.currentCell.column)) {
|
||||
this.currentCell = {
|
||||
row: row,
|
||||
column: column,
|
||||
isEditable: this.dataSet.columnDefinitions[column]
|
||||
? this.dataSet.columnDefinitions[column].isEditable
|
||||
: false,
|
||||
isDirty: false
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import { ApplicationRef, ComponentFactoryResolver, NgModule, Inject, forwardRef, Type } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { SlickGrid } from 'angular2-slickgrid';
|
||||
|
||||
import { EditDataComponent } from 'sql/workbench/parts/grid/views/editData/editData.component';
|
||||
import { IBootstrapParams, ISelector, providerIterator } from 'sql/platform/bootstrap/node/bootstrapService';
|
||||
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const EditDataModule = (params: IBootstrapParams, selector: string, instantiationService: IInstantiationService): Type<any> => {
|
||||
|
||||
@NgModule({
|
||||
|
||||
imports: [
|
||||
CommonModule,
|
||||
BrowserModule
|
||||
],
|
||||
|
||||
declarations: [
|
||||
EditDataComponent,
|
||||
SlickGrid
|
||||
],
|
||||
|
||||
entryComponents: [
|
||||
EditDataComponent
|
||||
],
|
||||
providers: [
|
||||
{ provide: IBootstrapParams, useValue: params },
|
||||
{ provide: ISelector, useValue: selector },
|
||||
...providerIterator(instantiationService)
|
||||
]
|
||||
})
|
||||
class ModuleClass {
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
|
||||
@Inject(ISelector) private selector: string
|
||||
) {
|
||||
}
|
||||
|
||||
ngDoBootstrap(appRef: ApplicationRef) {
|
||||
const factory = this._resolver.resolveComponentFactory(EditDataComponent);
|
||||
(<any>factory).factory.selector = this.selector;
|
||||
appRef.bootstrap(factory);
|
||||
}
|
||||
}
|
||||
|
||||
return ModuleClass;
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IGridInfo } from 'sql/workbench/parts/grid/common/interfaces';
|
||||
import { DataService } from 'sql/workbench/parts/grid/services/dataService';
|
||||
import { GridActionProvider } from 'sql/workbench/parts/grid/views/gridActions';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
|
||||
export class EditDataGridActionProvider extends GridActionProvider {
|
||||
|
||||
constructor(
|
||||
dataService: DataService,
|
||||
selectAllCallback: (index: number) => void,
|
||||
private _deleteRowCallback: (index: number) => void,
|
||||
private _revertRowCallback: () => void
|
||||
) {
|
||||
super(dataService, selectAllCallback);
|
||||
}
|
||||
/**
|
||||
* Return actions given a click on an edit data grid
|
||||
*/
|
||||
public getGridActions(): IAction[] {
|
||||
let actions: IAction[] = [];
|
||||
actions.push(new DeleteRowAction(DeleteRowAction.ID, DeleteRowAction.LABEL, this._deleteRowCallback));
|
||||
actions.push(new RevertRowAction(RevertRowAction.ID, RevertRowAction.LABEL, this._revertRowCallback));
|
||||
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
export class DeleteRowAction extends Action {
|
||||
public static ID = 'grid.deleteRow';
|
||||
public static LABEL = localize('deleteRow', 'Delete Row');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private callback: (index: number) => void
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(gridInfo: IGridInfo): Promise<boolean> {
|
||||
this.callback(gridInfo.rowIndex);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class RevertRowAction extends Action {
|
||||
public static ID = 'grid.revertRow';
|
||||
public static LABEL = localize('revertRow', 'Revert Current Row');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private callback: () => void
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(gridInfo: IGridInfo): Promise<boolean> {
|
||||
this.callback();
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.editdata-component * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#workbench\.editor\.editDataEditor .monaco-toolbar .monaco-select-box {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
124
src/sql/workbench/parts/grid/views/gridActions.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IGridInfo, SaveFormat } from 'sql/workbench/parts/grid/common/interfaces';
|
||||
import { DataService } from 'sql/workbench/parts/grid/services/dataService';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
|
||||
export const GRID_SAVECSV_ID = 'grid.saveAsCsv';
|
||||
export const GRID_SAVEJSON_ID = 'grid.saveAsJson';
|
||||
export const GRID_SAVEEXCEL_ID = 'grid.saveAsExcel';
|
||||
export const GRID_SAVEXML_ID = 'grid.saveAsXml';
|
||||
export const GRID_COPY_ID = 'grid.copySelection';
|
||||
export const GRID_COPYWITHHEADERS_ID = 'grid.copyWithHeaders';
|
||||
export const GRID_SELECTALL_ID = 'grid.selectAll';
|
||||
export const MESSAGES_SELECTALL_ID = 'grid.messages.selectAll';
|
||||
export const MESSAGES_COPY_ID = 'grid.messages.copy';
|
||||
export const TOGGLERESULTS_ID = 'grid.toggleResultPane';
|
||||
export const TOGGLEMESSAGES_ID = 'grid.toggleMessagePane';
|
||||
export const GOTONEXTQUERYOUTPUTTAB_ID = 'query.goToNextQueryOutputTab';
|
||||
export const GRID_VIEWASCHART_ID = 'grid.viewAsChart';
|
||||
export const GRID_GOTONEXTGRID_ID = 'grid.goToNextGrid';
|
||||
|
||||
export class GridActionProvider {
|
||||
|
||||
constructor(
|
||||
protected _dataService: DataService,
|
||||
protected _selectAllCallback: (index: number) => void
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return actions given a click on a grid
|
||||
*/
|
||||
public getGridActions(): IAction[] {
|
||||
const actions: IAction[] = [];
|
||||
actions.push(new SaveResultAction(SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveFormat.CSV, this._dataService));
|
||||
actions.push(new SaveResultAction(SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveFormat.JSON, this._dataService));
|
||||
actions.push(new SaveResultAction(SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveFormat.EXCEL, this._dataService));
|
||||
actions.push(new SaveResultAction(SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveFormat.XML, this._dataService));
|
||||
actions.push(new SelectAllGridAction(SelectAllGridAction.ID, SelectAllGridAction.LABEL, this._selectAllCallback));
|
||||
actions.push(new CopyResultAction(CopyResultAction.COPY_ID, CopyResultAction.COPY_LABEL, false, this._dataService));
|
||||
actions.push(new CopyResultAction(CopyResultAction.COPYWITHHEADERS_ID, CopyResultAction.COPYWITHHEADERS_LABEL, true, this._dataService));
|
||||
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
class SaveResultAction extends Action {
|
||||
public static SAVECSV_ID = GRID_SAVECSV_ID;
|
||||
public static SAVECSV_LABEL = localize('saveAsCsv', "Save As CSV");
|
||||
|
||||
public static SAVEJSON_ID = GRID_SAVEJSON_ID;
|
||||
public static SAVEJSON_LABEL = localize('saveAsJson', "Save As JSON");
|
||||
|
||||
public static SAVEEXCEL_ID = GRID_SAVEEXCEL_ID;
|
||||
public static SAVEEXCEL_LABEL = localize('saveAsExcel', "Save As Excel");
|
||||
|
||||
public static SAVEXML_ID = GRID_SAVEXML_ID;
|
||||
public static SAVEXML_LABEL = localize('saveAsXml', "Save As XML");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private format: SaveFormat,
|
||||
private dataService: DataService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(gridInfo: IGridInfo): Promise<boolean> {
|
||||
this.dataService.sendSaveRequest({
|
||||
batchIndex: gridInfo.batchIndex,
|
||||
resultSetNumber: gridInfo.resultSetNumber,
|
||||
selection: gridInfo.selection,
|
||||
format: this.format
|
||||
});
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
class CopyResultAction extends Action {
|
||||
public static COPY_ID = GRID_COPY_ID;
|
||||
public static COPY_LABEL = localize('copySelection', "Copy");
|
||||
|
||||
public static COPYWITHHEADERS_ID = GRID_COPYWITHHEADERS_ID;
|
||||
public static COPYWITHHEADERS_LABEL = localize('copyWithHeaders', "Copy With Headers");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private copyHeader: boolean,
|
||||
private dataService: DataService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(gridInfo: IGridInfo): Promise<boolean> {
|
||||
this.dataService.copyResults(gridInfo.selection, gridInfo.batchIndex, gridInfo.resultSetNumber, this.copyHeader);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
class SelectAllGridAction extends Action {
|
||||
public static ID = GRID_SELECTALL_ID;
|
||||
public static LABEL = localize('selectAll', "Select All");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private selectAllCallback: (index: number) => void
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(gridInfo: IGridInfo): Promise<boolean> {
|
||||
this.selectAllCallback(gridInfo.gridIndex);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
88
src/sql/workbench/parts/grid/views/gridCommands.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as GridContentEvents from 'sql/workbench/parts/grid/common/gridContentEvents';
|
||||
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
|
||||
import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
|
||||
import { EditDataEditor } from 'sql/parts/editData/editor/editDataEditor';
|
||||
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
function runActionOnActiveResultsEditor(accessor: ServicesAccessor, eventName: string): void {
|
||||
let editorService = accessor.get(IEditorService);
|
||||
const candidates = [editorService.activeControl, ...editorService.visibleControls].filter(e => {
|
||||
if (e) {
|
||||
let id = e.getId();
|
||||
if (id === QueryEditor.ID || id === EditDataEditor.ID) {
|
||||
// This is a query or edit data editor
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (candidates.length > 0) {
|
||||
let queryModelService: IQueryModelService = accessor.get(IQueryModelService);
|
||||
let uri = (<any>candidates[0].input).uri;
|
||||
queryModelService.sendGridContentEvent(uri, eventName);
|
||||
}
|
||||
}
|
||||
|
||||
export const copySelection = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.CopySelection);
|
||||
};
|
||||
|
||||
export const copyMessagesSelection = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.CopyMessagesSelection);
|
||||
};
|
||||
|
||||
export const copyWithHeaders = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.CopyWithHeaders);
|
||||
};
|
||||
|
||||
export const toggleMessagePane = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.ToggleMessagePane);
|
||||
};
|
||||
|
||||
export const toggleResultsPane = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.ToggleResultPane);
|
||||
};
|
||||
|
||||
export const goToNextQueryOutputTab = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.GoToNextQueryOutputTab);
|
||||
};
|
||||
|
||||
export const saveAsCsv = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.SaveAsCsv);
|
||||
};
|
||||
|
||||
export const saveAsJson = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.SaveAsJSON);
|
||||
};
|
||||
|
||||
export const saveAsExcel = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.SaveAsExcel);
|
||||
};
|
||||
|
||||
export const saveAsXml = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.SaveAsXML);
|
||||
};
|
||||
|
||||
export const selectAll = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.SelectAll);
|
||||
};
|
||||
|
||||
export const selectAllMessages = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.SelectAllMessages);
|
||||
};
|
||||
|
||||
export const viewAsChart = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.ViewAsChart);
|
||||
};
|
||||
|
||||
export const goToNextGrid = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.GoToNextGrid);
|
||||
};
|
||||
581
src/sql/workbench/parts/grid/views/gridParentComponent.ts
Normal file
@@ -0,0 +1,581 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!sql/workbench/parts/grid/media/flexbox';
|
||||
import 'vs/css!sql/workbench/parts/grid/media/styles';
|
||||
|
||||
import { Subscription, Subject } from 'rxjs/Rx';
|
||||
import { ElementRef, QueryList, ChangeDetectorRef, ViewChildren } from '@angular/core';
|
||||
import { SlickGrid } from 'angular2-slickgrid';
|
||||
import { toDisposableSubscription } from 'sql/base/node/rxjsUtils';
|
||||
import * as Constants from 'sql/parts/query/common/constants';
|
||||
import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants';
|
||||
import { IGridInfo, IGridDataSet, SaveFormat } from 'sql/workbench/parts/grid/common/interfaces';
|
||||
import * as Utils from 'sql/platform/connection/common/utils';
|
||||
import { DataService } from 'sql/workbench/parts/grid/services/dataService';
|
||||
import * as actions from 'sql/workbench/parts/grid/views/gridActions';
|
||||
import * as Services from 'sql/base/browser/ui/table/formatters';
|
||||
import * as GridContentEvents from 'sql/workbench/parts/grid/common/gridContentEvents';
|
||||
import { ResultsVisibleContext, ResultsGridFocussedContext, ResultsMessagesFocussedContext, QueryEditorVisibleContext } from 'sql/parts/query/common/queryContext';
|
||||
import { error } from 'sql/base/common/log';
|
||||
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
|
||||
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
|
||||
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
|
||||
export abstract class GridParentComponent {
|
||||
// CONSTANTS
|
||||
// tslint:disable:no-unused-variable
|
||||
|
||||
protected get selectionModel() { return new CellSelectionModel(); }
|
||||
protected _rowHeight = 29;
|
||||
protected _defaultNumShowingRows = 8;
|
||||
protected Constants = Constants;
|
||||
protected LocalizedConstants = LocalizedConstants;
|
||||
protected Utils = Utils;
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
protected startString = new Date().toLocaleTimeString();
|
||||
|
||||
protected shortcutfunc: { [name: string]: Function };
|
||||
|
||||
// tslint:enable
|
||||
|
||||
// FIELDS
|
||||
// Service for interaction with the IQueryModel
|
||||
protected dataService: DataService;
|
||||
protected actionProvider: actions.GridActionProvider;
|
||||
|
||||
protected toDispose: IDisposable[];
|
||||
|
||||
|
||||
// Context keys to set when keybindings are available
|
||||
private resultsVisibleContextKey: IContextKey<boolean>;
|
||||
private gridFocussedContextKey: IContextKey<boolean>;
|
||||
private messagesFocussedContextKey: IContextKey<boolean>;
|
||||
private queryEditorVisible: IContextKey<boolean>;
|
||||
|
||||
// All datasets
|
||||
// Place holder data sets to buffer between data sets and rendered data sets
|
||||
protected placeHolderDataSets: IGridDataSet[] = [];
|
||||
// Datasets currently being rendered on the DOM
|
||||
protected renderedDataSets: IGridDataSet[] = this.placeHolderDataSets;
|
||||
protected resultActive = true;
|
||||
protected _messageActive = true;
|
||||
protected activeGrid = 0;
|
||||
|
||||
@ViewChildren('slickgrid') slickgrids: QueryList<SlickGrid>;
|
||||
|
||||
set messageActive(input: boolean) {
|
||||
this._messageActive = input;
|
||||
if (this.resultActive) {
|
||||
this.resizeGrids();
|
||||
}
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
|
||||
get messageActive(): boolean {
|
||||
return this._messageActive;
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected _el: ElementRef,
|
||||
protected _cd: ChangeDetectorRef,
|
||||
protected contextMenuService: IContextMenuService,
|
||||
protected keybindingService: IKeybindingService,
|
||||
protected contextKeyService: IContextKeyService,
|
||||
protected configurationService: IConfigurationService,
|
||||
protected clipboardService: IClipboardService,
|
||||
protected queryEditorService: IQueryEditorService
|
||||
) {
|
||||
this.toDispose = [];
|
||||
}
|
||||
|
||||
protected baseInit(): void {
|
||||
const self = this;
|
||||
this.initShortcutsBase();
|
||||
if (this.configurationService) {
|
||||
let sqlConfig = this.configurationService.getValue('sql');
|
||||
if (sqlConfig) {
|
||||
this._messageActive = sqlConfig['messagesDefaultOpen'];
|
||||
}
|
||||
}
|
||||
this.subscribeWithDispose(this.dataService.gridContentObserver, (type) => {
|
||||
switch (type) {
|
||||
case GridContentEvents.RefreshContents:
|
||||
self.refreshResultsets();
|
||||
break;
|
||||
case GridContentEvents.ResizeContents:
|
||||
self.resizeGrids();
|
||||
break;
|
||||
case GridContentEvents.CopySelection:
|
||||
self.copySelection();
|
||||
break;
|
||||
case GridContentEvents.CopyWithHeaders:
|
||||
self.copyWithHeaders();
|
||||
break;
|
||||
case GridContentEvents.CopyMessagesSelection:
|
||||
self.copyMessagesSelection();
|
||||
break;
|
||||
case GridContentEvents.ToggleResultPane:
|
||||
self.toggleResultPane();
|
||||
break;
|
||||
case GridContentEvents.ToggleMessagePane:
|
||||
self.toggleMessagePane();
|
||||
break;
|
||||
case GridContentEvents.SelectAll:
|
||||
self.onSelectAllForActiveGrid();
|
||||
break;
|
||||
case GridContentEvents.SelectAllMessages:
|
||||
self.selectAllMessages();
|
||||
break;
|
||||
case GridContentEvents.SaveAsCsv:
|
||||
self.sendSaveRequest(SaveFormat.CSV);
|
||||
break;
|
||||
case GridContentEvents.SaveAsJSON:
|
||||
self.sendSaveRequest(SaveFormat.JSON);
|
||||
break;
|
||||
case GridContentEvents.SaveAsExcel:
|
||||
self.sendSaveRequest(SaveFormat.EXCEL);
|
||||
break;
|
||||
case GridContentEvents.SaveAsXML:
|
||||
self.sendSaveRequest(SaveFormat.XML);
|
||||
break;
|
||||
case GridContentEvents.GoToNextQueryOutputTab:
|
||||
self.goToNextQueryOutputTab();
|
||||
break;
|
||||
case GridContentEvents.ViewAsChart:
|
||||
self.showChartForGrid(self.activeGrid);
|
||||
break;
|
||||
case GridContentEvents.GoToNextGrid:
|
||||
self.goToNextGrid();
|
||||
break;
|
||||
default:
|
||||
error('Unexpected grid content event type "' + type + '" sent');
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.bindKeys(this.contextKeyService);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the subscription to the list of things to be disposed on destroy, or else on a new component init
|
||||
* may get the "destroyed" object still getting called back.
|
||||
*/
|
||||
protected subscribeWithDispose<T>(subject: Subject<T>, event: (value: any) => void): void {
|
||||
let sub: Subscription = subject.subscribe(event);
|
||||
this.toDispose.push(toDisposableSubscription(sub));
|
||||
}
|
||||
|
||||
private bindKeys(contextKeyService: IContextKeyService): void {
|
||||
if (contextKeyService) {
|
||||
this.queryEditorVisible = QueryEditorVisibleContext.bindTo(contextKeyService);
|
||||
this.queryEditorVisible.set(true);
|
||||
|
||||
let gridContextKeyService = this.contextKeyService.createScoped(this._el.nativeElement);
|
||||
this.toDispose.push(gridContextKeyService);
|
||||
this.resultsVisibleContextKey = ResultsVisibleContext.bindTo(gridContextKeyService);
|
||||
this.resultsVisibleContextKey.set(true);
|
||||
|
||||
this.gridFocussedContextKey = ResultsGridFocussedContext.bindTo(gridContextKeyService);
|
||||
this.messagesFocussedContextKey = ResultsMessagesFocussedContext.bindTo(gridContextKeyService);
|
||||
}
|
||||
}
|
||||
|
||||
protected baseDestroy(): void {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
|
||||
protected toggleResultPane(): void {
|
||||
this.resultActive = !this.resultActive;
|
||||
if (this.resultActive) {
|
||||
this.resizeGrids();
|
||||
}
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
|
||||
protected toggleMessagePane(): void {
|
||||
this.messageActive = !this.messageActive;
|
||||
}
|
||||
|
||||
protected onGridFocus() {
|
||||
this.gridFocussedContextKey.set(true);
|
||||
}
|
||||
|
||||
protected onGridFocusout() {
|
||||
this.gridFocussedContextKey.set(false);
|
||||
}
|
||||
|
||||
protected onMessagesFocus() {
|
||||
this.messagesFocussedContextKey.set(true);
|
||||
}
|
||||
|
||||
protected onMessagesFocusout() {
|
||||
this.messagesFocussedContextKey.set(false);
|
||||
}
|
||||
|
||||
protected getSelection(index?: number): Slick.Range[] {
|
||||
let selection = this.slickgrids.toArray()[index || this.activeGrid].getSelectedRanges();
|
||||
if (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;
|
||||
}
|
||||
}
|
||||
|
||||
private copySelection(): void {
|
||||
let messageText = this.getMessageText();
|
||||
if (messageText.length > 0) {
|
||||
this.clipboardService.writeText(messageText);
|
||||
} else {
|
||||
let activeGrid = this.activeGrid;
|
||||
this.dataService.copyResults(this.getSelection(activeGrid), this.renderedDataSets[activeGrid].batchId, this.renderedDataSets[activeGrid].resultId);
|
||||
}
|
||||
}
|
||||
|
||||
private copyWithHeaders(): void {
|
||||
let activeGrid = this.activeGrid;
|
||||
this.dataService.copyResults(this.getSelection(activeGrid), this.renderedDataSets[activeGrid].batchId,
|
||||
this.renderedDataSets[activeGrid].resultId, true);
|
||||
}
|
||||
|
||||
private copyMessagesSelection(): void {
|
||||
let messageText = this.getMessageText();
|
||||
if (messageText.length === 0) {
|
||||
// Since we know we're specifically copying messages, do a select all if nothing is selected
|
||||
this.selectAllMessages();
|
||||
messageText = this.getMessageText();
|
||||
}
|
||||
if (messageText.length > 0) {
|
||||
this.clipboardService.writeText(messageText);
|
||||
}
|
||||
}
|
||||
|
||||
private getMessageText(): string {
|
||||
if (document.activeElement === this.getMessagesElement()) {
|
||||
if (window.getSelection()) {
|
||||
return window.getSelection().toString();
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
protected goToNextQueryOutputTab(): void {
|
||||
}
|
||||
|
||||
protected showChartForGrid(index: number) {
|
||||
}
|
||||
|
||||
protected goToNextGrid() {
|
||||
if (this.renderedDataSets.length > 0) {
|
||||
let next = this.activeGrid + 1;
|
||||
if (next >= this.renderedDataSets.length) {
|
||||
next = 0;
|
||||
}
|
||||
this.navigateToGrid(next);
|
||||
}
|
||||
}
|
||||
|
||||
protected navigateToGrid(index: number) {
|
||||
}
|
||||
|
||||
private initShortcutsBase(): void {
|
||||
let shortcuts = {
|
||||
'ToggleResultPane': () => {
|
||||
this.toggleResultPane();
|
||||
},
|
||||
'ToggleMessagePane': () => {
|
||||
this.toggleMessagePane();
|
||||
},
|
||||
'CopySelection': () => {
|
||||
this.copySelection();
|
||||
},
|
||||
'CopyWithHeaders': () => {
|
||||
this.copyWithHeaders();
|
||||
},
|
||||
'SelectAll': () => {
|
||||
this.onSelectAllForActiveGrid();
|
||||
},
|
||||
'SaveAsCSV': () => {
|
||||
this.sendSaveRequest(SaveFormat.CSV);
|
||||
},
|
||||
'SaveAsJSON': () => {
|
||||
this.sendSaveRequest(SaveFormat.JSON);
|
||||
},
|
||||
'SaveAsExcel': () => {
|
||||
this.sendSaveRequest(SaveFormat.EXCEL);
|
||||
},
|
||||
'SaveAsXML': () => {
|
||||
this.sendSaveRequest(SaveFormat.XML);
|
||||
},
|
||||
'GoToNextQueryOutputTab': () => {
|
||||
this.goToNextQueryOutputTab();
|
||||
}
|
||||
};
|
||||
|
||||
this.initShortcuts(shortcuts);
|
||||
this.shortcutfunc = shortcuts;
|
||||
}
|
||||
|
||||
protected abstract initShortcuts(shortcuts: { [name: string]: Function }): void;
|
||||
|
||||
/**
|
||||
* Send save result set request to service
|
||||
*/
|
||||
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 });
|
||||
break;
|
||||
case 'savejson':
|
||||
this.dataService.sendSaveRequest({ batchIndex: event.batchId, resultSetNumber: event.resultId, format: SaveFormat.JSON, selection: event.selection });
|
||||
break;
|
||||
case 'saveexcel':
|
||||
this.dataService.sendSaveRequest({ batchIndex: event.batchId, resultSetNumber: event.resultId, format: SaveFormat.EXCEL, selection: event.selection });
|
||||
break;
|
||||
case 'savexml':
|
||||
this.dataService.sendSaveRequest({ batchIndex: event.batchId, resultSetNumber: event.resultId, format: SaveFormat.XML, selection: event.selection });
|
||||
break;
|
||||
case 'selectall':
|
||||
this.activeGrid = event.index;
|
||||
this.onSelectAllForActiveGrid();
|
||||
break;
|
||||
case 'copySelection':
|
||||
this.dataService.copyResults(event.selection, event.batchId, event.resultId);
|
||||
break;
|
||||
case 'copyWithHeaders':
|
||||
this.dataService.copyResults(event.selection, event.batchId, event.resultId, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private sendSaveRequest(format: SaveFormat) {
|
||||
let activeGrid = this.activeGrid;
|
||||
let batchId = this.renderedDataSets[activeGrid].batchId;
|
||||
let resultId = this.renderedDataSets[activeGrid].resultId;
|
||||
this.dataService.sendSaveRequest({ batchIndex: batchId, resultSetNumber: resultId, format: format, selection: this.getSelection(activeGrid) });
|
||||
}
|
||||
|
||||
protected _keybindingFor(action: IAction): ResolvedKeybinding {
|
||||
var [kb] = this.keybindingService.lookupKeybindings(action.id);
|
||||
return kb;
|
||||
}
|
||||
|
||||
openContextMenu(event, batchId, resultId, index): void {
|
||||
let slick: any = this.slickgrids.toArray()[index];
|
||||
let grid = slick._grid;
|
||||
|
||||
let selection = this.getSelection(index);
|
||||
|
||||
if (selection && selection.length === 0) {
|
||||
let cell = (grid as Slick.Grid<any>).getCellFromEvent(event);
|
||||
selection = [new Slick.Range(cell.row, cell.cell - 1)];
|
||||
}
|
||||
|
||||
let rowIndex = grid.getCellFromEvent(event).row;
|
||||
|
||||
let actionContext: IGridInfo = {
|
||||
batchIndex: batchId,
|
||||
resultSetNumber: resultId,
|
||||
selection: selection,
|
||||
gridIndex: index,
|
||||
rowIndex: rowIndex
|
||||
};
|
||||
|
||||
let anchor = { x: event.pageX + 1, y: event.pageY };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => this.actionProvider.getGridActions(),
|
||||
getKeyBinding: (action) => this._keybindingFor(action),
|
||||
getActionsContext: () => (actionContext)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that selects all elements of a grid. This needs to
|
||||
* return a function in order to capture the scope for this component
|
||||
* @private
|
||||
* @returns {(gridIndex: number) => void}
|
||||
*
|
||||
* @memberOf QueryComponent
|
||||
*/
|
||||
protected onGridSelectAll(): (gridIndex: number) => void {
|
||||
let self = this;
|
||||
return (gridIndex: number) => {
|
||||
self.activeGrid = gridIndex;
|
||||
let grid = self.slickgrids.toArray()[self.activeGrid];
|
||||
grid.setActive();
|
||||
grid.selection = true;
|
||||
};
|
||||
}
|
||||
|
||||
private onSelectAllForActiveGrid(): void {
|
||||
if (this.activeGrid >= 0 && this.slickgrids.length > this.activeGrid) {
|
||||
this.slickgrids.toArray()[this.activeGrid].selection = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a resultset take up the full result height if this is not already true
|
||||
* Otherwise rerenders the result sets from default
|
||||
*/
|
||||
magnify(index: number): void {
|
||||
const self = this;
|
||||
if (this.renderedDataSets.length > 1) {
|
||||
this.renderedDataSets = [this.placeHolderDataSets[index]];
|
||||
} else {
|
||||
this.renderedDataSets = this.placeHolderDataSets;
|
||||
this.onScroll(0);
|
||||
}
|
||||
setTimeout(() => {
|
||||
self.resizeGrids();
|
||||
self.slickgrids.toArray()[0].setActive();
|
||||
self._cd.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
abstract onScroll(scrollTop): void;
|
||||
|
||||
protected getResultsElement(): any {
|
||||
return this._el.nativeElement.querySelector('#results');
|
||||
}
|
||||
protected getMessagesElement(): any {
|
||||
return this._el.nativeElement.querySelector('#messages');
|
||||
}
|
||||
/**
|
||||
* Force angular to re-render the results grids. Calling this upon unhide (upon focus) fixes UI
|
||||
* glitches that occur when a QueryRestulsEditor is hidden then unhidden while it is running a query.
|
||||
*/
|
||||
refreshResultsets(): void {
|
||||
let tempRenderedDataSets = this.renderedDataSets;
|
||||
this.renderedDataSets = [];
|
||||
this._cd.detectChanges();
|
||||
this.renderedDataSets = tempRenderedDataSets;
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
|
||||
getSelectedRangeUnderMessages(): Selection {
|
||||
if (document.activeElement === this.getMessagesElement()) {
|
||||
return window.getSelection();
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
selectAllMessages(): void {
|
||||
let msgEl = this._el.nativeElement.querySelector('#messages');
|
||||
this.selectElementContents(msgEl);
|
||||
}
|
||||
|
||||
selectElementContents(el: HTMLElement): void {
|
||||
let range = document.createRange();
|
||||
range.selectNodeContents(el);
|
||||
let sel = window.getSelection();
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add handler for clicking on xml link
|
||||
*/
|
||||
xmlLinkHandler = (cellRef: string, row: number, dataContext: JSON, colDef: any) => {
|
||||
const self = this;
|
||||
|
||||
let value = self.getCellValueString(dataContext, colDef);
|
||||
if (value.startsWith('<ShowPlanXML') && colDef.name !== 'XML Showplan') {
|
||||
self.handleQueryPlanLink(cellRef, value);
|
||||
} else {
|
||||
self.handleLink(cellRef, row, dataContext, colDef, 'xml');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add handler for clicking on json link
|
||||
*/
|
||||
jsonLinkHandler = (cellRef: string, row: number, dataContext: JSON, colDef: any) => {
|
||||
const self = this;
|
||||
self.handleLink(cellRef, row, dataContext, colDef, 'json');
|
||||
}
|
||||
|
||||
private handleQueryPlanLink(cellRef: string, value: string): void {
|
||||
const self = this;
|
||||
jQuery(cellRef).children('.xmlLink').click(function (): void {
|
||||
self.queryEditorService.newQueryPlanEditor(value);
|
||||
});
|
||||
}
|
||||
|
||||
private handleLink(cellRef: string, row: number, dataContext: JSON, colDef: any, linkType: string): void {
|
||||
const self = this;
|
||||
let value = self.getCellValueString(dataContext, colDef);
|
||||
jQuery(cellRef).children('.xmlLink').click(function (): void {
|
||||
self.dataService.openLink(value, colDef.name, linkType);
|
||||
});
|
||||
}
|
||||
|
||||
private getCellValueString(dataContext: JSON, colDef: any): string {
|
||||
let returnVal = '';
|
||||
let value = dataContext[colDef.field];
|
||||
if (Services.DBCellValue.isDBCellValue(value)) {
|
||||
returnVal = value.displayValue;
|
||||
} else if (typeof value === 'string') {
|
||||
returnVal = value;
|
||||
}
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return asyncPostRender handler based on type
|
||||
*/
|
||||
public linkHandler(type: string): Function {
|
||||
if (type === 'xml') {
|
||||
return this.xmlLinkHandler;
|
||||
} else { // default to JSON handler
|
||||
return this.jsonLinkHandler;
|
||||
}
|
||||
}
|
||||
|
||||
keyEvent(e: KeyboardEvent): void {
|
||||
if (this.tryHandleKeyEvent(new StandardKeyboardEvent(e))) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
// Else assume that keybinding service handles routing this to a command
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by keyEvent method to give child classes a chance to
|
||||
* handle key events.
|
||||
*
|
||||
* @protected
|
||||
* @abstract
|
||||
* @param {StandardKeyboardEvent} e
|
||||
* @returns {boolean}
|
||||
*
|
||||
* @memberOf GridParentComponent
|
||||
*/
|
||||
protected abstract tryHandleKeyEvent(e: StandardKeyboardEvent): boolean;
|
||||
|
||||
resizeGrids(): void {
|
||||
const self = this;
|
||||
setTimeout(() => {
|
||||
for (let grid of self.renderedDataSets) {
|
||||
grid.resized.emit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Private Helper Functions ////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { textFormatter } from 'sql/parts/grid/services/sharedServices';
|
||||
import { textFormatter } from 'sql/base/browser/ui/table/formatters';
|
||||
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
|
||||
import { escape } from 'sql/base/common/strings';
|
||||
import { IDataResource } from 'sql/workbench/services/notebook/sql/sqlSessionManager';
|
||||
@@ -86,30 +86,30 @@ export function renderDataResource(
|
||||
|
||||
// SlickGrid requires columns and data to be in a very specific format; this code was adapted from tableInsight.component.ts
|
||||
function transformData(rows: any[], columns: Slick.Column<any>[]): { [key: string]: string }[] {
|
||||
return rows.map(row => {
|
||||
let dataWithSchema = {};
|
||||
Object.keys(row).forEach((val, index) => {
|
||||
return rows.map(row => {
|
||||
let dataWithSchema = {};
|
||||
Object.keys(row).forEach((val, index) => {
|
||||
let displayValue = String(Object.values(row)[index]);
|
||||
// Since the columns[0] represents the row number, start at 1
|
||||
dataWithSchema[columns[index + 1].field] = {
|
||||
displayValue: displayValue,
|
||||
ariaLabel: escape(displayValue),
|
||||
isNull: false
|
||||
};
|
||||
});
|
||||
return dataWithSchema;
|
||||
});
|
||||
dataWithSchema[columns[index + 1].field] = {
|
||||
displayValue: displayValue,
|
||||
ariaLabel: escape(displayValue),
|
||||
isNull: false
|
||||
};
|
||||
});
|
||||
return dataWithSchema;
|
||||
});
|
||||
}
|
||||
|
||||
function transformColumns(columns: string[]): Slick.Column<any>[] {
|
||||
return columns.map((col, index) => {
|
||||
return <Slick.Column<any>>{
|
||||
name: col,
|
||||
id: col,
|
||||
field: index.toString(),
|
||||
formatter: textFormatter
|
||||
};
|
||||
});
|
||||
return columns.map((col, index) => {
|
||||
return <Slick.Column<any>>{
|
||||
name: col,
|
||||
id: col,
|
||||
field: index.toString(),
|
||||
formatter: textFormatter
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||