mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 11:01:37 -05:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
216b6eecc0 | ||
|
|
c0917e9276 | ||
|
|
0bbcbf0d2d | ||
|
|
9b998e3fca | ||
|
|
d7d65cdf21 | ||
|
|
27925289d4 | ||
|
|
03ea265bab | ||
|
|
917f9eead3 | ||
|
|
08f2e72af8 | ||
|
|
a2fb0ec029 | ||
|
|
084042ad13 | ||
|
|
8da3defe24 | ||
|
|
58f9cd32a5 | ||
|
|
f7abf5a2d5 | ||
|
|
c8c6d072f6 | ||
|
|
4d9cc604b9 | ||
|
|
1dc76fa171 | ||
|
|
1d37b9ae9c | ||
|
|
26828602a8 | ||
|
|
e253f3ac89 | ||
|
|
4d59fdea1b | ||
|
|
98a313eb5b | ||
|
|
77e1cd8b32 | ||
|
|
dede5c5ef5 | ||
|
|
d156c0be3d | ||
|
|
dc0bc6e606 | ||
|
|
05040425df | ||
|
|
36f7c283b8 | ||
|
|
9fe4237033 | ||
|
|
b03c0a3e2d | ||
|
|
87946996ed | ||
|
|
cc55023440 | ||
|
|
1f19dfc50d | ||
|
|
950a440350 | ||
|
|
c92b88bfaf | ||
|
|
10875f26dc | ||
|
|
d62e809c18 | ||
|
|
d85bf4f6dd | ||
|
|
801e201cc3 | ||
|
|
e18e0da0c1 | ||
|
|
d046b0a412 | ||
|
|
72084b8fc1 | ||
|
|
2639b2bd2c | ||
|
|
82aa493dfd | ||
|
|
6c3c7c40b5 | ||
|
|
5616751c04 | ||
|
|
7d898ca34d | ||
|
|
e26556b21a | ||
|
|
89e6d363e2 | ||
|
|
c559ac7be9 | ||
|
|
b3fbe47f0a | ||
|
|
c73af4c480 | ||
|
|
8887fe1eac | ||
|
|
ed861a6c96 | ||
|
|
e63bb6a8ec | ||
|
|
8ec09d25ce | ||
|
|
a9a01ae479 | ||
|
|
31a3864789 | ||
|
|
a5c537197c | ||
|
|
4ea13bdbc0 | ||
|
|
b06ddf2dc7 | ||
|
|
2c45ac9df3 | ||
|
|
7735f68502 | ||
|
|
ffb0f5a1c7 | ||
|
|
80c7f9e855 | ||
|
|
709ef4e39f | ||
|
|
4ceb869420 | ||
|
|
432a209184 | ||
|
|
8444271c58 | ||
|
|
2bc97c23d4 |
2
.yarnrc
2
.yarnrc
@@ -1,3 +1,3 @@
|
||||
disturl "https://atom.io/download/electron"
|
||||
target "2.0.7"
|
||||
target "2.0.9"
|
||||
runtime "electron"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"azure-storage": "^2.1.0",
|
||||
"decompress": "^4.2.0",
|
||||
"documentdb": "1.13.0",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.2",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||
"fs-extra-promise": "^1.0.1",
|
||||
"mime": "^1.3.4",
|
||||
"minimist": "^1.2.0",
|
||||
|
||||
@@ -1002,7 +1002,7 @@ hoek@4.x.x:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
|
||||
|
||||
http-proxy-agent@^2.0.0:
|
||||
http-proxy-agent@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
||||
dependencies:
|
||||
@@ -1025,7 +1025,7 @@ http-signature@~1.2.0:
|
||||
jsprim "^1.2.2"
|
||||
sshpk "^1.7.0"
|
||||
|
||||
https-proxy-agent@^2.1.1:
|
||||
https-proxy-agent@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
||||
dependencies:
|
||||
@@ -1843,14 +1843,14 @@ semver@^5.4.1:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
|
||||
|
||||
"service-downloader@github:anthonydresser/service-downloader#0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/2aa9b336b6442e17e24693ddc907030575539798"
|
||||
"service-downloader@github:anthonydresser/service-downloader#0.1.5":
|
||||
version "0.1.5"
|
||||
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/6ebb0465573cc140e461a22f334260f55ef45546"
|
||||
dependencies:
|
||||
decompress "^4.2.0"
|
||||
eventemitter2 "^5.0.1"
|
||||
http-proxy-agent "^2.0.0"
|
||||
https-proxy-agent "^2.1.1"
|
||||
http-proxy-agent "^2.1.0"
|
||||
https-proxy-agent "^2.2.1"
|
||||
mkdirp "^0.5.1"
|
||||
tmp "^0.0.33"
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ let localize = nls.loadMessageBundle();
|
||||
|
||||
export class AzureAccountProviderService implements vscode.Disposable {
|
||||
// CONSTANTS ///////////////////////////////////////////////////////////////
|
||||
private static CommandClearTokenCache = 'accounts.azure.clearTokenCache';
|
||||
private static CommandClearTokenCache = 'accounts.clearTokenCache';
|
||||
private static ConfigurationSection = 'accounts.azure';
|
||||
private static CredentialNamespace = 'azureAccountProviderCredentials';
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
import { ExtensionContext, workspace, window, Disposable, commands, Uri, OutputChannel } from 'vscode';
|
||||
import { ExtensionContext, workspace, window, Disposable, commands, OutputChannel } from 'vscode';
|
||||
import { findGit, Git, IGit } from './git';
|
||||
import { Model } from './model';
|
||||
import { CommandCenter } from './commands';
|
||||
@@ -98,28 +98,29 @@ export async function activate(context: ExtensionContext): Promise<API> {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const config = workspace.getConfiguration('git');
|
||||
const shouldIgnore = config.get<boolean>('ignoreMissingGitWarning') === true;
|
||||
// {{SQL CARBON EDIT}} turn-off Git missing prompt
|
||||
//const config = workspace.getConfiguration('git');
|
||||
//const shouldIgnore = config.get<boolean>('ignoreMissingGitWarning') === true;
|
||||
|
||||
if (!shouldIgnore) {
|
||||
console.warn(err.message);
|
||||
outputChannel.appendLine(err.message);
|
||||
outputChannel.show();
|
||||
// if (!shouldIgnore) {
|
||||
// console.warn(err.message);
|
||||
// outputChannel.appendLine(err.message);
|
||||
// outputChannel.show();
|
||||
|
||||
const download = localize('downloadgit', "Download Git");
|
||||
const neverShowAgain = localize('neverShowAgain', "Don't Show Again");
|
||||
const choice = await window.showWarningMessage(
|
||||
localize('notfound', "Git not found. Install it or configure it using the 'git.path' setting."),
|
||||
download,
|
||||
neverShowAgain
|
||||
);
|
||||
// const download = localize('downloadgit', "Download Git");
|
||||
// const neverShowAgain = localize('neverShowAgain', "Don't Show Again");
|
||||
// const choice = await window.showWarningMessage(
|
||||
// localize('notfound', "Git not found. Install it or configure it using the 'git.path' setting."),
|
||||
// download,
|
||||
// neverShowAgain
|
||||
// );
|
||||
|
||||
if (choice === download) {
|
||||
commands.executeCommand('vscode.open', Uri.parse('https://git-scm.com/'));
|
||||
} else if (choice === neverShowAgain) {
|
||||
await config.update('ignoreMissingGitWarning', true, true);
|
||||
}
|
||||
}
|
||||
// if (choice === download) {
|
||||
// commands.executeCommand('vscode.open', Uri.parse('https://git-scm.com/'));
|
||||
// } else if (choice === neverShowAgain) {
|
||||
// await config.update('ignoreMissingGitWarning', true, true);
|
||||
// }
|
||||
// }
|
||||
|
||||
return new NoopAPIImpl();
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
"dependencies": {
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.7",
|
||||
"opener": "^1.4.3",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.4",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||
"vscode-extension-telemetry": "^0.0.5",
|
||||
"vscode-nls": "^3.2.1"
|
||||
},
|
||||
|
||||
@@ -177,14 +177,14 @@ graceful-fs@^4.1.10:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
||||
|
||||
http-proxy-agent@^2.0.0:
|
||||
http-proxy-agent@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
||||
dependencies:
|
||||
agent-base "4"
|
||||
debug "3.1.0"
|
||||
|
||||
https-proxy-agent@^2.1.1:
|
||||
https-proxy-agent@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
||||
dependencies:
|
||||
@@ -297,14 +297,14 @@ seek-bzip@^1.0.5:
|
||||
dependencies:
|
||||
commander "~2.8.1"
|
||||
|
||||
"service-downloader@github:anthonydresser/service-downloader#0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/3c0abdf8603aca85d2eacfac3c547173e41bf0c7"
|
||||
"service-downloader@github:anthonydresser/service-downloader#0.1.5":
|
||||
version "0.1.5"
|
||||
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/6ebb0465573cc140e461a22f334260f55ef45546"
|
||||
dependencies:
|
||||
decompress "^4.2.0"
|
||||
eventemitter2 "^5.0.1"
|
||||
http-proxy-agent "^2.0.0"
|
||||
https-proxy-agent "^2.1.1"
|
||||
http-proxy-agent "^2.1.0"
|
||||
https-proxy-agent "^2.2.1"
|
||||
mkdirp "^0.5.1"
|
||||
tmp "^0.0.33"
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"dependencies": {
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.8",
|
||||
"opener": "^1.4.3",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.4",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||
"vscode-extension-telemetry": "^0.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "1.5.0-alpha.28",
|
||||
"version": "1.5.0-alpha.34",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-netcoreapp2.1.zip",
|
||||
"Windows_64": "win-x64-netcoreapp2.1.zip",
|
||||
|
||||
@@ -191,14 +191,14 @@ graceful-fs@^4.1.10:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
||||
|
||||
http-proxy-agent@^2.0.0:
|
||||
http-proxy-agent@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
||||
dependencies:
|
||||
agent-base "4"
|
||||
debug "3.1.0"
|
||||
|
||||
https-proxy-agent@^2.1.1:
|
||||
https-proxy-agent@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
||||
dependencies:
|
||||
@@ -315,14 +315,14 @@ semver@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
|
||||
|
||||
"service-downloader@github:anthonydresser/service-downloader#0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/3c0abdf8603aca85d2eacfac3c547173e41bf0c7"
|
||||
"service-downloader@github:anthonydresser/service-downloader#0.1.5":
|
||||
version "0.1.5"
|
||||
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/6ebb0465573cc140e461a22f334260f55ef45546"
|
||||
dependencies:
|
||||
decompress "^4.2.0"
|
||||
eventemitter2 "^5.0.1"
|
||||
http-proxy-agent "^2.0.0"
|
||||
https-proxy-agent "^2.1.1"
|
||||
http-proxy-agent "^2.1.0"
|
||||
https-proxy-agent "^2.2.1"
|
||||
mkdirp "^0.5.1"
|
||||
tmp "^0.0.33"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sqlops",
|
||||
"version": "0.33.2",
|
||||
"version": "0.33.7",
|
||||
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
@@ -64,7 +64,7 @@
|
||||
"reflect-metadata": "^0.1.8",
|
||||
"rxjs": "5.4.0",
|
||||
"semver": "^5.5.0",
|
||||
"slickgrid": "github:anthonydresser/SlickGrid#2.3.25",
|
||||
"slickgrid": "github:anthonydresser/SlickGrid#2.3.27",
|
||||
"spdlog": "0.7.1",
|
||||
"sudo-prompt": "8.2.0",
|
||||
"svg.js": "^2.2.5",
|
||||
@@ -167,5 +167,8 @@
|
||||
"windows-foreground-love": "0.1.0",
|
||||
"windows-mutex": "^0.2.0",
|
||||
"windows-process-tree": "0.2.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"rc": "1.2.8"
|
||||
}
|
||||
}
|
||||
|
||||
2791
samples/extensionSamples/package-lock.json
generated
2791
samples/extensionSamples/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
2642
samples/serverReports/package-lock.json
generated
2642
samples/serverReports/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
2794
samples/sp_whoIsActive/package-lock.json
generated
2794
samples/sp_whoIsActive/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
2687
samples/sqlservices/package-lock.json
generated
2687
samples/sqlservices/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -518,7 +518,8 @@ export default class MainController implements vscode.Disposable {
|
||||
let runButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: 'Run',
|
||||
iconPath: runIcon
|
||||
iconPath: runIcon,
|
||||
title: 'Run title'
|
||||
}).component();
|
||||
|
||||
let monitorLightPath = vscode.Uri.file(path.join(__dirname, '..', 'media', 'monitor.svg'));
|
||||
@@ -530,7 +531,8 @@ export default class MainController implements vscode.Disposable {
|
||||
let monitorButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: 'Monitor',
|
||||
iconPath: monitorIcon
|
||||
iconPath: monitorIcon,
|
||||
title: 'Monitor title'
|
||||
}).component();
|
||||
let toolbarModel = view.modelBuilder.toolbarContainer()
|
||||
.withToolbarItems([{
|
||||
|
||||
@@ -23,6 +23,18 @@ export class Button extends vsButton {
|
||||
this.$el.style('outline-color', this.buttonFocusOutline ? this.buttonFocusOutline.toString() : null);
|
||||
this.$el.style('outline-width', '1px');
|
||||
});
|
||||
|
||||
this.$el.on(DOM.EventType.MOUSE_DOWN, (e) => {
|
||||
const mouseEvent = e as MouseEvent;
|
||||
if (!this.$el.hasClass('disabled') && mouseEvent.button === 0) {
|
||||
this.$el.addClass('active');
|
||||
}
|
||||
});
|
||||
|
||||
this.$el.on([DOM.EventType.MOUSE_UP], (e) => {
|
||||
DOM.EventHelper.stop(e);
|
||||
this.$el.removeClass('active');
|
||||
});
|
||||
}
|
||||
|
||||
public style(styles: IButtonStyles): void {
|
||||
|
||||
@@ -217,7 +217,7 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
if (this._modalOptions.isAngular === false && this._modalOptions.hasErrors) {
|
||||
let builder = errorMessagesInFooter ? this._leftFooter : body;
|
||||
builder.div({ class: 'dialogErrorMessage', id: 'dialogErrorMessage' }, (errorMessageContainer) => {
|
||||
errorMessageContainer.div({ class: 'icon error' }, (iconContainer) => {
|
||||
errorMessageContainer.div({ class: 'sql icon error' }, (iconContainer) => {
|
||||
this._errorIconElement = iconContainer.getHTMLElement();
|
||||
this._errorIconElement.style.visibility = 'hidden';
|
||||
});
|
||||
@@ -518,5 +518,6 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
public dispose() {
|
||||
super.dispose();
|
||||
this._keydownListener.dispose();
|
||||
this._footerButtons = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@ export class TabbedPanel extends Disposable implements IThemable {
|
||||
private _onTabChange = new Emitter<PanelTabIdentifier>();
|
||||
public onTabChange: Event<PanelTabIdentifier> = this._onTabChange.event;
|
||||
|
||||
private tabHistory: string[] = [];
|
||||
|
||||
constructor(private container: HTMLElement, private options: IPanelOptions = defaultOptions) {
|
||||
super();
|
||||
this.$parent = this._register($('.tabbedPanel'));
|
||||
@@ -152,6 +154,7 @@ export class TabbedPanel extends Disposable implements IThemable {
|
||||
}
|
||||
|
||||
this._shownTab = id;
|
||||
this.tabHistory.push(id);
|
||||
this.$body.clearChildren();
|
||||
let tab = this._tabMap.get(this._shownTab);
|
||||
this.$body.attr('aria-labelledby', tab.identifier);
|
||||
@@ -173,6 +176,26 @@ export class TabbedPanel extends Disposable implements IThemable {
|
||||
}
|
||||
this._tabMap.get(tab).header.destroy();
|
||||
this._tabMap.delete(tab);
|
||||
if (this._shownTab === tab) {
|
||||
this._shownTab = undefined;
|
||||
while (this._shownTab === undefined && this.tabHistory.length > 0) {
|
||||
let lastTab = this.tabHistory.shift();
|
||||
if (this._tabMap.get(lastTab)) {
|
||||
this.showTab(lastTab);
|
||||
}
|
||||
}
|
||||
|
||||
// this shouldn't happen but just in case
|
||||
if (this._shownTab === undefined && this._tabMap.size > 0) {
|
||||
this.showTab(this._tabMap.keys().next().value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.options.showHeaderWhenSingleView && this._tabMap.size === 1 && this._headerVisible) {
|
||||
this.$header.offDOM();
|
||||
this._headerVisible = false;
|
||||
this.layout(this._currentDimensions);
|
||||
}
|
||||
}
|
||||
|
||||
public style(styles: IPanelStyles): void {
|
||||
@@ -180,14 +203,16 @@ export class TabbedPanel extends Disposable implements IThemable {
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
this._currentDimensions = dimension;
|
||||
this.$parent.style('height', dimension.height + 'px');
|
||||
this.$parent.style('width', dimension.width + 'px');
|
||||
this.$header.style('width', dimension.width + 'px');
|
||||
this.$body.style('width', dimension.width + 'px');
|
||||
const bodyHeight = dimension.height - (this._headerVisible ? this.headersize : 0);
|
||||
this.$body.style('height', bodyHeight + 'px');
|
||||
this._layoutCurrentTab(new Dimension(dimension.width, bodyHeight));
|
||||
if (dimension) {
|
||||
this._currentDimensions = dimension;
|
||||
this.$parent.style('height', dimension.height + 'px');
|
||||
this.$parent.style('width', dimension.width + 'px');
|
||||
this.$header.style('width', dimension.width + 'px');
|
||||
this.$body.style('width', dimension.width + 'px');
|
||||
const bodyHeight = dimension.height - (this._headerVisible ? this.headersize : 0);
|
||||
this.$body.style('height', bodyHeight + 'px');
|
||||
this._layoutCurrentTab(new Dimension(dimension.width, bodyHeight));
|
||||
}
|
||||
}
|
||||
|
||||
private _layoutCurrentTab(dimension: Dimension): void {
|
||||
|
||||
@@ -34,6 +34,8 @@ export interface IView extends HeightIView {
|
||||
readonly onDidChange: Event<number | undefined>;
|
||||
render(container: HTMLElement, orientation: Orientation): void;
|
||||
layout(size: number, orientation: Orientation): void;
|
||||
onAdd?(): void;
|
||||
onRemove?(): void;
|
||||
}
|
||||
|
||||
interface ISashEvent {
|
||||
@@ -48,6 +50,8 @@ interface IViewItem extends HeightIViewItem {
|
||||
container: HTMLElement;
|
||||
disposable: IDisposable;
|
||||
layout(): void;
|
||||
onRemove: () => void;
|
||||
onAdd: () => void;
|
||||
}
|
||||
|
||||
interface ISashItem {
|
||||
@@ -109,6 +113,9 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
||||
private _onDidSashReset = new Emitter<void>();
|
||||
readonly onDidSashReset = this._onDidSashReset.event;
|
||||
|
||||
private _onScroll = new Emitter<number>();
|
||||
readonly onScroll = this._onScroll.event;
|
||||
|
||||
get length(): number {
|
||||
return this.viewItems.length;
|
||||
}
|
||||
@@ -124,6 +131,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
||||
debounceEvent(this.scrollable.onScroll, (l, e) => e, 25)(e => {
|
||||
this.render(e.scrollTop, e.height);
|
||||
this.relayout();
|
||||
this._onScroll.fire(e.scrollTop);
|
||||
});
|
||||
let domNode = this.scrollable.getDomNode();
|
||||
dom.addClass(this.el, 'monaco-scroll-split-view');
|
||||
@@ -155,6 +163,9 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
||||
});
|
||||
const disposable = combinedDisposable([onChangeDisposable, containerDisposable]);
|
||||
|
||||
const onAdd = view.onAdd ? () => view.onAdd() : () => { };
|
||||
const onRemove = view.onRemove ? () => view.onRemove() : () => { };
|
||||
|
||||
const layoutContainer = this.orientation === Orientation.VERTICAL
|
||||
? size => item.container.style.height = `${item.size}px`
|
||||
: size => item.container.style.width = `${item.size}px`;
|
||||
@@ -165,7 +176,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
||||
};
|
||||
|
||||
size = Math.round(size);
|
||||
const item: IViewItem = { view, container, size, layout, disposable, height: size, top: 0, width: 0 };
|
||||
const item: IViewItem = { onRemove, onAdd, view, container, size, layout, disposable, height: size, top: 0, width: 0 };
|
||||
this.viewItems.splice(viewIndex, 0, item);
|
||||
|
||||
this.onInsertItems(new ArrayIterator([item]), viewIndex > 0 ? this.viewItems[viewIndex - 1].view.id : undefined);
|
||||
@@ -220,6 +231,9 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
||||
});
|
||||
const disposable = combinedDisposable([onChangeDisposable, containerDisposable]);
|
||||
|
||||
const onAdd = view.onAdd ? () => view.onAdd() : () => { };
|
||||
const onRemove = view.onRemove ? () => view.onRemove() : () => { };
|
||||
|
||||
const layoutContainer = this.orientation === Orientation.VERTICAL
|
||||
? size => item.container.style.height = `${item.size}px`
|
||||
: size => item.container.style.width = `${item.size}px`;
|
||||
@@ -230,7 +244,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
||||
};
|
||||
|
||||
size = Math.round(size);
|
||||
const item: IViewItem = { view, container, size, layout, disposable, height: size, top: 0, width: 0 };
|
||||
const item: IViewItem = { onAdd, onRemove, view, container, size, layout, disposable, height: size, top: 0, width: 0 };
|
||||
this.viewItems.splice(index, 0, item);
|
||||
|
||||
this.onInsertItems(new ArrayIterator([item]), index > 0 ? this.viewItems[index - 1].view.id : undefined);
|
||||
@@ -330,9 +344,16 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
||||
this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndex);
|
||||
}
|
||||
|
||||
public setScrollPosition(position: number) {
|
||||
this.scrollable.setScrollPosition({ scrollTop: position });
|
||||
}
|
||||
|
||||
layout(size: number): void {
|
||||
const previousSize = Math.max(this.size, this.contentSize);
|
||||
const previousSize = this.size;
|
||||
this.size = size;
|
||||
this.contentSize = 0;
|
||||
this.lastRenderHeight = undefined;
|
||||
this.lastRenderTop = undefined;
|
||||
this.resize(this.viewItems.length - 1, size - previousSize);
|
||||
}
|
||||
|
||||
@@ -484,6 +505,8 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
||||
}
|
||||
|
||||
item.layout();
|
||||
|
||||
item.onAdd();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -493,6 +516,8 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
||||
}
|
||||
|
||||
this.el.removeChild(item.container);
|
||||
|
||||
item.onRemove();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { range } from 'vs/base/common/arrays';
|
||||
|
||||
/**
|
||||
* Implements the various additional navigation keybindings we want out of slickgrid
|
||||
@@ -50,6 +51,14 @@ export class AdditionalKeyBindings<T> implements Slick.Plugin<T> {
|
||||
}
|
||||
} else if (event.equals(KeyCode.End | KeyMod.CtrlCmd)) {
|
||||
this.grid.setActiveCell(this.grid.getDataLength() - 1, this.grid.getColumns().length - 1);
|
||||
} else if (event.equals(KeyCode.KEY_A | KeyMod.CtrlCmd)) {
|
||||
// check if we can set the rows directly on the selectionModel, its cleaner
|
||||
let selectionModel = this.grid.getSelectionModel();
|
||||
if (selectionModel) {
|
||||
selectionModel.setSelectedRanges([new Slick.Range(0, 0, this.grid.getDataLength() - 1, this.grid.getColumns().length - 1)]);
|
||||
} else {
|
||||
this.grid.setSelectedRows(range(this.grid.getDataLength()));
|
||||
}
|
||||
} else {
|
||||
handled = false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
|
||||
require.__$__nodeRequire('slickgrid/plugins/slick.cellrangedecorator');
|
||||
|
||||
const defaultOptions: ICellRangeSelectorOptions = {
|
||||
selectionCss: {
|
||||
'border': '2px dashed blue'
|
||||
@@ -44,6 +42,8 @@ export class CellRangeSelector<T> implements ICellRangeSelector<T> {
|
||||
public onCellRangeSelected = new Slick.Event<{ range: Slick.Range }>();
|
||||
|
||||
constructor(private options: ICellRangeSelectorOptions) {
|
||||
require.__$__nodeRequire('slickgrid/plugins/slick.cellrangedecorator');
|
||||
|
||||
this.options = mixin(this.options, defaultOptions, false);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ export class CellRangeSelector<T> implements ICellRangeSelector<T> {
|
||||
|
||||
this.canvas.classList.add(this.options.dragClass);
|
||||
|
||||
this.grid.focus();
|
||||
this.grid.setActiveCell(cell.row, cell.cell);
|
||||
|
||||
let start = this.grid.getCellFromPoint(
|
||||
dd.startX - $(this.canvas).offset().left,
|
||||
@@ -128,8 +128,11 @@ export class CellRangeSelector<T> implements ICellRangeSelector<T> {
|
||||
this.canvas.classList.remove(this.options.dragClass);
|
||||
this.dragging = false;
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
this.decorator.hide();
|
||||
// if this happens to fast there is a chance we don't have the necessary information to actually do proper selection
|
||||
if (!dd || !dd.range || !dd.range.start || !dd.range.end) {
|
||||
return;
|
||||
}
|
||||
this.onCellRangeSelected.notify({
|
||||
range: new Slick.Range(
|
||||
dd.range.start.row,
|
||||
|
||||
@@ -6,8 +6,6 @@ import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
|
||||
import { CellRangeSelector, ICellRangeSelector } from 'sql/base/browser/ui/table/plugins/cellRangeSelector';
|
||||
|
||||
require.__$__nodeRequire('slickgrid/plugins/slick.cellrangedecorator');
|
||||
|
||||
export interface ICellSelectionModelOptions {
|
||||
cellRangeSelector?: any;
|
||||
selectActiveCell?: boolean;
|
||||
|
||||
@@ -38,8 +38,8 @@ export class CopyKeybind<T> implements Slick.Plugin<T> {
|
||||
let selectionModel = this.grid.getSelectionModel();
|
||||
let ranges: Slick.Range[];
|
||||
// check to see if we can get the range from the model directly
|
||||
if (selectionModel && (<any>selectionModel).getSelectedRanges) {
|
||||
ranges = (<any>selectionModel).getSelectedRanges();
|
||||
if (selectionModel) {
|
||||
ranges = selectionModel.getSelectedRanges();
|
||||
} else {
|
||||
let selectedRows = this.grid.getSelectedRows();
|
||||
let startColumn = 0;
|
||||
|
||||
@@ -36,23 +36,31 @@ export class RowNumberColumn<T> implements Slick.Plugin<T> {
|
||||
private handleClick(e: MouseEvent, args: Slick.OnClickEventArgs<T>): void {
|
||||
if (this.grid.getColumns()[args.cell].id === 'rowNumber') {
|
||||
this.grid.setActiveCell(args.row, 1);
|
||||
this.grid.setSelectedRows([args.row]);
|
||||
if (this.grid.getSelectionModel()) {
|
||||
this.grid.setSelectedRows([args.row]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>): void {
|
||||
if (args.column.id === 'rowNumber') {
|
||||
this.grid.setActiveCell(0, 1);
|
||||
this.grid.setSelectedRows(range(this.grid.getDataLength()));
|
||||
if (this.grid.getSelectionModel()) {
|
||||
this.grid.setSelectedRows(range(this.grid.getDataLength()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getColumnDefinition(): Slick.Column<T> {
|
||||
// that smallest we can make it is 22 due to padding and margins in the cells
|
||||
let columnWidth = Math.max(this.options.numberOfRows.toString().length * sizePerDigit, 22);
|
||||
return {
|
||||
id: 'rowNumber',
|
||||
name: '',
|
||||
field: 'rowNumber',
|
||||
width: this.options.numberOfRows.toString().length * sizePerDigit,
|
||||
width: columnWidth,
|
||||
minWidth: columnWidth,
|
||||
maxWidth: columnWidth,
|
||||
resizable: false,
|
||||
cssClass: this.options.cssClass,
|
||||
focusable: false,
|
||||
|
||||
@@ -166,6 +166,10 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
|
||||
this._grid.setData(this._data, true);
|
||||
}
|
||||
|
||||
getData(): Slick.DataProvider<T> {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
get columns(): Slick.Column<T>[] {
|
||||
return this._grid.getColumns();
|
||||
}
|
||||
@@ -244,7 +248,6 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
|
||||
}
|
||||
}
|
||||
this.resizeCanvas();
|
||||
this.autosizeColumns();
|
||||
}
|
||||
|
||||
autosizeColumns() {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
'use strict';
|
||||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
/**
|
||||
* Implementation of vs/base/common/event/echo that is clearable
|
||||
@@ -37,3 +38,52 @@ export function echo<T>(event: Event<T>, nextTick = false, buffer: T[] = []): {
|
||||
clear
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of vs/base/common/event/debounceEvent that is clearable
|
||||
*/
|
||||
export function debounceEvent<T>(event: Event<T>, merger: (last: T, event: T) => T, delay?: number, leading?: boolean): { clear: () => void; event: Event<T> };
|
||||
export function debounceEvent<I, O>(event: Event<I>, merger: (last: O, event: I) => O, delay?: number, leading?: boolean): { clear: () => void; event: Event<O> };
|
||||
export function debounceEvent<I, O>(event: Event<I>, merger: (last: O, event: I) => O, delay: number = 100, leading = false): { clear: () => void; event: Event<O> } {
|
||||
|
||||
let subscription: IDisposable;
|
||||
let output: O = undefined;
|
||||
let handle: any = undefined;
|
||||
let numDebouncedCalls = 0;
|
||||
|
||||
const clear = () => output = undefined;
|
||||
|
||||
const emitter = new Emitter<O>({
|
||||
onFirstListenerAdd() {
|
||||
subscription = event(cur => {
|
||||
numDebouncedCalls++;
|
||||
output = merger(output, cur);
|
||||
|
||||
if (leading && !handle) {
|
||||
emitter.fire(output);
|
||||
}
|
||||
|
||||
clearTimeout(handle);
|
||||
handle = setTimeout(() => {
|
||||
let _output = output;
|
||||
output = undefined;
|
||||
handle = undefined;
|
||||
if (!leading || numDebouncedCalls > 1) {
|
||||
emitter.fire(_output);
|
||||
}
|
||||
|
||||
numDebouncedCalls = 0;
|
||||
}, delay);
|
||||
});
|
||||
},
|
||||
onLastListenerRemove() {
|
||||
subscription.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
event: emitter.event,
|
||||
clear
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -63,21 +63,21 @@
|
||||
background: url("globalerror.svg") center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .icon.error,
|
||||
.vs-dark .icon.error,
|
||||
.hc-black .icon.error {
|
||||
.vs .sql.icon.error,
|
||||
.vs-dark .sql.icon.error,
|
||||
.hc-black .sql.icon.error {
|
||||
content: url("status_error.svg");
|
||||
}
|
||||
|
||||
.vs .icon.warning,
|
||||
.vs-dark .icon.warning,
|
||||
.hc-black .icon.warning {
|
||||
.vs .sql.icon.warning,
|
||||
.vs-dark .sql.icon.warning,
|
||||
.hc-black .sql.icon.warning {
|
||||
content: url("status_warning.svg");
|
||||
}
|
||||
|
||||
.vs .icon.info,
|
||||
.vs-dark .icon.info,
|
||||
.hc-black .icon.info {
|
||||
.vs .sql.icon.info,
|
||||
.vs-dark .sql.icon.info,
|
||||
.hc-black .sql.icon.info {
|
||||
content: url("status_info.svg");
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ export class AccountPicker extends Disposable {
|
||||
|
||||
// Create refresh account action
|
||||
this._refreshContainer = DOM.append(this._rootElement, DOM.$('div.refresh-container'));
|
||||
DOM.append(this._refreshContainer, DOM.$('div.icon warning'));
|
||||
DOM.append(this._refreshContainer, DOM.$('div.sql icon warning'));
|
||||
let actionBar = new ActionBar(this._refreshContainer, { animated: false });
|
||||
this._refreshAccountAction = this._instantiationService.createInstance(RefreshAccountAction);
|
||||
actionBar.push(this._refreshAccountAction, { icon: false, label: true });
|
||||
|
||||
@@ -123,6 +123,7 @@ export class ConnectionStore {
|
||||
.then(savedCred => {
|
||||
if (savedCred) {
|
||||
credentialsItem.password = savedCred.password;
|
||||
credentialsItem.options['password'] = savedCred.password;
|
||||
}
|
||||
resolve({ profile: credentialsItem, savedCred: !!savedCred });
|
||||
},
|
||||
|
||||
@@ -156,11 +156,6 @@ export class ConnectionWidget {
|
||||
}
|
||||
|
||||
private fillInConnectionForm(): void {
|
||||
// Connection name
|
||||
let connectionNameOption = this._optionsMaps[ConnectionOptionSpecialType.connectionName];
|
||||
let connectionNameBuilder = DialogHelper.appendRow(this._tableContainer, connectionNameOption.displayName, 'connection-label', 'connection-input');
|
||||
this._connectionNameInputBox = new InputBox(connectionNameBuilder.getHTMLElement(), this._contextViewService, { ariaLabel: connectionNameOption.displayName });
|
||||
|
||||
// Server name
|
||||
let serverNameOption = this._optionsMaps[ConnectionOptionSpecialType.serverName];
|
||||
let serverNameBuilder = DialogHelper.appendRow(this._tableContainer, serverNameOption.displayName, 'connection-label', 'connection-input');
|
||||
@@ -222,6 +217,11 @@ export class ConnectionWidget {
|
||||
let serverGroupBuilder = DialogHelper.appendRow(this._tableContainer, this._serverGroupDisplayString, 'connection-label', 'connection-input');
|
||||
DialogHelper.appendInputSelectBox(serverGroupBuilder, this._serverGroupSelectBox);
|
||||
|
||||
// Connection name
|
||||
let connectionNameOption = this._optionsMaps[ConnectionOptionSpecialType.connectionName];
|
||||
let connectionNameBuilder = DialogHelper.appendRow(this._tableContainer, connectionNameOption.displayName, 'connection-label', 'connection-input');
|
||||
this._connectionNameInputBox = new InputBox(connectionNameBuilder.getHTMLElement(), this._contextViewService, { ariaLabel: connectionNameOption.displayName });
|
||||
|
||||
let AdvancedLabel = localize('advanced', 'Advanced...');
|
||||
this._advancedButton = this.createAdvancedButton(this._tableContainer, AdvancedLabel);
|
||||
}
|
||||
@@ -398,7 +398,7 @@ export class ConnectionWidget {
|
||||
|
||||
public focusOnOpen(): void {
|
||||
this._handleClipboard();
|
||||
this._connectionNameInputBox.focus();
|
||||
this._serverNameInputBox.focus();
|
||||
this.focusPasswordIfNeeded();
|
||||
this.clearValidationMessages();
|
||||
}
|
||||
@@ -492,6 +492,7 @@ export class ConnectionWidget {
|
||||
this._databaseNameInputBox.enabled = false;
|
||||
this._userNameInputBox.disable();
|
||||
this._passwordInputBox.disable();
|
||||
this._connectionNameInputBox.disable();
|
||||
this._rememberPasswordCheckBox.enabled = false;
|
||||
if (this._authTypeSelectBox) {
|
||||
this._authTypeSelectBox.disable();
|
||||
@@ -503,6 +504,7 @@ export class ConnectionWidget {
|
||||
|
||||
this._serverGroupSelectBox.enable();
|
||||
this._serverNameInputBox.enable();
|
||||
this._connectionNameInputBox.enable();
|
||||
this._databaseNameInputBox.enabled = true;
|
||||
let currentAuthType: AuthenticationType = undefined;
|
||||
if (this._authTypeSelectBox) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as TelemetryUtils from 'sql/common/telemetryUtilities';
|
||||
import { IInsightsView, IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
|
||||
import { memoize, unmemoize } from 'sql/base/common/decorators';
|
||||
import { mixin } from 'sql/base/common/objects';
|
||||
import { LegendPosition, DataDirection, ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
import * as colors from 'vs/platform/theme/common/colorRegistry';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
@@ -21,29 +22,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
declare var Chart: any;
|
||||
|
||||
export enum ChartType {
|
||||
Bar = 'bar',
|
||||
Doughnut = 'doughnut',
|
||||
HorizontalBar = 'horizontalBar',
|
||||
Line = 'line',
|
||||
Pie = 'pie',
|
||||
TimeSeries = 'timeSeries',
|
||||
Scatter = 'scatter'
|
||||
}
|
||||
|
||||
export enum DataDirection {
|
||||
Vertical = 'vertical',
|
||||
Horizontal = 'horizontal'
|
||||
}
|
||||
|
||||
export enum LegendPosition {
|
||||
Top = 'top',
|
||||
Bottom = 'bottom',
|
||||
Left = 'left',
|
||||
Right = 'right',
|
||||
None = 'none'
|
||||
}
|
||||
|
||||
export function customMixin(destination: any, source: any, overwrite?: boolean): any {
|
||||
if (types.isObject(source)) {
|
||||
mixin(destination, source, overwrite, customMixin);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export enum ChartType {
|
||||
Bar = 'bar',
|
||||
Doughnut = 'doughnut',
|
||||
HorizontalBar = 'horizontalBar',
|
||||
Line = 'line',
|
||||
Pie = 'pie',
|
||||
TimeSeries = 'timeSeries',
|
||||
Scatter = 'scatter'
|
||||
}
|
||||
|
||||
export enum DataDirection {
|
||||
Vertical = 'vertical',
|
||||
Horizontal = 'horizontal'
|
||||
}
|
||||
|
||||
export enum LegendPosition {
|
||||
Top = 'top',
|
||||
Bottom = 'bottom',
|
||||
Left = 'left',
|
||||
Right = 'right',
|
||||
None = 'none'
|
||||
}
|
||||
|
||||
export enum DataType {
|
||||
Number = 'number',
|
||||
Point = 'point'
|
||||
}
|
||||
@@ -3,8 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ChartInsight, ChartType, customMixin, IChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import { ChartInsight, customMixin, IChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import { mixin } from 'sql/base/common/objects';
|
||||
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import * as colors from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import PieChart from './pieChart.component';
|
||||
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
export default class DoughnutChart extends PieChart {
|
||||
protected readonly chartType: ChartType = ChartType.Doughnut;
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import BarChart from './barChart.component';
|
||||
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
export default class HorizontalBarChart extends BarChart {
|
||||
protected readonly chartType: ChartType = ChartType.HorizontalBar;
|
||||
|
||||
@@ -3,16 +3,13 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ChartType, customMixin, defaultChartConfig, IDataSet, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
|
||||
import { defaultChartConfig, IDataSet, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import BarChart, { IBarChartConfig } from './barChart.component';
|
||||
import { memoize, unmemoize } from 'sql/base/common/decorators';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { clone } from 'sql/base/common/objects';
|
||||
|
||||
export enum DataType {
|
||||
Number = 'number',
|
||||
Point = 'point'
|
||||
}
|
||||
import { ChartType, DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
export interface ILineConfig extends IBarChartConfig {
|
||||
dataType?: DataType;
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ChartInsight, ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import { ChartInsight } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
export default class PieChart extends ChartInsight {
|
||||
protected readonly chartType: ChartType = ChartType.Pie;
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ChartType, defaultChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import { defaultChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import LineChart, { ILineConfig } from './lineChart.component';
|
||||
import { clone } from 'sql/base/common/objects';
|
||||
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { clone } from 'sql/base/common/objects';
|
||||
|
||||
const defaultScatterConfig = mixin(clone(defaultChartConfig), { dataType: 'point', dataDirection: 'horizontal' }) as ILineConfig;
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { defaultChartConfig, IPointDataSet, ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import { defaultChartConfig, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import LineChart, { ILineConfig } from './lineChart.component';
|
||||
import { clone } from 'sql/base/common/objects';
|
||||
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
|
||||
@@ -86,7 +86,7 @@ export class TasksWidget extends DashboardWidget implements IDashboardWidget, On
|
||||
}).filter(i => !!i);
|
||||
}
|
||||
|
||||
this._tasks = tasks.map(i => TaskRegistry.getCommandActionById(i)).filter(v => !!v);
|
||||
this._tasks = tasks.map(i => MenuRegistry.getCommand(i)).filter(v => !!v);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<div class="option check" #encryptCheckContainer>
|
||||
</div>
|
||||
<div class="option" #encryptWarningContainer>
|
||||
<div class="icon warning">
|
||||
<div class="sql icon warning">
|
||||
</div>
|
||||
<div class="warning-message">
|
||||
{{localizedStrings.NO_ENCRYPTOR_WARNING}}
|
||||
|
||||
@@ -25,7 +25,7 @@ classes should alter those!
|
||||
overflow: hidden;
|
||||
-o-text-overflow: ellipsis;
|
||||
text-overflow: ellipsis;
|
||||
height: 16px;
|
||||
height: 28px;
|
||||
line-height: 16px;
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
@@ -35,7 +35,7 @@ classes should alter those!
|
||||
border-bottom: 2px solid #bbb;
|
||||
float: left;
|
||||
background-color: #eee;
|
||||
box-sizing: content-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.slick-headerrow-column.ui-state-default, .slick-footerrow-column.ui-state-default {
|
||||
@@ -104,6 +104,11 @@ classes should alter those!
|
||||
white-space: nowrap;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.slick-cell .grid-cell-value-container.missing-value {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.slick-cell, .slick-headerrow-column{
|
||||
border-bottom-color: silver;
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
|
||||
this.onCellEditEnd = (event: Slick.OnCellChangeEventArgs<any>): void => {
|
||||
// Store the value that was set
|
||||
self.currentEditCellValue = event.item[event.cell - 1];
|
||||
self.currentEditCellValue = event.item[event.cell];
|
||||
};
|
||||
|
||||
this.overrideCellFn = (rowNumber, columnId, value?, data?): string => {
|
||||
@@ -270,18 +270,18 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
|
||||
return self.dataService.updateCell(sessionRowId, self.currentCell.column - 1, self.currentEditCellValue)
|
||||
.then(
|
||||
result => {
|
||||
// Cell update was successful, update the flags
|
||||
self.currentEditCellValue = null;
|
||||
self.setCellDirtyState(row, self.currentCell.column, result.cell.isDirty);
|
||||
self.setRowDirtyState(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);
|
||||
}
|
||||
result => {
|
||||
// Cell update was successful, update the flags
|
||||
self.currentEditCellValue = null;
|
||||
self.setCellDirtyState(row, self.currentCell.column, result.cell.isDirty);
|
||||
self.setRowDirtyState(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);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -377,10 +377,11 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
index => { return {}; }
|
||||
),
|
||||
columnDefinitions: [rowNumberColumn.getColumnDefinition()].concat(resultSet.columnInfo.map((c, i) => {
|
||||
let columnIndex = (i + 1).toString();
|
||||
return {
|
||||
id: i.toString(),
|
||||
id: columnIndex,
|
||||
name: escape(c.columnName),
|
||||
field: i.toString(),
|
||||
field: columnIndex,
|
||||
formatter: Services.textFormatter,
|
||||
isEditable: c.isUpdatable
|
||||
};
|
||||
@@ -464,7 +465,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
} else {
|
||||
try {
|
||||
// Perform a revert row operation
|
||||
if (this.currentCell) {
|
||||
if (this.currentCell && this.currentCell.row !== undefined && this.currentCell.row !== null) {
|
||||
await this.dataService.revertRow(this.currentCell.row);
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { IGridDataSet } from 'sql/parts/grid/common/interfaces';
|
||||
import { IInsightData, IInsightsView, IInsightsConfig } from 'sql/parts/dashboard/widgets/insights/interfaces';
|
||||
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
|
||||
import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
|
||||
import { DataType, ILineConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component';
|
||||
import { ILineConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component';
|
||||
import * as PathUtilities from 'sql/common/pathUtilities';
|
||||
import { IChartViewActionContext, CopyAction, CreateInsightAction, SaveImageAction } from 'sql/parts/grid/views/query/chartViewerActions';
|
||||
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
||||
@@ -24,10 +24,11 @@ import * as Constants from 'sql/parts/query/common/constants';
|
||||
import { SelectBox as AngularSelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component';
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
||||
import { LegendPosition, DataDirection, DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
/* Insights */
|
||||
import {
|
||||
ChartInsight, DataDirection, LegendPosition
|
||||
ChartInsight
|
||||
} from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
@@ -188,8 +188,16 @@ export class InsightsDialogView extends Modal {
|
||||
for (let i = 0; i < this._model.columns.length; i++) {
|
||||
resourceArray.push({ label: this._model.columns[i], value: element.data[i], data: element.data });
|
||||
}
|
||||
|
||||
this._bottomTableData.clear();
|
||||
this._bottomTableData.push(resourceArray);
|
||||
// this table view has to be collapsed and expanded
|
||||
// because the initial expand doesn't have the
|
||||
// loaded data
|
||||
if (bottomTableView.isExpanded()) {
|
||||
bottomTableView.collapse();
|
||||
bottomTableView.expand();
|
||||
}
|
||||
this._enableTaskButtons(true);
|
||||
} else {
|
||||
this._enableTaskButtons(false);
|
||||
@@ -334,6 +342,7 @@ export class InsightsDialogView extends Modal {
|
||||
this.hide();
|
||||
dispose(this._taskButtonDisposables);
|
||||
this._taskButtonDisposables = [];
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
protected onClose(e: StandardKeyboardEvent) {
|
||||
|
||||
@@ -70,8 +70,7 @@ export abstract class JobManagementView extends TabChild implements AfterContent
|
||||
abstract onFirstVisible();
|
||||
|
||||
protected openContextMenu(event): void {
|
||||
let grid = this._table.grid;
|
||||
let rowIndex = grid.getCellFromEvent(event).row;
|
||||
let rowIndex = event.cell.row;
|
||||
|
||||
let targetObject = this.getCurrentTableObject(rowIndex);
|
||||
let actions = this.getTableActions();
|
||||
|
||||
@@ -13,11 +13,11 @@ import * as sqlops from 'sqlops';
|
||||
import { ComponentWithIconBase } from 'sql/parts/modelComponents/componentWithIconBase';
|
||||
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
|
||||
import { attachButtonStyler } from 'sql/common/theme/styler';
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
|
||||
import { SIDE_BAR_BACKGROUND, SIDE_BAR_TITLE_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { focusBorder, foreground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ export default class ButtonComponent extends ComponentWithIconBase implements IC
|
||||
super.setProperties(properties);
|
||||
this._button.enabled = this.enabled;
|
||||
this._button.label = this.label;
|
||||
this._button.title = this.title;
|
||||
if (this.width) {
|
||||
this._button.setWidth(this.convertSize(this.width.toString()));
|
||||
}
|
||||
@@ -165,4 +166,13 @@ export default class ButtonComponent extends ComponentWithIconBase implements IC
|
||||
private setFileProperties(properties: sqlops.ButtonProperties, isFile: boolean): void {
|
||||
properties.isFile = isFile;
|
||||
}
|
||||
|
||||
private get title(): string {
|
||||
return this.getPropertyOrDefault<sqlops.ButtonProperties, string>((props) => props.title, '');
|
||||
}
|
||||
|
||||
private set title(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.ButtonProperties, string>((properties, title) => { properties.title = title; }, newValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ export class ItemDescriptor<T> {
|
||||
constructor(public descriptor: IComponentDescriptor, public config: T) { }
|
||||
}
|
||||
|
||||
const ids = new IdGenerator('model-view-component-icon-');
|
||||
|
||||
export abstract class ComponentWithIconBase extends ComponentBase {
|
||||
|
||||
protected _iconClass: string;
|
||||
@@ -42,7 +44,6 @@ export abstract class ComponentWithIconBase extends ComponentBase {
|
||||
if (this.iconPath && this.iconPath !== this._iconPath) {
|
||||
this._iconPath = this.iconPath;
|
||||
if (!this._iconClass) {
|
||||
const ids = new IdGenerator('model-view-component-icon-' + Math.round(Math.random() * 1000));
|
||||
this._iconClass = ids.nextId();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import DivContainer from './divContainer.component';
|
||||
import FlexContainer from './flexContainer.component';
|
||||
import FormContainer from './formContainer.component';
|
||||
import ToolbarContainer from './toolbarContainer.component';
|
||||
@@ -22,9 +22,13 @@ import TextComponent from './text.component';
|
||||
import LoadingComponent from './loadingComponent.component';
|
||||
import FileBrowserTreeComponent from './fileBrowserTree.component';
|
||||
import EditorComponent from './editor.component';
|
||||
import DomComponent from './dom.component';
|
||||
import { registerComponentType } from 'sql/platform/dashboard/common/modelComponentRegistry';
|
||||
import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
export const DIV_CONTAINER = 'div-container';
|
||||
registerComponentType(DIV_CONTAINER, ModelComponentTypes.DivContainer, DivContainer);
|
||||
|
||||
export const FLEX_CONTAINER = 'flex-container';
|
||||
registerComponentType(FLEX_CONTAINER, ModelComponentTypes.FlexContainer, FlexContainer);
|
||||
|
||||
@@ -82,3 +86,6 @@ registerComponentType(FILEBROWSERTREE_COMPONENT, ModelComponentTypes.FileBrowser
|
||||
|
||||
export const EDITOR_COMPONENT = 'editor-component';
|
||||
registerComponentType(EDITOR_COMPONENT, ModelComponentTypes.Editor, EditorComponent);
|
||||
|
||||
export const DOM_COMPONENT = 'dom-component';
|
||||
registerComponentType(DOM_COMPONENT, ModelComponentTypes.Dom, DomComponent);
|
||||
|
||||
120
src/sql/parts/modelComponents/divContainer.component.ts
Normal file
120
src/sql/parts/modelComponents/divContainer.component.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./divContainer';
|
||||
|
||||
import {
|
||||
Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
|
||||
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList,
|
||||
} from '@angular/core';
|
||||
|
||||
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/parts/modelComponents/interfaces';
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { ContainerBase } from 'sql/parts/modelComponents/componentBase';
|
||||
import { ModelComponentWrapper } from 'sql/parts/modelComponents/modelComponentWrapper.component';
|
||||
|
||||
import types = require('vs/base/common/types');
|
||||
|
||||
class DivItem {
|
||||
constructor(public descriptor: IComponentDescriptor, public config: sqlops.DivItemLayout) { }
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div #divContainer *ngIf="items" class="divContainer" [style.height]="height" [style.width]="width">
|
||||
<div *ngFor="let item of items" [style.order]="getItemOrder(item)" [ngStyle]="getItemStyles(item)">
|
||||
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
|
||||
</model-component-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export default class DivContainer extends ContainerBase<sqlops.DivItemLayout> implements IComponent, OnDestroy {
|
||||
@Input() descriptor: IComponentDescriptor;
|
||||
@Input() modelStore: IModelStore;
|
||||
@ViewChild('divContainer', { read: ElementRef }) divContainer;
|
||||
private _height: string;
|
||||
private _width: string;
|
||||
private _overflowY: string;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => ElementRef)) el: ElementRef
|
||||
) {
|
||||
super(changeRef, el);
|
||||
this._overflowY = ''; // default
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.baseInit();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.baseDestroy();
|
||||
}
|
||||
|
||||
|
||||
/// IComponent implementation
|
||||
|
||||
public setLayout(layout: sqlops.DivLayout): void {
|
||||
this._height = this.convertSize(layout.height);
|
||||
this._width = this.convertSize(layout.width);
|
||||
this.layout();
|
||||
}
|
||||
|
||||
public setProperties(properties: { [key: string]: any; }): void {
|
||||
super.setProperties(properties);
|
||||
if (this.overflowY !== this._overflowY) {
|
||||
this.updateOverflowY();
|
||||
}
|
||||
this.updateScroll();
|
||||
}
|
||||
|
||||
private updateOverflowY() {
|
||||
this._overflowY = this.overflowY;
|
||||
if (this._overflowY) {
|
||||
let element = <HTMLElement> this.divContainer.nativeElement;
|
||||
element.style.overflowY = this._overflowY;
|
||||
}
|
||||
}
|
||||
|
||||
private updateScroll() {
|
||||
let element = <HTMLElement> this.divContainer.nativeElement;
|
||||
element.scrollTop = element.scrollTop - this.yOffsetChange;
|
||||
element.dispatchEvent(new Event('scroll'));
|
||||
}
|
||||
|
||||
// CSS-bound properties
|
||||
public get height(): string {
|
||||
return this._height;
|
||||
}
|
||||
|
||||
public get width(): string {
|
||||
return this._width;
|
||||
}
|
||||
|
||||
// CSS-bound properties
|
||||
public get overflowY(): string {
|
||||
return this.getPropertyOrDefault<sqlops.DivContainerProperties, any>((props) => props.overflowY, '');
|
||||
}
|
||||
public set overflowY(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.DivContainerProperties, any>((properties, newValue) => { properties.overflowY = newValue; }, newValue);
|
||||
}
|
||||
|
||||
public get yOffsetChange(): number {
|
||||
return this.getPropertyOrDefault<sqlops.DivContainerProperties, any>((props) => props.yOffsetChange, 0);
|
||||
}
|
||||
public set yOffsetChange(newValue: number) {
|
||||
this.setPropertyFromUI<sqlops.DivContainerProperties, any>((properties, newValue) => { properties.yOffsetChange = newValue; }, newValue);
|
||||
}
|
||||
|
||||
private getItemOrder(item: DivItem): number {
|
||||
return item.config ? item.config.order : 0;
|
||||
}
|
||||
private getItemStyles(item: DivItem): { [key: string]: string } {
|
||||
return item.config && item.config.CSSStyles ? item.config.CSSStyles : {};
|
||||
}
|
||||
}
|
||||
5
src/sql/parts/modelComponents/divContainer.css
Normal file
5
src/sql/parts/modelComponents/divContainer.css
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
.divContainer {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
92
src/sql/parts/modelComponents/dom.component.ts
Normal file
92
src/sql/parts/modelComponents/dom.component.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./dom';
|
||||
import 'vs/css!./highlight';
|
||||
import 'vs/css!./markdown';
|
||||
import {
|
||||
Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
|
||||
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList
|
||||
} from '@angular/core';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { $, Builder } from 'vs/base/browser/builder';
|
||||
|
||||
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
|
||||
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
|
||||
|
||||
@Component({
|
||||
template: '',
|
||||
selector: 'modelview-dom-component'
|
||||
})
|
||||
export default class DomComponent extends ComponentBase implements IComponent, OnDestroy {
|
||||
@Input() descriptor: IComponentDescriptor;
|
||||
@Input() modelStore: IModelStore;
|
||||
private _renderedHtml: string;
|
||||
private _rootElement: Builder;
|
||||
private _bodyElement: Builder;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => ElementRef)) el: ElementRef
|
||||
) {
|
||||
super(changeRef, el);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.baseInit();
|
||||
this.createDomElement();
|
||||
this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, e => {
|
||||
this.layout();
|
||||
}));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.baseDestroy();
|
||||
}
|
||||
|
||||
private createDomElement() {
|
||||
this._rootElement = new Builder(this._el.nativeElement);
|
||||
this._bodyElement = $('.dom-body');
|
||||
this._rootElement.append(this._bodyElement);
|
||||
}
|
||||
|
||||
/// Dom Functions
|
||||
private setHtml(): void {
|
||||
if (this.html) {
|
||||
this._renderedHtml = this.html;
|
||||
this._bodyElement.innerHtml(this._renderedHtml);
|
||||
}
|
||||
}
|
||||
|
||||
/// IComponent implementation
|
||||
public layout(): void {
|
||||
super.layout();
|
||||
let element = <HTMLElement>this._el.nativeElement;
|
||||
element.style.width = this.getWidth();
|
||||
element.style.height = this.getHeight();
|
||||
}
|
||||
|
||||
public setLayout(layout: any): void {
|
||||
// TODO allow configuring the look and feel
|
||||
this.layout();
|
||||
}
|
||||
|
||||
public setProperties(properties: { [key: string]: any; }): void {
|
||||
super.setProperties(properties);
|
||||
if (this.html !== this._renderedHtml) {
|
||||
this.setHtml();
|
||||
}
|
||||
}
|
||||
|
||||
// CSS-bound properties
|
||||
public get html(): string {
|
||||
return this.getPropertyOrDefault<sqlops.DomProperties, string>((props) => props.html, '');
|
||||
}
|
||||
|
||||
public set html(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.DomProperties, string>((properties, html) => { properties.html = html; }, newValue);
|
||||
}
|
||||
}
|
||||
9
src/sql/parts/modelComponents/dom.css
Normal file
9
src/sql/parts/modelComponents/dom.css
Normal file
@@ -0,0 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
modelview-dom-component {
|
||||
display: block;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
183
src/sql/parts/modelComponents/highlight.css
Normal file
183
src/sql/parts/modelComponents/highlight.css
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
https://raw.githubusercontent.com/isagalaev/highlight.js/master/src/styles/vs2015.css
|
||||
*/
|
||||
/*
|
||||
* Visual Studio 2015 dark style
|
||||
* Author: Nicolas LLOBERA <nllobera@gmail.com>
|
||||
*/
|
||||
|
||||
|
||||
modelview-dom-component .hljs-keyword,
|
||||
modelview-dom-component .hljs-literal,
|
||||
modelview-dom-component .hljs-symbol,
|
||||
modelview-dom-component .hljs-name {
|
||||
color: #569CD6;
|
||||
}
|
||||
modelview-dom-component .hljs-link {
|
||||
color: #569CD6;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
modelview-dom-component .hljs-built_in,
|
||||
modelview-dom-component .hljs-type {
|
||||
color: #4EC9B0;
|
||||
}
|
||||
|
||||
modelview-dom-component .hljs-number,
|
||||
modelview-dom-component .hljs-class {
|
||||
color: #B8D7A3;
|
||||
}
|
||||
|
||||
modelview-dom-component .hljs-string,
|
||||
modelview-dom-component .hljs-meta-string {
|
||||
color: #D69D85;
|
||||
}
|
||||
|
||||
modelview-dom-component .hljs-regexp,
|
||||
modelview-dom-component .hljs-template-tag {
|
||||
color: #9A5334;
|
||||
}
|
||||
|
||||
modelview-dom-component .hljs-subst,
|
||||
modelview-dom-component .hljs-function,
|
||||
modelview-dom-component .hljs-title,
|
||||
modelview-dom-component .hljs-params,
|
||||
modelview-dom-component .hljs-formula {
|
||||
color: #DCDCDC;
|
||||
}
|
||||
|
||||
modelview-dom-component .hljs-comment,
|
||||
modelview-dom-component .hljs-quote {
|
||||
color: #57A64A;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
modelview-dom-component .hljs-doctag {
|
||||
color: #608B4E;
|
||||
}
|
||||
|
||||
modelview-dom-component .hljs-meta,
|
||||
modelview-dom-component .hljs-meta-keyword,
|
||||
modelview-dom-component .hljs-tag {
|
||||
color: #9B9B9B;
|
||||
}
|
||||
|
||||
modelview-dom-component .hljs-variable,
|
||||
modelview-dom-component .hljs-template-variable {
|
||||
color: #BD63C5;
|
||||
}
|
||||
|
||||
modelview-dom-component .hljs-attr,
|
||||
modelview-dom-component .hljs-attribute,
|
||||
modelview-dom-component .hljs-builtin-name {
|
||||
color: #9CDCFE;
|
||||
}
|
||||
|
||||
modelview-dom-component .hljs-section {
|
||||
color: gold;
|
||||
}
|
||||
|
||||
modelview-dom-component .hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
modelview-dom-component .hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/*.hljs-code {
|
||||
font-family:'Monospace';
|
||||
}*/
|
||||
|
||||
modelview-dom-component .hljs-bullet,
|
||||
modelview-dom-component .hljs-selector-tag,
|
||||
modelview-dom-component .hljs-selector-id,
|
||||
modelview-dom-component .hljs-selector-class,
|
||||
modelview-dom-component .hljs-selector-attr,
|
||||
modelview-dom-component .hljs-selector-pseudo {
|
||||
color: #D7BA7D;
|
||||
}
|
||||
|
||||
modelview-dom-component .hljs-addition {
|
||||
background-color: #144212;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
modelview-dom-component .hljs-deletion {
|
||||
background-color: #600;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
From https://raw.githubusercontent.com/isagalaev/highlight.js/master/src/styles/vs.css
|
||||
*/
|
||||
/*
|
||||
|
||||
Visual Studio-like style based on original C# coloring by Jason Diamond <jason@diamond.name>
|
||||
|
||||
*/
|
||||
/*
|
||||
.vscode-light .hljs-function,
|
||||
.vscode-light .hljs-params {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.vscode-light .hljs-comment,
|
||||
.vscode-light .hljs-quote,
|
||||
.vscode-light .hljs-variable {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
.vscode-light .hljs-keyword,
|
||||
.vscode-light .hljs-selector-tag,
|
||||
.vscode-light .hljs-built_in,
|
||||
.vscode-light .hljs-name,
|
||||
.vscode-light .hljs-tag {
|
||||
color: #00f;
|
||||
}
|
||||
|
||||
.vscode-light .hljs-string,
|
||||
.vscode-light .hljs-title,
|
||||
.vscode-light .hljs-section,
|
||||
.vscode-light .hljs-attribute,
|
||||
.vscode-light .hljs-literal,
|
||||
.vscode-light .hljs-template-tag,
|
||||
.vscode-light .hljs-template-variable,
|
||||
.vscode-light .hljs-type,
|
||||
.vscode-light .hljs-addition {
|
||||
color: #a31515;
|
||||
}
|
||||
|
||||
.vscode-light .hljs-deletion,
|
||||
.vscode-light .hljs-selector-attr,
|
||||
.vscode-light .hljs-selector-pseudo,
|
||||
.vscode-light .hljs-meta {
|
||||
color: #2b91af;
|
||||
}
|
||||
|
||||
.vscode-light .hljs-doctag {
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.vscode-light .hljs-attr {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
.vscode-light .hljs-symbol,
|
||||
.vscode-light .hljs-bullet,
|
||||
.vscode-light .hljs-link {
|
||||
color: #00b0e8;
|
||||
}
|
||||
|
||||
|
||||
.vscode-light .hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.vscode-light .hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
*/
|
||||
238
src/sql/parts/modelComponents/markdown.css
Normal file
238
src/sql/parts/modelComponents/markdown.css
Normal file
@@ -0,0 +1,238 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
modelview-dom-component {
|
||||
font-family: "Segoe WPC", "Segoe UI", "SFUIText-Light", "HelveticaNeue-Light", sans-serif, "Droid Sans Fallback";
|
||||
font-size: 14px;
|
||||
padding: 0 26px;
|
||||
line-height: 22px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
modelview-dom-component #code-csp-warning {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
color: white;
|
||||
margin: 16px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-family: sans-serif;
|
||||
background-color:#444444;
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
box-shadow: 1px 1px 1px rgba(0,0,0,.25);
|
||||
}
|
||||
|
||||
modelview-dom-component #code-csp-warning:hover {
|
||||
text-decoration: none;
|
||||
background-color:#007acc;
|
||||
box-shadow: 2px 2px 2px rgba(0,0,0,.25);
|
||||
}
|
||||
|
||||
|
||||
modelview-dom-component .scrollBeyondLastLine {
|
||||
margin-bottom: calc(100vh - 22px);
|
||||
}
|
||||
|
||||
modelview-dom-component .showEditorSelection .code-line {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
modelview-dom-component .showEditorSelection .code-active-line:before,
|
||||
modelview-dom-component .showEditorSelection .code-line:hover:before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -12px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
modelview-dom-component .showEditorSelection li.code-active-line:before,
|
||||
modelview-dom-component .showEditorSelection li.code-line:hover:before {
|
||||
left: -30px;
|
||||
}
|
||||
|
||||
modelview-dom-component .showEditorSelection .code-active-line:before {
|
||||
border-left: 3px solid rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
modelview-dom-component .showEditorSelection .code-line:hover:before {
|
||||
border-left: 3px solid rgba(0, 0, 0, 0.40);
|
||||
}
|
||||
|
||||
modelview-dom-component .showEditorSelection .code-line .code-line:hover:before {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench modelview-dom-component .showEditorSelection .code-active-line:before
|
||||
.hc-black .monaco-workbench modelview-dom-component .showEditorSelection .code-active-line:before {
|
||||
border-left: 3px solid rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench modelview-dom-component .showEditorSelection .code-line:hover:before
|
||||
.hc-black .monaco-workbench modelview-dom-component .showEditorSelection .code-line:hover:before {
|
||||
border-left: 3px solid rgba(255, 255, 255, 0.60);
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench modelview-dom-component .showEditorSelection .code-line .code-line:hover:before
|
||||
.hc-black .monaco-workbench modelview-dom-component .showEditorSelection .code-line .code-line:hover:before {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.hc-black modelview-dom-component .showEditorSelection .code-active-line:before {
|
||||
border-left: 3px solid rgba(255, 160, 0, 0.7);
|
||||
}
|
||||
|
||||
.hc-black modelview-dom-component .showEditorSelection .code-line:hover:before {
|
||||
border-left: 3px solid rgba(255, 160, 0, 1);
|
||||
}
|
||||
|
||||
modelview-dom-component .showEditorSelection .code-line .code-line:hover:before {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
modelview-dom-component img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
modelview-dom-component a, modelview-dom-component a:link{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
modelview-dom-component a:hover, modelview-dom-component a:link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
modelview-dom-component a:focus,
|
||||
modelview-dom-component input:focus,
|
||||
modelview-dom-component select:focus,
|
||||
modelview-dom-component textarea:focus {
|
||||
outline: 1px solid -webkit-focus-ring-color;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
modelview-dom-component hr {
|
||||
border: 0;
|
||||
height: 2px;
|
||||
border-bottom: 2px solid;
|
||||
}
|
||||
|
||||
modelview-dom-component h1 {
|
||||
padding-bottom: 0.3em;
|
||||
line-height: 1.2;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
modelview-dom-component h1,
|
||||
modelview-dom-component h2,
|
||||
modelview-dom-component h3 {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
modelview-dom-component h1 code,
|
||||
modelview-dom-component h2 code,
|
||||
modelview-dom-component h3 code,
|
||||
modelview-dom-component h4 code,
|
||||
modelview-dom-component h5 code,
|
||||
modelview-dom-component h6 code {
|
||||
font-size: inherit;
|
||||
line-height: auto;
|
||||
}
|
||||
|
||||
modelview-dom-component table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
modelview-dom-component table > thead > tr > th {
|
||||
text-align: left;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
|
||||
modelview-dom-component table > thead > tr > th,
|
||||
modelview-dom-component table > thead > tr > td,
|
||||
modelview-dom-component table > tbody > tr > th,
|
||||
modelview-dom-component table > tbody > tr > td {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
modelview-dom-component table > tbody > tr + tr > td {
|
||||
border-top: 1px solid;
|
||||
}
|
||||
|
||||
modelview-dom-component blockquote {
|
||||
margin: 0 7px 0 5px;
|
||||
padding: 0 16px 0 10px;
|
||||
border-left-width: 5px;
|
||||
border-left-style: solid;
|
||||
}
|
||||
|
||||
modelview-dom-component code {
|
||||
font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
modelview-dom-component .wordWrap pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
modelview-dom-component .mac code {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
modelview-dom-component pre:not(.hljs),
|
||||
modelview-dom-component pre.hljs code > div {
|
||||
padding: 16px;
|
||||
border-radius: 3px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/** Theming */
|
||||
|
||||
modelview-dom-component pre code {
|
||||
color: var(--vscode-editor-foreground);
|
||||
}
|
||||
|
||||
|
||||
modelview-dom-component pre {
|
||||
background-color: rgba(220, 220, 220, 0.4);
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench modelview-dom-component pre {
|
||||
background-color: rgba(10, 10, 10, 0.4);
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench modelview-dom-component pre {
|
||||
background-color: rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench modelview-dom-component h1 {
|
||||
border-color: rgb(0, 0, 0);
|
||||
}
|
||||
|
||||
modelview-dom-component table > thead > tr > th {
|
||||
border-color: rgba(0, 0, 0, 0.69);
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench modelview-dom-component table > thead > tr > th {
|
||||
border-color: rgba(255, 255, 255, 0.69);
|
||||
}
|
||||
|
||||
modelview-dom-component h1,
|
||||
modelview-dom-component hr,
|
||||
modelview-dom-component table > tbody > tr + tr > td {
|
||||
border-color: rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench modelview-dom-component h1,
|
||||
.vs-dark .monaco-workbench modelview-dom-component hr,
|
||||
.vs-dark .monaco-workbench modelview-dom-component table > tbody > tr + tr > td {
|
||||
border-color: rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
@@ -3,16 +3,50 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IEditorModel } from 'vs/platform/editor/common/editor';
|
||||
import { EditorInput } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, EditorModel, ConfirmResult } from 'vs/workbench/common/editor';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
import { DialogPane } from 'sql/platform/dialog/dialogPane';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>;
|
||||
|
||||
export class ModelViewInputModel extends EditorModel {
|
||||
private dirty: boolean;
|
||||
private readonly _onDidChangeDirty: Emitter<void> = this._register(new Emitter<void>());
|
||||
get onDidChangeDirty(): Event<void> { return this._onDidChangeDirty.event; }
|
||||
|
||||
constructor(public readonly modelViewId, private readonly handle: number, private saveHandler?: ModeViewSaveHandler) {
|
||||
super();
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
get isDirty(): boolean {
|
||||
return this.dirty;
|
||||
}
|
||||
|
||||
public setDirty(dirty: boolean): void {
|
||||
if (this.dirty === dirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dirty = dirty;
|
||||
this._onDidChangeDirty.fire();
|
||||
}
|
||||
|
||||
save(): TPromise<boolean> {
|
||||
if (this.saveHandler) {
|
||||
return TPromise.wrap(this.saveHandler(this.handle));
|
||||
}
|
||||
return TPromise.wrap(true);
|
||||
}
|
||||
}
|
||||
export class ModelViewInput extends EditorInput {
|
||||
|
||||
public static ID: string = 'workbench.editorinputs.ModelViewEditorInput';
|
||||
@@ -20,14 +54,15 @@ export class ModelViewInput extends EditorInput {
|
||||
private _dialogPaneContainer: HTMLElement;
|
||||
private _dialogPane: DialogPane;
|
||||
|
||||
constructor(private _title: string, private _modelViewId: string,
|
||||
constructor(private _title: string, private _model: ModelViewInputModel,
|
||||
private _options: sqlops.ModelViewEditorOptions,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IPartService private readonly _partService: IPartService
|
||||
) {
|
||||
super();
|
||||
this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire());
|
||||
this._container = document.createElement('div');
|
||||
this._container.id = `modelView-${_modelViewId}`;
|
||||
this._container.id = `modelView-${_model.modelViewId}`;
|
||||
this._partService.getContainer(Parts.EDITOR_PART).appendChild(this._container);
|
||||
|
||||
}
|
||||
@@ -37,7 +72,7 @@ export class ModelViewInput extends EditorInput {
|
||||
}
|
||||
|
||||
public get modelViewId(): string {
|
||||
return this._modelViewId;
|
||||
return this._model.modelViewId;
|
||||
}
|
||||
|
||||
public getTypeId(): string {
|
||||
@@ -85,6 +120,31 @@ export class ModelViewInput extends EditorInput {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
/**
|
||||
* An editor that is dirty will be asked to be saved once it closes.
|
||||
*/
|
||||
isDirty(): boolean {
|
||||
return this._model.isDirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses should bring up a proper dialog for the user if the editor is dirty and return the result.
|
||||
*/
|
||||
confirmSave(): TPromise<ConfirmResult> {
|
||||
// TODO #2530 support save on close / confirm save. This is significantly more work
|
||||
// as we need to either integrate with textFileService (seems like this isn't viable)
|
||||
// or register our own complimentary service that handles the lifecycle operations such
|
||||
// as close all, auto save etc.
|
||||
return TPromise.wrap(ConfirmResult.DONT_SAVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation.
|
||||
*/
|
||||
save(): TPromise<boolean> {
|
||||
return this._model.save();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._dialogPane) {
|
||||
this._dialogPane.dispose();
|
||||
@@ -93,6 +153,9 @@ export class ModelViewInput extends EditorInput {
|
||||
this._container.remove();
|
||||
this._container = undefined;
|
||||
}
|
||||
if (this._model) {
|
||||
this._model.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export class ToolbarItem {
|
||||
template: `
|
||||
<div #container *ngIf="items" [class]="toolbarClass" >
|
||||
<ng-container *ngFor="let item of items">
|
||||
<div class="modelview-toolbar-item" [title]="getItemTitle(item)" [style.paddingTop]="paddingTop" tabindex="0">
|
||||
<div class="modelview-toolbar-item" [style.paddingTop]="paddingTop">
|
||||
<div *ngIf="shouldShowTitle(item)" class="modelview-toolbar-title" >
|
||||
{{getItemTitle(item)}}
|
||||
</div>
|
||||
|
||||
@@ -62,4 +62,12 @@
|
||||
margin-right: 0.3em;
|
||||
background-position: 50% 50%;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.modelview-toolbar-container .modelview-toolbar-component modelview-button .monaco-text-button.active {
|
||||
-ms-transform: scale(1.272019649, 1.272019649); /* 1.272019649 = √φ */
|
||||
-webkit-transform: scale(1.272019649, 1.272019649);
|
||||
-moz-transform: scale(1.272019649, 1.272019649);
|
||||
-o-transform: scale(1.272019649, 1.272019649);
|
||||
transform: scale(1.272019649, 1.272019649);
|
||||
}
|
||||
|
||||
@@ -72,6 +72,8 @@ export interface IObjectExplorerService {
|
||||
getActiveConnectionNodes(): TreeNode[];
|
||||
|
||||
getTreeNode(connectionId: string, nodePath: string): Thenable<TreeNode>;
|
||||
|
||||
refreshNodeInView(connectionId: string, nodePath: string): Thenable<TreeNode>;
|
||||
}
|
||||
|
||||
interface SessionStatus {
|
||||
@@ -476,6 +478,20 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
||||
return Object.values(this._activeObjectExplorerNodes);
|
||||
}
|
||||
|
||||
public async refreshNodeInView(connectionId: string, nodePath: string): Promise<TreeNode> {
|
||||
// Get the tree node and call refresh from the provider
|
||||
let treeNode = await this.getTreeNode(connectionId, nodePath);
|
||||
await this.refreshTreeNode(treeNode.getSession(), treeNode);
|
||||
|
||||
// Get the new tree node, refresh it in the view, and expand it if needed
|
||||
treeNode = await this.getTreeNode(connectionId, nodePath);
|
||||
await this._serverTreeView.refreshElement(treeNode);
|
||||
if (treeNode.children.length > 0) {
|
||||
await treeNode.setExpandedState(TreeItemCollapsibleState.Expanded);
|
||||
}
|
||||
return treeNode;
|
||||
}
|
||||
|
||||
private async setNodeExpandedState(treeNode: TreeNode, expandedState: TreeItemCollapsibleState): Promise<void> {
|
||||
treeNode = await this.getUpdatedTreeNode(treeNode);
|
||||
let expandNode = this.getTreeItem(treeNode);
|
||||
|
||||
@@ -39,7 +39,7 @@ const viewletDescriptor = new ViewletDescriptor(
|
||||
VIEWLET_ID,
|
||||
'Servers',
|
||||
'connectionViewlet',
|
||||
-100
|
||||
0
|
||||
);
|
||||
|
||||
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(viewletDescriptor);
|
||||
|
||||
@@ -167,12 +167,12 @@ export class ServerTreeActionProvider extends ContributableActionProvider {
|
||||
if (TreeUpdateUtils.isDatabaseNode(treeNode)) {
|
||||
if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
|
||||
actions.push(this._instantiationService.createInstance(ManageConnectionAction, ManageConnectionAction.ID, ManageConnectionAction.LABEL, context.tree));
|
||||
this.addNewQueryAction(context, actions);
|
||||
} else {
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
this.addNewQueryAction(context, actions);
|
||||
this.addScriptingActions(context, actions);
|
||||
actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, context.tree, treeNode));
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { ConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
|
||||
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
|
||||
import { ITree, IDataSource } from 'vs/base/parts/tree/browser/tree';
|
||||
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
|
||||
import { TreeNode, TreeItemCollapsibleState } from 'sql/parts/objectExplorer/common/treeNode';
|
||||
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { TreeUpdateUtils } from 'sql/parts/objectExplorer/viewlet/treeUpdateUtils';
|
||||
@@ -72,9 +72,13 @@ export class ServerTreeDataSource implements IDataSource {
|
||||
if (node.children) {
|
||||
resolve(node.children);
|
||||
} else {
|
||||
// These similar changes are probably needed for a ConnectionProfile group element as well. However, we do not have a repro of a failiure in that scenario so they will be tackled in a future checkin.
|
||||
// It has been tested for connecting to the server in profile itself and things work fine there.
|
||||
this._objectExplorerService.resolveTreeNodeChildren(node.getSession(), node).then(() => {
|
||||
resolve(node.children);
|
||||
}, expandError => {
|
||||
node.setExpandedState(TreeItemCollapsibleState.Collapsed);
|
||||
node.errorStateMessage = expandError;
|
||||
this.showError(expandError);
|
||||
resolve([]);
|
||||
});
|
||||
|
||||
@@ -138,7 +138,7 @@ export class ServerTreeView {
|
||||
|
||||
let expandGroups: boolean = self._configurationService.getValue(SERVER_GROUP_CONFIG)[SERVER_GROUP_AUTOEXPAND_CONFIG];
|
||||
if (expandGroups) {
|
||||
self._tree.expandAll(ConnectionProfileGroup.getSubgroups(root));
|
||||
self._tree.expandAll(ConnectionProfileGroup.getSubgroups(root));
|
||||
}
|
||||
|
||||
if (root && !root.hasValidConnections) {
|
||||
@@ -244,6 +244,10 @@ export class ServerTreeView {
|
||||
TreeUpdateUtils.registeredServerUpdate(this._tree, this._connectionManagementService);
|
||||
}
|
||||
|
||||
public refreshElement(element: any): Thenable<void> {
|
||||
return this._tree.refresh(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter connections based on view (recent/active)
|
||||
*/
|
||||
|
||||
@@ -10,6 +10,27 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { EditorInput } from 'vs/workbench/common/editor';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
import { GridPanelState } from 'sql/parts/query/editor/gridPanel';
|
||||
import { MessagePanelState } from 'sql/parts/query/editor/messagePanel';
|
||||
import { QueryPlanState } from 'sql/parts/queryPlan/queryPlan';
|
||||
import { ChartState } from 'sql/parts/query/editor/charting/chartView';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
export class ResultsViewState {
|
||||
public gridPanelState: GridPanelState = new GridPanelState();
|
||||
public messagePanelState: MessagePanelState = new MessagePanelState(this.configurationService);
|
||||
public chartState: ChartState = new ChartState();
|
||||
public queryPlanState: QueryPlanState = new QueryPlanState();
|
||||
public gridPanelSize: number;
|
||||
public messagePanelSize: number;
|
||||
public activeTab: string;
|
||||
public visibleTabs: Set<string> = new Set<string>();
|
||||
|
||||
constructor(@IConfigurationService private configurationService: IConfigurationService) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Input for the QueryResultsEditor. This input helps with logic for the viewing and editing of
|
||||
* data in the results grid.
|
||||
@@ -29,7 +50,11 @@ export class QueryResultsInput extends EditorInput {
|
||||
public readonly onRestoreViewStateEmitter = new Emitter<void>();
|
||||
public readonly onSaveViewStateEmitter = new Emitter<void>();
|
||||
|
||||
constructor(private _uri: string) {
|
||||
public readonly state = new ResultsViewState(this.configurationService);
|
||||
|
||||
constructor(private _uri: string,
|
||||
@IConfigurationService private configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
this._visible = false;
|
||||
this._hasBootstrapped = false;
|
||||
|
||||
@@ -11,13 +11,14 @@ import { localize } from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
import QueryRunner from 'sql/parts/query/execution/queryRunner';
|
||||
import { SaveFormat } from 'sql/parts/grid/common/interfaces';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { GridTableState } from 'sql/parts/query/editor/gridPanel';
|
||||
import { QueryEditor } from './queryEditor';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
|
||||
|
||||
export interface IGridActionContext {
|
||||
cell: { row: number; cell: number; };
|
||||
@@ -26,6 +27,7 @@ export interface IGridActionContext {
|
||||
batchId: number;
|
||||
resultId: number;
|
||||
table: Table<any>;
|
||||
selectionModel: CellSelectionModel<any>;
|
||||
tableState: GridTableState;
|
||||
}
|
||||
|
||||
@@ -113,7 +115,7 @@ export class SelectAllGridAction extends Action {
|
||||
}
|
||||
|
||||
public run(context: IGridActionContext): TPromise<boolean> {
|
||||
context.table.setSelectedRows(true);
|
||||
context.selectionModel.setSelectedRanges([new Slick.Range(0, 0, context.table.getData().getLength() - 1, context.table.columns.length - 1)]);
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
@@ -167,13 +169,13 @@ export class MaximizeTableAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class MinimizeTableAction extends Action {
|
||||
public static ID = 'grid.minimize';
|
||||
public static LABEL = localize('minimize', 'Minimize');
|
||||
export class RestoreTableAction extends Action {
|
||||
public static ID = 'grid.restore';
|
||||
public static LABEL = localize('restore', 'Restore');
|
||||
public static ICON = 'exitFullScreen';
|
||||
|
||||
constructor() {
|
||||
super(MinimizeTableAction.ID, MinimizeTableAction.LABEL, MinimizeTableAction.ICON);
|
||||
super(RestoreTableAction.ID, RestoreTableAction.LABEL, RestoreTableAction.ICON);
|
||||
}
|
||||
|
||||
public run(context: IGridActionContext): TPromise<boolean> {
|
||||
@@ -221,5 +223,4 @@ export class ShowQueryPlanAction extends Action {
|
||||
return TPromise.as(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,10 +8,9 @@
|
||||
import { localize } from 'vs/nls';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
|
||||
import { ChartType, DataDirection, LegendPosition } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
|
||||
import { DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component';
|
||||
import { InsightType, IInsightOptions } from './insights/interfaces';
|
||||
import { DataDirection, ChartType, LegendPosition, DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
const insightRegistry = Registry.as<IInsightRegistry>(Extensions.InsightContribution);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
|
||||
export class ChartTab implements IPanelTab {
|
||||
public readonly title = localize('chartTabTitle', 'Chart');
|
||||
public readonly identifier = generateUuid();
|
||||
public readonly identifier = 'ChartTab';
|
||||
public readonly view: ChartView;
|
||||
|
||||
constructor(@IInstantiationService instantiationService: IInstantiationService) {
|
||||
|
||||
@@ -12,12 +12,13 @@ import { Insight } from './insights/insight';
|
||||
import QueryRunner from 'sql/parts/query/execution/queryRunner';
|
||||
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
|
||||
import { ChartOptions, IChartOption, ControlType } from './chartOptions';
|
||||
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
|
||||
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox';
|
||||
import { IInsightOptions } from './insights/interfaces';
|
||||
import { CopyAction, SaveImageAction, CreateInsightAction, IChartActionContext } from './actions';
|
||||
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
|
||||
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Dimension, $, getContentHeight, getContentWidth } from 'vs/base/browser/dom';
|
||||
import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
@@ -27,11 +28,21 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { attachSelectBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
|
||||
export class ChartState {
|
||||
dataId: { batchId: number, resultId: number };
|
||||
options: IInsightOptions = {
|
||||
type: ChartType.Bar
|
||||
};
|
||||
}
|
||||
|
||||
declare class Proxy {
|
||||
constructor(object, handler);
|
||||
}
|
||||
|
||||
const insightRegistry = Registry.as<IInsightRegistry>(Extensions.InsightContribution);
|
||||
|
||||
export class ChartView implements IPanelView {
|
||||
private insight: Insight;
|
||||
private _queryRunner: QueryRunner;
|
||||
@@ -43,6 +54,8 @@ export class ChartView implements IPanelView {
|
||||
private _copyAction: CopyAction;
|
||||
private _saveAction: SaveImageAction;
|
||||
|
||||
private _state: ChartState;
|
||||
|
||||
private options: IInsightOptions = {
|
||||
type: ChartType.Bar
|
||||
};
|
||||
@@ -61,7 +74,7 @@ export class ChartView implements IPanelView {
|
||||
private chartingContainer: HTMLElement;
|
||||
|
||||
private optionDisposables: IDisposable[] = [];
|
||||
private optionMap: { [x: string]: HTMLElement } = {};
|
||||
private optionMap: { [x: string]: { element: HTMLElement; set: (val) => void } } = {};
|
||||
|
||||
constructor(
|
||||
@IContextViewService private _contextViewService: IContextViewService,
|
||||
@@ -95,6 +108,10 @@ export class ChartView implements IPanelView {
|
||||
}
|
||||
|
||||
let result = Reflect.set(target, key, value, receiver);
|
||||
// mirror the change in our state
|
||||
if (self.state) {
|
||||
Reflect.set(self.state.options, key, value);
|
||||
}
|
||||
|
||||
if (change) {
|
||||
self.taskbar.context = <IChartActionContext>{ options: self.options, insight: self.insight ? self.insight.insight : undefined };
|
||||
@@ -109,6 +126,7 @@ export class ChartView implements IPanelView {
|
||||
}
|
||||
}) as IInsightOptions;
|
||||
|
||||
ChartOptions.general[0].options = insightRegistry.getAllIds();
|
||||
ChartOptions.general.map(o => {
|
||||
this.createOption(o, generalControls);
|
||||
});
|
||||
@@ -138,6 +156,7 @@ export class ChartView implements IPanelView {
|
||||
}
|
||||
|
||||
public chart(dataId: { batchId: number, resultId: number }) {
|
||||
this.state.dataId = dataId;
|
||||
this._currentData = dataId;
|
||||
this.shouldGraph();
|
||||
}
|
||||
@@ -172,7 +191,7 @@ export class ChartView implements IPanelView {
|
||||
});
|
||||
}
|
||||
}
|
||||
// if we have the necessary information but the information isn't avaiable yet,
|
||||
// if we have the necessary information but the information isn't available yet,
|
||||
// we should be smart and retrying when the information might be available
|
||||
}
|
||||
}
|
||||
@@ -180,7 +199,9 @@ export class ChartView implements IPanelView {
|
||||
private buildOptions() {
|
||||
dispose(this.optionDisposables);
|
||||
this.optionDisposables = [];
|
||||
this.optionMap = {};
|
||||
this.optionMap = {
|
||||
'type': this.optionMap['type']
|
||||
};
|
||||
new Builder(this.typeControls).clearChildren();
|
||||
|
||||
this.updateActionbar();
|
||||
@@ -200,9 +221,9 @@ export class ChartView implements IPanelView {
|
||||
let option = ChartOptions[this.options.type].find(e => e.configEntry === key);
|
||||
if (option && option.if) {
|
||||
if (option.if(this.options)) {
|
||||
new Builder(this.optionMap[key]).show();
|
||||
new Builder(this.optionMap[key].element).show();
|
||||
} else {
|
||||
new Builder(this.optionMap[key]).hide();
|
||||
new Builder(this.optionMap[key].element).hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,6 +232,7 @@ export class ChartView implements IPanelView {
|
||||
|
||||
private updateActionbar() {
|
||||
if (this.insight && this.insight.isCopyable) {
|
||||
this.taskbar.context = { insight: this.insight.insight, options: this.options };
|
||||
this.taskbar.setContent([
|
||||
{ action: this._createInsightAction },
|
||||
{ action: this._copyAction },
|
||||
@@ -226,57 +248,104 @@ export class ChartView implements IPanelView {
|
||||
label.innerText = option.label;
|
||||
let optionContainer = $('div.option-container');
|
||||
optionContainer.appendChild(label);
|
||||
let setFunc: (val) => void;
|
||||
let value = this.state ? this.state.options[option.configEntry] || option.default : option.default;
|
||||
switch (option.type) {
|
||||
case ControlType.checkbox:
|
||||
let checkbox = new Checkbox(optionContainer, {
|
||||
label: '',
|
||||
ariaLabel: option.label,
|
||||
checked: option.default,
|
||||
checked: value,
|
||||
onChange: () => {
|
||||
if (this.options[option.configEntry] !== checkbox.checked) {
|
||||
this.options[option.configEntry] = checkbox.checked;
|
||||
this.insight.options = this.options;
|
||||
if (this.insight) {
|
||||
this.insight.options = this.options;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
setFunc = (val: boolean) => {
|
||||
checkbox.checked = val;
|
||||
};
|
||||
break;
|
||||
case ControlType.combo:
|
||||
let dropdown = new SelectBox(option.displayableOptions || option.options, 0, this._contextViewService);
|
||||
dropdown.select(option.options.indexOf(option.default));
|
||||
dropdown.select(option.options.indexOf(value));
|
||||
dropdown.render(optionContainer);
|
||||
dropdown.onDidSelect(e => {
|
||||
if (this.options[option.configEntry] !== option.options[e.index]) {
|
||||
this.options[option.configEntry] = option.options[e.index];
|
||||
this.insight.options = this.options;
|
||||
if (this.insight) {
|
||||
this.insight.options = this.options;
|
||||
}
|
||||
}
|
||||
});
|
||||
setFunc = (val: string) => {
|
||||
if (!isUndefinedOrNull(val)) {
|
||||
dropdown.select(option.options.indexOf(val));
|
||||
}
|
||||
};
|
||||
this.optionDisposables.push(attachSelectBoxStyler(dropdown, this._themeService));
|
||||
break;
|
||||
case ControlType.input:
|
||||
let input = new InputBox(optionContainer, this._contextViewService);
|
||||
input.value = option.default || '';
|
||||
input.value = value || '';
|
||||
input.onDidChange(e => {
|
||||
if (this.options[option.configEntry] !== e) {
|
||||
this.options[option.configEntry] = e;
|
||||
this.insight.options = this.options;
|
||||
if (this.insight) {
|
||||
this.insight.options = this.options;
|
||||
}
|
||||
}
|
||||
});
|
||||
setFunc = (val: string) => {
|
||||
if (!isUndefinedOrNull(val)) {
|
||||
input.value = val;
|
||||
}
|
||||
};
|
||||
this.optionDisposables.push(attachInputBoxStyler(input, this._themeService));
|
||||
break;
|
||||
case ControlType.numberInput:
|
||||
let numberInput = new InputBox(optionContainer, this._contextViewService, { type: 'number' });
|
||||
numberInput.value = option.default || '';
|
||||
numberInput.value = value || '';
|
||||
numberInput.onDidChange(e => {
|
||||
if (this.options[option.configEntry] !== Number(e)) {
|
||||
this.options[option.configEntry] = Number(e);
|
||||
this.insight.options = this.options;
|
||||
if (this.insight) {
|
||||
this.insight.options = this.options;
|
||||
}
|
||||
}
|
||||
});
|
||||
setFunc = (val: string) => {
|
||||
if (!isUndefinedOrNull(val)) {
|
||||
numberInput.value = val;
|
||||
}
|
||||
};
|
||||
this.optionDisposables.push(attachInputBoxStyler(numberInput, this._themeService));
|
||||
break;
|
||||
}
|
||||
this.optionMap[option.configEntry] = optionContainer;
|
||||
this.optionMap[option.configEntry] = { element: optionContainer, set: setFunc };
|
||||
container.appendChild(optionContainer);
|
||||
this.options[option.configEntry] = option.default;
|
||||
this.options[option.configEntry] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public set state(val: ChartState) {
|
||||
this._state = val;
|
||||
if (this.state.options) {
|
||||
for (let key in this.state.options) {
|
||||
if (this.state.options.hasOwnProperty(key) && this.optionMap[key]) {
|
||||
this.options[key] = this.state.options[key];
|
||||
this.optionMap[key].set(this.state.options[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.state.dataId) {
|
||||
this.chart(this.state.dataId);
|
||||
}
|
||||
}
|
||||
|
||||
public get state(): ChartState {
|
||||
return this._state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ import * as colors from 'vs/platform/theme/common/colorRegistry';
|
||||
import { editorLineNumbers } from 'vs/editor/common/view/editorColorRegistry';
|
||||
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
import { ChartType, DataDirection, LegendPosition } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
|
||||
import { IInsightOptions, IInsight } from './interfaces';
|
||||
import { ChartType, DataDirection, LegendPosition } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
const noneLineGraphs = [ChartType.Doughnut, ChartType.Pie];
|
||||
|
||||
@@ -149,6 +149,8 @@ export class Graph implements IInsight {
|
||||
let foreground = foregroundColor ? foregroundColor.toString() : null;
|
||||
let gridLinesColor = this._theme.getColor(editorLineNumbers);
|
||||
let gridLines = gridLinesColor ? gridLinesColor.toString() : null;
|
||||
let backgroundColor = this._theme.getColor(colors.editorBackground);
|
||||
let background = backgroundColor ? backgroundColor.toString() : null;
|
||||
|
||||
if (options) {
|
||||
retval.scales = {};
|
||||
@@ -187,12 +189,20 @@ export class Graph implements IInsight {
|
||||
}];
|
||||
}
|
||||
|
||||
retval.legend = {
|
||||
retval.legend = <ChartJs.ChartLegendOptions>{
|
||||
position: options.legendPosition as ChartJs.PositionType,
|
||||
display: options.legendPosition !== LegendPosition.None
|
||||
display: options.legendPosition !== LegendPosition.None,
|
||||
labels: {
|
||||
fontColor: foreground
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// these are custom options that will throw compile errors
|
||||
(<any>retval).viewArea = {
|
||||
backgroundColor: background
|
||||
};
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
'use strict';
|
||||
|
||||
import { Graph } from './graphInsight';
|
||||
import { ChartType, DataDirection } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
|
||||
|
||||
import { Builder } from 'vs/base/browser/builder';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { DataDirection, ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
import { ImageInsight } from './imageInsight';
|
||||
import { TableInsight } from './tableInsight';
|
||||
import { IInsightOptions, IInsight, InsightType, IInsightCtor } from './interfaces';
|
||||
import { CountInsight } from './countInsight';
|
||||
|
||||
import { Builder } from 'vs/base/browser/builder';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
|
||||
const defaultOptions: IInsightOptions = {
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
|
||||
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
|
||||
import { ChartType, LegendPosition, DataDirection } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component';
|
||||
import { DataDirection, ChartType, LegendPosition, DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
|
||||
|
||||
export interface IInsightOptions {
|
||||
type: InsightType | ChartType;
|
||||
|
||||
@@ -12,11 +12,13 @@ import { ScrollableSplitView } from 'sql/base/browser/ui/scrollableSplitview/scr
|
||||
import { MouseWheelSupport } from 'sql/base/browser/ui/table/plugins/mousewheelTableScroll.plugin';
|
||||
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
|
||||
import { SaveFormat } from 'sql/parts/grid/common/interfaces';
|
||||
import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, MinimizeTableAction, ChartDataAction, ShowQueryPlanAction } from 'sql/parts/query/editor/actions';
|
||||
import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, RestoreTableAction, ChartDataAction, ShowQueryPlanAction } from 'sql/parts/query/editor/actions';
|
||||
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
|
||||
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
|
||||
import { escape } from 'sql/base/common/strings';
|
||||
import { hyperLinkFormatter, textFormatter } from 'sql/parts/grid/services/sharedServices';
|
||||
import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugin';
|
||||
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
@@ -34,25 +36,30 @@ import { $ } from 'vs/base/browser/builder';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Separator, ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Dimension, getContentWidth } from 'vs/base/browser/dom';
|
||||
import { Dimension, getContentWidth, isInDOM } from 'vs/base/browser/dom';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugin';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
|
||||
const ROW_HEIGHT = 29;
|
||||
const HEADER_HEIGHT = 26;
|
||||
const MIN_GRID_HEIGHT_ROWS = 8;
|
||||
const ESTIMATED_SCROLL_BAR_HEIGHT = 10;
|
||||
const BOTTOM_PADDING = 5;
|
||||
const BOTTOM_PADDING = 15;
|
||||
const ACTIONBAR_WIDTH = 26;
|
||||
|
||||
// minimum height needed to show the full actionbar
|
||||
const ACTIONBAR_HEIGHT = 100;
|
||||
|
||||
// this handles min size if rows is greater than the min grid visible rows
|
||||
const MIN_GRID_HEIGHT = (MIN_GRID_HEIGHT_ROWS * ROW_HEIGHT) + HEADER_HEIGHT + ESTIMATED_SCROLL_BAR_HEIGHT + BOTTOM_PADDING;
|
||||
const MIN_GRID_HEIGHT = (MIN_GRID_HEIGHT_ROWS * ROW_HEIGHT) + HEADER_HEIGHT + ESTIMATED_SCROLL_BAR_HEIGHT;
|
||||
|
||||
export class GridPanelState {
|
||||
public tableStates: GridTableState[] = [];
|
||||
public scrollPosition: number;
|
||||
public collapsed = false;
|
||||
}
|
||||
|
||||
export interface IGridTableState {
|
||||
canBeMaximized: boolean;
|
||||
@@ -66,13 +73,29 @@ export class GridTableState {
|
||||
private _onMaximizedChange = new Emitter<boolean>();
|
||||
public onMaximizedChange: Event<boolean> = this._onMaximizedChange.event;
|
||||
|
||||
public canBeMaximized: boolean;
|
||||
private _onCanBeMaximizedChange = new Emitter<boolean>();
|
||||
public onCanBeMaximizedChange: Event<boolean> = this._onCanBeMaximizedChange.event;
|
||||
|
||||
constructor(state?: IGridTableState) {
|
||||
if (state) {
|
||||
this._maximized = state.maximized;
|
||||
this.canBeMaximized = state.canBeMaximized;
|
||||
private _canBeMaximized: boolean;
|
||||
|
||||
/* The top row of the current scroll */
|
||||
public scrollPosition = 0;
|
||||
public selection: Slick.Range[];
|
||||
public activeCell: Slick.Cell;
|
||||
|
||||
constructor(public readonly resultId: number, public readonly batchId: number) {
|
||||
}
|
||||
|
||||
public get canBeMaximized(): boolean {
|
||||
return this._canBeMaximized;
|
||||
}
|
||||
|
||||
public set canBeMaximized(val: boolean) {
|
||||
if (val === this._canBeMaximized) {
|
||||
return;
|
||||
}
|
||||
this._canBeMaximized = val;
|
||||
this._onCanBeMaximizedChange.fire(val);
|
||||
}
|
||||
|
||||
public get maximized(): boolean {
|
||||
@@ -86,10 +109,6 @@ export class GridTableState {
|
||||
this._maximized = val;
|
||||
this._onMaximizedChange.fire(val);
|
||||
}
|
||||
|
||||
public clone(): GridTableState {
|
||||
return new GridTableState({ canBeMaximized: this.canBeMaximized, maximized: this.maximized });
|
||||
}
|
||||
}
|
||||
|
||||
export class GridPanel extends ViewletPanel {
|
||||
@@ -103,6 +122,7 @@ export class GridPanel extends ViewletPanel {
|
||||
private runner: QueryRunner;
|
||||
|
||||
private maximizedGrid: GridTable<any>;
|
||||
private _state: GridPanelState;
|
||||
|
||||
constructor(
|
||||
options: IViewletPanelOptions,
|
||||
@@ -114,6 +134,16 @@ export class GridPanel extends ViewletPanel {
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService);
|
||||
this.splitView = new ScrollableSplitView(this.container, { enableResizing: false });
|
||||
this.splitView.onScroll(e => {
|
||||
if (this.state) {
|
||||
this.state.scrollPosition = e;
|
||||
}
|
||||
});
|
||||
this.onDidChange(e => {
|
||||
if (this.state) {
|
||||
this.state.collapsed = !this.isExpanded();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
@@ -138,7 +168,12 @@ export class GridPanel extends ViewletPanel {
|
||||
this.queryRunnerDisposables = [];
|
||||
this.runner = runner;
|
||||
this.queryRunnerDisposables.push(this.runner.onResultSet(e => this.onResultSet(e)));
|
||||
this.queryRunnerDisposables.push(this.runner.onQueryStart(() => this.reset()));
|
||||
this.queryRunnerDisposables.push(this.runner.onQueryStart(() => {
|
||||
if (this.state) {
|
||||
this.state.tableStates = [];
|
||||
}
|
||||
this.reset();
|
||||
}));
|
||||
}
|
||||
|
||||
private onResultSet(resultSet: sqlops.ResultSetSummary | sqlops.ResultSetSummary[]) {
|
||||
@@ -151,6 +186,10 @@ export class GridPanel extends ViewletPanel {
|
||||
this.maximumBodySize = this.tables.reduce((p, c) => {
|
||||
return p + c.maximumSize;
|
||||
}, 0);
|
||||
|
||||
if (this.state && this.state.scrollPosition) {
|
||||
this.splitView.setScrollPosition(this.state.scrollPosition);
|
||||
}
|
||||
}
|
||||
|
||||
private addResultSet(resultSet: sqlops.ResultSetSummary | sqlops.ResultSetSummary[]) {
|
||||
@@ -164,8 +203,18 @@ export class GridPanel extends ViewletPanel {
|
||||
let tables: GridTable<any>[] = [];
|
||||
|
||||
for (let set of resultsToAdd) {
|
||||
let tableState = new GridTableState();
|
||||
let table = this.instantiationService.createInstance(GridTable, this.runner, tableState, set);
|
||||
let tableState: GridTableState;
|
||||
if (this._state) {
|
||||
tableState = this.state.tableStates.find(e => e.batchId === set.batchId && e.resultId === set.id);
|
||||
}
|
||||
if (!tableState) {
|
||||
tableState = new GridTableState(set.id, set.batchId);
|
||||
if (this._state) {
|
||||
this._state.tableStates.push(tableState);
|
||||
}
|
||||
}
|
||||
let table = this.instantiationService.createInstance(GridTable, this.runner, set);
|
||||
table.state = tableState;
|
||||
tableState.onMaximizedChange(e => {
|
||||
if (e) {
|
||||
this.maximizeTable(table.id);
|
||||
@@ -191,7 +240,6 @@ export class GridPanel extends ViewletPanel {
|
||||
for (let i = this.splitView.length - 1; i >= 0; i--) {
|
||||
this.splitView.removeView(i);
|
||||
}
|
||||
|
||||
dispose(this.tables);
|
||||
this.tables = [];
|
||||
|
||||
@@ -224,6 +272,24 @@ export class GridPanel extends ViewletPanel {
|
||||
this.splitView.addViews(this.tables, this.tables.map(i => i.minimumSize));
|
||||
}
|
||||
}
|
||||
|
||||
public set state(val: GridPanelState) {
|
||||
this._state = val;
|
||||
this.tables.map(t => {
|
||||
let state = this.state.tableStates.find(s => s.batchId === t.resultSet.batchId && s.resultId === t.resultSet.id);
|
||||
if (!state) {
|
||||
this.state.tableStates.push(t.state);
|
||||
}
|
||||
if (state) {
|
||||
t.state = state;
|
||||
}
|
||||
});
|
||||
this.setExpanded(!this.state.collapsed);
|
||||
}
|
||||
|
||||
public get state(): GridPanelState {
|
||||
return this._state;
|
||||
}
|
||||
}
|
||||
|
||||
class GridTable<T> extends Disposable implements IView {
|
||||
@@ -242,13 +308,16 @@ class GridTable<T> extends Disposable implements IView {
|
||||
public id = generateUuid();
|
||||
readonly element: HTMLElement = this.container;
|
||||
|
||||
private _state: GridTableState;
|
||||
|
||||
private scrolled = false;
|
||||
|
||||
// this handles if the row count is small, like 4-5 rows
|
||||
private readonly maxSize = ((this.resultSet.rowCount) * ROW_HEIGHT) + HEADER_HEIGHT + ESTIMATED_SCROLL_BAR_HEIGHT + BOTTOM_PADDING;
|
||||
private readonly maxSize = ((this.resultSet.rowCount) * ROW_HEIGHT) + HEADER_HEIGHT + ESTIMATED_SCROLL_BAR_HEIGHT;
|
||||
|
||||
constructor(
|
||||
private runner: QueryRunner,
|
||||
public state: GridTableState,
|
||||
private resultSet: sqlops.ResultSetSummary,
|
||||
public readonly resultSet: sqlops.ResultSetSummary,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@@ -257,7 +326,7 @@ class GridTable<T> extends Disposable implements IView {
|
||||
super();
|
||||
this.container.style.width = '100%';
|
||||
this.container.style.height = '100%';
|
||||
this.container.style.marginBottom = BOTTOM_PADDING + 'px';
|
||||
// this.container.style.marginBottom = BOTTOM_PADDING + 'px';
|
||||
this.container.className = 'grid-panel';
|
||||
|
||||
this.columns = this.resultSet.columnInfo.map((c, i) => {
|
||||
@@ -274,6 +343,11 @@ class GridTable<T> extends Disposable implements IView {
|
||||
});
|
||||
}
|
||||
|
||||
public onRemove() {
|
||||
// when we are removed slickgrid acts badly so we need to account for that
|
||||
this.scrolled = false;
|
||||
}
|
||||
|
||||
public render(container: HTMLElement, orientation: Orientation): void {
|
||||
container.appendChild(this.container);
|
||||
}
|
||||
@@ -294,15 +368,7 @@ class GridTable<T> extends Disposable implements IView {
|
||||
let numberColumn = new RowNumberColumn({ numberOfRows: this.resultSet.rowCount });
|
||||
let copyHandler = new CopyKeybind();
|
||||
copyHandler.onCopy(e => {
|
||||
new CopyResultAction(CopyResultAction.COPY_ID, CopyResultAction.COPY_LABEL, false).run({
|
||||
selection: e,
|
||||
batchId: this.resultSet.batchId,
|
||||
resultId: this.resultSet.id,
|
||||
cell: this.table.grid.getActiveCell(),
|
||||
runner: this.runner,
|
||||
table: this.table,
|
||||
tableState: this.state
|
||||
});
|
||||
new CopyResultAction(CopyResultAction.COPY_ID, CopyResultAction.COPY_LABEL, false).run(this.generateContext());
|
||||
});
|
||||
this.columns.unshift(numberColumn.getColumnDefinition());
|
||||
let tableOptions: Slick.GridOptions<T> = {
|
||||
@@ -317,6 +383,7 @@ class GridTable<T> extends Disposable implements IView {
|
||||
this.table.registerPlugin(new AutoColumnSize());
|
||||
this.table.registerPlugin(copyHandler);
|
||||
this.table.registerPlugin(numberColumn);
|
||||
this.table.registerPlugin(new AdditionalKeyBindings());
|
||||
this._register(this.table.onContextMenu(this.contextMenu, this));
|
||||
this._register(this.table.onClick(this.onTableClick, this));
|
||||
|
||||
@@ -324,22 +391,7 @@ class GridTable<T> extends Disposable implements IView {
|
||||
this.table.style(this.styles);
|
||||
}
|
||||
|
||||
let actions = [];
|
||||
|
||||
if (this.state.canBeMaximized) {
|
||||
if (this.state.maximized) {
|
||||
actions.splice(1, 0, new MinimizeTableAction());
|
||||
} else {
|
||||
actions.splice(1, 0, new MaximizeTableAction());
|
||||
}
|
||||
}
|
||||
|
||||
actions.push(
|
||||
new SaveResultAction(SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
|
||||
new SaveResultAction(SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
|
||||
new SaveResultAction(SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON),
|
||||
this.instantiationService.createInstance(ChartDataAction)
|
||||
);
|
||||
let actions = this.getCurrentActions();
|
||||
|
||||
let actionBarContainer = document.createElement('div');
|
||||
actionBarContainer.style.width = ACTIONBAR_WIDTH + 'px';
|
||||
@@ -356,7 +408,65 @@ class GridTable<T> extends Disposable implements IView {
|
||||
tableState: this.state
|
||||
}
|
||||
});
|
||||
// update context before we run an action
|
||||
this.selectionModel.onSelectedRangesChanged.subscribe(e => {
|
||||
this.actionBar.context = this.generateContext();
|
||||
});
|
||||
this.actionBar.push(actions, { icon: true, label: false });
|
||||
|
||||
this.selectionModel.onSelectedRangesChanged.subscribe(e => {
|
||||
if (this.state) {
|
||||
this.state.selection = this.selectionModel.getSelectedRanges();
|
||||
}
|
||||
});
|
||||
|
||||
this.table.grid.onScroll.subscribe((e, data) => {
|
||||
if (!this.scrolled && this.state.scrollPosition && isInDOM(this.container)) {
|
||||
this.scrolled = true;
|
||||
this.table.grid.scrollTo(this.state.scrollPosition);
|
||||
}
|
||||
if (this.state && isInDOM(this.container)) {
|
||||
this.state.scrollPosition = data.scrollTop;
|
||||
}
|
||||
});
|
||||
|
||||
this.table.grid.onActiveCellChanged.subscribe(e => {
|
||||
if (this.state) {
|
||||
this.state.activeCell = this.table.grid.getActiveCell();
|
||||
}
|
||||
});
|
||||
|
||||
this.setupState();
|
||||
}
|
||||
|
||||
private setupState() {
|
||||
// change actionbar on maximize change
|
||||
this.state.onMaximizedChange(this.rebuildActionBar, this);
|
||||
|
||||
this.state.onCanBeMaximizedChange(this.rebuildActionBar, this);
|
||||
|
||||
if (this.state.scrollPosition) {
|
||||
// most of the time this won't do anything
|
||||
this.table.grid.scrollTo(this.state.scrollPosition);
|
||||
// the problem here is that the scrolling state slickgrid uses
|
||||
// doesn't work with it offDOM.
|
||||
}
|
||||
|
||||
if (this.state.selection) {
|
||||
this.selectionModel.setSelectedRanges(this.state.selection);
|
||||
}
|
||||
|
||||
if (this.state.activeCell) {
|
||||
this.table.setActiveCell(this.state.activeCell.row, this.state.activeCell.cell);
|
||||
}
|
||||
}
|
||||
|
||||
public get state(): GridTableState {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public set state(val: GridTableState) {
|
||||
this._state = val;
|
||||
}
|
||||
|
||||
private onTableClick(event: ITableMouseEvent) {
|
||||
@@ -372,6 +482,48 @@ class GridTable<T> extends Disposable implements IView {
|
||||
}
|
||||
}
|
||||
|
||||
private generateContext(cell?: Slick.Cell): IGridActionContext {
|
||||
const selection = this.selectionModel.getSelectedRanges();
|
||||
return <IGridActionContext>{
|
||||
cell,
|
||||
selection,
|
||||
runner: this.runner,
|
||||
batchId: this.resultSet.batchId,
|
||||
resultId: this.resultSet.id,
|
||||
table: this.table,
|
||||
tableState: this.state,
|
||||
selectionModel: this.selectionModel
|
||||
};
|
||||
}
|
||||
|
||||
private rebuildActionBar() {
|
||||
let actions = this.getCurrentActions();
|
||||
this.actionBar.clear();
|
||||
this.actionBar.push(actions, { icon: true, label: false });
|
||||
}
|
||||
|
||||
private getCurrentActions(): IAction[] {
|
||||
|
||||
let actions = [];
|
||||
|
||||
if (this.state.canBeMaximized) {
|
||||
if (this.state.maximized) {
|
||||
actions.splice(1, 0, new RestoreTableAction());
|
||||
} else {
|
||||
actions.splice(1, 0, new MaximizeTableAction());
|
||||
}
|
||||
}
|
||||
|
||||
actions.push(
|
||||
new SaveResultAction(SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
|
||||
new SaveResultAction(SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
|
||||
new SaveResultAction(SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON),
|
||||
this.instantiationService.createInstance(ChartDataAction)
|
||||
);
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
public layout(size?: number): void {
|
||||
if (!this.table) {
|
||||
this.build();
|
||||
@@ -384,24 +536,27 @@ class GridTable<T> extends Disposable implements IView {
|
||||
this.table.layout(
|
||||
new Dimension(
|
||||
getContentWidth(this.container) - ACTIONBAR_WIDTH,
|
||||
size - BOTTOM_PADDING
|
||||
size
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public get minimumSize(): number {
|
||||
// clamp between ensuring we can show the actionbar, while also making sure we don't take too much space
|
||||
return Math.max(Math.min(this.maxSize, MIN_GRID_HEIGHT), ACTIONBAR_HEIGHT);
|
||||
return Math.max(Math.min(this.maxSize, MIN_GRID_HEIGHT), ACTIONBAR_HEIGHT + BOTTOM_PADDING);
|
||||
}
|
||||
|
||||
public get maximumSize(): number {
|
||||
return Math.max(this.maxSize, ACTIONBAR_HEIGHT);
|
||||
return Math.max(this.maxSize, ACTIONBAR_HEIGHT + BOTTOM_PADDING);
|
||||
}
|
||||
|
||||
private loadData(offset: number, count: number): Thenable<T[]> {
|
||||
return this.runner.getQueryRows(offset, count, this.resultSet.batchId, this.resultSet.id).then(response => {
|
||||
if (this.runner.isQueryPlan) {
|
||||
this.instantiationService.createInstance(ShowQueryPlanAction).run(response.resultSubset.rows[0][0].displayValue);
|
||||
// it's a show plan response
|
||||
if (response.resultSubset.rowCount === 1) {
|
||||
this.instantiationService.createInstance(ShowQueryPlanAction).run(response.resultSubset.rows[0][0].displayValue);
|
||||
}
|
||||
}
|
||||
return response.resultSubset.rows.map(r => {
|
||||
let dataWithSchema = {};
|
||||
@@ -419,7 +574,6 @@ class GridTable<T> extends Disposable implements IView {
|
||||
}
|
||||
|
||||
private contextMenu(e: ITableMouseEvent): void {
|
||||
const selection = this.selectionModel.getSelectedRanges();
|
||||
const { cell } = e;
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
@@ -437,7 +591,7 @@ class GridTable<T> extends Disposable implements IView {
|
||||
|
||||
if (this.state.canBeMaximized) {
|
||||
if (this.state.maximized) {
|
||||
actions.splice(1, 0, new MinimizeTableAction());
|
||||
actions.splice(1, 0, new RestoreTableAction());
|
||||
} else {
|
||||
actions.splice(1, 0, new MaximizeTableAction());
|
||||
}
|
||||
@@ -446,15 +600,7 @@ class GridTable<T> extends Disposable implements IView {
|
||||
return TPromise.as(actions);
|
||||
},
|
||||
getActionsContext: () => {
|
||||
return <IGridActionContext>{
|
||||
cell,
|
||||
selection,
|
||||
runner: this.runner,
|
||||
batchId: this.resultSet.batchId,
|
||||
resultId: this.resultSet.id,
|
||||
table: this.table,
|
||||
tableState: this.state
|
||||
};
|
||||
return this.generateContext(cell);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,18 +13,28 @@
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.monaco-workbench .message-tree .monaco-tree .monaco-tree-rows>.monaco-tree-row {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.message-tree .time-stamp {
|
||||
width: 100px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.message-tree .message,
|
||||
.message-tree .batch-start {
|
||||
.message-tree .batch-start,
|
||||
.message-tree .error-message {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.message-tree .batch-start {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.message-tree .error-message {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.message-tree .batch-start:hover {
|
||||
|
||||
5
src/sql/parts/query/editor/media/queryActions.css
Normal file
5
src/sql/parts/query/editor/media/queryActions.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.monaco-select-box {
|
||||
cursor: pointer;
|
||||
min-width: 150px;
|
||||
padding: 2px;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import 'vs/css!./media/messagePanel';
|
||||
import { IMessagesActionContext, SelectAllMessagesAction, CopyMessagesAction } from './actions';
|
||||
import QueryRunner from 'sql/parts/query/execution/queryRunner';
|
||||
|
||||
import { IResultMessage, BatchSummary, ISelectionData } from 'sqlops';
|
||||
import { IResultMessage, ISelectionData } from 'sqlops';
|
||||
|
||||
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||
import { IDataSource, ITree, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
|
||||
@@ -25,11 +25,11 @@ import { OpenMode, ClickBehavior, ICancelableEvent, IControllerOptions } from 'v
|
||||
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { $ } from 'vs/base/browser/builder';
|
||||
import { isArray } from 'vs/base/common/types';
|
||||
import { isArray, isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditor } from 'vs/editor/common/editorCommon';
|
||||
import { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||
|
||||
export interface IResultMessageIntern extends IResultMessage {
|
||||
id?: string;
|
||||
@@ -56,9 +56,22 @@ interface IBatchTemplate extends IMessageTemplate {
|
||||
const TemplateIds = {
|
||||
MESSAGE: 'message',
|
||||
BATCH: 'batch',
|
||||
MODEL: 'model'
|
||||
MODEL: 'model',
|
||||
ERROR: 'error'
|
||||
};
|
||||
|
||||
export class MessagePanelState {
|
||||
public scrollPosition: number;
|
||||
public collapsed = false;
|
||||
|
||||
constructor(@IConfigurationService configurationService: IConfigurationService) {
|
||||
let messagesOpenedSettings = configurationService.getValue<boolean>('sql.messagesDefaultOpen');
|
||||
if (!isUndefinedOrNull(messagesOpenedSettings)) {
|
||||
this.collapsed = !messagesOpenedSettings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MessagePanel extends ViewletPanel {
|
||||
private ds = new MessageDataSource();
|
||||
private renderer = new MessageRenderer();
|
||||
@@ -67,6 +80,7 @@ export class MessagePanel extends ViewletPanel {
|
||||
private container = $('div message-tree').getHTMLElement();
|
||||
|
||||
private queryRunnerDisposables: IDisposable[] = [];
|
||||
private _state: MessagePanelState;
|
||||
|
||||
private tree: ITree;
|
||||
|
||||
@@ -86,6 +100,16 @@ export class MessagePanel extends ViewletPanel {
|
||||
renderer: this.renderer,
|
||||
controller: this.controller
|
||||
}, { keyboardSupport: false });
|
||||
this.tree.onDidScroll(e => {
|
||||
if (this.state) {
|
||||
this.state.scrollPosition = this.tree.getScrollPosition();
|
||||
}
|
||||
});
|
||||
this.onDidChange(e => {
|
||||
if (this.state) {
|
||||
this.state.collapsed = !this.isExpanded();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
@@ -99,8 +123,12 @@ export class MessagePanel extends ViewletPanel {
|
||||
protected layoutBody(size: number): void {
|
||||
const previousScrollPosition = this.tree.getScrollPosition();
|
||||
this.tree.layout(size);
|
||||
if (previousScrollPosition === 1) {
|
||||
this.tree.setScrollPosition(1);
|
||||
if (this.state && this.state.scrollPosition) {
|
||||
this.tree.setScrollPosition(this.state.scrollPosition);
|
||||
} else {
|
||||
if (previousScrollPosition === 1) {
|
||||
this.tree.setScrollPosition(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,17 +141,30 @@ export class MessagePanel extends ViewletPanel {
|
||||
}
|
||||
|
||||
private onMessage(message: IResultMessage | IResultMessage[]) {
|
||||
let hasError = false;
|
||||
if (isArray(message)) {
|
||||
hasError = message.find(e => e.isError) ? true : false;
|
||||
this.model.messages.push(...message);
|
||||
} else {
|
||||
hasError = message.isError;
|
||||
this.model.messages.push(message);
|
||||
}
|
||||
const previousScrollPosition = this.tree.getScrollPosition();
|
||||
this.tree.refresh(this.model).then(() => {
|
||||
if (previousScrollPosition === 1) {
|
||||
if (hasError) {
|
||||
this.setExpanded(true);
|
||||
}
|
||||
if (this.state.scrollPosition) {
|
||||
this.tree.refresh(this.model).then(() => {
|
||||
this.tree.setScrollPosition(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const previousScrollPosition = this.tree.getScrollPosition();
|
||||
this.tree.refresh(this.model).then(() => {
|
||||
if (previousScrollPosition === 1) {
|
||||
this.tree.setScrollPosition(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.maximumBodySize = this.model.messages.length * 22;
|
||||
}
|
||||
|
||||
private reset() {
|
||||
@@ -131,6 +172,17 @@ export class MessagePanel extends ViewletPanel {
|
||||
this.model.totalExecuteMessage = undefined;
|
||||
this.tree.refresh(this.model);
|
||||
}
|
||||
|
||||
public set state(val: MessagePanelState) {
|
||||
this._state = val;
|
||||
if (this.state.scrollPosition) {
|
||||
this.tree.setScrollPosition(this.state.scrollPosition);
|
||||
}
|
||||
this.setExpanded(!this.state.collapsed);
|
||||
}
|
||||
public get state(): MessagePanelState {
|
||||
return this._state;
|
||||
}
|
||||
}
|
||||
|
||||
class MessageDataSource implements IDataSource {
|
||||
@@ -176,6 +228,8 @@ class MessageRenderer implements IRenderer {
|
||||
return TemplateIds.MODEL;
|
||||
} else if (element.selection) {
|
||||
return TemplateIds.BATCH;
|
||||
} else if (element.isError) {
|
||||
return TemplateIds.ERROR;
|
||||
} else {
|
||||
return TemplateIds.MESSAGE;
|
||||
}
|
||||
@@ -191,15 +245,19 @@ class MessageRenderer implements IRenderer {
|
||||
const timeStamp = $('div.time-stamp').appendTo(container).getHTMLElement();
|
||||
const message = $('div.batch-start').appendTo(container).getHTMLElement();
|
||||
return { message, timeStamp };
|
||||
} else if (templateId === TemplateIds.ERROR) {
|
||||
$('div.time-stamp').appendTo(container);
|
||||
const message = $('div.error-message').appendTo(container).getHTMLElement();
|
||||
return { message };
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
renderElement(tree: ITree, element: IResultMessage, templateId: string, templateData: IMessageTemplate | IBatchTemplate): void {
|
||||
if (templateId === TemplateIds.MESSAGE) {
|
||||
if (templateId === TemplateIds.MESSAGE || templateId === TemplateIds.ERROR) {
|
||||
let data: IMessageTemplate = templateData;
|
||||
data.message.innerText = element.message;
|
||||
data.message.innerText = element.message.replace(/(\r\n|\n|\r)/g, ' ');
|
||||
} else if (templateId === TemplateIds.BATCH) {
|
||||
let data = templateData as IBatchTemplate;
|
||||
data.timeStamp.innerText = element.time;
|
||||
@@ -245,14 +303,8 @@ export class MessageController extends WorkbenchTreeController {
|
||||
if (element.selection) {
|
||||
let selection: ISelectionData = element.selection;
|
||||
// this is a batch statement
|
||||
let control = this.workbenchEditorService.activeControl.getControl() as IEditor;
|
||||
control.setSelection({
|
||||
startColumn: selection.startColumn + 1,
|
||||
endColumn: selection.endColumn + 1,
|
||||
endLineNumber: selection.endLine + 1,
|
||||
startLineNumber: selection.startLine + 1
|
||||
});
|
||||
control.focus();
|
||||
let input = this.workbenchEditorService.activeEditor as QueryInput;
|
||||
input.updateSelection(selection);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -43,7 +43,6 @@ import {
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import { IEditorDescriptorService } from 'sql/parts/query/editor/editorDescriptorService';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { attachEditableDropdownStyler } from 'sql/common/theme/styler';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
@@ -378,6 +377,26 @@ export class QueryEditor extends BaseEditor {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getAllSelection(): ISelectionData {
|
||||
if (this._sqlEditor && this._sqlEditor.getControl()) {
|
||||
let control = this._sqlEditor.getControl();
|
||||
let codeEditor: ICodeEditor = <ICodeEditor>control;
|
||||
if (codeEditor) {
|
||||
let model = codeEditor.getModel();
|
||||
let totalLines = model.getLineCount();
|
||||
let endColumn = model.getLineMaxColumn(totalLines);
|
||||
let selection: ISelectionData = {
|
||||
startLine: 0,
|
||||
startColumn: 0,
|
||||
endLine: totalLines - 1,
|
||||
endColumn: endColumn - 1,
|
||||
};
|
||||
return selection;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getSelectionText(): string {
|
||||
if (this._sqlEditor && this._sqlEditor.getControl()) {
|
||||
let control = this._sqlEditor.getControl();
|
||||
@@ -500,7 +519,7 @@ export class QueryEditor extends BaseEditor {
|
||||
public get listDatabasesActionItem(): ListDatabasesActionItem {
|
||||
if (!this._listDatabasesActionItem) {
|
||||
this._listDatabasesActionItem = this._instantiationService.createInstance(ListDatabasesActionItem, this, this._listDatabasesAction);
|
||||
this._register(attachEditableDropdownStyler(this._listDatabasesActionItem, this.themeService));
|
||||
this._register(this._listDatabasesActionItem.attachStyler(this.themeService));
|
||||
}
|
||||
return this._listDatabasesActionItem;
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ export class QueryResultsEditor extends BaseEditor {
|
||||
protected _input: QueryResultsInput;
|
||||
|
||||
private resultsView: QueryResultsView;
|
||||
private styleSheet = DOM.createStyleSheet();
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@@ -103,12 +104,13 @@ export class QueryResultsEditor extends BaseEditor {
|
||||
) {
|
||||
super(QueryResultsEditor.ID, telemetryService, themeService);
|
||||
this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel());
|
||||
// this._configurationService.onDidChangeConfiguration(e => {
|
||||
// if (e.affectsConfiguration('resultsGrid')) {
|
||||
// this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel());
|
||||
// this.applySettings();
|
||||
// }
|
||||
// });
|
||||
this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('resultsGrid')) {
|
||||
this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel());
|
||||
this.applySettings();
|
||||
}
|
||||
});
|
||||
this.applySettings();
|
||||
}
|
||||
|
||||
public get input(): QueryResultsInput {
|
||||
@@ -116,23 +118,20 @@ export class QueryResultsEditor extends BaseEditor {
|
||||
}
|
||||
|
||||
private applySettings() {
|
||||
if (this.input && this.input.container) {
|
||||
if (!this.input.css) {
|
||||
this.input.css = DOM.createStyleSheet(this.input.container);
|
||||
}
|
||||
let cssRuleText = '';
|
||||
if (types.isNumber(this._rawOptions.cellPadding)) {
|
||||
cssRuleText = this._rawOptions.cellPadding + 'px';
|
||||
} else {
|
||||
cssRuleText = this._rawOptions.cellPadding.join('px ') + 'px;';
|
||||
}
|
||||
let content = `.grid .slick-cell { padding: ${cssRuleText}; }`;
|
||||
content += `.grid { ${getBareResultsGridInfoStyles(this._rawOptions)} }`;
|
||||
this.input.css.innerHTML = content;
|
||||
let cssRuleText = '';
|
||||
if (types.isNumber(this._rawOptions.cellPadding)) {
|
||||
cssRuleText = this._rawOptions.cellPadding + 'px';
|
||||
} else {
|
||||
cssRuleText = this._rawOptions.cellPadding.join('px ') + 'px;';
|
||||
}
|
||||
let content = `.grid-panel .monaco-table .slick-cell { padding: ${cssRuleText} }`;
|
||||
content += `.grid-panel .monaco-table { ${getBareResultsGridInfoStyles(this._rawOptions)} }`;
|
||||
this.styleSheet.innerHTML = content;
|
||||
}
|
||||
|
||||
createEditor(parent: HTMLElement): void {
|
||||
this.styleSheet.remove();
|
||||
parent.appendChild(this.styleSheet);
|
||||
if (!this.resultsView) {
|
||||
this.resultsView = new QueryResultsView(parent, this._instantiationService, this._queryModelService);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
|
||||
import { QueryResultsInput, ResultsViewState } from 'sql/parts/query/common/queryResultsInput';
|
||||
import { TabbedPanel, IPanelTab, IPanelView } from 'sql/base/browser/ui/panel/panel';
|
||||
import { IQueryModelService } from '../execution/queryModel';
|
||||
import QueryRunner from 'sql/parts/query/execution/queryRunner';
|
||||
@@ -14,11 +14,11 @@ import { ChartTab } from './charting/chartTab';
|
||||
import { QueryPlanTab } from 'sql/parts/queryPlan/queryPlan';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as UUID from 'vs/base/common/uuid';
|
||||
import { PanelViewlet } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { once } from 'vs/base/common/event';
|
||||
import { once, anyEvent } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
|
||||
class ResultsView implements IPanelView {
|
||||
private panelViewlet: PanelViewlet;
|
||||
@@ -26,25 +26,72 @@ class ResultsView implements IPanelView {
|
||||
private messagePanel: MessagePanel;
|
||||
private container = document.createElement('div');
|
||||
private currentDimension: DOM.Dimension;
|
||||
private needsGridResize = false;
|
||||
private _state: ResultsViewState;
|
||||
|
||||
constructor(instantiationService: IInstantiationService) {
|
||||
this.panelViewlet = instantiationService.createInstance(PanelViewlet, 'resultsView', { showHeaderInTitleWhenSingleView: false });
|
||||
this.gridPanel = instantiationService.createInstance(GridPanel, { title: nls.localize('gridPanel', 'Results') });
|
||||
this.messagePanel = instantiationService.createInstance(MessagePanel, { title: nls.localize('messagePanel', 'Messages') });
|
||||
constructor(private instantiationService: IInstantiationService) {
|
||||
|
||||
this.panelViewlet = this.instantiationService.createInstance(PanelViewlet, 'resultsView', { showHeaderInTitleWhenSingleView: false });
|
||||
this.gridPanel = this.instantiationService.createInstance(GridPanel, { title: nls.localize('gridPanel', 'Results'), id: 'gridPanel' });
|
||||
this.messagePanel = this.instantiationService.createInstance(MessagePanel, { title: nls.localize('messagePanel', 'Messages'), minimumBodySize: 0, id: 'messagePanel' });
|
||||
this.gridPanel.render();
|
||||
this.messagePanel.render();
|
||||
this.panelViewlet.create(this.container).then(() => {
|
||||
this.gridPanel.setVisible(false);
|
||||
this.panelViewlet.addPanels([
|
||||
{ panel: this.gridPanel, size: 1000, index: 0 },
|
||||
{ panel: this.messagePanel, size: this.messagePanel.minimumSize, index: 1 }
|
||||
]);
|
||||
});
|
||||
let gridResizeList = this.gridPanel.onDidChange(e => {
|
||||
this.panelViewlet.resizePanel(this.gridPanel, this.gridPanel.maximumSize);
|
||||
anyEvent(this.gridPanel.onDidChange, this.messagePanel.onDidChange)(e => {
|
||||
let size = this.gridPanel.maximumBodySize;
|
||||
if (size < 1 && this.gridPanel.isVisible()) {
|
||||
this.gridPanel.setVisible(false);
|
||||
this.panelViewlet.removePanels([this.gridPanel]);
|
||||
this.gridPanel.layout(0);
|
||||
} else if (size > 0 && !this.gridPanel.isVisible()) {
|
||||
this.gridPanel.setVisible(true);
|
||||
let panelSize: number;
|
||||
if (this.state && this.state.gridPanelSize) {
|
||||
panelSize = this.state.gridPanelSize;
|
||||
} else if (this.currentDimension) {
|
||||
panelSize = Math.round(this.currentDimension.height * .7);
|
||||
} else {
|
||||
panelSize = 200;
|
||||
this.needsGridResize = true;
|
||||
}
|
||||
this.panelViewlet.addPanels([{ panel: this.gridPanel, index: 0, size: panelSize }]);
|
||||
}
|
||||
});
|
||||
let resizeList = anyEvent(this.gridPanel.onDidChange, this.messagePanel.onDidChange)(() => {
|
||||
let panelSize: number;
|
||||
if (this.state && this.state.gridPanelSize) {
|
||||
panelSize = this.state.gridPanelSize;
|
||||
} else if (this.currentDimension) {
|
||||
panelSize = Math.round(this.currentDimension.height * .7);
|
||||
} else {
|
||||
panelSize = 200;
|
||||
this.needsGridResize = true;
|
||||
}
|
||||
if (this.state.messagePanelSize) {
|
||||
this.panelViewlet.resizePanel(this.gridPanel, this.state.messagePanelSize);
|
||||
}
|
||||
this.panelViewlet.resizePanel(this.gridPanel, panelSize);
|
||||
})
|
||||
// once the user changes the sash we should stop trying to resize the grid
|
||||
once(this.panelViewlet.onDidSashChange)(e => {
|
||||
gridResizeList.dispose();
|
||||
this.needsGridResize = false;
|
||||
resizeList.dispose();
|
||||
});
|
||||
|
||||
this.panelViewlet.onDidSashChange(e => {
|
||||
if (this.state) {
|
||||
if (this.gridPanel.isExpanded()) {
|
||||
this.state.gridPanelSize = this.panelViewlet.getPanelSize(this.gridPanel);
|
||||
}
|
||||
if (this.messagePanel.isExpanded()) {
|
||||
this.state.messagePanelSize = this.panelViewlet.getPanelSize(this.messagePanel);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -59,6 +106,9 @@ class ResultsView implements IPanelView {
|
||||
this.gridPanel.layout(dimension.height);
|
||||
}
|
||||
this.currentDimension = dimension;
|
||||
if (this.needsGridResize) {
|
||||
this.panelViewlet.resizePanel(this.gridPanel, this.state.gridPanelSize || Math.round(this.currentDimension.height * .7));
|
||||
}
|
||||
}
|
||||
|
||||
remove(): void {
|
||||
@@ -73,11 +123,21 @@ class ResultsView implements IPanelView {
|
||||
public hideResultHeader() {
|
||||
this.gridPanel.headerVisible = false;
|
||||
}
|
||||
|
||||
public set state(val: ResultsViewState) {
|
||||
this._state = val;
|
||||
this.gridPanel.state = val.gridPanelState;
|
||||
this.messagePanel.state = val.messagePanelState;
|
||||
}
|
||||
|
||||
public get state(): ResultsViewState {
|
||||
return this._state;
|
||||
}
|
||||
}
|
||||
|
||||
class ResultsTab implements IPanelTab {
|
||||
public readonly title = nls.localize('resultsTabTitle', 'Results');
|
||||
public readonly identifier = UUID.generateUuid();
|
||||
public readonly identifier = 'resultsTab';
|
||||
public readonly view: ResultsView;
|
||||
|
||||
constructor(instantiationService: IInstantiationService) {
|
||||
@@ -96,6 +156,8 @@ export class QueryResultsView {
|
||||
private chartTab: ChartTab;
|
||||
private qpTab: QueryPlanTab;
|
||||
|
||||
private runnerDisposables: IDisposable[];
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@@ -105,6 +167,12 @@ export class QueryResultsView {
|
||||
this.chartTab = new ChartTab(instantiationService);
|
||||
this._panelView = new TabbedPanel(container, { showHeaderWhenSingleView: false });
|
||||
this.qpTab = new QueryPlanTab();
|
||||
this._panelView.pushTab(this.resultsTab);
|
||||
this._panelView.onTabChange(e => {
|
||||
if (this.input) {
|
||||
this.input.state.activeTab = e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public style() {
|
||||
@@ -112,11 +180,32 @@ export class QueryResultsView {
|
||||
|
||||
public set input(input: QueryResultsInput) {
|
||||
this._input = input;
|
||||
dispose(this.runnerDisposables);
|
||||
this.runnerDisposables = [];
|
||||
this.resultsTab.view.state = this.input.state;
|
||||
this.qpTab.view.state = this.input.state.queryPlanState;
|
||||
this.chartTab.view.state = this.input.state.chartState;
|
||||
let queryRunner = this.queryModelService._getQueryInfo(input.uri).queryRunner;
|
||||
this.resultsTab.queryRunner = queryRunner;
|
||||
this.chartTab.queryRunner = queryRunner;
|
||||
if (!this._panelView.contains(this.resultsTab)) {
|
||||
this._panelView.pushTab(this.resultsTab);
|
||||
this.runnerDisposables.push(queryRunner.onQueryStart(e => {
|
||||
this.hideChart();
|
||||
this.hidePlan();
|
||||
this.input.state.visibleTabs = new Set();
|
||||
this.input.state.activeTab = this.resultsTab.identifier;
|
||||
}));
|
||||
if (this.input.state.visibleTabs.has(this.chartTab.identifier)) {
|
||||
if (!this._panelView.contains(this.chartTab)) {
|
||||
this._panelView.pushTab(this.chartTab);
|
||||
}
|
||||
}
|
||||
if (this.input.state.visibleTabs.has(this.qpTab.identifier)) {
|
||||
if (!this._panelView.contains(this.qpTab)) {
|
||||
this._panelView.pushTab(this.qpTab);
|
||||
}
|
||||
}
|
||||
if (this.input.state.activeTab) {
|
||||
this._panelView.showTab(this.input.state.activeTab);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +222,7 @@ export class QueryResultsView {
|
||||
}
|
||||
|
||||
public chartData(dataId: { resultId: number, batchId: number }): void {
|
||||
this.input.state.visibleTabs.add(this.chartTab.identifier);
|
||||
if (!this._panelView.contains(this.chartTab)) {
|
||||
this._panelView.pushTab(this.chartTab);
|
||||
}
|
||||
@@ -141,7 +231,14 @@ export class QueryResultsView {
|
||||
this.chartTab.chart(dataId);
|
||||
}
|
||||
|
||||
public hideChart() {
|
||||
if (this._panelView.contains(this.chartTab)) {
|
||||
this._panelView.removeTab(this.chartTab.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
public showPlan(xml: string) {
|
||||
this.input.state.visibleTabs.add(this.qpTab.identifier);
|
||||
if (!this._panelView.contains(this.qpTab)) {
|
||||
this._panelView.pushTab(this.qpTab);
|
||||
}
|
||||
@@ -149,4 +246,10 @@ export class QueryResultsView {
|
||||
this._panelView.showTab(this.qpTab.identifier);
|
||||
this.qpTab.view.showPlan(xml);
|
||||
}
|
||||
|
||||
public hidePlan() {
|
||||
if (this._panelView.contains(this.qpTab)) {
|
||||
this._panelView.removeTab(this.qpTab.identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!sql/parts/query/editor/media/queryActions';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { Dropdown } from 'sql/base/browser/ui/editableDropdown/dropdown';
|
||||
@@ -12,6 +13,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { attachEditableDropdownStyler, attachSelectBoxStyler } from 'sql/common/theme/styler';
|
||||
|
||||
import { ISelectionData } from 'sqlops';
|
||||
import {
|
||||
@@ -25,6 +27,8 @@ import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
/**
|
||||
* Action class that query-based Actions will extend. This base class automatically handles activating and
|
||||
@@ -267,7 +271,11 @@ export class ActualQueryPlanAction extends QueryTaskbarAction {
|
||||
}
|
||||
|
||||
if (this.isConnected(editor)) {
|
||||
editor.currentQueryInput.runQuery(editor.getSelection(), {
|
||||
let selection = editor.getSelection();
|
||||
if (!selection) {
|
||||
selection = editor.getAllSelection();
|
||||
}
|
||||
editor.currentQueryInput.runQuery(selection, {
|
||||
displayActualQueryPlan: true
|
||||
});
|
||||
}
|
||||
@@ -431,6 +439,9 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
|
||||
private _isConnected: boolean;
|
||||
private $databaseListDropdown: Builder;
|
||||
private _dropdown: Dropdown;
|
||||
private _databaseSelectBox: SelectBox;
|
||||
private _isInAccessibilityMode: boolean;
|
||||
private readonly _selectDatabaseString: string = nls.localize("selectDatabase", "Select Database");
|
||||
|
||||
// CONSTRUCTOR /////////////////////////////////////////////////////////
|
||||
constructor(
|
||||
@@ -439,23 +450,33 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
@IContextViewService contextViewProvider: IContextViewService,
|
||||
@IThemeService themeService: IThemeService
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
this._toDispose = [];
|
||||
this.$databaseListDropdown = $('.databaseListDropdown');
|
||||
let selectString = nls.localize("selectDatabase", "Select Database");
|
||||
this._dropdown = new Dropdown(this.$databaseListDropdown.getHTMLElement(), contextViewProvider, themeService, {
|
||||
strictSelection: true,
|
||||
placeholder: selectString,
|
||||
ariaLabel: selectString,
|
||||
actionLabel: nls.localize('listDatabases.toggleDatabaseNameDropdown', 'Select Database Toggle Dropdown')
|
||||
});
|
||||
this._dropdown.onValueChange(s => this.databaseSelected(s));
|
||||
this._isInAccessibilityMode = this._configurationService.getValue('editor.accessibilitySupport') === 'on';
|
||||
|
||||
if (this._isInAccessibilityMode) {
|
||||
this._databaseSelectBox = new SelectBox([this._selectDatabaseString], this._selectDatabaseString, contextViewProvider, undefined, { ariaLabel: this._selectDatabaseString });
|
||||
this._databaseSelectBox.render(this.$databaseListDropdown.getHTMLElement());
|
||||
this._databaseSelectBox.onDidSelect(e => { this.databaseSelected(e.selected); });
|
||||
this._databaseSelectBox.disable();
|
||||
|
||||
} else {
|
||||
this._dropdown = new Dropdown(this.$databaseListDropdown.getHTMLElement(), contextViewProvider, themeService, {
|
||||
strictSelection: true,
|
||||
placeholder: this._selectDatabaseString,
|
||||
ariaLabel: this._selectDatabaseString,
|
||||
actionLabel: nls.localize('listDatabases.toggleDatabaseNameDropdown', 'Select Database Toggle Dropdown')
|
||||
});
|
||||
this._dropdown.onValueChange(s => this.databaseSelected(s));
|
||||
this._toDispose.push(this._dropdown.onFocus(() => { self.onDropdownFocus(); }));
|
||||
}
|
||||
|
||||
// Register event handlers
|
||||
let self = this;
|
||||
this._toDispose.push(this._dropdown.onFocus(() => { self.onDropdownFocus(); }));
|
||||
this._toDispose.push(this._connectionManagementService.onConnectionChanged(params => { self.onConnectionChanged(params); }));
|
||||
}
|
||||
|
||||
@@ -465,7 +486,12 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
|
||||
}
|
||||
|
||||
public style(styles) {
|
||||
this._dropdown.style(styles);
|
||||
if (this._isInAccessibilityMode) {
|
||||
this._databaseSelectBox.style(styles);
|
||||
}
|
||||
else {
|
||||
this._dropdown.style(styles);
|
||||
}
|
||||
}
|
||||
|
||||
public setActionContext(context: any): void {
|
||||
@@ -477,11 +503,27 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this._dropdown.focus();
|
||||
if (this._isInAccessibilityMode) {
|
||||
this._databaseSelectBox.focus();
|
||||
} else {
|
||||
this._dropdown.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public blur(): void {
|
||||
this._dropdown.blur();
|
||||
if (this._isInAccessibilityMode) {
|
||||
this._databaseSelectBox.blur();
|
||||
} else {
|
||||
this._dropdown.blur();
|
||||
}
|
||||
}
|
||||
|
||||
public attachStyler(themeService: IThemeService): IDisposable {
|
||||
if (this._isInAccessibilityMode) {
|
||||
return attachSelectBoxStyler(this, themeService);
|
||||
} else {
|
||||
return attachEditableDropdownStyler(this, themeService);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -496,9 +538,15 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
|
||||
|
||||
public onDisconnect(): void {
|
||||
this._isConnected = false;
|
||||
this._dropdown.enabled = false;
|
||||
this._currentDatabaseName = undefined;
|
||||
this._dropdown.value = '';
|
||||
|
||||
if (this._isInAccessibilityMode) {
|
||||
this._databaseSelectBox.disable();
|
||||
this._databaseSelectBox.setOptions([this._selectDatabaseString]);
|
||||
} else {
|
||||
this._dropdown.enabled = false;
|
||||
this._dropdown.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// PRIVATE HELPERS /////////////////////////////////////////////////////
|
||||
@@ -515,22 +563,22 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
|
||||
|
||||
this._connectionManagementService.changeDatabase(this._editor.uri, dbName)
|
||||
.then(
|
||||
result => {
|
||||
if (!result) {
|
||||
result => {
|
||||
if (!result) {
|
||||
this.resetDatabaseName();
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: nls.localize('changeDatabase.failed', "Failed to change database")
|
||||
});
|
||||
}
|
||||
},
|
||||
error => {
|
||||
this.resetDatabaseName();
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: nls.localize('changeDatabase.failed', "Failed to change database")
|
||||
message: nls.localize('changeDatabase.failedWithError', "Failed to change database {0}", error)
|
||||
});
|
||||
}
|
||||
},
|
||||
error => {
|
||||
this.resetDatabaseName();
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: nls.localize('changeDatabase.failedWithError', "Failed to change database {0}", error)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getCurrentDatabaseName() {
|
||||
@@ -545,7 +593,11 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
|
||||
}
|
||||
|
||||
private resetDatabaseName() {
|
||||
this._dropdown.value = this.getCurrentDatabaseName();
|
||||
if (this._isInAccessibilityMode) {
|
||||
this._databaseSelectBox.selectWithOptionName(this.getCurrentDatabaseName());
|
||||
} else {
|
||||
this._dropdown.value = this.getCurrentDatabaseName();
|
||||
}
|
||||
}
|
||||
|
||||
private onConnectionChanged(connParams: IConnectionParams): void {
|
||||
@@ -579,9 +631,26 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
|
||||
|
||||
private updateConnection(databaseName: string) {
|
||||
this._isConnected = true;
|
||||
this._dropdown.enabled = true;
|
||||
this._currentDatabaseName = databaseName;
|
||||
this._dropdown.value = databaseName;
|
||||
|
||||
if (this._isInAccessibilityMode) {
|
||||
this._databaseSelectBox.enable();
|
||||
let self = this;
|
||||
let uri = self._editor.connectedUri;
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
self._connectionManagementService.listDatabases(uri)
|
||||
.then(result => {
|
||||
if (result && result.databaseNames) {
|
||||
this._databaseSelectBox.setOptions(result.databaseNames);
|
||||
}
|
||||
this._databaseSelectBox.selectWithOptionName(databaseName);
|
||||
});
|
||||
} else {
|
||||
this._dropdown.enabled = true;
|
||||
this._dropdown.value = databaseName;
|
||||
}
|
||||
}
|
||||
|
||||
// TESTING PROPERTIES //////////////////////////////////////////////////
|
||||
|
||||
@@ -12,7 +12,7 @@ import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
||||
import { IQueryManagementService } from 'sql/parts/query/common/queryManagement';
|
||||
import * as Utils from 'sql/parts/connection/common/utils';
|
||||
import { SaveFormat } from 'sql/parts/grid/common/interfaces';
|
||||
import { echo } from 'sql/base/common/event';
|
||||
import { echo, debounceEvent } from 'sql/base/common/event';
|
||||
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
@@ -22,9 +22,10 @@ import * as types from 'vs/base/common/types';
|
||||
import { EventEmitter } from 'sql/base/common/eventEmitter';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { Emitter, debounceEvent, Event } from 'vs/base/common/event';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ResultSerializer } from 'sql/parts/query/common/resultSerializer';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
export interface IEditSessionReadyEvent {
|
||||
ownerUri: string;
|
||||
@@ -73,25 +74,27 @@ export default class QueryRunner {
|
||||
public get isQueryPlan(): boolean { return this._isQueryPlan; }
|
||||
|
||||
private _onMessage = new Emitter<sqlops.IResultMessage>();
|
||||
private _echoedMessages = echo(debounceEvent<sqlops.IResultMessage, sqlops.IResultMessage[]>(this._onMessage.event, (l, e) => {
|
||||
private _debouncedMessage = debounceEvent<sqlops.IResultMessage, sqlops.IResultMessage[]>(this._onMessage.event, (l, e) => {
|
||||
// on first run
|
||||
if (types.isUndefinedOrNull(l)) {
|
||||
return [e];
|
||||
} else {
|
||||
return l.concat(e);
|
||||
}
|
||||
}));
|
||||
});
|
||||
private _echoedMessages = echo(this._debouncedMessage.event);
|
||||
public readonly onMessage = this._echoedMessages.event;
|
||||
|
||||
private _onResultSet = new Emitter<sqlops.ResultSetSummary>();
|
||||
private _echoedResultSet = echo(debounceEvent<sqlops.ResultSetSummary, sqlops.ResultSetSummary[]>(this._onResultSet.event, (l, e) => {
|
||||
private _debouncedResultSet = debounceEvent<sqlops.ResultSetSummary, sqlops.ResultSetSummary[]>(this._onResultSet.event, (l, e) => {
|
||||
// on first run
|
||||
if (types.isUndefinedOrNull(l)) {
|
||||
return [e];
|
||||
} else {
|
||||
return l.concat(e);
|
||||
}
|
||||
}));
|
||||
});
|
||||
private _echoedResultSet = echo(this._debouncedResultSet.event);
|
||||
public readonly onResultSet = this._echoedResultSet.event;
|
||||
|
||||
private _onQueryStart = new Emitter<void>();
|
||||
@@ -171,8 +174,13 @@ export default class QueryRunner {
|
||||
private doRunQuery(input: string, runCurrentStatement: boolean, runOptions?: sqlops.ExecutionPlanOptions): Thenable<void>;
|
||||
private doRunQuery(input: sqlops.ISelectionData, runCurrentStatement: boolean, runOptions?: sqlops.ExecutionPlanOptions): Thenable<void>;
|
||||
private doRunQuery(input, runCurrentStatement: boolean, runOptions?: sqlops.ExecutionPlanOptions): Thenable<void> {
|
||||
if (this.isExecuting) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
this._echoedMessages.clear();
|
||||
this._echoedResultSet.clear();
|
||||
this._debouncedMessage.clear();
|
||||
this._debouncedResultSet.clear();
|
||||
let ownerUri = this.uri;
|
||||
this._batchSets = [];
|
||||
this._hasCompleted = false;
|
||||
|
||||
@@ -13,9 +13,13 @@ import { localize } from 'vs/nls';
|
||||
import * as UUID from 'vs/base/common/uuid';
|
||||
import { Builder } from 'vs/base/browser/builder';
|
||||
|
||||
export class QueryPlanState {
|
||||
xml: string;
|
||||
}
|
||||
|
||||
export class QueryPlanTab implements IPanelTab {
|
||||
public readonly title = localize('queryPlanTitle', 'Query Plan');
|
||||
public readonly identifier = UUID.generateUuid();
|
||||
public readonly identifier = 'QueryPlanTab';
|
||||
public readonly view: QueryPlanView;
|
||||
|
||||
constructor() {
|
||||
@@ -27,6 +31,7 @@ export class QueryPlanView implements IPanelView {
|
||||
private qp: QueryPlan;
|
||||
private xml: string;
|
||||
private container = document.createElement('div');
|
||||
private _state: QueryPlanState;
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
if (!this.qp) {
|
||||
@@ -36,6 +41,7 @@ export class QueryPlanView implements IPanelView {
|
||||
}
|
||||
}
|
||||
container.appendChild(this.container);
|
||||
container.style.overflow = 'scroll';
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
@@ -47,6 +53,20 @@ export class QueryPlanView implements IPanelView {
|
||||
} else {
|
||||
this.xml = xml;
|
||||
}
|
||||
if (this.state) {
|
||||
this.state.xml = xml;
|
||||
}
|
||||
}
|
||||
|
||||
public set state(val: QueryPlanState) {
|
||||
this._state = val;
|
||||
if (this.state.xml) {
|
||||
this.showPlan(this.state.xml);
|
||||
}
|
||||
}
|
||||
|
||||
public get state(): QueryPlanState {
|
||||
return this._state;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ const viewletDescriptor = new ViewletDescriptor(
|
||||
VIEWLET_ID,
|
||||
'Task History',
|
||||
'taskHistoryViewlet',
|
||||
-90
|
||||
1
|
||||
);
|
||||
|
||||
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(viewletDescriptor);
|
||||
|
||||
@@ -48,9 +48,7 @@ export abstract class Task {
|
||||
id: this.id,
|
||||
handler: (accessor, profile, args) => this.runTask(accessor, profile, args),
|
||||
description: this._description,
|
||||
iconClass: this._iconClass,
|
||||
iconPath: this.opts.iconPath,
|
||||
title: this.title
|
||||
iconClass: this._iconClass
|
||||
};
|
||||
}
|
||||
|
||||
@@ -62,10 +60,8 @@ export abstract class Task {
|
||||
};
|
||||
}
|
||||
|
||||
public registerTask(showInCommandPalette: boolean = true): IDisposable {
|
||||
if (showInCommandPalette) {
|
||||
MenuRegistry.addCommand(this.toCommandAction());
|
||||
}
|
||||
public registerTask(): IDisposable {
|
||||
MenuRegistry.addCommand(this.toCommandAction());
|
||||
return TaskRegistry.registerTask(this.toITask());
|
||||
}
|
||||
|
||||
@@ -100,8 +96,6 @@ export interface ITask {
|
||||
precondition?: ContextKeyExpr;
|
||||
description?: ITaskHandlerDescription;
|
||||
iconClass?: string;
|
||||
iconPath?: { dark: string; light?: string; };
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface ITaskRegistry {
|
||||
@@ -110,7 +104,6 @@ export interface ITaskRegistry {
|
||||
getTasks(): string[];
|
||||
getOrCreateTaskIconClassName(item: ICommandAction): string;
|
||||
onTaskRegistered: Event<string>;
|
||||
getCommandActionById(id: string): ICommandAction;
|
||||
}
|
||||
|
||||
const ids = new IdGenerator('task-icon-');
|
||||
@@ -121,7 +114,6 @@ export const TaskRegistry: ITaskRegistry = new class implements ITaskRegistry {
|
||||
private _onTaskRegistered = new Emitter<string>();
|
||||
public readonly onTaskRegistered: Event<string> = this._onTaskRegistered.event;
|
||||
private taskIdToIconClassNameMap: Map<string /* task id */, string /* CSS rule */> = new Map<string, string>();
|
||||
private taskIdToCommandActionMap: Map<string, ICommandAction> = new Map<string, ICommandAction>();
|
||||
|
||||
registerTask(idOrTask: string | ITask, handler?: ITaskHandler): IDisposable {
|
||||
let disposable: IDisposable;
|
||||
@@ -133,16 +125,6 @@ export const TaskRegistry: ITaskRegistry = new class implements ITaskRegistry {
|
||||
if (idOrTask.iconClass) {
|
||||
this.taskIdToIconClassNameMap.set(idOrTask.id, idOrTask.iconClass);
|
||||
}
|
||||
if (idOrTask.iconPath && idOrTask.title) {
|
||||
this.taskIdToCommandActionMap.set(idOrTask.id, {
|
||||
iconLocation: {
|
||||
dark: URI.parse(idOrTask.iconPath.dark),
|
||||
light: URI.parse(idOrTask.iconPath.light),
|
||||
},
|
||||
id: idOrTask.id,
|
||||
title: idOrTask.title
|
||||
});
|
||||
}
|
||||
disposable = CommandsRegistry.registerCommand(idOrTask);
|
||||
id = idOrTask.id;
|
||||
}
|
||||
@@ -177,8 +159,4 @@ export const TaskRegistry: ITaskRegistry = new class implements ITaskRegistry {
|
||||
getTasks(): string[] {
|
||||
return this._tasks.slice(0);
|
||||
}
|
||||
|
||||
getCommandActionById(taskId: string): ICommandAction {
|
||||
return this.taskIdToCommandActionMap.get(taskId);
|
||||
}
|
||||
};
|
||||
|
||||
5
src/sql/sqlops.d.ts
vendored
5
src/sql/sqlops.d.ts
vendored
@@ -185,6 +185,11 @@ declare module 'sqlops' {
|
||||
* Get the parent node. Returns undefined if there is none.
|
||||
*/
|
||||
getParent(): Thenable<ObjectExplorerNode>;
|
||||
|
||||
/**
|
||||
* Refresh the node, expanding it if it has children
|
||||
*/
|
||||
refresh(): Thenable<void>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
88
src/sql/sqlops.proposed.d.ts
vendored
88
src/sql/sqlops.proposed.d.ts
vendored
@@ -17,7 +17,9 @@ declare module 'sqlops' {
|
||||
*/
|
||||
export interface ModelBuilder {
|
||||
navContainer(): ContainerBuilder<NavContainer, any, any>;
|
||||
divContainer(): DivBuilder;
|
||||
flexContainer(): FlexBuilder;
|
||||
dom(): ComponentBuilder<DomComponent>
|
||||
card(): ComponentBuilder<CardComponent>;
|
||||
inputBox(): ComponentBuilder<InputBoxComponent>;
|
||||
checkBox(): ComponentBuilder<CheckBoxComponent>;
|
||||
@@ -72,6 +74,10 @@ declare module 'sqlops' {
|
||||
|
||||
}
|
||||
|
||||
export interface DivBuilder extends ContainerBuilder<DivContainer, DivLayout, DivItemLayout> {
|
||||
|
||||
}
|
||||
|
||||
export interface GroupBuilder extends ContainerBuilder<GroupContainer, GroupLayout, GroupItemLayout> {
|
||||
}
|
||||
|
||||
@@ -346,6 +352,33 @@ declare module 'sqlops' {
|
||||
export interface GroupItemLayout {
|
||||
}
|
||||
|
||||
export interface DivLayout {
|
||||
/**
|
||||
* Container Height
|
||||
*/
|
||||
height?: number | string;
|
||||
|
||||
/**
|
||||
* Container Width
|
||||
*/
|
||||
width?: number | string;
|
||||
}
|
||||
|
||||
export interface DivItemLayout {
|
||||
/**
|
||||
* Matches the order CSS property and its available values.
|
||||
*/
|
||||
order?: number;
|
||||
|
||||
/**
|
||||
* Matches the CSS style key and its available values.
|
||||
*/
|
||||
CSSStyles?: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface DivContainer extends Container<DivLayout, DivItemLayout>, DivContainerProperties {
|
||||
}
|
||||
|
||||
export interface FlexContainer extends Container<FlexLayout, FlexItemLayout> {
|
||||
}
|
||||
|
||||
@@ -543,6 +576,13 @@ declare module 'sqlops' {
|
||||
options?: vscode.WebviewOptions;
|
||||
}
|
||||
|
||||
export interface DomProperties extends ComponentProperties {
|
||||
/**
|
||||
* Contents of the DOM component.
|
||||
*/
|
||||
html?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Editor properties for the editor component
|
||||
*/
|
||||
@@ -561,17 +601,35 @@ declare module 'sqlops' {
|
||||
label?: string;
|
||||
isFile?: boolean;
|
||||
fileContent?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface LoadingComponentProperties {
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export interface DivContainerProperties extends ComponentProperties {
|
||||
/**
|
||||
* Matches the overflow-y CSS property and its available values.
|
||||
*/
|
||||
overflowY?: string;
|
||||
|
||||
/**
|
||||
* Setting the scroll based on the y offset
|
||||
* This is used when its child component is webview
|
||||
*/
|
||||
yOffsetChange?: number;
|
||||
}
|
||||
|
||||
export interface CardComponent extends Component, CardProperties {
|
||||
onDidActionClick: vscode.Event<ActionDescriptor>;
|
||||
onCardSelectedChanged: vscode.Event<any>;
|
||||
}
|
||||
|
||||
export interface DomComponent extends Component, DomProperties {
|
||||
|
||||
}
|
||||
|
||||
export interface TextComponent extends Component {
|
||||
value: string;
|
||||
}
|
||||
@@ -662,8 +720,22 @@ declare module 'sqlops' {
|
||||
}
|
||||
|
||||
export interface ButtonComponent extends Component, ButtonProperties {
|
||||
/**
|
||||
* The label for the button
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* The title for the button. This title will show when it hovers
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* Icon Path for the button.
|
||||
*/
|
||||
iconPath: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri };
|
||||
|
||||
/**
|
||||
* An event called when the button is clicked
|
||||
*/
|
||||
onDidClick: vscode.Event<any>;
|
||||
}
|
||||
|
||||
@@ -1100,11 +1172,22 @@ declare module 'sqlops' {
|
||||
export function createModelViewEditor(title: string, options?: ModelViewEditorOptions): ModelViewEditor;
|
||||
|
||||
export interface ModelViewEditor extends window.modelviewdialog.ModelViewPanel {
|
||||
/**
|
||||
* `true` if there are unpersisted changes.
|
||||
* This is editable to support extensions updating the dirty status.
|
||||
*/
|
||||
isDirty: boolean;
|
||||
|
||||
/**
|
||||
* Opens the editor
|
||||
*/
|
||||
openEditor(position?: vscode.ViewColumn): Thenable<void>;
|
||||
|
||||
/**
|
||||
* Registers a save handler for this editor. This will be called if [supportsSave](#ModelViewEditorOptions.supportsSave)
|
||||
* is set to true and the editor is marked as dirty
|
||||
*/
|
||||
registerSaveHandler(handler: () => Thenable<boolean>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1113,6 +1196,11 @@ declare module 'sqlops' {
|
||||
* Should the model view editor's context be kept around even when the editor is no longer visible? It is false by default
|
||||
*/
|
||||
readonly retainContextWhenHidden?: boolean;
|
||||
|
||||
/**
|
||||
* Does this model view editor support save?
|
||||
*/
|
||||
readonly supportsSave?: boolean;
|
||||
}
|
||||
|
||||
export enum DataProviderType {
|
||||
|
||||
@@ -131,6 +131,7 @@ export enum FrequencyRelativeIntervals {
|
||||
|
||||
export enum ModelComponentTypes {
|
||||
NavContainer,
|
||||
DivContainer,
|
||||
FlexContainer,
|
||||
Card,
|
||||
InputBox,
|
||||
@@ -151,7 +152,8 @@ export enum ModelComponentTypes {
|
||||
LoadingComponent,
|
||||
TreeComponent,
|
||||
FileBrowserTree,
|
||||
Editor
|
||||
Editor,
|
||||
Dom
|
||||
}
|
||||
|
||||
export interface IComponentShape {
|
||||
|
||||
@@ -90,14 +90,16 @@ export class ExtHostAccountManagement extends ExtHostAccountManagementShape {
|
||||
}
|
||||
|
||||
public $getSecurityToken(account: sqlops.Account): Thenable<{}> {
|
||||
for (const handle in this._accounts) {
|
||||
const providerHandle = parseInt(handle);
|
||||
if (this._accounts[handle].findIndex((acct) => acct.key.accountId === account.key.accountId) !== -1) {
|
||||
return this._withProvider(providerHandle, (provider: sqlops.AccountProvider) => provider.getSecurityToken(account));
|
||||
return this.$getAllAccounts().then(() => {
|
||||
for (const handle in this._accounts) {
|
||||
const providerHandle = parseInt(handle);
|
||||
if (this._accounts[handle].findIndex((acct) => acct.key.accountId === account.key.accountId) !== -1) {
|
||||
return this._withProvider(providerHandle, (provider: sqlops.AccountProvider) => provider.getSecurityToken(account));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Account ${account.key.accountId} not found.`);
|
||||
throw new Error(`Account ${account.key.accountId} not found.`);
|
||||
});
|
||||
}
|
||||
|
||||
public get onDidChangeAccounts(): Event<sqlops.DidChangeAccountsParams> {
|
||||
|
||||
@@ -32,14 +32,21 @@ class ModelBuilderImpl implements sqlops.ModelBuilder {
|
||||
|
||||
navContainer(): sqlops.ContainerBuilder<sqlops.NavContainer, any, any> {
|
||||
let id = this.getNextComponentId();
|
||||
let container: ContainerBuilderImpl<sqlops.NavContainer, any, any> = new ContainerBuilderImpl(this._proxy, this._handle, ModelComponentTypes.NavContainer, id);
|
||||
let container: GenericContainerBuilder<sqlops.NavContainer, any, any> = new GenericContainerBuilder(this._proxy, this._handle, ModelComponentTypes.NavContainer, id);
|
||||
this._componentBuilders.set(id, container);
|
||||
return container;
|
||||
}
|
||||
|
||||
divContainer(): sqlops.DivBuilder {
|
||||
let id = this.getNextComponentId();
|
||||
let container = new DivContainerBuilder(this._proxy, this._handle, ModelComponentTypes.DivContainer, id);
|
||||
this._componentBuilders.set(id, container);
|
||||
return container;
|
||||
}
|
||||
|
||||
flexContainer(): sqlops.FlexBuilder {
|
||||
let id = this.getNextComponentId();
|
||||
let container: ContainerBuilderImpl<sqlops.FlexContainer, any, any> = new ContainerBuilderImpl<sqlops.FlexContainer, sqlops.FlexLayout, sqlops.FlexItemLayout>(this._proxy, this._handle, ModelComponentTypes.FlexContainer, id);
|
||||
let container: GenericContainerBuilder<sqlops.FlexContainer, any, any> = new GenericContainerBuilder<sqlops.FlexContainer, sqlops.FlexLayout, sqlops.FlexItemLayout>(this._proxy, this._handle, ModelComponentTypes.FlexContainer, id);
|
||||
this._componentBuilders.set(id, container);
|
||||
return container;
|
||||
}
|
||||
@@ -60,7 +67,7 @@ class ModelBuilderImpl implements sqlops.ModelBuilder {
|
||||
|
||||
groupContainer(): sqlops.GroupBuilder {
|
||||
let id = this.getNextComponentId();
|
||||
let container: ContainerBuilderImpl<sqlops.GroupContainer, any, any> = new ContainerBuilderImpl<sqlops.GroupContainer, sqlops.GroupLayout, sqlops.GroupItemLayout>(this._proxy, this._handle, ModelComponentTypes.Group, id);
|
||||
let container: GenericContainerBuilder<sqlops.GroupContainer, any, any> = new GenericContainerBuilder<sqlops.GroupContainer, sqlops.GroupLayout, sqlops.GroupItemLayout>(this._proxy, this._handle, ModelComponentTypes.Group, id);
|
||||
this._componentBuilders.set(id, container);
|
||||
return container;
|
||||
}
|
||||
@@ -184,6 +191,13 @@ class ModelBuilderImpl implements sqlops.ModelBuilder {
|
||||
return builder;
|
||||
}
|
||||
|
||||
dom(): sqlops.ComponentBuilder<sqlops.DomComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
let builder: ComponentBuilderImpl<sqlops.DomComponent> = this.getComponentBuilder(new DomComponentWrapper(this._proxy, this._handle, id), id);
|
||||
this._componentBuilders.set(id, builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
getComponentBuilder<T extends sqlops.Component>(component: ComponentWrapper, id: string): ComponentBuilderImpl<T> {
|
||||
let componentBuilder: ComponentBuilderImpl<T> = new ComponentBuilderImpl<T>(component);
|
||||
this._componentBuilders.set(id, componentBuilder);
|
||||
@@ -241,17 +255,9 @@ class ComponentBuilderImpl<T extends sqlops.Component> implements sqlops.Compone
|
||||
}
|
||||
}
|
||||
|
||||
class GenericComponentBuilder<T extends sqlops.Component> extends ComponentBuilderImpl<T> {
|
||||
constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) {
|
||||
super(new ComponentWrapper(proxy, handle, type, id));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class ContainerBuilderImpl<T extends sqlops.Component, TLayout, TItemLayout> extends ComponentBuilderImpl<T> implements sqlops.ContainerBuilder<T, TLayout, TItemLayout> {
|
||||
constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) {
|
||||
super(new ComponentWrapper(proxy, handle, type, id));
|
||||
constructor(componentWrapper: ComponentWrapper) {
|
||||
super(componentWrapper);
|
||||
}
|
||||
|
||||
withLayout(layout: TLayout): sqlops.ContainerBuilder<T, TLayout, TItemLayout> {
|
||||
@@ -268,7 +274,19 @@ class ContainerBuilderImpl<T extends sqlops.Component, TLayout, TItemLayout> ext
|
||||
}
|
||||
}
|
||||
|
||||
class FormContainerBuilder extends ContainerBuilderImpl<sqlops.FormContainer, sqlops.FormLayout, sqlops.FormItemLayout> implements sqlops.FormBuilder {
|
||||
class GenericContainerBuilder<T extends sqlops.Component, TLayout, TItemLayout> extends ContainerBuilderImpl<T, TLayout, TItemLayout> {
|
||||
constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) {
|
||||
super(new ComponentWrapper(proxy, handle, type, id));
|
||||
}
|
||||
}
|
||||
|
||||
class DivContainerBuilder extends ContainerBuilderImpl<sqlops.DivContainer, sqlops.DivLayout, sqlops.DivItemLayout> {
|
||||
constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) {
|
||||
super(new DivContainerWrapper(proxy, handle, type, id));
|
||||
}
|
||||
}
|
||||
|
||||
class FormContainerBuilder extends GenericContainerBuilder<sqlops.FormContainer, sqlops.FormLayout, sqlops.FormItemLayout> implements sqlops.FormBuilder {
|
||||
constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string, private _builder: ModelBuilderImpl) {
|
||||
super(proxy, handle, type, id);
|
||||
}
|
||||
@@ -376,7 +394,7 @@ class FormContainerBuilder extends ContainerBuilderImpl<sqlops.FormContainer, sq
|
||||
}
|
||||
}
|
||||
|
||||
class ToolbarContainerBuilder extends ContainerBuilderImpl<sqlops.ToolbarContainer, sqlops.ToolbarLayout, any> implements sqlops.ToolbarBuilder {
|
||||
class ToolbarContainerBuilder extends GenericContainerBuilder<sqlops.ToolbarContainer, sqlops.ToolbarLayout, any> implements sqlops.ToolbarBuilder {
|
||||
withToolbarItems(components: sqlops.ToolbarComponent[]): sqlops.ContainerBuilder<sqlops.ToolbarContainer, any, any> {
|
||||
this._component.itemConfigs = components.map(item => {
|
||||
return this.convertToItemConfig(item);
|
||||
@@ -829,8 +847,8 @@ class WebViewWrapper extends ComponentWrapper implements sqlops.WebViewComponent
|
||||
public get html(): string {
|
||||
return this.properties['html'];
|
||||
}
|
||||
public set html(v: string) {
|
||||
this.setProperty('html', v);
|
||||
public set html(html: string) {
|
||||
this.setProperty('html', html);
|
||||
}
|
||||
|
||||
public get onMessage(): vscode.Event<any> {
|
||||
@@ -844,9 +862,21 @@ class WebViewWrapper extends ComponentWrapper implements sqlops.WebViewComponent
|
||||
public set options(o: vscode.WebviewOptions) {
|
||||
this.setProperty('options', o);
|
||||
}
|
||||
}
|
||||
|
||||
class DomComponentWrapper extends ComponentWrapper implements sqlops.DomComponent {
|
||||
|
||||
constructor(proxy: MainThreadModelViewShape, handle: number, id: string) {
|
||||
super(proxy, handle, ModelComponentTypes.Dom, id);
|
||||
this.properties = {};
|
||||
}
|
||||
|
||||
public get html(): string {
|
||||
return this.properties['html'];
|
||||
}
|
||||
public set html(html: string) {
|
||||
this.setProperty('html', html);
|
||||
}
|
||||
}
|
||||
|
||||
class EditorWrapper extends ComponentWrapper implements sqlops.EditorComponent {
|
||||
@@ -1103,6 +1133,13 @@ class ButtonWrapper extends ComponentWrapper implements sqlops.ButtonComponent {
|
||||
this.setProperty('iconPath', v);
|
||||
}
|
||||
|
||||
public get title(): string {
|
||||
return this.properties['title'];
|
||||
}
|
||||
public set title(v: string) {
|
||||
this.setProperty('title', v);
|
||||
}
|
||||
|
||||
public get onDidClick(): vscode.Event<any> {
|
||||
let emitter = this._emitterMap.get(ComponentEventType.onDidClick);
|
||||
return emitter && emitter.event;
|
||||
@@ -1155,6 +1192,24 @@ class FileBrowserTreeComponentWrapper extends ComponentWrapper implements sqlops
|
||||
}
|
||||
}
|
||||
|
||||
class DivContainerWrapper extends ComponentWrapper implements sqlops.DivContainer {
|
||||
public get overflowY(): string {
|
||||
return this.properties['overflowY'];
|
||||
}
|
||||
|
||||
public set overflowY(value: string) {
|
||||
this.setProperty('overflowY', value);
|
||||
}
|
||||
|
||||
public get yOffsetChange(): number {
|
||||
return this.properties['yOffsetChange'];
|
||||
}
|
||||
|
||||
public set yOffsetChange(value: number) {
|
||||
this.setProperty('yOffsetChange', value);
|
||||
}
|
||||
}
|
||||
|
||||
class TreeComponentWrapper<T> extends ComponentWrapper implements sqlops.TreeComponent<T> {
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -75,6 +75,9 @@ class ModelViewPanelImpl implements sqlops.window.modelviewdialog.ModelViewPanel
|
||||
}
|
||||
|
||||
class ModelViewEditorImpl extends ModelViewPanelImpl implements sqlops.workspace.ModelViewEditor {
|
||||
private _isDirty: boolean;
|
||||
private _saveHandler: () => Thenable<boolean>;
|
||||
|
||||
constructor(
|
||||
extHostModelViewDialog: ExtHostModelViewDialog,
|
||||
extHostModelView: ExtHostModelViewShape,
|
||||
@@ -84,10 +87,32 @@ class ModelViewEditorImpl extends ModelViewPanelImpl implements sqlops.workspace
|
||||
private _options: sqlops.ModelViewEditorOptions
|
||||
) {
|
||||
super('modelViewEditor', extHostModelViewDialog, extHostModelView, extensionLocation);
|
||||
this._isDirty = false;
|
||||
}
|
||||
|
||||
public openEditor(position?: vscode.ViewColumn): Thenable<void> {
|
||||
return this._proxy.$openEditor(this._modelViewId, this._title, this._options, position);
|
||||
return this._proxy.$openEditor(this.handle, this._modelViewId, this._title, this._options, position);
|
||||
}
|
||||
|
||||
public get isDirty(): boolean {
|
||||
return this._isDirty;
|
||||
}
|
||||
|
||||
public set isDirty(value: boolean) {
|
||||
this._isDirty = value;
|
||||
this._proxy.$setDirty(this.handle, value);
|
||||
}
|
||||
|
||||
registerSaveHandler(handler: () => Thenable<boolean>) {
|
||||
this._saveHandler = handler;
|
||||
}
|
||||
|
||||
public handleSave(): Thenable<boolean> {
|
||||
if (this._saveHandler) {
|
||||
return Promise.resolve(this._saveHandler());
|
||||
} else {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,6 +495,11 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
|
||||
return dialog.validateClose();
|
||||
}
|
||||
|
||||
public $handleSave(handle: number): Thenable<boolean> {
|
||||
let editor = this._objectsByHandle.get(handle) as ModelViewEditorImpl;
|
||||
return editor.handleSave();
|
||||
}
|
||||
|
||||
public openDialog(dialog: sqlops.window.modelviewdialog.Dialog): void {
|
||||
let handle = this.getHandle(dialog);
|
||||
this.updateDialogContent(dialog);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ExtHostObjectExplorerShape, SqlMainContext, MainThreadObjectExplorerSha
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class ExtHostObjectExplorer implements ExtHostObjectExplorerShape {
|
||||
export class ExtHostObjectExplorer implements ExtHostObjectExplorerShape {
|
||||
|
||||
private _proxy: MainThreadObjectExplorerShape;
|
||||
|
||||
@@ -44,7 +44,7 @@ class ExtHostObjectExplorerNode implements sqlops.objectexplorer.ObjectExplorerN
|
||||
public errorMessage: string;
|
||||
|
||||
constructor(nodeInfo: sqlops.NodeInfo, connectionId: string, private _proxy: MainThreadObjectExplorerShape) {
|
||||
Object.entries(nodeInfo).forEach(([key, value]) => this[key] = value);
|
||||
this.getDetailsFromInfo(nodeInfo);
|
||||
this.connectionId = connectionId;
|
||||
}
|
||||
|
||||
@@ -71,4 +71,12 @@ class ExtHostObjectExplorerNode implements sqlops.objectexplorer.ObjectExplorerN
|
||||
}
|
||||
return this._proxy.$getNode(this.connectionId, this.nodePath.slice(0, parentPathEndIndex)).then(nodeInfo => nodeInfo ? new ExtHostObjectExplorerNode(nodeInfo, this.connectionId, this._proxy) : undefined);
|
||||
}
|
||||
|
||||
refresh(): Thenable<void> {
|
||||
return this._proxy.$refresh(this.connectionId, this.nodePath).then(nodeInfo => this.getDetailsFromInfo(nodeInfo));
|
||||
}
|
||||
|
||||
private getDetailsFromInfo(nodeInfo: sqlops.NodeInfo): void {
|
||||
Object.entries(nodeInfo).forEach(([key, value]) => this[key] = value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IEditor } from 'vs/workbench/common/editor';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -14,7 +15,7 @@ import { MainThreadModelViewDialogShape, SqlMainContext, ExtHostModelViewDialogS
|
||||
import { Dialog, DialogTab, DialogButton, WizardPage, Wizard } from 'sql/platform/dialog/dialogTypes';
|
||||
import { CustomDialogService } from 'sql/platform/dialog/customDialogService';
|
||||
import { IModelViewDialogDetails, IModelViewTabDetails, IModelViewButtonDetails, IModelViewWizardPageDetails, IModelViewWizardDetails } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { ModelViewInput } from 'sql/parts/modelComponents/modelEditor/modelViewInput';
|
||||
import { ModelViewInput, ModelViewInputModel, ModeViewSaveHandler } from 'sql/parts/modelComponents/modelEditor/modelViewInput';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as sqlops from 'sqlops';
|
||||
@@ -28,6 +29,7 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
|
||||
private readonly _wizardPages = new Map<number, WizardPage>();
|
||||
private readonly _wizardPageHandles = new Map<WizardPage, number>();
|
||||
private readonly _wizards = new Map<number, Wizard>();
|
||||
private readonly _editorInputModels = new Map<number, ModelViewInputModel>();
|
||||
private _dialogService: CustomDialogService;
|
||||
|
||||
constructor(
|
||||
@@ -43,15 +45,18 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
public $openEditor(modelViewId: string, title: string, options?: sqlops.ModelViewEditorOptions, position?: vscode.ViewColumn): Thenable<void> {
|
||||
public $openEditor(handle: number, modelViewId: string, title: string, options?: sqlops.ModelViewEditorOptions, position?: vscode.ViewColumn): Thenable<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let input = this._instatiationService.createInstance(ModelViewInput, title, modelViewId, options);
|
||||
let saveHandler: ModeViewSaveHandler = options && options.supportsSave ? (h) => this.handleSave(h) : undefined;
|
||||
let model = new ModelViewInputModel(modelViewId, handle, saveHandler);
|
||||
let input = this._instatiationService.createInstance(ModelViewInput, title, model, options);
|
||||
let editorOptions = {
|
||||
preserveFocus: true,
|
||||
pinned: true
|
||||
};
|
||||
|
||||
this._editorService.openEditor(input, editorOptions, position as any).then(() => {
|
||||
this._editorService.openEditor(input, editorOptions, position as any).then((editor) => {
|
||||
this._editorInputModels.set(handle, model);
|
||||
resolve();
|
||||
}, error => {
|
||||
reject(error);
|
||||
@@ -59,6 +64,10 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
|
||||
});
|
||||
}
|
||||
|
||||
private handleSave(handle: number): Thenable<boolean> {
|
||||
return this._proxy.$handleSave(handle);
|
||||
}
|
||||
|
||||
public $openDialog(handle: number): Thenable<void> {
|
||||
let dialog = this.getDialog(handle);
|
||||
this._dialogService.showDialog(dialog);
|
||||
@@ -213,6 +222,21 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
$setDirty(handle: number, isDirty: boolean): void {
|
||||
let model = this.getEditor(handle);
|
||||
if (model) {
|
||||
model.setDirty(isDirty);
|
||||
}
|
||||
}
|
||||
|
||||
private getEditor(handle: number): ModelViewInputModel {
|
||||
let model = this._editorInputModels.get(handle);
|
||||
if (!model) {
|
||||
throw new Error('No editor matching the given handle');
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
private getDialog(handle: number): Dialog {
|
||||
let dialog = this._dialogs.get(handle);
|
||||
if (!dialog) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
|
||||
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { TreeItemCollapsibleState } from 'sql/parts/objectExplorer/common/treeNode';
|
||||
|
||||
@extHostNamedCustomer(SqlMainContext.MainThreadObjectExplorer)
|
||||
export class MainThreadObjectExplorer implements MainThreadObjectExplorerShape {
|
||||
@@ -50,7 +51,7 @@ export class MainThreadObjectExplorer implements MainThreadObjectExplorerShape {
|
||||
public $getActiveConnectionNodes(): Thenable<NodeInfoWithConnection[]> {
|
||||
let connectionNodes = this._objectExplorerService.getActiveConnectionNodes();
|
||||
return Promise.resolve(connectionNodes.map(node => {
|
||||
return {connectionId: node.connection.id, nodeInfo: node.toNodeInfo()};
|
||||
return { connectionId: node.connection.id, nodeInfo: node.toNodeInfo() };
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -73,4 +74,8 @@ export class MainThreadObjectExplorer implements MainThreadObjectExplorerShape {
|
||||
public $findNodes(connectionId: string, type: string, schema: string, name: string, database: string, parentObjectNames: string[]): Thenable<sqlops.NodeInfo[]> {
|
||||
return this._objectExplorerService.findNodes(connectionId, type, schema, name, database, parentObjectNames);
|
||||
}
|
||||
|
||||
public $refresh(connectionId: string, nodePath: string): Thenable<sqlops.NodeInfo> {
|
||||
return this._objectExplorerService.refreshNodeInView(connectionId, nodePath).then(node => node.toNodeInfo());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export abstract class ExtHostAccountManagementShape {
|
||||
|
||||
export abstract class ExtHostConnectionManagementShape {
|
||||
$onConnectionOpened(handleId: string, connection: sqlops.connection.Connection): void { throw ni; }
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ExtHostDataProtocolShape {
|
||||
|
||||
@@ -663,6 +663,7 @@ export interface MainThreadObjectExplorerShape extends IDisposable {
|
||||
$getChildren(connectionId: string, nodePath: string): Thenable<sqlops.NodeInfo[]>;
|
||||
$isExpanded(connectionId: string, nodePath: string): Thenable<boolean>;
|
||||
$findNodes(connectionId: string, type: string, schema: string, name: string, database: string, parentObjectNames: string[]): Thenable<sqlops.NodeInfo[]>;
|
||||
$refresh(connectionId: string, nodePath: string): Thenable<sqlops.NodeInfo>;
|
||||
}
|
||||
|
||||
export interface ExtHostModelViewDialogShape {
|
||||
@@ -672,10 +673,11 @@ export interface ExtHostModelViewDialogShape {
|
||||
$updateWizardPageInfo(handle: number, pageHandles: number[], currentPageIndex: number): void;
|
||||
$validateNavigation(handle: number, info: sqlops.window.modelviewdialog.WizardPageChangeInfo): Thenable<boolean>;
|
||||
$validateDialogClose(handle: number): Thenable<boolean>;
|
||||
$handleSave(handle: number): Thenable<boolean>;
|
||||
}
|
||||
|
||||
export interface MainThreadModelViewDialogShape extends IDisposable {
|
||||
$openEditor(modelViewId: string, title: string, options?: sqlops.ModelViewEditorOptions, position?: vscode.ViewColumn): Thenable<void>;
|
||||
$openEditor(handle: number, modelViewId: string, title: string, options?: sqlops.ModelViewEditorOptions, position?: vscode.ViewColumn): Thenable<void>;
|
||||
$openDialog(handle: number): Thenable<void>;
|
||||
$closeDialog(handle: number): Thenable<void>;
|
||||
$setDialogDetails(handle: number, details: IModelViewDialogDetails): Thenable<void>;
|
||||
@@ -688,6 +690,7 @@ export interface MainThreadModelViewDialogShape extends IDisposable {
|
||||
$addWizardPage(wizardHandle: number, pageHandle: number, pageIndex: number): Thenable<void>;
|
||||
$removeWizardPage(wizardHandle: number, pageIndex: number): Thenable<void>;
|
||||
$setWizardPage(wizardHandle: number, pageIndex: number): Thenable<void>;
|
||||
$setDirty(handle: number, isDirty: boolean): void;
|
||||
}
|
||||
export interface ExtHostQueryEditorShape {
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ import { ShowCurrentReleaseNotesAction } from 'sql/workbench/update/releaseNotes
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
|
||||
new Actions.BackupAction().registerTask(false);
|
||||
new Actions.RestoreAction().registerTask(false);
|
||||
new Actions.BackupAction().registerTask();
|
||||
new Actions.RestoreAction().registerTask();
|
||||
new Actions.NewQueryAction().registerTask();
|
||||
new Actions.ConfigureDashboardAction().registerTask();
|
||||
|
||||
|
||||
@@ -302,6 +302,12 @@ export class BackupAction extends Task {
|
||||
}
|
||||
|
||||
runTask(accessor: ServicesAccessor, profile: IConnectionProfile): TPromise<void> {
|
||||
if (!profile) {
|
||||
let objectExplorerService = accessor.get<IObjectExplorerService>(IObjectExplorerService);
|
||||
let connectionManagementService = accessor.get<IConnectionManagementService>(IConnectionManagementService);
|
||||
let workbenchEditorService = accessor.get<IEditorService>(IEditorService);
|
||||
profile = TaskUtilities.getCurrentGlobalConnection(objectExplorerService, connectionManagementService, workbenchEditorService);
|
||||
}
|
||||
let configurationService = accessor.get<IWorkspaceConfigurationService>(IWorkspaceConfigurationService);
|
||||
let previewFeaturesEnabled: boolean = configurationService.getValue('workbench')['enablePreviewFeatures'];
|
||||
if (!previewFeaturesEnabled) {
|
||||
@@ -435,7 +441,7 @@ export class NewDatabaseAction extends Action {
|
||||
|
||||
export class ConfigureDashboardAction extends Task {
|
||||
public static readonly ID = 'configureDashboard';
|
||||
public static readonly LABEL = nls.localize('configureDashboard', 'Configure');
|
||||
public static readonly LABEL = nls.localize('configureDashboard', 'Learn How To Configure The Dashboard');
|
||||
public static readonly ICON = 'configure-dashboard';
|
||||
private static readonly configHelpUri = 'https://aka.ms/sqldashboardconfig';
|
||||
|
||||
|
||||
@@ -108,13 +108,13 @@ export class ErrorMessageDialog extends Modal {
|
||||
private updateIconTitle(): void {
|
||||
switch (this._severity) {
|
||||
case Severity.Error:
|
||||
this.titleIconClassName = 'icon error';
|
||||
this.titleIconClassName = 'sql icon error';
|
||||
break;
|
||||
case Severity.Warning:
|
||||
this.titleIconClassName = 'icon warning';
|
||||
this.titleIconClassName = 'sql icon warning';
|
||||
break;
|
||||
case Severity.Info:
|
||||
this.titleIconClassName = 'icon info';
|
||||
this.titleIconClassName = 'sql icon info';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +301,8 @@ suite('SQL Object Explorer Service tests', () => {
|
||||
reveal: element => Promise.resolve() as Thenable<void>,
|
||||
setSelected: (element, selected, clearOtherSelections) => undefined,
|
||||
isExpanded: element => undefined,
|
||||
onSelectionOrFocusChange: Event.None
|
||||
onSelectionOrFocusChange: Event.None,
|
||||
refreshElement: (element) => Promise.resolve() as Thenable<void>
|
||||
} as ServerTreeView);
|
||||
});
|
||||
|
||||
@@ -738,4 +739,22 @@ suite('SQL Object Explorer Service tests', () => {
|
||||
}, err => done(err));
|
||||
});
|
||||
});
|
||||
|
||||
test('refreshInView refreshes the node, expands it, and returns the refreshed node', async () => {
|
||||
// Set up the session and tree view
|
||||
await objectExplorerService.createNewSession('MSSQL', connection);
|
||||
objectExplorerService.onSessionCreated(1, objectExplorerSession);
|
||||
serverTreeView.setup(x => x.refreshElement(TypeMoq.It.isAny())).returns(() => Promise.resolve());
|
||||
objectExplorerService.registerServerTreeView(serverTreeView.object);
|
||||
|
||||
// Refresh the node
|
||||
let nodePath = objectExplorerSession.rootNode.nodePath;
|
||||
let refreshedNode = await objectExplorerService.refreshNodeInView(connection.id, nodePath);
|
||||
|
||||
// Verify that it was refreshed, expanded, and the refreshed detailed were returned
|
||||
sqlOEProvider.verify(x => x.refreshNode(TypeMoq.It.is(refreshNode => refreshNode.nodePath === nodePath)), TypeMoq.Times.once());
|
||||
refreshedNode.children.forEach((childNode, index) => {
|
||||
assert.equal(childNode.nodePath, objectExplorerExpandInfoRefresh.nodes[index].nodePath);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -28,6 +28,7 @@ import { ConnectionManagementService } from 'sql/parts/connection/common/connect
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
|
||||
import { TestThemeService } from 'sqltest/stubs/themeTestService';
|
||||
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
|
||||
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as assert from 'assert';
|
||||
@@ -40,6 +41,7 @@ suite('SQL QueryAction Tests', () => {
|
||||
let editor: TypeMoq.Mock<QueryEditor>;
|
||||
let calledRunQueryOnInput: boolean = undefined;
|
||||
let testQueryInput: TypeMoq.Mock<QueryInput>;
|
||||
let configurationService: TypeMoq.Mock<ConfigurationService>;
|
||||
|
||||
setup(() => {
|
||||
// Setup a reusable mock QueryInput
|
||||
@@ -56,6 +58,13 @@ suite('SQL QueryAction Tests', () => {
|
||||
editor.setup(x => x.getSelection()).returns(() => undefined);
|
||||
editor.setup(x => x.getSelection(false)).returns(() => undefined);
|
||||
editor.setup(x => x.isSelectionEmpty()).returns(() => false);
|
||||
configurationService = TypeMoq.Mock.ofInstance({
|
||||
getValue: () => undefined,
|
||||
onDidChangeConfiguration: () => undefined
|
||||
} as any);
|
||||
configurationService.setup(x => x.getValue(TypeMoq.It.isAny())).returns(() => {
|
||||
return {};
|
||||
});
|
||||
});
|
||||
|
||||
test('setClass sets child CSS class correctly', (done) => {
|
||||
@@ -463,7 +472,7 @@ suite('SQL QueryAction Tests', () => {
|
||||
});
|
||||
|
||||
// If I query without having initialized anything, state should be clear
|
||||
listItem = new ListDatabasesActionItem(editor.object, undefined, connectionManagementService.object, undefined, undefined, undefined);
|
||||
listItem = new ListDatabasesActionItem(editor.object, undefined, connectionManagementService.object, undefined, undefined, undefined, configurationService.object);
|
||||
|
||||
assert.equal(listItem.isEnabled(), false, 'do not expect dropdown enabled unless connected');
|
||||
assert.equal(listItem.currentDatabaseName, undefined, 'do not expect dropdown to have entries unless connected');
|
||||
@@ -498,7 +507,7 @@ suite('SQL QueryAction Tests', () => {
|
||||
cms.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => <IConnectionProfile>{ databaseName: databaseName });
|
||||
|
||||
// ... Create a database dropdown that has been connected
|
||||
let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null);
|
||||
let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null, configurationService.object);
|
||||
listItem.onConnected();
|
||||
|
||||
// If: I raise a connection changed event
|
||||
@@ -522,7 +531,7 @@ suite('SQL QueryAction Tests', () => {
|
||||
cms.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => <IConnectionProfile>{ databaseName: databaseName });
|
||||
|
||||
// ... Create a database dropdown that has been connected
|
||||
let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null);
|
||||
let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null, configurationService.object);
|
||||
listItem.onConnected();
|
||||
|
||||
// If: I raise a connection changed event for the 'wrong' URI
|
||||
@@ -549,7 +558,7 @@ suite('SQL QueryAction Tests', () => {
|
||||
cms.setup(x => x.onConnectionChanged).returns(() => dbChangedEmitter.event);
|
||||
|
||||
// ... Create a database dropdown
|
||||
let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null);
|
||||
let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null, configurationService.object);
|
||||
|
||||
// If: I raise a connection changed event
|
||||
let eventParams = <IConnectionParams>{
|
||||
|
||||
@@ -93,7 +93,7 @@ suite('SQL QueryEditor Tests', () => {
|
||||
instantiationService.setup(x => x.createInstance(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((classDef, editor, action) => {
|
||||
if (classDef.ID) {
|
||||
if (classDef.ID === 'listDatabaseQueryActionItem') {
|
||||
return new ListDatabasesActionItem(editor, action, connectionManagementService.object, undefined, undefined, undefined);
|
||||
return new ListDatabasesActionItem(editor, action, connectionManagementService.object, undefined, undefined, undefined, configurationService.object);
|
||||
}
|
||||
}
|
||||
// Default
|
||||
@@ -110,18 +110,26 @@ suite('SQL QueryEditor Tests', () => {
|
||||
editorDescriptorService = TypeMoq.Mock.ofType(EditorDescriptorService, TypeMoq.MockBehavior.Loose);
|
||||
editorDescriptorService.setup(x => x.getEditor(TypeMoq.It.isAny())).returns(() => descriptor);
|
||||
|
||||
configurationService = TypeMoq.Mock.ofInstance({
|
||||
getValue: () => undefined,
|
||||
onDidChangeConfiguration: () => undefined
|
||||
} as any);
|
||||
configurationService.setup(x => x.getValue(TypeMoq.It.isAny())).returns(() => {
|
||||
return { enablePreviewFeatures: true };
|
||||
});
|
||||
|
||||
// Create a QueryInput
|
||||
let filePath = 'someFile.sql';
|
||||
let uri: URI = URI.parse(filePath);
|
||||
let fileInput = new UntitledEditorInput(uri, false, '', '', '', instantiationService.object, undefined, undefined, undefined);
|
||||
let queryResultsInput: QueryResultsInput = new QueryResultsInput(uri.fsPath);
|
||||
let queryResultsInput: QueryResultsInput = new QueryResultsInput(uri.fsPath, configurationService.object);
|
||||
queryInput = new QueryInput('first', fileInput, queryResultsInput, undefined, undefined, undefined, undefined, undefined);
|
||||
|
||||
// Create a QueryInput to compare to the previous one
|
||||
let filePath2 = 'someFile2.sql';
|
||||
let uri2: URI = URI.parse(filePath2);
|
||||
let fileInput2 = new UntitledEditorInput(uri2, false, '', '', '', instantiationService.object, undefined, undefined, undefined);
|
||||
let queryResultsInput2: QueryResultsInput = new QueryResultsInput(uri2.fsPath);
|
||||
let queryResultsInput2: QueryResultsInput = new QueryResultsInput(uri2.fsPath, configurationService.object);
|
||||
queryInput2 = new QueryInput('second', fileInput2, queryResultsInput2, undefined, undefined, undefined, undefined, undefined);
|
||||
|
||||
// Mock IMessageService
|
||||
@@ -136,14 +144,6 @@ suite('SQL QueryEditor Tests', () => {
|
||||
|
||||
// Create a QueryModelService
|
||||
queryModelService = new QueryModelService(instantiationService.object, notificationService.object);
|
||||
|
||||
configurationService = TypeMoq.Mock.ofInstance({
|
||||
getValue: () => undefined,
|
||||
onDidChangeConfiguration: () => undefined
|
||||
} as any);
|
||||
configurationService.setup(x => x.getValue(TypeMoq.It.isAny())).returns(() => {
|
||||
return { enablePreviewFeatures: true };
|
||||
});
|
||||
});
|
||||
|
||||
test('createEditor creates only the taskbar', (done) => {
|
||||
@@ -344,7 +344,7 @@ suite('SQL QueryEditor Tests', () => {
|
||||
queryActionInstantiationService.setup(x => x.createInstance(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
|
||||
.returns((definition, editor, action, selectBox) => {
|
||||
if (definition.ID === 'listDatabaseQueryActionItem') {
|
||||
let item = new ListDatabasesActionItem(editor, action, queryConnectionService.object, undefined, undefined, undefined);
|
||||
let item = new ListDatabasesActionItem(editor, action, queryConnectionService.object, undefined, undefined, undefined,configurationService.object);
|
||||
return item;
|
||||
}
|
||||
// Default
|
||||
@@ -393,7 +393,7 @@ suite('SQL QueryEditor Tests', () => {
|
||||
done();
|
||||
});
|
||||
test('Test that we attempt to dispose query when the queryInput is disposed', (done) => {
|
||||
let queryResultsInput = new QueryResultsInput('testUri');
|
||||
let queryResultsInput = new QueryResultsInput('testUri', configurationService.object);
|
||||
queryInput['_results'] = queryResultsInput;
|
||||
queryInput.dispose();
|
||||
queryModelService.verify(x => x.disposeQuery(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user