* added select all event to context menu; added events for save as and select all

* added shortcuts to context menu when present

* fixed build errors

* fixed select all

* added refresh (#195)

* fixed null values in grid (#218)

* added config
This commit is contained in:
Anthony Dresser 2016-10-31 16:28:34 -07:00 коммит произвёл GitHub
Родитель d1624968a9
Коммит 7760ed16db
11 изменённых файлов: 262 добавлений и 50 удалений

Просмотреть файл

@ -359,6 +359,26 @@
}
}
}
},
"mssql.shortcuts": {
"type": "object",
"description": "Shortcuts related to the results window",
"default": {
"_comment": "Short cuts must follow the format (ctrl)+(shift)+(alt)+[key]",
"event.toggleResultPane": "ctrl+alt+r",
"event.toggleMessagePane": "ctrl+alt+y",
"event.prevGrid": "ctrl+up",
"event.nextGrid": "ctrl+down",
"event.copySelection": "ctrl+c",
"event.toggleMagnify": "",
"event.selectAll": "",
"event.saveAsJSON": "",
"event.saveAsCSV": ""
}
},
"mssql.messagesDefaultOpen": {
"type": "boolean",
"description": "true for the messages pane to be open by default; false for closed"
}
}
}

Просмотреть файл

@ -24,6 +24,11 @@ class QueryRunnerState {
}
}
class ResultsConfig implements Interfaces.IResultsConfig {
shortcuts: { [key: string]: string };
messagesDefaultOpen: boolean;
}
export class SqlOutputContentProvider implements vscode.TextDocumentContentProvider {
// CONSTANTS ///////////////////////////////////////////////////////////
public static providerName = 'tsqloutput';
@ -46,7 +51,7 @@ export class SqlOutputContentProvider implements vscode.TextDocumentContentProvi
this._service = new LocalWebService(context.extensionPath);
// add http handler for '/root'
this._service.addHandler(Interfaces.ContentType.Root, function(req, res): void {
this._service.addHandler(Interfaces.ContentType.Root, (req, res): void => {
let uri: string = req.query.uri;
if (self._queryResultsMap.has(uri)) {
clearTimeout(self._queryResultsMap.get(uri).timeout);
@ -71,7 +76,7 @@ export class SqlOutputContentProvider implements vscode.TextDocumentContentProvi
});
// add http handler for '/resultsetsMeta' - return metadata about columns & rows in multiple resultsets
this._service.addHandler(Interfaces.ContentType.ResultsetsMeta, function(req, res): void {
this._service.addHandler(Interfaces.ContentType.ResultsetsMeta, (req, res): void => {
let tempBatchSets: Interfaces.IGridBatchMetaData[] = [];
let uri: string = req.query.uri;
if (self._queryResultsMap.has(uri)) {
@ -122,7 +127,7 @@ export class SqlOutputContentProvider implements vscode.TextDocumentContentProvi
});
// add http handler for '/columns' - return column metadata as a JSON string
this._service.addHandler(Interfaces.ContentType.Columns, function(req, res): void {
this._service.addHandler(Interfaces.ContentType.Columns, (req, res): void => {
let resultId = req.query.resultId;
let batchId = req.query.batchId;
let uri: string = req.query.uri;
@ -134,7 +139,7 @@ export class SqlOutputContentProvider implements vscode.TextDocumentContentProvi
});
// add http handler for '/rows' - return rows end-point for a specific resultset
this._service.addHandler(Interfaces.ContentType.Rows, function(req, res): void {
this._service.addHandler(Interfaces.ContentType.Rows, (req, res): void => {
let resultId = req.query.resultId;
let batchId = req.query.batchId;
let rowStart = req.query.rowStart;
@ -146,8 +151,18 @@ export class SqlOutputContentProvider implements vscode.TextDocumentContentProvi
});
});
this._service.addHandler(Interfaces.ContentType.Config, (req, res): void => {
let extConfig = this._vscodeWrapper.getConfiguration(Constants.extensionConfigSectionName);
let config = new ResultsConfig();
for (let key of Constants.extConfigResultKeys) {
config[key] = extConfig[key];
}
let json = JSON.stringify(config);
res.send(json);
});
// add http handler for '/saveResults' - return success message as JSON
this._service.addPostHandler(Interfaces.ContentType.SaveResults, function(req, res): void {
this._service.addPostHandler(Interfaces.ContentType.SaveResults, (req, res): void => {
let uri: string = req.query.uri;
let queryUri = self._queryResultsMap.get(uri).queryRunner.uri;
let selectedResultSetNo: number = Number(req.query.resultSetNo);
@ -161,7 +176,7 @@ export class SqlOutputContentProvider implements vscode.TextDocumentContentProvi
});
// add http handler for '/openLink' - open content in a new vscode editor pane
this._service.addPostHandler(Interfaces.ContentType.OpenLink, function(req, res): void {
this._service.addPostHandler(Interfaces.ContentType.OpenLink, (req, res): void => {
let content: string = req.body.content;
let columnName: string = req.body.columnName;
let linkType: string = req.body.type;
@ -171,7 +186,7 @@ export class SqlOutputContentProvider implements vscode.TextDocumentContentProvi
});
// add http post handler for copying results
this._service.addPostHandler(Interfaces.ContentType.Copy, function(req, res): void {
this._service.addPostHandler(Interfaces.ContentType.Copy, (req, res): void => {
let uri = req.query.uri;
let resultId = req.query.resultId;
let batchId = req.query.batchId;
@ -183,7 +198,7 @@ export class SqlOutputContentProvider implements vscode.TextDocumentContentProvi
});
// add http post handler for setting the selection in the editor
this._service.addPostHandler(Interfaces.ContentType.EditorSelection, function(req, res): void {
this._service.addPostHandler(Interfaces.ContentType.EditorSelection, (req, res): void => {
let uri = req.query.uri;
let selection: ISelectionData = req.body;
self._queryResultsMap.get(uri).queryRunner.setEditorSelection(selection).then(() => {
@ -193,7 +208,7 @@ export class SqlOutputContentProvider implements vscode.TextDocumentContentProvi
});
// add http post handler for showing errors to user
this._service.addPostHandler(Interfaces.ContentType.ShowError, function(req, res): void {
this._service.addPostHandler(Interfaces.ContentType.ShowError, (req, res): void => {
let message: string = req.body.message;
self._vscodeWrapper.showErrorMessage(message);
// not attached to show function callback, since callback returns only after user closes message
@ -202,7 +217,7 @@ export class SqlOutputContentProvider implements vscode.TextDocumentContentProvi
});
// add http post handler for showing warning to user
this._service.addPostHandler(Interfaces.ContentType.ShowWarning, function(req, res): void {
this._service.addPostHandler(Interfaces.ContentType.ShowWarning, (req, res): void => {
let message: string = req.body.message;
self._vscodeWrapper.showWarningMessage(message);
// not attached to show function callback, since callback returns only after user closes message

Просмотреть файл

@ -33,6 +33,7 @@ export const outputContentTypeMessages = 'messages';
export const outputContentTypeResultsetMeta = 'resultsetsMeta';
export const outputContentTypeColumns = 'columns';
export const outputContentTypeRows = 'rows';
export const outputContentTypeConfig = 'config';
export const outputContentTypeSaveResults = 'saveResults';
export const outputContentTypeOpenLink = 'openLink';
export const outputContentTypeCopy = 'copyResults';
@ -208,6 +209,8 @@ export const sqlToolsServiceExecutableFilesConfigKey = 'executableFiles';
export const sqlToolsServiceVersionConfigKey = 'version';
export const sqlToolsServiceDownloadUrlConfigKey = 'downloadUrl';
export const extConfigResultKeys = ['shortcuts', 'messagesDefaultOpen'];
export const titleResultsPane = 'Results: {0}';
export const macOpenSslErrorMessage = `OpenSSL version >=1.0.1 is required to connect.`;

Просмотреть файл

@ -14,7 +14,8 @@ export enum ContentType {
EditorSelection = 7,
OpenLink = 8,
ShowError = 9,
ShowWarning = 10
ShowWarning = 10,
Config = 11
};
export interface ISlickRange {
@ -41,7 +42,8 @@ export const ContentTypes = [
Constants.outputContentTypeEditorSelection,
Constants.outputContentTypeOpenLink,
Constants.outputContentTypeShowError,
Constants.outputContentTypeShowWarning
Constants.outputContentTypeShowWarning,
Constants.outputContentTypeConfig
];
/**
@ -264,3 +266,7 @@ export interface IGridBatchMetaData {
totalTime: string;
}
export interface IResultsConfig {
shortcuts: { [key: string]: string };
messagesDefaultOpen: boolean;
}

Просмотреть файл

@ -1,7 +1,7 @@
<div class="fullsize vertBox">
<div *ngIf="dataSets.length > 0" class="boxRow header collapsible" [class.collapsed]="!resultActive" (click)="resultActive = !resultActive">
<span> {{Constants.resultPaneLabel}} </span>
<span class="shortCut"> {{Utils.stringCodeFor('event.toggleResultPane')}} </span>
<span class="shortCut"> {{resultShortcut}} </span>
</div>
<div id="results" *ngIf="renderedDataSets.length > 0" class="results vertBox scrollable"
(onScroll)="onScroll($event)" [scrollEnabled]="scrollEnabled" [class.hidden]="!resultActive">
@ -34,7 +34,7 @@
<div class="boxRow header collapsible" [class.collapsed]="!messageActive" (click)="messageActive = !messageActive" style="position: relative">
<div id="messageResizeHandle" class="resizableHandle"></div>
<span> {{Constants.messagePaneLabel}} </span>
<span class="shortCut"> {{Utils.stringCodeFor('event.toggleMessagePane')}} </span>
<span class="shortCut"> {{messageShortcut}} </span>
</div>
<div id="messages" class="scrollable messages" [class.hidden]="!messageActive">
<br>

Просмотреть файл

@ -1,8 +1,8 @@
<ul class="contextMenu" style="display:none;position:absolute">
<li id="savecsv" (click)="handleContextActionClick('savecsv')" [class.disabled]="isDisabled"> {{Constants.saveCSVLabel}}
<span style="float: right; color: lightgrey; padding-left: 10px">{{Utils.stringCodeFor('event.saveAsCSV')}}</span></li>
<span style="float: right; color: lightgrey; padding-left: 10px">{{keys['event.saveAsCSV']}}</span></li>
<li id="savejson" (click)="handleContextActionClick('savejson')" [class.disabled]="isDisabled"> {{Constants.saveJSONLabel}}
<span style="float: right; color: lightgrey; padding-left: 10px">{{Utils.stringCodeFor('event.saveAsJSON')}}</span></li>
<span style="float: right; color: lightgrey; padding-left: 10px">{{keys['event.saveAsJSON']}}</span></li>
<li id="selectall" (click)="handleContextActionClick('selectall')" [class.disabled]="isDisabled"> {{Constants.selectAll}}
<span style="float: right; color: lightgrey; padding-left: 10px">{{Utils.stringCodeFor('event.selectAll')}}</span></li>
<span style="float: right; color: lightgrey; padding-left: 10px">{{keys['event.selectAll']}}</span></li>
</ul>

Просмотреть файл

@ -15,6 +15,7 @@ import {VirtualizedCollection} from './../slickgrid/VirtualizedCollection';
import { FieldType } from './../slickgrid/EngineAPI';
import {DataService} from './../services/data.service';
import {ShortcutService} from './../services/shortcuts.service';
import { ContextMenu } from './contextmenu.component';
import { IGridIcon, IGridBatchMetaData, ISelectionData, IResultMessage } from './../interfaces';
@ -25,17 +26,6 @@ import * as Utils from './../utils';
import {enableProdMode} from '@angular/core';
enableProdMode();
const shortcuts = {
'_comment': 'Short cuts must follow the format (ctrl)+(shift)+(alt)+(key)',
'ctrl+alt+r': 'event.toggleResultPane',
'ctrl+alt+y': 'event.toggleMessagePane',
'ctrl+up': 'event.prevGrid',
'ctrl+down': 'event.nextGrid',
'ctrl+c': 'event.copySelection',
'undefined': 'event.toggleMagnifyCurrent'
};
const keycodes = require('./../keycodes.json!');
enum SelectedTab {
Results = 0,
Messages = 1,
@ -68,7 +58,7 @@ interface IMessages {
host: { '(window:keydown)': 'keyEvent($event)', '(window:gridnav)': 'keyEvent($event)' },
templateUrl: 'dist/html/app.html',
directives: [ContextMenu, SlickGrid],
providers: [DataService],
providers: [DataService, ShortcutService],
styles: [`
.errorMessage {
color: var(--color-error);
@ -176,6 +166,7 @@ export class AppComponent implements OnInit, AfterViewChecked {
];
// tslint:disable-next-line:no-unused-variable
private startString = new Date().toLocaleTimeString();
private config;
// FIELDS
// All datasets
@ -199,6 +190,8 @@ export class AppComponent implements OnInit, AfterViewChecked {
private resultsScrollTop = 0;
// tslint:disable-next-line:no-unused-variable
private activeGrid = 0;
private messageShortcut;
private resultShortcut;
private totalElapseExecution: number;
@ViewChild(ContextMenu) contextMenu: ContextMenu;
@ViewChildren(SlickGrid) slickgrids: QueryList<SlickGrid>;
@ -217,6 +210,7 @@ export class AppComponent implements OnInit, AfterViewChecked {
}
constructor(@Inject(forwardRef(() => DataService)) private dataService: DataService,
@Inject(forwardRef(() => ShortcutService)) private shortcuts: ShortcutService,
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
@Inject(forwardRef(() => ChangeDetectorRef)) private cd: ChangeDetectorRef) {}
@ -227,8 +221,18 @@ export class AppComponent implements OnInit, AfterViewChecked {
const self = this;
this.totalElapseExecution = 0;
this.setupResizeBind();
this.dataService.config.then((config) => {
this.config = config;
this.shortcuts.stringCodeFor('event.toggleMessagePane').then((result) => {
self.messageShortcut = result;
});
this.shortcuts.stringCodeFor('event.toggleResultPane').then((result) => {
self.resultShortcut = result;
});
});
this.dataService.getBatches().then((batchs: IGridBatchMetaData[]) => {
self.messages = [];
self._messageActive = self.config.showMessagesDefault;
for (let [batchId, batch] of batchs.entries()) {
let exeTime = Utils.parseTimeString(batch.totalTime);
if (exeTime) {
@ -241,6 +245,9 @@ export class AppComponent implements OnInit, AfterViewChecked {
startTime: new Date(batch.startTime).toLocaleTimeString(),
endTime: new Date(batch.endTime).toLocaleTimeString()
};
if (batch.hasError) {
self._messageActive = true;
}
for (let message of batch.messages) {
let date = new Date(message.time);
let timeString = date.toLocaleTimeString();
@ -557,6 +564,7 @@ export class AppComponent implements OnInit, AfterViewChecked {
*
*/
keyEvent(e): void {
const self = this;
if (e.detail) {
e.which = e.detail.which;
e.ctrlKey = e.detail.ctrlKey;
@ -564,25 +572,13 @@ export class AppComponent implements OnInit, AfterViewChecked {
e.altKey = e.detail.altKey;
e.shiftKey = e.detail.shiftKey;
}
let eString = this.buildEventString(e);
if (shortcuts[eString]) {
this.shortcutfunc[shortcuts[eString]]();
e.stopImmediatePropagation();
}
}
/**
* Builds a event string of ctrl, shift, alt, and a-z + up, down, left, right
* based on a passed Jquery event object, i.e 'ctrl+alt+t'
* @param e The Jquery event object to build the string from
*/
buildEventString(e): string {
let resString = '';
resString += (e.ctrlKey || e.metaKey) ? 'ctrl+' : '';
resString += e.altKey ? 'alt+' : '';
resString += e.shiftKey ? 'shift+' : '';
resString += e.which >= 65 && e.which <= 90 ? String.fromCharCode(e.which).toLowerCase() : keycodes[e.which];
return resString;
let eString = this.shortcuts.buildEventString(e);
this.shortcuts.getEvent(eString).then((result) => {
if (result) {
self.shortcutfunc[<string> result]();
e.stopImmediatePropagation();
}
});
}
/**

Просмотреть файл

@ -2,9 +2,10 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import { Component, Output, EventEmitter } from '@angular/core';
import { Component, Output, EventEmitter, Inject, forwardRef } from '@angular/core';
import {ISlickRange} from './../slickgrid/SelectionModel';
import {ShortcutService} from './../services/shortcuts.service';
import * as Constants from './../constants';
import * as Utils from './../utils';
@ -14,6 +15,7 @@ import * as Utils from './../utils';
@Component({
selector: 'context-menu',
providers: [ShortcutService],
templateUrl: 'dist/html/contextmenu.component.html'
})
@ -30,6 +32,22 @@ export class ContextMenu {
private index: number;
private selection: ISlickRange[];
private isDisabled: boolean;
private keys = {
'event.saveAsCSV': '',
'event.saveAsJSON': '',
'event.selectAll': ''
};
constructor(@Inject(forwardRef(() => ShortcutService)) private shortcuts: ShortcutService) {
const self = this;
for (let key in this.keys) {
if (this.keys.hasOwnProperty(key)) {
this.shortcuts.stringCodeFor(key).then((result) => {
self.keys[key] = result;
});
}
}
}
show(x: number, y: number, batchId: number, resultId: number, index: number, selection: ISlickRange[]): void {
this.batchId = batchId;

Просмотреть файл

@ -73,3 +73,8 @@ export interface IGridIcon {
hoverText: () => string;
functionality: (batchId: number, resultId: number, index: number) => void;
}
export interface IResultsConfig {
shortcuts: { [key: string]: string };
messagesDefaultOpen: boolean;
}

Просмотреть файл

@ -8,7 +8,8 @@ import {Observable} from 'rxjs/Rx';
import { ISlickRange } from './../slickGrid/SelectionModel';
import { IDbColumn, ResultSetSubset, IGridBatchMetaData, ISelectionData, IResultMessage } from './../interfaces';
import { IDbColumn, ResultSetSubset, IGridBatchMetaData, ISelectionData,
IResultMessage, IResultsConfig } from './../interfaces';
/**
* Service which performs the http requests to get the data resultsets from the server.
@ -18,6 +19,8 @@ import { IDbColumn, ResultSetSubset, IGridBatchMetaData, ISelectionData, IResult
export class DataService {
uri: string;
private batchSets: IGridBatchMetaData[];
private _shortcuts;
private _config;
constructor(@Inject(forwardRef(() => Http)) private http) {
// grab the uri from the document for requests
@ -305,4 +308,40 @@ export class DataService {
headers.append('Content-Type', 'application/json');
self.http.post(url, JSON.stringify({ 'message': message }), { headers: headers }).subscribe();
}
get config(): Promise<{[key: string]: string}> {
const self = this;
if (this._config) {
return Promise.resolve(this._config);
} else {
return new Promise<{[key: string]: string}>((resolve, reject) => {
self.http.get('/config').map((res): IResultsConfig => {
return res.json();
}).subscribe((result) => {
self._shortcuts = result.resultShortcuts;
delete result.resultShortcuts;
self._config = result;
resolve(self._config);
});
});
}
}
get shortcuts(): Promise<any> {
const self = this;
if (this._shortcuts) {
return Promise.resolve(this._shortcuts);
} else {
return new Promise<any>((resolve, reject) => {
self.http.get('/config').map((res): IResultsConfig => {
return res.json();
}).subscribe((result) => {
self._shortcuts = result.shortcuts;
delete result.resultShortcuts;
self._config = result;
resolve(self._shortcuts);
});
});
}
}
}

Просмотреть файл

@ -0,0 +1,110 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import {Injectable, Inject, forwardRef} from '@angular/core';
import { DataService } from './data.service';
const keycodes = require('./../keycodes.json!');
const displayCodes = require('./../displayCodes.json!');
/**
* Service which performs the http requests to get the data resultsets from the server.
*/
@Injectable()
export class ShortcutService {
shortcuts: { [key: string]: string };
private waitPromise: Promise<void>;
constructor(@Inject(forwardRef(() => DataService)) private dataService: DataService) {
this.waitPromise = this.dataService.shortcuts.then((result) => {
this.shortcuts = result;
});
}
/**
* determines the platform aware shortcut string for an event for display purposes
* @param eventString The exact event string of the keycode you require (e.g event.toggleMessagePane)
*/
stringCodeFor(eventString: string): Promise<string> {
const self = this;
if (this.shortcuts) {
return Promise.resolve(this.stringCodeForInternal(eventString));
} else {
return new Promise<string>((resolve, reject) => {
self.waitPromise.then(() => {
resolve(self.stringCodeForInternal(eventString));
});
});
}
}
private stringCodeForInternal(eventString: string): string {
let keyString = this.shortcuts[eventString];
if (keyString) {
let platString = window.navigator.platform;
// find the current platform
if (platString.match(/win/i)) {
// iterate through the display replacement that are defined
for (let key in displayCodes['windows']) {
if (displayCodes['windows'].hasOwnProperty(key)) {
keyString = keyString.replace(key, displayCodes['windows'][key]);
}
}
} else if (platString.match(/linux/i)) {
for (let key in displayCodes['linux']) {
if (displayCodes['linux'].hasOwnProperty(key)) {
keyString = keyString.replace(key, displayCodes['linux'][key]);
}
}
} else if (platString.match(/mac/i)) {
for (let key in displayCodes['mac']) {
if (displayCodes['mac'].hasOwnProperty(key)) {
keyString = keyString.replace(key, displayCodes['mac'][key]);
}
}
}
return keyString;
}
}
getEvent(shortcut: string): Promise<string | boolean> {
const self = this;
if (this.shortcuts) {
return Promise.resolve(this.getEventInternal(shortcut));
} else {
return new Promise<string | boolean>((resolve, reject) => {
self.waitPromise.then(() => {
resolve(self.getEventInternal(shortcut));
});
});
}
}
private getEventInternal(shortcut: string): string | boolean {
for (let event in this.shortcuts) {
if (this.shortcuts.hasOwnProperty(event)) {
if (this.shortcuts[event] === shortcut) {
return event;
}
}
}
return false;
}
/**
* Builds a event string of ctrl, shift, alt, and a-z + up, down, left, right
* based on a passed Jquery event object, i.e 'ctrl+alt+t'
* @param e The Jquery event object to build the string from
*/
buildEventString(e): string {
let resString = '';
resString += (e.ctrlKey || e.metaKey) ? 'ctrl+' : '';
resString += e.altKey ? 'alt+' : '';
resString += e.shiftKey ? 'shift+' : '';
resString += e.which >= 65 && e.which <= 90 ? String.fromCharCode(e.which).toLowerCase() : keycodes[e.which];
return resString;
}
}