mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Compare commits
144 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9717b7516d | ||
|
|
b92c23df2b | ||
|
|
df617f19e0 | ||
|
|
fb565c2548 | ||
|
|
2bfc3a6c85 | ||
|
|
4ab5d84b94 | ||
|
|
154213b705 | ||
|
|
4cce29ea9d | ||
|
|
9d5d00aa8f | ||
|
|
70d47c1757 | ||
|
|
befa34790f | ||
|
|
84da9d289b | ||
|
|
7a30d535e8 | ||
|
|
199701d26b | ||
|
|
3ac2cfa528 | ||
|
|
78d68aa1b9 | ||
|
|
8042b78f1e | ||
|
|
000d064276 | ||
|
|
600a78f35f | ||
|
|
6f5e4c30dc | ||
|
|
8e7457911e | ||
|
|
43b3207937 | ||
|
|
22f2151c21 | ||
|
|
7c744f2307 | ||
|
|
08d57fed86 | ||
|
|
6e8a0fe0ef | ||
|
|
6668ec4b5d | ||
|
|
08a8288293 | ||
|
|
3001640eec | ||
|
|
efa3658ced | ||
|
|
92bc253cf7 | ||
|
|
a190190843 | ||
|
|
9c40bd1a23 | ||
|
|
dc2193138d | ||
|
|
033c8cb8b1 | ||
|
|
21c4429c6e | ||
|
|
348a96b033 | ||
|
|
0c930d7c0f | ||
|
|
98aca2b988 | ||
|
|
8d7f497e0c | ||
|
|
1b6328b451 | ||
|
|
d10e08e63e | ||
|
|
a8f21b56f0 | ||
|
|
173842510c | ||
|
|
19c08fe0eb | ||
|
|
db817a7192 | ||
|
|
2c8e93cc96 | ||
|
|
44e9a97f09 | ||
|
|
298ddc4195 | ||
|
|
d9134d6085 | ||
|
|
b17b4ce880 | ||
|
|
2304c32453 | ||
|
|
2b68e4a7df | ||
|
|
8f06e72318 | ||
|
|
5fa740ead4 | ||
|
|
e5096e61e5 | ||
|
|
2a3195636e | ||
|
|
5fb9b8ccd3 | ||
|
|
7089e2299a | ||
|
|
b553cbb68c | ||
|
|
c712411e77 | ||
|
|
48d5cc554c | ||
|
|
39bfd69dc9 | ||
|
|
d690b80493 | ||
|
|
eb48a9f993 | ||
|
|
5a54abaf44 | ||
|
|
47c161f9f1 | ||
|
|
807f8e68f3 | ||
|
|
fba8536c33 | ||
|
|
36fc1bb71a | ||
|
|
ccaa96c81e | ||
|
|
ac2f279c88 | ||
|
|
91cb99b8c8 | ||
|
|
02b1a525e3 | ||
|
|
9bf4a4b18c | ||
|
|
8a01553c49 | ||
|
|
21b913845f | ||
|
|
220e4feb1d | ||
|
|
13884c0457 | ||
|
|
b3bb6ebc6e | ||
|
|
424eb90dd8 | ||
|
|
df804d0729 | ||
|
|
79269cdfd5 | ||
|
|
2a650d4d74 | ||
|
|
017b4ecdb3 | ||
|
|
9c84bf3fd5 | ||
|
|
397b54a8c3 | ||
|
|
cb3604c0a1 | ||
|
|
1a7f0673ea | ||
|
|
0d043207b9 | ||
|
|
f995dea971 | ||
|
|
49e20488bc | ||
|
|
3e47b27192 | ||
|
|
f4fa18ec05 | ||
|
|
c6d1fa2b7d | ||
|
|
c4df7667ff | ||
|
|
6a303cfa25 | ||
|
|
4ffa5cc1da | ||
|
|
172e1cf3bf | ||
|
|
e6a32e52f5 | ||
|
|
d2b6f6844d | ||
|
|
e9ef95ef1f | ||
|
|
10eeb5374f | ||
|
|
332951bc8e | ||
|
|
4daf3280ff | ||
|
|
87bb2c74d9 | ||
|
|
ba011853a0 | ||
|
|
461a158ac3 | ||
|
|
85f59f1103 | ||
|
|
335b9f445f | ||
|
|
7cda45c904 | ||
|
|
4159fdc1a3 | ||
|
|
190da30979 | ||
|
|
ed9c74b900 | ||
|
|
6783766c33 | ||
|
|
b27018b379 | ||
|
|
021d07e04a | ||
|
|
bf0baec392 | ||
|
|
923cbac400 | ||
|
|
ab938f2536 | ||
|
|
a55b1804e9 | ||
|
|
4bfa6b3a5d | ||
|
|
d20f24be18 | ||
|
|
8a17bae7a6 | ||
|
|
d14c73fad5 | ||
|
|
ce878e1def | ||
|
|
a64a0d1db6 | ||
|
|
feab43f16d | ||
|
|
0d60fe775f | ||
|
|
6680be6a73 | ||
|
|
e026ab85a7 | ||
|
|
e53c903205 | ||
|
|
03dbe8565f | ||
|
|
708793cb23 | ||
|
|
43ae4fb0aa | ||
|
|
6b1d552277 | ||
|
|
f24f576b72 | ||
|
|
c23328564f | ||
|
|
cd6dd3dafa | ||
|
|
4081e15bef | ||
|
|
b05e3813d1 | ||
|
|
3048311f40 | ||
|
|
1045392d91 | ||
|
|
7b23ca8ee7 |
2
.yarnrc
2
.yarnrc
@@ -1,3 +1,3 @@
|
||||
disturl "https://atom.io/download/electron"
|
||||
target "1.7.12"
|
||||
target "1.7.16"
|
||||
runtime "electron"
|
||||
|
||||
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,5 +1,26 @@
|
||||
# Change Log
|
||||
|
||||
## Version 0.31.4
|
||||
* Release date: July 19, 2018
|
||||
* Release status: Public Preview
|
||||
|
||||
## What's new in this version
|
||||
* SQL Server Agent for SQL Operations Studio extension improvements
|
||||
* Added view of Alerts, Operators, and Proxies and icons on left pane
|
||||
* Added dialogs for New Job, New Job Step, New Alert, and New Operator
|
||||
* Added Delete Job, Delete Alert, and Delete Operator (right-click)
|
||||
* Added Previous Runs visualization
|
||||
* Added Filters for each column name
|
||||
* SQL Server Profiler for SQL Operations Studio extension improvements
|
||||
* Added Hotkeys to quickly launch and start/stop Profiler
|
||||
* Added 5 Default Templates to view Extended Events
|
||||
* Added Server/Database connection name
|
||||
* Added support for Azure SQL Database instances
|
||||
* Added suggestion to exit Profiler when tab is closed when Profiler is still running
|
||||
* Release of Combine Scripts Extension
|
||||
* Wizard and Dialog Extensibility
|
||||
* Fix GitHub Issues
|
||||
|
||||
## Version 0.30.6
|
||||
* Release date: June 20, 2018
|
||||
* Release status: Public Preview
|
||||
|
||||
14
README.md
14
README.md
@@ -8,12 +8,12 @@ SQL Operations Studio is a data management tool that enables you to work with SQ
|
||||
|
||||
Platform | Link
|
||||
-- | --
|
||||
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=875602
|
||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=875603
|
||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=875604
|
||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=875605
|
||||
Linux RPM | https://go.microsoft.com/fwlink/?linkid=875606
|
||||
Linux DEB | https://go.microsoft.com/fwlink/?linkid=875607
|
||||
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=2005949
|
||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2005950
|
||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2005959
|
||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2005960
|
||||
Linux RPM | https://go.microsoft.com/fwlink/?linkid=2006083
|
||||
Linux DEB | https://go.microsoft.com/fwlink/?linkid=2006084
|
||||
|
||||
Go to our [download page](https://aka.ms/sqlopsstudio) for more specific instructions.
|
||||
|
||||
@@ -85,7 +85,7 @@ We would like to thank all our users who raised issues, and in particular the fo
|
||||
* Chinese (Traditional): Bruce Chen, Chiayi Yen, Kevin Yang, Winnie Lin, 保哥 Will, 謝政廷
|
||||
* Korean: Do-Kyun Kim, Evelyn Kim, Helen Jung, Hong Jmee, jeongwoo choi, Jun Hyoung Lee, Jungsun Kim정선, Justin Yoo, Kavrith mucha, Kiwoong Youm, MinGyu Ju, MVP_JUNO BEA, Sejun Kim, SOONMAN KWON, sung man ko, Yeongrak Choi, younggun kim, Youngjae Kim, 소영 이
|
||||
* Russian: Andrey Veselov, Anton Fontanov, Anton Savin, Elena Ostrovskaia, Igor Babichev, Maxim Zelensky, Rodion Fedechkin, Tasha T, Vladimir Zyryanov
|
||||
* Portuguese Brazil: Daniel de Sousa, Diogo Duarte, Douglas Correa, Douglas Eccker, José Emanuel Mendes, Marcelo Fernandes, Marcondes Alexandre, Roberto Fonseca, Rodrigo Crespi
|
||||
* Portuguese Brazil: Daniel de Sousa, Diogo Duarte, Douglas Correa, Douglas Eccker, José Emanuel Mendes, Marcelo Fernandes, Marcondes Alexandre, Roberto Fonseca, Rodrigo Crespi
|
||||
|
||||
|
||||
And of course we'd like to thank the authors of all upstream dependencies. Please see a full list in the [ThirdPartyNotices.txt](https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/ThirdPartyNotices.txt)
|
||||
|
||||
@@ -78,6 +78,7 @@ const sqlBuiltInExtensions = [
|
||||
// Add SQL built-in extensions here.
|
||||
// the extension will be excluded from SQLOps package and will have separate vsix packages
|
||||
'agent',
|
||||
'import',
|
||||
'profiler'
|
||||
];
|
||||
|
||||
@@ -273,7 +274,8 @@ function packageBuiltInExtensions() {
|
||||
console.info('Creating vsix for ' + element.path + ' result:' + packagePath);
|
||||
vsce.createVSIX({
|
||||
cwd: element.path,
|
||||
packagePath: packagePath
|
||||
packagePath: packagePath,
|
||||
useYarn: true
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -412,7 +414,7 @@ function packageTask(platform, arch, opts) {
|
||||
license,
|
||||
watermark,
|
||||
api,
|
||||
// {{SQL CARBON EDIT}}
|
||||
// {{SQL CARBON EDIT}}
|
||||
copiedModules,
|
||||
dataApi,
|
||||
sources,
|
||||
@@ -526,7 +528,9 @@ gulp.task('vscode-translations-push-test', ['optimize-vscode'], function () {
|
||||
gulp.src(pathToMetadata).pipe(i18n.createXlfFilesForCoreBundle()),
|
||||
gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()),
|
||||
gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions())
|
||||
).pipe(i18n.findObsoleteResources(apiHostname, apiName, apiToken)
|
||||
// {{SQL CARBON EDIT}}
|
||||
// disable since function makes calls to VS Code Transifex API
|
||||
// ).pipe(i18n.findObsoleteResources(apiHostname, apiName, apiToken)
|
||||
).pipe(vfs.dest('../vscode-transifex-input'));
|
||||
});
|
||||
|
||||
|
||||
@@ -60,7 +60,9 @@ Type: files; Name: "{app}\resources\app\Credits_45.0.2454.85.html"; Check: IsNot
|
||||
Type: filesandordirs; Name: "{app}\_"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce;
|
||||
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1
|
||||
Name: "associatewithfiles"; Description: "{cm:AssociateWithFiles,{#NameShort}}"; GroupDescription: "{cm:Other}"; Flags: unchecked
|
||||
Name: "addtopath"; Description: "{cm:AddToPath}"; GroupDescription: "{cm:Other}"
|
||||
Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{cm:Other}"; Check: WizardSilent
|
||||
|
||||
@@ -82,6 +84,13 @@ Root: HKCR; Subkey: "{#RegValueName}SourceFile\DefaultIcon"; ValueType: string;
|
||||
Root: HKCR; Subkey: "{#RegValueName}SourceFile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""
|
||||
Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\bin"; Tasks: addtopath; Check: NeedsAddPath(ExpandConstant('{app}\bin'))
|
||||
|
||||
Root: HKCU; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles
|
||||
Root: HKCU; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.sql"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles
|
||||
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SQL}"; Flags: uninsdeletekey; Tasks: associatewithfiles
|
||||
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles
|
||||
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\code_file.ico"; Tasks: associatewithfiles
|
||||
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles
|
||||
|
||||
[Code]
|
||||
// Don't allow installing conflicting architectures
|
||||
function InitializeSetup(): Boolean;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "agent",
|
||||
"displayName": "SQL Server Agent",
|
||||
"description": "Manage and troubleshoot SQL Server Agent jobs",
|
||||
"version": "0.31.1",
|
||||
"version": "0.32.7",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt",
|
||||
|
||||
@@ -63,8 +63,9 @@ export class AlertDialog extends AgentDialog<AlertData> {
|
||||
|
||||
private static readonly AlertTypes: string[] = [
|
||||
AlertData.AlertTypeSqlServerEventString,
|
||||
AlertData.AlertTypePerformanceConditionString,
|
||||
AlertData.AlertTypeWmiEventString
|
||||
// Disabled until next release
|
||||
// AlertData.AlertTypePerformanceConditionString,
|
||||
// AlertData.AlertTypeWmiEventString
|
||||
];
|
||||
|
||||
private static readonly AlertSeverities: string[] = [
|
||||
@@ -366,11 +367,18 @@ export class AlertDialog extends AgentDialog<AlertData> {
|
||||
title: AlertDialog.NewJobButtonLabel
|
||||
}], { componentWidth: '100%'}).component();
|
||||
|
||||
let previewTag = view.modelBuilder.text()
|
||||
.withProperties({
|
||||
value: 'Feature Preview'
|
||||
}).component();
|
||||
|
||||
this.notifyOperatorsCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: AlertDialog.NotifyOperatorsTextBoxLabel
|
||||
}).component();
|
||||
|
||||
this.notifyOperatorsCheckBox.enabled = false;
|
||||
|
||||
this.operatorsTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
@@ -422,6 +430,9 @@ export class AlertDialog extends AgentDialog<AlertData> {
|
||||
}, {
|
||||
component: executeJobContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: previewTag,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.notifyOperatorsCheckBox,
|
||||
title: ''
|
||||
|
||||
@@ -125,7 +125,6 @@ export class JobDialog extends AgentDialog<JobData> {
|
||||
this.initializeSchedulesTab();
|
||||
this.initializeNotificationsTab();
|
||||
this.dialog.content = [this.generalTab, this.stepsTab, this.schedulesTab, this.alertsTab, this.notificationsTab];
|
||||
|
||||
this.dialog.registerCloseValidator(() => {
|
||||
this.updateModel();
|
||||
let validationResult = this.model.validate();
|
||||
@@ -194,6 +193,10 @@ export class JobDialog extends AgentDialog<JobData> {
|
||||
|
||||
private initializeStepsTab() {
|
||||
this.stepsTab.registerContent(async view => {
|
||||
let previewTag = view.modelBuilder.text()
|
||||
.withProperties({
|
||||
value: 'Feature Preview'
|
||||
}).component();
|
||||
this.stepsTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
@@ -219,6 +222,9 @@ export class JobDialog extends AgentDialog<JobData> {
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.moveStepUpButton.enabled = false;
|
||||
this.moveStepDownButton.enabled = false;
|
||||
|
||||
this.newStepButton = view.modelBuilder.button().withProperties({
|
||||
label: this.NewStepButtonString,
|
||||
width: 80
|
||||
@@ -243,8 +249,14 @@ export class JobDialog extends AgentDialog<JobData> {
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.stepsTable.enabled = false;
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: previewTag,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: this.stepsTable,
|
||||
title: this.JobStepsTopLabelString,
|
||||
actions: [this.moveStepUpButton, this.moveStepDownButton, this.newStepButton, this.editStepButton, this.deleteStepButton]
|
||||
@@ -255,6 +267,10 @@ export class JobDialog extends AgentDialog<JobData> {
|
||||
|
||||
private initializeAlertsTab() {
|
||||
this.alertsTab.registerContent(async view => {
|
||||
let previewTag = view.modelBuilder.text()
|
||||
.withProperties({
|
||||
value: 'Feature Preview'
|
||||
}).component();
|
||||
this.alertsTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
@@ -279,6 +295,9 @@ export class JobDialog extends AgentDialog<JobData> {
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: previewTag,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.alertsTable,
|
||||
title: this.AlertsTopLabelString,
|
||||
actions: [this.newAlertButton]
|
||||
|
||||
@@ -30,6 +30,7 @@ export class JobStepDialog {
|
||||
private readonly PreviousButtonText: string = localize('jobStepDialog.previous','Previous');
|
||||
private readonly SuccessfulParseText: string = localize('jobStepDialog.successParse', 'The command was successfully parsed.');
|
||||
private readonly FailureParseText: string = localize('jobStepDialog.failParse', 'The command failed.');
|
||||
private readonly BlankStepNameErrorText: string = localize('jobStepDialog.blankStepName', 'The step name cannot be left blank');
|
||||
|
||||
// General Control Titles
|
||||
private readonly StepNameLabelString: string = localize('jobStepDialog.stepNameLabel', 'Step Name');
|
||||
@@ -139,13 +140,19 @@ export class JobStepDialog {
|
||||
this.openButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: this.OpenCommandText,
|
||||
width: '80px'
|
||||
width: '80px',
|
||||
isFile: true
|
||||
}).component();
|
||||
this.parseButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: this.ParseCommandText,
|
||||
width: '80px'
|
||||
width: '80px',
|
||||
isFile: false
|
||||
}).component();
|
||||
this.openButton.onDidClick(e => {
|
||||
let queryContent = e;
|
||||
this.commandTextBox.value = queryContent;
|
||||
});
|
||||
this.parseButton.onDidClick(e => {
|
||||
if (this.commandTextBox.value) {
|
||||
queryProvider.parseSyntax(this.ownerUri, this.commandTextBox.value).then(result => {
|
||||
@@ -185,6 +192,12 @@ export class JobStepDialog {
|
||||
.withProperties({
|
||||
}).component();
|
||||
this.nameTextBox.required = true;
|
||||
this.nameTextBox.onTextChanged(() => {
|
||||
if (this.nameTextBox.value.length > 0) {
|
||||
this.dialog.message = null;
|
||||
}
|
||||
});
|
||||
|
||||
this.typeDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: this.TSQLScript,
|
||||
@@ -465,7 +478,12 @@ export class JobStepDialog {
|
||||
return outputFileForm;
|
||||
}
|
||||
|
||||
private async execute() {
|
||||
protected execute() {
|
||||
this.model.stepName = this.nameTextBox.value;
|
||||
if (!this.model.stepName || this.model.stepName.length === 0) {
|
||||
this.dialog.message = this.dialog.message = { text: this.BlankStepNameErrorText };
|
||||
return;
|
||||
}
|
||||
this.model.jobName = this.jobName;
|
||||
this.model.id = this.stepId;
|
||||
this.model.server = this.server;
|
||||
@@ -479,7 +497,6 @@ export class JobStepDialog {
|
||||
this.model.failureAction = this.failureActionDropdown.value as string;
|
||||
this.model.outputFileName = this.outputFileNameBox.value;
|
||||
this.model.appendToLogFile = this.appendToExistingFileCheckbox.checked;
|
||||
await this.model.save();
|
||||
}
|
||||
|
||||
public async openNewStepDialog() {
|
||||
|
||||
@@ -368,6 +368,10 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
|
||||
private initializeNotificationTab() {
|
||||
this.notificationsTab.registerContent(async view => {
|
||||
|
||||
let previewTag = view.modelBuilder.text()
|
||||
.withProperties({
|
||||
value: 'Feature Preview'
|
||||
}).component();
|
||||
this.alertsTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
@@ -381,6 +385,9 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: previewTag,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.alertsTable,
|
||||
title: OperatorDialog.AlertsTableLabel
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
|
||||
1
extensions/import/.gitignore
vendored
Normal file
1
extensions/import/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
flatfileimportservice/
|
||||
2
extensions/import/.vscodeignore
Normal file
2
extensions/import/.vscodeignore
Normal file
@@ -0,0 +1,2 @@
|
||||
client/src/**
|
||||
client/tsconfig.json
|
||||
25
extensions/import/README.md
Normal file
25
extensions/import/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Microsoft SQL Server Import for SQL Operations Studio
|
||||
|
||||
|
||||
Import Flat File Wizard is a simple way to copy data from a flat file (.csv, .txt) to a destination. This overview describes the reasons for using this wizard, how to find this wizard, and a simple example to follow.
|
||||
|
||||

|
||||
|
||||
## Why would I use this wizard?
|
||||
This wizard was created to improve the current import experience leveraging an intelligent framework known as Program Synthesis using Examples ([PROSE](https://microsoft.github.io/prose/)). For a user without specialized domain knowledge, importing data can often be a complex, error prone, and tedious task. This wizard streamlines the import process as simple as selecting an input file and unique table name, and the PROSE framework handles the rest.
|
||||
|
||||
PROSE analyzes data patterns in your input file to infer column names, types, delimiters, and more. This framework learns the structure of the file and does all of the hard work so users don't have to.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
||||
## Privacy Statement
|
||||
|
||||
The [Microsoft Enterprise and Developer Privacy Statement](https://privacy.microsoft.com/en-us/privacystatement) describes the privacy statement of this software.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Licensed under the [Source EULA](https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt).
|
||||
1
extensions/import/images/dark_icon.svg
Normal file
1
extensions/import/images/dark_icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#231f20;}.cls-3{fill:#0095d7;}</style></defs><title>importflatfile_inverse</title><path class="cls-1" d="M13.34,1.57c-2.81,0-7.52.53-7.65,2.49v3.2L7,8.54V5.66a17.11,17.11,0,0,0,6.37,1,17.1,17.1,0,0,0,6.38-1V17.58c-.17.46-2.55,1.35-6.38,1.35a19.63,19.63,0,0,1-3.43-.27V20a23.78,23.78,0,0,0,3.43.25c2.86,0,7.66-.57,7.66-2.64V4.06C20.87,2.1,16.16,1.57,13.34,1.57Zm6.38,2.55c-.2.45-2.56,1.28-6.38,1.28S7.24,4.6,7,4.14c.27-.47,2.6-1.29,6.37-1.29s6.16.85,6.38,1.25h0Z"/><polygon class="cls-2" points="18.55 3.06 18.53 3.07 18.53 3.04 18.55 3.06"/><path class="cls-1" d="M7,10,5.69,8.68,5,8H0V19.85H8.91v-8ZM5.2,9.24l.49.49L7,11l.67.67H5.2Zm3,9.86H.74V8.71H4.46v3.71H8.17Z"/><path class="cls-3" d="M16.5,15a.27.27,0,0,1-.08.2L14.2,17.4a.26.26,0,0,1-.19.08.28.28,0,0,1-.2-.08.26.26,0,0,1-.08-.2.82.82,0,0,1,0-.14l.06-.25.08-.32.08-.32.07-.27,0-.17H4.5v-1.5h9.59l0-.17L14,13.79l-.08-.32-.08-.31-.06-.25a.91.91,0,0,1,0-.14.26.26,0,0,1,.08-.2.28.28,0,0,1,.2-.08.26.26,0,0,1,.19.08l2.22,2.22A.26.26,0,0,1,16.5,15Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
extensions/import/images/light_icon.svg
Normal file
1
extensions/import/images/light_icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21"><defs><style>.cls-1{fill:#212121;}.cls-2{fill:#231f20;}.cls-3{fill:#00539c;}</style></defs><title>importflatfile</title><path class="cls-1" d="M13.34,1.3c-2.81,0-7.52.53-7.65,2.49V7L7,8.27V5.39a17.11,17.11,0,0,0,6.37,1,17.1,17.1,0,0,0,6.38-1V17.31c-.17.46-2.55,1.35-6.38,1.35a19.63,19.63,0,0,1-3.43-.27V19.7a23.78,23.78,0,0,0,3.43.25C16.2,20,21,19.38,21,17.31V3.79C20.87,1.83,16.16,1.3,13.34,1.3Zm6.38,2.55c-.2.45-2.56,1.28-6.38,1.28S7.24,4.33,7,3.87c.27-.47,2.6-1.29,6.37-1.29s6.16.85,6.38,1.25h0Z"/><polygon class="cls-2" points="18.55 2.79 18.53 2.81 18.53 2.78 18.55 2.79"/><path class="cls-1" d="M7,9.69,5.69,8.41,5,7.7H0V19.58H8.91v-8ZM5.2,9l.49.49L7,10.74l.67.67H5.2Zm3,9.86H.74V8.44H4.46v3.71H8.17Z"/><path class="cls-3" d="M16.5,14.72a.27.27,0,0,1-.08.2L14.2,17.14a.26.26,0,0,1-.19.08.28.28,0,0,1-.2-.08.26.26,0,0,1-.08-.2.82.82,0,0,1,0-.14l.06-.25.08-.32.08-.32.07-.27,0-.17H4.5V14h9.59l0-.17L14,13.53l-.08-.32-.08-.31-.06-.25a.91.91,0,0,1,0-.14.26.26,0,0,1,.08-.2.28.28,0,0,1,.2-.08.26.26,0,0,1,.19.08l2.22,2.22A.26.26,0,0,1,16.5,14.72Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
extensions/import/images/sqlserver.png
Normal file
BIN
extensions/import/images/sqlserver.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
91
extensions/import/package.json
Normal file
91
extensions/import/package.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"name": "import",
|
||||
"displayName": "SQL Server Import",
|
||||
"description": "Imports data from a flat file.",
|
||||
"version": "0.0.2",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"engines": {
|
||||
"vscode": "^1.25.0",
|
||||
"sqlops": "*"
|
||||
},
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt",
|
||||
"icon": "images/sqlserver.png",
|
||||
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"main": "./out/main",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/sqlopsstudio.git"
|
||||
},
|
||||
"extensionDependencies": [
|
||||
"Microsoft.mssql"
|
||||
],
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "flatFileImport.start",
|
||||
"title": "Import wizard",
|
||||
"category": "Flat File Import",
|
||||
"icon": {
|
||||
"light": "./images/light_icon.svg",
|
||||
"dark": "./images/dark_icon.svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
{
|
||||
"command": "flatFileImport.start",
|
||||
"key": "ctrl+i",
|
||||
"mac": "ctrl+i"
|
||||
}
|
||||
],
|
||||
"dashboard.tabs": [
|
||||
{
|
||||
"id": "flat-file-import",
|
||||
"title": "Flat File Import",
|
||||
"description": "The flat file importer.",
|
||||
"container": {
|
||||
"flat-file-import-container": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"dashboard.containers": [
|
||||
{
|
||||
"id": "flat-file-import-container",
|
||||
"container": {
|
||||
"widgets-container": [
|
||||
{
|
||||
"name": "Tasks",
|
||||
"widget": {
|
||||
"tasks-widget": [
|
||||
"flatFileImport.start"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"objectExplorer/item/context": [
|
||||
{
|
||||
"command": "flatFileImport.start",
|
||||
"when": "connectionProvider == MSSQL && nodeType && nodeType == Database",
|
||||
"group": "import"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.7",
|
||||
"opener": "^1.4.3",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.4",
|
||||
"vscode-extension-telemetry": "^0.0.5",
|
||||
"vscode-nls": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
}
|
||||
}
|
||||
14
extensions/import/src/constants.ts
Normal file
14
extensions/import/src/constants.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export const extensionConfigSectionName = 'flatFileImport';
|
||||
export const serviceName = 'Flat File Import Service';
|
||||
export const providerId = 'FlatFileImport';
|
||||
export const configLogDebugInfo = 'logDebugInfo';
|
||||
export const sqlConfigSectionName = 'sql';
|
||||
|
||||
export const serviceCrashLink = 'https://github.com/Microsoft/sqlopsstudio/issues/2090';
|
||||
|
||||
30
extensions/import/src/controllers/controllerBase.ts
Normal file
30
extensions/import/src/controllers/controllerBase.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export default abstract class ControllerBase implements vscode.Disposable {
|
||||
protected _context: vscode.ExtensionContext;
|
||||
|
||||
protected constructor(context: vscode.ExtensionContext) {
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
public get extensionContext(): vscode.ExtensionContext {
|
||||
return this._context;
|
||||
}
|
||||
|
||||
abstract activate(): Promise<boolean>;
|
||||
|
||||
abstract deactivate(): void;
|
||||
|
||||
public dispose(): void {
|
||||
this.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
44
extensions/import/src/controllers/mainController.ts
Normal file
44
extensions/import/src/controllers/mainController.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as constants from '../constants';
|
||||
import * as sqlops from 'sqlops';
|
||||
import ControllerBase from './controllerBase';
|
||||
import * as vscode from 'vscode';
|
||||
import { FlatFileWizard } from '../wizard/flatFileWizard';
|
||||
import { ServiceClient } from '../services/serviceClient';
|
||||
import { ApiType, managerInstance } from '../services/serviceApiManager';
|
||||
import { FlatFileProvider } from '../services/contracts';
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
*/
|
||||
export default class MainController extends ControllerBase {
|
||||
|
||||
public constructor(context: vscode.ExtensionContext) {
|
||||
super(context);
|
||||
}
|
||||
/**
|
||||
*/
|
||||
public deactivate(): void {
|
||||
}
|
||||
|
||||
public activate(): Promise<boolean> {
|
||||
const outputChannel = vscode.window.createOutputChannel(constants.serviceName);
|
||||
new ServiceClient(outputChannel).startService(this._context);
|
||||
|
||||
managerInstance.onRegisteredApi<FlatFileProvider>(ApiType.FlatFileProvider)(provider => {
|
||||
this.initializeFlatFileProvider(provider);
|
||||
});
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private initializeFlatFileProvider(provider: FlatFileProvider) {
|
||||
sqlops.tasks.registerTask('flatFileImport.start', (profile: sqlops.IConnectionProfile, ...args: any[]) => new FlatFileWizard(provider).start(profile, args));
|
||||
}
|
||||
}
|
||||
37
extensions/import/src/main.ts
Normal file
37
extensions/import/src/main.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import ControllerBase from './controllers/controllerBase';
|
||||
import MainController from './controllers/mainController';
|
||||
|
||||
let controllers: ControllerBase[] = [];
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
let activations: Promise<boolean>[] = [];
|
||||
|
||||
// Start the main controller
|
||||
let mainController = new MainController(context);
|
||||
controllers.push(mainController);
|
||||
context.subscriptions.push(mainController);
|
||||
activations.push(mainController.activate());
|
||||
|
||||
return Promise.all(activations)
|
||||
.then((results: boolean[]) => {
|
||||
for (let result of results) {
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
for (let controller of controllers) {
|
||||
controller.deactivate();
|
||||
}
|
||||
}
|
||||
16
extensions/import/src/services/config.json
Normal file
16
extensions/import/src/services/config.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"downloadUrl": "https://sqlopsextensions.blob.core.windows.net/extensions/import/{#fileName#}",
|
||||
"useDefaultLinuxRuntime": true,
|
||||
"version": "0.0.1",
|
||||
"downloadFileNames": {
|
||||
"Windows_64": "win-x64.zip",
|
||||
"Windows_86": "win-x86.zip",
|
||||
"OSX": "osx.tar.gz",
|
||||
"Linux_64": "linux-x64.tar.gz"
|
||||
},
|
||||
"installDirectory": "flatfileimportservice/{#platform#}/{#version#}",
|
||||
"executableFiles": [
|
||||
"MicrosoftSqlToolsFlatFileImport",
|
||||
"MicrosoftSqlToolsFlatFileImport.exe"
|
||||
]
|
||||
}
|
||||
149
extensions/import/src/services/contracts.ts
Normal file
149
extensions/import/src/services/contracts.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { RequestType, NotificationType } from 'vscode-languageclient';
|
||||
|
||||
/**
|
||||
* @interface IMessage
|
||||
*/
|
||||
export interface IMessage {
|
||||
jsonrpc: string;
|
||||
}
|
||||
|
||||
// ------------------------------- < Telemetry Sent Event > ------------------------------------
|
||||
|
||||
/**
|
||||
* Event sent when the language service send a telemetry event
|
||||
*/
|
||||
export namespace TelemetryNotification {
|
||||
export const type = new NotificationType<TelemetryParams, void>('telemetry/sqlevent');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update event parameters
|
||||
*/
|
||||
export class TelemetryParams {
|
||||
public params: {
|
||||
eventName: string;
|
||||
properties: ITelemetryEventProperties;
|
||||
measures: ITelemetryEventMeasures;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ITelemetryEventProperties {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface ITelemetryEventMeasures {
|
||||
[key: string]: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contract Classes
|
||||
*/
|
||||
export interface Result {
|
||||
success: boolean;
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
export interface ColumnInfo {
|
||||
name: string;
|
||||
sqlType: string;
|
||||
isNullable: boolean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* PROSEDiscoveryRequest
|
||||
* Send this request to create a new PROSE session with a new file and preview it
|
||||
*/
|
||||
const proseDiscoveryRequestName = 'flatfile/proseDiscovery';
|
||||
|
||||
export interface PROSEDiscoveryParams {
|
||||
filePath: string;
|
||||
tableName: string;
|
||||
schemaName?: string;
|
||||
fileType?: string;
|
||||
}
|
||||
|
||||
export interface PROSEDiscoveryResponse {
|
||||
dataPreview: string[][];
|
||||
columnInfo: ColumnInfo[];
|
||||
}
|
||||
|
||||
/**
|
||||
* InsertDataRequest
|
||||
*/
|
||||
const insertDataRequestName = 'flatfile/insertData';
|
||||
|
||||
export interface InsertDataParams {
|
||||
connectionString: string;
|
||||
batchSize: number;
|
||||
}
|
||||
|
||||
export interface InsertDataResponse {
|
||||
result: Result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* GetColumnInfoRequest
|
||||
*/
|
||||
const getColumnInfoRequestName = 'flatfile/getColumnInfo';
|
||||
|
||||
export interface GetColumnInfoParams {
|
||||
}
|
||||
|
||||
export interface GetColumnInfoResponse {
|
||||
columnInfo: ColumnInfo[];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ChangeColumnSettingsRequest
|
||||
*/
|
||||
const changeColumnSettingsRequestName = 'flatfile/changeColumnSettings';
|
||||
|
||||
export interface ChangeColumnSettingsParams {
|
||||
index: number;
|
||||
newName?: string;
|
||||
newDataType?: string;
|
||||
newNullable?: boolean;
|
||||
newInPrimaryKey?: boolean;
|
||||
}
|
||||
|
||||
export interface ChangeColumnSettingsResponse {
|
||||
result: Result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests
|
||||
*/
|
||||
export namespace PROSEDiscoveryRequest {
|
||||
export const type = new RequestType<PROSEDiscoveryParams, PROSEDiscoveryResponse, void, void>(proseDiscoveryRequestName);
|
||||
}
|
||||
|
||||
export namespace InsertDataRequest {
|
||||
export const type = new RequestType<InsertDataParams, InsertDataResponse, void, void>(insertDataRequestName);
|
||||
}
|
||||
|
||||
export namespace GetColumnInfoRequest {
|
||||
export const type = new RequestType<GetColumnInfoParams, GetColumnInfoResponse, void, void>(getColumnInfoRequestName);
|
||||
}
|
||||
|
||||
export namespace ChangeColumnSettingsRequest {
|
||||
export const type = new RequestType<ChangeColumnSettingsParams, ChangeColumnSettingsResponse, void, void>(changeColumnSettingsRequestName);
|
||||
}
|
||||
|
||||
|
||||
export interface FlatFileProvider {
|
||||
providerId?: string;
|
||||
|
||||
sendPROSEDiscoveryRequest(params: PROSEDiscoveryParams): Thenable<PROSEDiscoveryResponse>;
|
||||
sendInsertDataRequest(params: InsertDataParams): Thenable<InsertDataResponse>;
|
||||
sendGetColumnInfoRequest(params: GetColumnInfoParams): Thenable<GetColumnInfoResponse>;
|
||||
sendChangeColumnSettingsRequest(params: ChangeColumnSettingsParams): Thenable<ChangeColumnSettingsResponse>;
|
||||
}
|
||||
96
extensions/import/src/services/features.ts
Normal file
96
extensions/import/src/services/features.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { SqlOpsDataClient, SqlOpsFeature } from 'dataprotocol-client';
|
||||
import {
|
||||
ClientCapabilities,
|
||||
StaticFeature,
|
||||
RPCMessageType,
|
||||
ServerCapabilities
|
||||
} from 'vscode-languageclient';
|
||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||
import { Disposable } from 'vscode';
|
||||
|
||||
import { Telemetry } from './telemetry';
|
||||
import * as serviceUtils from './serviceUtils';
|
||||
import * as Contracts from './contracts';
|
||||
import { managerInstance, ApiType } from './serviceApiManager';
|
||||
|
||||
export class TelemetryFeature implements StaticFeature {
|
||||
|
||||
constructor(private _client: SqlOpsDataClient) {
|
||||
}
|
||||
|
||||
fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||
serviceUtils.ensure(capabilities, 'telemetry')!.telemetry = true;
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
this._client.onNotification(Contracts.TelemetryNotification.type, e => {
|
||||
Telemetry.sendTelemetryEvent(e.params.eventName, e.params.properties, e.params.measures);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class FlatFileImportFeature extends SqlOpsFeature<undefined> {
|
||||
private static readonly messagesTypes: RPCMessageType[] = [
|
||||
Contracts.PROSEDiscoveryRequest.type
|
||||
];
|
||||
|
||||
constructor(client: SqlOpsDataClient) {
|
||||
super(client, FlatFileImportFeature.messagesTypes);
|
||||
}
|
||||
|
||||
public fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||
}
|
||||
|
||||
public initialize(capabilities: ServerCapabilities): void {
|
||||
this.register(this.messages, {
|
||||
id: UUID.generateUuid(),
|
||||
registerOptions: undefined
|
||||
});
|
||||
}
|
||||
|
||||
protected registerProvider(options: undefined): Disposable {
|
||||
const client = this._client;
|
||||
|
||||
let requestSender = (requestType, params) => {
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => {
|
||||
return r as any;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.reject(e);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let sendPROSEDiscoveryRequest = (params: Contracts.PROSEDiscoveryParams): Thenable<Contracts.PROSEDiscoveryResponse> => {
|
||||
return requestSender(Contracts.PROSEDiscoveryRequest.type, params);
|
||||
};
|
||||
|
||||
let sendInsertDataRequest = (params: Contracts.InsertDataParams): Thenable<Contracts.InsertDataResponse> => {
|
||||
return requestSender(Contracts.InsertDataRequest.type, params);
|
||||
};
|
||||
|
||||
let sendGetColumnInfoRequest = (params: Contracts.GetColumnInfoParams): Thenable<Contracts.GetColumnInfoResponse> => {
|
||||
return requestSender(Contracts.GetColumnInfoRequest.type, params);
|
||||
};
|
||||
|
||||
let sendChangeColumnSettingsRequest = (params: Contracts.ChangeColumnSettingsParams): Thenable<Contracts.ChangeColumnSettingsResponse> => {
|
||||
return requestSender(Contracts.ChangeColumnSettingsRequest.type, params);
|
||||
};
|
||||
|
||||
return managerInstance.registerApi<Contracts.FlatFileProvider>(ApiType.FlatFileProvider, {
|
||||
providerId: client.providerId,
|
||||
sendPROSEDiscoveryRequest,
|
||||
sendChangeColumnSettingsRequest,
|
||||
sendGetColumnInfoRequest,
|
||||
sendInsertDataRequest
|
||||
});
|
||||
}
|
||||
}
|
||||
64
extensions/import/src/services/serviceApiManager.ts
Normal file
64
extensions/import/src/services/serviceApiManager.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import * as contracts from './contracts';
|
||||
import { SqlOpsDataClient } from 'dataprotocol-client/lib/main';
|
||||
|
||||
export enum ApiType {
|
||||
FlatFileProvider = 'FlatFileProvider'
|
||||
}
|
||||
|
||||
export interface IServiceApi {
|
||||
onRegisteredApi<T>(type: ApiType): vscode.Event<T>;
|
||||
registerApi<T>(type: ApiType, feature: T): vscode.Disposable;
|
||||
}
|
||||
|
||||
export interface IModelViewDefinition {
|
||||
id: string;
|
||||
modelView: sqlops.ModelView;
|
||||
}
|
||||
|
||||
export class ServiceApiManager implements IServiceApi {
|
||||
private modelViewRegistrations: { [id: string]: boolean } = {};
|
||||
private featureEventChannels: { [type: string]: vscode.EventEmitter<any> } = {};
|
||||
private _onRegisteredModelView = new vscode.EventEmitter<IModelViewDefinition>();
|
||||
|
||||
public onRegisteredApi<T>(type: ApiType): vscode.Event<T> {
|
||||
let featureEmitter = this.featureEventChannels[type];
|
||||
if (!featureEmitter) {
|
||||
featureEmitter = new vscode.EventEmitter<T>();
|
||||
this.featureEventChannels[type] = featureEmitter;
|
||||
}
|
||||
return featureEmitter.event;
|
||||
}
|
||||
|
||||
public registerApi<T>(type: ApiType, feature: T): vscode.Disposable {
|
||||
let featureEmitter = this.featureEventChannels[type];
|
||||
if (featureEmitter) {
|
||||
featureEmitter.fire(feature);
|
||||
}
|
||||
// TODO handle unregistering API on close
|
||||
return {
|
||||
dispose: () => undefined
|
||||
};
|
||||
}
|
||||
|
||||
public get onRegisteredModelView(): vscode.Event<IModelViewDefinition> {
|
||||
return this._onRegisteredModelView.event;
|
||||
}
|
||||
|
||||
public registerModelView(id: string, modelView: sqlops.ModelView): void {
|
||||
this._onRegisteredModelView.fire({
|
||||
id: id,
|
||||
modelView: modelView
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export let managerInstance = new ServiceApiManager();
|
||||
165
extensions/import/src/services/serviceClient.ts
Normal file
165
extensions/import/src/services/serviceClient.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client';
|
||||
import { IConfig, ServerProvider, Events } from 'service-downloader';
|
||||
import { ServerOptions, TransportKind } from 'vscode-languageclient';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
import * as path from 'path';
|
||||
import { EventAndListener } from 'eventemitter2';
|
||||
|
||||
import { Telemetry, LanguageClientErrorHandler } from './telemetry';
|
||||
import * as Constants from '../constants';
|
||||
import { TelemetryFeature, FlatFileImportFeature } from './features';
|
||||
import * as serviceUtils from './serviceUtils';
|
||||
|
||||
const baseConfig = require('./config.json');
|
||||
|
||||
export class ServiceClient {
|
||||
private statusView: vscode.StatusBarItem;
|
||||
|
||||
constructor(private outputChannel: vscode.OutputChannel) {
|
||||
this.statusView = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
|
||||
}
|
||||
|
||||
public startService(context: vscode.ExtensionContext): Promise<SqlOpsDataClient> {
|
||||
let config: IConfig = JSON.parse(JSON.stringify(baseConfig));
|
||||
config.installDirectory = path.join(context.extensionPath, config.installDirectory);
|
||||
config.proxy = vscode.workspace.getConfiguration('http').get('proxy');
|
||||
config.strictSSL = vscode.workspace.getConfiguration('http').get('proxyStrictSSL') || true;
|
||||
|
||||
const serverdownloader = new ServerProvider(config);
|
||||
serverdownloader.eventEmitter.onAny(this.generateHandleServerProviderEvent());
|
||||
|
||||
let clientOptions: ClientOptions = this.createClientOptions();
|
||||
|
||||
const installationStart = Date.now();
|
||||
let client: SqlOpsDataClient;
|
||||
return new Promise((resolve, reject) => {
|
||||
serverdownloader.getOrDownloadServer().then(e => {
|
||||
const installationComplete = Date.now();
|
||||
let serverOptions = this.generateServerOptions(e);
|
||||
client = new SqlOpsDataClient(Constants.serviceName, serverOptions, clientOptions);
|
||||
const processStart = Date.now();
|
||||
client.onReady().then(() => {
|
||||
const processEnd = Date.now();
|
||||
this.statusView.text = localize('serviceStarted', 'Service Started');
|
||||
setTimeout(() => {
|
||||
this.statusView.hide();
|
||||
}, 1500);
|
||||
Telemetry.sendTelemetryEvent('startup/LanguageClientStarted', {
|
||||
installationTime: String(installationComplete - installationStart),
|
||||
processStartupTime: String(processEnd - processStart),
|
||||
totalTime: String(processEnd - installationStart),
|
||||
beginningTimestamp: String(installationStart)
|
||||
});
|
||||
});
|
||||
this.statusView.show();
|
||||
this.statusView.text = localize('serviceStarting', 'Starting service');
|
||||
let disposable = client.start();
|
||||
context.subscriptions.push(disposable);
|
||||
resolve(client);
|
||||
}, e => {
|
||||
Telemetry.sendTelemetryEvent('ServiceInitializingFailed');
|
||||
vscode.window.showErrorMessage(localize('flatFileImport.serviceStartFailed', 'Failed to start Import service{0}', e));
|
||||
// Just resolve to avoid unhandled promise. We show the error to the user.
|
||||
resolve(undefined);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private createClientOptions(): ClientOptions {
|
||||
return {
|
||||
providerId: Constants.providerId,
|
||||
errorHandler: new LanguageClientErrorHandler(),
|
||||
synchronize: {
|
||||
configurationSection: [Constants.extensionConfigSectionName, Constants.sqlConfigSectionName]
|
||||
},
|
||||
features: [
|
||||
// we only want to add new features
|
||||
TelemetryFeature,
|
||||
FlatFileImportFeature
|
||||
],
|
||||
outputChannel: new CustomOutputChannel()
|
||||
};
|
||||
}
|
||||
|
||||
private generateServerOptions(executablePath: string): ServerOptions {
|
||||
let launchArgs = [];
|
||||
launchArgs.push('--log-dir');
|
||||
let logFileLocation = path.join(serviceUtils.getDefaultLogLocation(), 'flatfileimport');
|
||||
launchArgs.push(logFileLocation);
|
||||
let config = vscode.workspace.getConfiguration(Constants.extensionConfigSectionName);
|
||||
if (config) {
|
||||
let logDebugInfo = config[Constants.configLogDebugInfo];
|
||||
if (logDebugInfo) {
|
||||
launchArgs.push('--enable-logging');
|
||||
}
|
||||
}
|
||||
|
||||
return { command: executablePath, args: launchArgs, transport: TransportKind.stdio };
|
||||
}
|
||||
|
||||
private generateHandleServerProviderEvent(): EventAndListener {
|
||||
let dots = 0;
|
||||
return (e: string, ...args: any[]) => {
|
||||
this.outputChannel.show();
|
||||
this.statusView.show();
|
||||
switch (e) {
|
||||
case Events.INSTALL_START:
|
||||
this.outputChannel.appendLine(localize('installingServiceDetailed', 'Installing {0} service to {1}', Constants.serviceName, args[0]));
|
||||
this.statusView.text = localize('installingService', 'Installing Service');
|
||||
break;
|
||||
case Events.INSTALL_END:
|
||||
this.outputChannel.appendLine(localize('serviceInstalled', 'Installed'));
|
||||
break;
|
||||
case Events.DOWNLOAD_START:
|
||||
this.outputChannel.appendLine(localize('downloadingService', 'Downloading {0}', args[0]));
|
||||
this.outputChannel.append(`(${Math.ceil(args[1] / 1024)} KB)`);
|
||||
this.statusView.text = localize('downloadingServiceStatus', 'Downloading Service');
|
||||
break;
|
||||
case Events.DOWNLOAD_PROGRESS:
|
||||
let newDots = Math.ceil(args[0] / 5);
|
||||
if (newDots > dots) {
|
||||
this.outputChannel.append('.'.repeat(newDots - dots));
|
||||
dots = newDots;
|
||||
}
|
||||
break;
|
||||
case Events.DOWNLOAD_END:
|
||||
this.outputChannel.appendLine(localize('downloadingServiceComplete', 'Done!'));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class CustomOutputChannel implements vscode.OutputChannel {
|
||||
name: string;
|
||||
append(value: string): void {
|
||||
}
|
||||
appendLine(value: string): void {
|
||||
}
|
||||
// tslint:disable-next-line:no-empty
|
||||
clear(): void {
|
||||
}
|
||||
show(preserveFocus?: boolean): void;
|
||||
show(column?: vscode.ViewColumn, preserveFocus?: boolean): void;
|
||||
// tslint:disable-next-line:no-empty
|
||||
show(column?: any, preserveFocus?: any): void {
|
||||
}
|
||||
// tslint:disable-next-line:no-empty
|
||||
hide(): void {
|
||||
}
|
||||
// tslint:disable-next-line:no-empty
|
||||
dispose(): void {
|
||||
}
|
||||
}
|
||||
|
||||
156
extensions/import/src/services/serviceUtils.ts
Normal file
156
extensions/import/src/services/serviceUtils.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as crypto from 'crypto';
|
||||
import * as os from 'os';
|
||||
|
||||
const baseConfig = require('./config.json');
|
||||
|
||||
// The function is a duplicate of \src\paths.js. IT would be better to import path.js but it doesn't
|
||||
// work for now because the extension is running in different process.
|
||||
export function getAppDataPath(): string {
|
||||
let platform = process.platform;
|
||||
switch (platform) {
|
||||
case 'win32': return process.env['APPDATA'] || path.join(process.env['USERPROFILE'], 'AppData', 'Roaming');
|
||||
case 'darwin': return path.join(os.homedir(), 'Library', 'Application Support');
|
||||
case 'linux': return process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
|
||||
default: throw new Error('Platform not supported');
|
||||
}
|
||||
}
|
||||
|
||||
export function getDefaultLogLocation(): string {
|
||||
return path.join(getAppDataPath(), 'sqlops');
|
||||
}
|
||||
|
||||
export function ensure(target: object, key: string): any {
|
||||
if (target[key] === void 0) {
|
||||
target[key] = {} as any;
|
||||
}
|
||||
return target[key];
|
||||
}
|
||||
|
||||
export interface IPackageInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
aiKey: string;
|
||||
}
|
||||
|
||||
export function getPackageInfo(packageJson: any): IPackageInfo {
|
||||
if (packageJson) {
|
||||
return {
|
||||
name: packageJson.name,
|
||||
version: packageJson.version,
|
||||
aiKey: packageJson.aiKey
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function generateUserId(): Promise<string> {
|
||||
return new Promise<string>(resolve => {
|
||||
try {
|
||||
let interfaces = os.networkInterfaces();
|
||||
let mac;
|
||||
for (let key of Object.keys(interfaces)) {
|
||||
let item = interfaces[key][0];
|
||||
if (!item.internal) {
|
||||
mac = item.mac;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mac) {
|
||||
resolve(crypto.createHash('sha256').update(mac + os.homedir(), 'utf8').digest('hex'));
|
||||
} else {
|
||||
resolve(generateGuid());
|
||||
}
|
||||
} catch (err) {
|
||||
resolve(generateGuid()); // fallback
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function generateGuid(): string {
|
||||
let hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||
// c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
|
||||
let oct: string = '';
|
||||
let tmp: number;
|
||||
/* tslint:disable:no-bitwise */
|
||||
for (let a: number = 0; a < 4; a++) {
|
||||
tmp = (4294967296 * Math.random()) | 0;
|
||||
oct += hexValues[tmp & 0xF] +
|
||||
hexValues[tmp >> 4 & 0xF] +
|
||||
hexValues[tmp >> 8 & 0xF] +
|
||||
hexValues[tmp >> 12 & 0xF] +
|
||||
hexValues[tmp >> 16 & 0xF] +
|
||||
hexValues[tmp >> 20 & 0xF] +
|
||||
hexValues[tmp >> 24 & 0xF] +
|
||||
hexValues[tmp >> 28 & 0xF];
|
||||
}
|
||||
|
||||
// 'Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively'
|
||||
let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
|
||||
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
|
||||
/* tslint:enable:no-bitwise */
|
||||
}
|
||||
|
||||
export function verifyPlatform(): Thenable<boolean> {
|
||||
if (os.platform() === 'darwin' && parseFloat(os.release()) < 16.0) {
|
||||
return Promise.resolve(false);
|
||||
} else {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
export function getServiceInstallConfig(basePath?: string): any {
|
||||
if (!basePath) {
|
||||
basePath = __dirname;
|
||||
}
|
||||
let config = JSON.parse(JSON.stringify(baseConfig));
|
||||
config.installDirectory = path.join(basePath, config.installDirectory);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export function getResolvedServiceInstallationPath(runtime: Runtime, basePath?: string): string {
|
||||
let config = getServiceInstallConfig(basePath);
|
||||
let dir = config.installDirectory;
|
||||
dir = dir.replace('{#version#}', config.version);
|
||||
dir = dir.replace('{#platform#}', getRuntimeDisplayName(runtime));
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
export function getRuntimeDisplayName(runtime: Runtime): string {
|
||||
switch (runtime) {
|
||||
case Runtime.Windows_64:
|
||||
return 'Windows';
|
||||
case Runtime.Windows_86:
|
||||
return 'Windows';
|
||||
case Runtime.OSX:
|
||||
return 'OSX';
|
||||
case Runtime.Linux_64:
|
||||
return 'Linux';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
export enum Runtime {
|
||||
Unknown = <any>'Unknown',
|
||||
Windows_86 = <any>'Windows_86',
|
||||
Windows_64 = <any>'Windows_64',
|
||||
OSX = <any>'OSX',
|
||||
CentOS_7 = <any>'CentOS_7',
|
||||
Debian_8 = <any>'Debian_8',
|
||||
Fedora_23 = <any>'Fedora_23',
|
||||
OpenSUSE_13_2 = <any>'OpenSUSE_13_2',
|
||||
SLES_12_2 = <any>'SLES_12_2',
|
||||
RHEL_7 = <any>'RHEL_7',
|
||||
Ubuntu_14 = <any>'Ubuntu_14',
|
||||
Ubuntu_16 = <any>'Ubuntu_16',
|
||||
Linux_64 = <any>'Linux_64',
|
||||
Linux_86 = <any>'Linux-86'
|
||||
}
|
||||
216
extensions/import/src/services/telemetry.ts
Normal file
216
extensions/import/src/services/telemetry.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { ErrorAction, CloseAction } from 'vscode-languageclient';
|
||||
import TelemetryReporter from 'vscode-extension-telemetry';
|
||||
import { PlatformInformation } from 'service-downloader/out/platform';
|
||||
import * as opener from 'opener';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import * as constants from '../constants';
|
||||
import * as serviceUtils from './serviceUtils';
|
||||
import { IMessage, ITelemetryEventProperties, ITelemetryEventMeasures } from './contracts';
|
||||
|
||||
|
||||
/**
|
||||
* Handle Language Service client errors
|
||||
* @class LanguageClientErrorHandler
|
||||
*/
|
||||
export class LanguageClientErrorHandler {
|
||||
|
||||
/**
|
||||
* Creates an instance of LanguageClientErrorHandler.
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an error message prompt with a link to known issues wiki page
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
showOnErrorPrompt(): void {
|
||||
// TODO add telemetry
|
||||
// Telemetry.sendTelemetryEvent('SqlToolsServiceCrash');
|
||||
let crashButtonText = localize('import.serviceCrashButton', 'Give Feedback');
|
||||
vscode.window.showErrorMessage(
|
||||
localize('serviceCrashMessage', 'service component could not start'),
|
||||
crashButtonText
|
||||
).then(action => {
|
||||
if (action && action === crashButtonText) {
|
||||
opener(constants.serviceCrashLink);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for language service client error
|
||||
*
|
||||
* @param {Error} error
|
||||
* @param {Message} message
|
||||
* @param {number} count
|
||||
* @returns {ErrorAction}
|
||||
*
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
error(error: Error, message: IMessage, count: number): ErrorAction {
|
||||
this.showOnErrorPrompt();
|
||||
|
||||
// we don't retry running the service since crashes leave the extension
|
||||
// in a bad, unrecovered state
|
||||
return ErrorAction.Shutdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for language service client closed
|
||||
*
|
||||
* @returns {CloseAction}
|
||||
*
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
closed(): CloseAction {
|
||||
this.showOnErrorPrompt();
|
||||
|
||||
// we don't retry running the service since crashes leave the extension
|
||||
// in a bad, unrecovered state
|
||||
return CloseAction.DoNotRestart;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters error paths to only include source files. Exported to support testing
|
||||
*/
|
||||
export function FilterErrorPath(line: string): string {
|
||||
if (line) {
|
||||
let values: string[] = line.split('/out/');
|
||||
if (values.length <= 1) {
|
||||
// Didn't match expected format
|
||||
return line;
|
||||
} else {
|
||||
return values[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Telemetry {
|
||||
private static reporter: TelemetryReporter;
|
||||
private static userId: string;
|
||||
private static platformInformation: PlatformInformation;
|
||||
private static disabled: boolean;
|
||||
|
||||
// Get the unique ID for the current user of the extension
|
||||
public static getUserId(): Promise<string> {
|
||||
return new Promise<string>(resolve => {
|
||||
// Generate the user id if it has not been created already
|
||||
if (typeof this.userId === 'undefined') {
|
||||
let id = serviceUtils.generateUserId();
|
||||
id.then(newId => {
|
||||
this.userId = newId;
|
||||
resolve(this.userId);
|
||||
});
|
||||
} else {
|
||||
resolve(this.userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static getPlatformInformation(): Promise<PlatformInformation> {
|
||||
if (this.platformInformation) {
|
||||
return Promise.resolve(this.platformInformation);
|
||||
} else {
|
||||
return new Promise<PlatformInformation>(resolve => {
|
||||
PlatformInformation.getCurrent().then(info => {
|
||||
this.platformInformation = info;
|
||||
resolve(this.platformInformation);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable telemetry reporting
|
||||
*/
|
||||
public static disable(): void {
|
||||
this.disabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the telemetry reporter for use.
|
||||
*/
|
||||
public static initialize(): void {
|
||||
if (typeof this.reporter === 'undefined') {
|
||||
// Check if the user has opted out of telemetry
|
||||
if (!vscode.workspace.getConfiguration('telemetry').get<boolean>('enableTelemetry', true)) {
|
||||
this.disable();
|
||||
return;
|
||||
}
|
||||
let packageInfo = vscode.extensions.getExtension('Microsoft.import').packageJSON;
|
||||
this.reporter = new TelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a telemetry event for an exception
|
||||
*/
|
||||
public static sendTelemetryEventForException(
|
||||
err: any, methodName: string, extensionConfigName: string): void {
|
||||
try {
|
||||
let stackArray: string[];
|
||||
let firstLine: string = '';
|
||||
if (err !== undefined && err.stack !== undefined) {
|
||||
stackArray = err.stack.split('\n');
|
||||
if (stackArray !== undefined && stackArray.length >= 2) {
|
||||
firstLine = stackArray[1]; // The fist line is the error message and we don't want to send that telemetry event
|
||||
firstLine = FilterErrorPath(firstLine);
|
||||
}
|
||||
}
|
||||
|
||||
// Only adding the method name and the fist line of the stack trace. We don't add the error message because it might have PII
|
||||
this.sendTelemetryEvent('Exception', { methodName: methodName, errorLine: firstLine });
|
||||
// Utils.logDebug('Unhandled Exception occurred. error: ' + err + ' method: ' + methodName, extensionConfigName);
|
||||
} catch (telemetryErr) {
|
||||
// If sending telemetry event fails ignore it so it won't break the extension
|
||||
// Utils.logDebug('Failed to send telemetry event. error: ' + telemetryErr, extensionConfigName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a telemetry event using application insights
|
||||
*/
|
||||
public static sendTelemetryEvent(
|
||||
eventName: string,
|
||||
properties?: ITelemetryEventProperties,
|
||||
measures?: ITelemetryEventMeasures): void {
|
||||
|
||||
if (typeof this.disabled === 'undefined') {
|
||||
this.disabled = false;
|
||||
}
|
||||
|
||||
if (this.disabled || typeof (this.reporter) === 'undefined') {
|
||||
// Don't do anything if telemetry is disabled
|
||||
return;
|
||||
}
|
||||
|
||||
if (!properties || typeof properties === 'undefined') {
|
||||
properties = {};
|
||||
}
|
||||
|
||||
// Augment the properties structure with additional common properties before sending
|
||||
Promise.all([this.getUserId(), this.getPlatformInformation()]).then(() => {
|
||||
properties['userId'] = this.userId;
|
||||
properties['distribution'] = (this.platformInformation && this.platformInformation.distribution) ?
|
||||
`${this.platformInformation.distribution.name}, ${this.platformInformation.distribution.version}` : '';
|
||||
|
||||
this.reporter.sendTelemetryEvent(eventName, properties, measures);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Telemetry.initialize();
|
||||
9
extensions/import/src/typings/ref.d.ts
vendored
Normal file
9
extensions/import/src/typings/ref.d.ts
vendored
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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference path='../../../../src/sql/sqlops.d.ts'/>
|
||||
/// <reference path='../../../../src/sql/sqlops.proposed.d.ts'/>
|
||||
/// <reference types='@types/node'/>
|
||||
59
extensions/import/src/wizard/api/importPage.ts
Normal file
59
extensions/import/src/wizard/api/importPage.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { ImportDataModel } from './models';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { FlatFileProvider } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
|
||||
export abstract class ImportPage {
|
||||
|
||||
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: FlatFileWizard;
|
||||
protected readonly model: ImportDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
protected readonly provider: FlatFileProvider;
|
||||
|
||||
protected constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||
this.instance = instance;
|
||||
this.wizardPage = wizardPage;
|
||||
this.model = model;
|
||||
this.view = view;
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method constructs all the elements of the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async abstract start(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* This method is called when the user is entering the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async abstract onPageEnter(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* This method is called when the user is leaving the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async abstract onPageLeave(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Sets up a navigation validator.
|
||||
* This will be called right before onPageEnter().
|
||||
*/
|
||||
public abstract setupNavigationValidator();
|
||||
|
||||
/**
|
||||
* Override this method to cleanup what you don't need cached in the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async cleanup(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
33
extensions/import/src/wizard/api/models.ts
Normal file
33
extensions/import/src/wizard/api/models.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
/**
|
||||
* The main data model that communicates between the pages.
|
||||
*/
|
||||
export interface ImportDataModel {
|
||||
ownerUri: string;
|
||||
proseColumns: ColumnMetadata[];
|
||||
proseDataPreview: string[][];
|
||||
server: sqlops.connection.Connection;
|
||||
serverId: string;
|
||||
database: string;
|
||||
table: string;
|
||||
schema: string;
|
||||
filePath: string;
|
||||
fileType: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata of a column
|
||||
*/
|
||||
export interface ColumnMetadata {
|
||||
columnName: string;
|
||||
dataType: string;
|
||||
primaryKey: boolean;
|
||||
nullable: boolean;
|
||||
}
|
||||
144
extensions/import/src/wizard/flatFileWizard.ts
Normal file
144
extensions/import/src/wizard/flatFileWizard.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { FlatFileProvider } from '../services/contracts';
|
||||
import { ImportDataModel } from './api/models';
|
||||
import { ImportPage } from './api/importPage';
|
||||
// pages
|
||||
import { FileConfigPage } from './pages/fileConfigPage';
|
||||
import { ProsePreviewPage } from './pages/prosePreviewPage';
|
||||
import { ModifyColumnsPage } from './pages/modifyColumnsPage';
|
||||
import { SummaryPage } from './pages/summaryPage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class FlatFileWizard {
|
||||
private readonly provider: FlatFileProvider;
|
||||
private wizard: sqlops.window.modelviewdialog.Wizard;
|
||||
|
||||
private importAnotherFileButton: sqlops.window.modelviewdialog.Button;
|
||||
|
||||
constructor(provider: FlatFileProvider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public async start(p: any, ...args: any[]) {
|
||||
let model = <ImportDataModel>{};
|
||||
|
||||
let profile = <sqlops.IConnectionProfile>p.connectionProfile;
|
||||
if (profile) {
|
||||
model.serverId = profile.id;
|
||||
model.database = profile.databaseName;
|
||||
}
|
||||
|
||||
let pages: Map<number, ImportPage> = new Map<number, ImportPage>();
|
||||
|
||||
|
||||
let connections = await sqlops.connection.getActiveConnections();
|
||||
if (!connections || connections.length === 0) {
|
||||
vscode.window.showErrorMessage(localize('import.needConnection', 'Please connect to a server before using this wizard.'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.wizard = sqlops.window.modelviewdialog.createWizard(localize('flatFileImport.wizardName', 'Import flat file wizard'));
|
||||
let page1 = sqlops.window.modelviewdialog.createWizardPage(localize('flatFileImport.page1Name', 'Specify Input File'));
|
||||
let page2 = sqlops.window.modelviewdialog.createWizardPage(localize('flatFileImport.page2Name', 'Preview Data'));
|
||||
let page3 = sqlops.window.modelviewdialog.createWizardPage(localize('flatFileImport.page3Name', 'Modify Columns'));
|
||||
let page4 = sqlops.window.modelviewdialog.createWizardPage(localize('flatFileImport.page4Name', 'Summary'));
|
||||
|
||||
let fileConfigPage: FileConfigPage;
|
||||
|
||||
page1.registerContent(async (view) => {
|
||||
fileConfigPage = new FileConfigPage(this, page1, model, view, this.provider);
|
||||
pages.set(0, fileConfigPage);
|
||||
await fileConfigPage.start().then(() => {
|
||||
fileConfigPage.setupNavigationValidator();
|
||||
fileConfigPage.onPageEnter();
|
||||
});
|
||||
});
|
||||
|
||||
let prosePreviewPage: ProsePreviewPage;
|
||||
page2.registerContent(async (view) => {
|
||||
prosePreviewPage = new ProsePreviewPage(this, page2, model, view, this.provider);
|
||||
pages.set(1, prosePreviewPage);
|
||||
await prosePreviewPage.start();
|
||||
});
|
||||
|
||||
let modifyColumnsPage: ModifyColumnsPage;
|
||||
page3.registerContent(async (view) => {
|
||||
modifyColumnsPage = new ModifyColumnsPage(this, page3, model, view, this.provider);
|
||||
pages.set(2, modifyColumnsPage);
|
||||
await modifyColumnsPage.start();
|
||||
});
|
||||
|
||||
let summaryPage: SummaryPage;
|
||||
|
||||
page4.registerContent(async (view) => {
|
||||
summaryPage = new SummaryPage(this, page4, model, view, this.provider);
|
||||
pages.set(3, summaryPage);
|
||||
await summaryPage.start();
|
||||
});
|
||||
|
||||
|
||||
this.importAnotherFileButton = sqlops.window.modelviewdialog.createButton(localize('flatFileImport.importNewFile', 'Import new file'));
|
||||
this.importAnotherFileButton.onClick(() => {
|
||||
//TODO replace this with proper cleanup for all the pages
|
||||
this.wizard.close();
|
||||
pages.forEach((page) => page.cleanup());
|
||||
this.wizard.open();
|
||||
});
|
||||
|
||||
this.importAnotherFileButton.hidden = true;
|
||||
this.wizard.customButtons = [this.importAnotherFileButton];
|
||||
|
||||
this.wizard.onPageChanged(async (event) => {
|
||||
let idx = event.newPage;
|
||||
|
||||
let page = pages.get(idx);
|
||||
|
||||
if (page) {
|
||||
page.setupNavigationValidator();
|
||||
page.onPageEnter();
|
||||
}
|
||||
});
|
||||
|
||||
this.wizard.onPageChanged(async (event) => {
|
||||
let idx = event.lastPage;
|
||||
|
||||
let page = pages.get(idx);
|
||||
if (page) {
|
||||
page.onPageLeave();
|
||||
}
|
||||
});
|
||||
|
||||
//not needed for this wizard
|
||||
this.wizard.generateScriptButton.hidden = true;
|
||||
|
||||
this.wizard.pages = [page1, page2, page3, page4];
|
||||
|
||||
this.wizard.open();
|
||||
}
|
||||
|
||||
public setImportAnotherFileVisibility(visibility: boolean) {
|
||||
this.importAnotherFileButton.hidden = !visibility;
|
||||
}
|
||||
|
||||
public registerNavigationValidator(validator: (pageChangeInfo: sqlops.window.modelviewdialog.WizardPageChangeInfo) => boolean) {
|
||||
this.wizard.registerNavigationValidator(validator);
|
||||
}
|
||||
|
||||
public changeNextButtonLabel(label: string) {
|
||||
this.wizard.nextButton.label = label;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
411
extensions/import/src/wizard/pages/fileConfigPage.ts
Normal file
411
extensions/import/src/wizard/pages/fileConfigPage.ts
Normal file
@@ -0,0 +1,411 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { ImportDataModel } from '../api/models';
|
||||
import { ImportPage } from '../api/importPage';
|
||||
import { FlatFileProvider } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class FileConfigPage extends ImportPage {
|
||||
|
||||
private serverDropdown: sqlops.DropDownComponent;
|
||||
private databaseDropdown: sqlops.DropDownComponent;
|
||||
private fileTextBox: sqlops.InputBoxComponent;
|
||||
private fileButton: sqlops.ButtonComponent;
|
||||
private tableNameTextBox: sqlops.InputBoxComponent;
|
||||
private schemaDropdown: sqlops.DropDownComponent;
|
||||
private form: sqlops.FormContainer;
|
||||
|
||||
private databaseLoader: sqlops.LoadingComponent;
|
||||
private schemaLoader: sqlops.LoadingComponent;
|
||||
|
||||
private tableNames: string[] = [];
|
||||
|
||||
public constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||
super(instance, wizardPage, model, view, provider);
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
let schemaComponent = await this.createSchemaDropdown();
|
||||
let tableNameComponent = await this.createTableNameBox();
|
||||
let fileBrowserComponent = await this.createFileBrowser();
|
||||
let databaseComponent = await this.createDatabaseDropdown();
|
||||
let serverComponent = await this.createServerDropdown();
|
||||
|
||||
this.form = this.view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[
|
||||
serverComponent,
|
||||
databaseComponent,
|
||||
fileBrowserComponent,
|
||||
tableNameComponent,
|
||||
schemaComponent
|
||||
]).component();
|
||||
|
||||
await this.view.initializeModel(this.form);
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
let r1 = await this.populateServerDropdown();
|
||||
let r2 = await this.populateDatabaseDropdown();
|
||||
let r3 = await this.populateSchemaDropdown();
|
||||
return r1 && r2 && r3;
|
||||
}
|
||||
|
||||
async onPageLeave(): Promise<boolean> {
|
||||
delete this.model.serverId;
|
||||
return true;
|
||||
}
|
||||
|
||||
public async cleanup(): Promise<boolean> {
|
||||
delete this.model.filePath;
|
||||
delete this.model.table;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public setupNavigationValidator() {
|
||||
this.instance.registerNavigationValidator((info) => {
|
||||
if (this.schemaLoader.loading || this.databaseLoader.loading) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private async createServerDropdown(): Promise<sqlops.FormComponent> {
|
||||
this.serverDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
|
||||
// Handle server changes
|
||||
this.serverDropdown.onValueChanged(async (params) => {
|
||||
this.model.server = (this.serverDropdown.value as ConnectionDropdownValue).connection;
|
||||
|
||||
await this.populateDatabaseDropdown();
|
||||
await this.populateSchemaDropdown();
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.serverDropdown,
|
||||
title: localize('flatFileImport.serverDropdownTitle', 'Server the database is in')
|
||||
};
|
||||
}
|
||||
|
||||
private async populateServerDropdown(): Promise<boolean> {
|
||||
let cons = await sqlops.connection.getActiveConnections();
|
||||
// This user has no active connections ABORT MISSION
|
||||
if (!cons || cons.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
let count = -1;
|
||||
let idx = -1;
|
||||
|
||||
|
||||
let values = cons.map(c => {
|
||||
// Handle the code to remember what the user's choice was from before
|
||||
count++;
|
||||
if (idx === -1) {
|
||||
if (this.model.server && c.connectionId === this.model.server.connectionId) {
|
||||
idx = count;
|
||||
} else if (this.model.serverId && c.connectionId === this.model.serverId) {
|
||||
idx = count;
|
||||
}
|
||||
}
|
||||
|
||||
let db = c.options.databaseDisplayName;
|
||||
let usr = c.options.user;
|
||||
let srv = c.options.server;
|
||||
|
||||
if (!db) {
|
||||
db = '<default>';
|
||||
}
|
||||
|
||||
if (!usr) {
|
||||
usr = 'default';
|
||||
}
|
||||
|
||||
let finalName = `${srv}, ${db} (${usr})`;
|
||||
return {
|
||||
connection: c,
|
||||
displayName: finalName,
|
||||
name: c.connectionId
|
||||
};
|
||||
});
|
||||
|
||||
if (idx >= 0) {
|
||||
let tmp = values[0];
|
||||
values[0] = values[idx];
|
||||
values[idx] = tmp;
|
||||
} else {
|
||||
delete this.model.server;
|
||||
delete this.model.serverId;
|
||||
delete this.model.database;
|
||||
delete this.model.schema;
|
||||
}
|
||||
|
||||
this.model.server = values[0].connection;
|
||||
|
||||
|
||||
this.serverDropdown.updateProperties({
|
||||
values: values
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private async createDatabaseDropdown(): Promise<sqlops.FormComponent> {
|
||||
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
|
||||
// Handle database changes
|
||||
this.databaseDropdown.onValueChanged(async (db) => {
|
||||
this.model.database = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
|
||||
//this.populateTableNames();
|
||||
this.populateSchemaDropdown();
|
||||
});
|
||||
|
||||
this.databaseLoader = this.view.modelBuilder.loadingComponent().withItem(this.databaseDropdown).component();
|
||||
|
||||
return {
|
||||
component: this.databaseLoader,
|
||||
title: localize('flatFileImport.databaseDropdownTitle', 'Database the table is created in')
|
||||
};
|
||||
}
|
||||
|
||||
private async populateDatabaseDropdown(): Promise<boolean> {
|
||||
this.databaseLoader.loading = true;
|
||||
this.databaseDropdown.updateProperties({ values: [] });
|
||||
this.schemaDropdown.updateProperties({ values: [] });
|
||||
|
||||
if (!this.model.server) {
|
||||
//TODO handle error case
|
||||
this.databaseLoader.loading = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
let idx = -1;
|
||||
let count = -1;
|
||||
let values = (await sqlops.connection.listDatabases(this.model.server.connectionId)).map(db => {
|
||||
count++;
|
||||
if (this.model.database && db === this.model.database) {
|
||||
idx = count;
|
||||
}
|
||||
|
||||
return {
|
||||
displayName: db,
|
||||
name: db
|
||||
};
|
||||
});
|
||||
|
||||
if (idx >= 0) {
|
||||
let tmp = values[0];
|
||||
values[0] = values[idx];
|
||||
values[idx] = tmp;
|
||||
} else {
|
||||
delete this.model.database;
|
||||
delete this.model.schema;
|
||||
}
|
||||
|
||||
this.model.database = values[0].name;
|
||||
|
||||
this.databaseDropdown.updateProperties({
|
||||
values: values
|
||||
});
|
||||
this.databaseLoader.loading = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async createFileBrowser(): Promise<sqlops.FormComponent> {
|
||||
this.fileTextBox = this.view.modelBuilder.inputBox().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
this.fileButton = this.view.modelBuilder.button().withProperties({
|
||||
label: localize('flatFileImport.browseFiles', 'Browse'),
|
||||
}).component();
|
||||
|
||||
this.fileButton.onDidClick(async (click) => {
|
||||
let fileUris = await vscode.window.showOpenDialog(
|
||||
{
|
||||
canSelectFiles: true,
|
||||
canSelectFolders: false,
|
||||
canSelectMany: false,
|
||||
openLabel: localize('flatFileImport.openFile', 'Open'),
|
||||
filters: {
|
||||
'CSV/TXT Files': ['csv', 'txt'],
|
||||
'All Files': ['*']
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!fileUris || fileUris.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let fileUri = fileUris[0];
|
||||
this.fileTextBox.value = fileUri.fsPath;
|
||||
|
||||
// Get the name of the file.
|
||||
let nameStart = fileUri.path.lastIndexOf('/');
|
||||
let nameEnd = fileUri.path.lastIndexOf('.');
|
||||
|
||||
// Handle files without extensions
|
||||
if (nameEnd === 0) {
|
||||
nameEnd = fileUri.path.length;
|
||||
}
|
||||
this.model.fileType = 'TXT';
|
||||
let extension = fileUri.path.substring(nameEnd + 1, fileUri.path.length);
|
||||
|
||||
if (extension.toLowerCase() === 'json') {
|
||||
this.model.fileType = 'JSON';
|
||||
}
|
||||
|
||||
this.tableNameTextBox.value = fileUri.path.substring(nameStart + 1, nameEnd);
|
||||
this.model.table = this.tableNameTextBox.value;
|
||||
this.tableNameTextBox.validate();
|
||||
|
||||
// Let then model know about the file path
|
||||
this.model.filePath = fileUri.fsPath;
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.fileTextBox,
|
||||
title: localize('flatFileImport.fileTextboxTitle', 'Location of the file to be imported'),
|
||||
actions: [this.fileButton]
|
||||
};
|
||||
}
|
||||
|
||||
private async createTableNameBox(): Promise<sqlops.FormComponent> {
|
||||
this.tableNameTextBox = this.view.modelBuilder.inputBox().withValidation((name) => {
|
||||
let tableName = name.value;
|
||||
|
||||
if (!tableName || tableName.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This won't actually do anything until table names are brought back in.
|
||||
if (this.tableNames.indexOf(tableName) !== -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).withProperties({
|
||||
required: true,
|
||||
}).component();
|
||||
|
||||
this.tableNameTextBox.onTextChanged((tableName) => {
|
||||
this.model.table = tableName;
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.tableNameTextBox,
|
||||
title: localize('flatFileImport.tableTextboxTitle', 'New table name'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private async createSchemaDropdown(): Promise<sqlops.FormComponent> {
|
||||
this.schemaDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
this.schemaLoader = this.view.modelBuilder.loadingComponent().withItem(this.schemaDropdown).component();
|
||||
|
||||
this.schemaDropdown.onValueChanged(() => {
|
||||
this.model.schema = (<sqlops.CategoryValue>this.schemaDropdown.value).name;
|
||||
});
|
||||
|
||||
|
||||
return {
|
||||
component: this.schemaLoader,
|
||||
title: localize('flatFileImport.schemaTextboxTitle', 'Table schema'),
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private async populateSchemaDropdown(): Promise<boolean> {
|
||||
this.schemaLoader.loading = true;
|
||||
let connectionUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||
let queryProvider = sqlops.dataprotocol.getProvider<sqlops.QueryProvider>(this.model.server.providerName, sqlops.DataProviderType.QueryProvider);
|
||||
|
||||
let query = `SELECT name FROM sys.schemas`;
|
||||
|
||||
let results = await queryProvider.runQueryAndReturn(connectionUri, query);
|
||||
|
||||
let idx = -1;
|
||||
let count = -1;
|
||||
|
||||
let values = results.rows.map(row => {
|
||||
let schemaName = row[0].displayValue;
|
||||
count++;
|
||||
if (this.model.schema && schemaName === this.model.schema) {
|
||||
idx = count;
|
||||
}
|
||||
let val = row[0].displayValue;
|
||||
|
||||
return {
|
||||
name: val,
|
||||
displayName: val
|
||||
};
|
||||
});
|
||||
|
||||
if (idx >= 0) {
|
||||
let tmp = values[0];
|
||||
values[0] = values[idx];
|
||||
values[idx] = tmp;
|
||||
}
|
||||
|
||||
this.model.schema = values[0].name;
|
||||
|
||||
this.schemaDropdown.updateProperties({
|
||||
values: values
|
||||
});
|
||||
|
||||
this.schemaLoader.loading = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// private async populateTableNames(): Promise<boolean> {
|
||||
// this.tableNames = [];
|
||||
// let databaseName = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
|
||||
//
|
||||
// if (!databaseName || databaseName.length === 0) {
|
||||
// this.tableNames = [];
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// let connectionUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||
// let queryProvider = sqlops.dataprotocol.getProvider<sqlops.QueryProvider>(this.model.server.providerName, sqlops.DataProviderType.QueryProvider);
|
||||
// let results: sqlops.SimpleExecuteResult;
|
||||
//
|
||||
// try {
|
||||
// //let query = sqlstring.format('USE ?; SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = \'BASE TABLE\'', [databaseName]);
|
||||
// //results = await queryProvider.runQueryAndReturn(connectionUri, query);
|
||||
// } catch (e) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// this.tableNames = results.rows.map(row => {
|
||||
// return row[0].displayValue;
|
||||
// });
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
interface ConnectionDropdownValue extends sqlops.CategoryValue {
|
||||
connection: sqlops.connection.Connection;
|
||||
}
|
||||
171
extensions/import/src/wizard/pages/modifyColumnsPage.ts
Normal file
171
extensions/import/src/wizard/pages/modifyColumnsPage.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { ColumnMetadata, ImportDataModel } from '../api/models';
|
||||
import { ImportPage } from '../api/importPage';
|
||||
import { FlatFileProvider } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class ModifyColumnsPage extends ImportPage {
|
||||
private readonly categoryValues = [
|
||||
{ name: 'bigint', displayName: 'bigint' },
|
||||
{ name: 'binary(50)', displayName: 'binary(50)' },
|
||||
{ name: 'bit', displayName: 'bit' },
|
||||
{ name: 'char(10)', displayName: 'char(10)' },
|
||||
{ name: 'date', displayName: 'date' },
|
||||
{ name: 'datetime', displayName: 'datetime' },
|
||||
{ name: 'datetime2(7)', displayName: 'datetime2(7)' },
|
||||
{ name: 'datetimeoffset(7)', displayName: 'datetimeoffset(7)' },
|
||||
{ name: 'decimal(18, 10)', displayName: 'decimal(18, 10)' },
|
||||
{ name: 'float', displayName: 'float' },
|
||||
{ name: 'geography', displayName: 'geography' },
|
||||
{ name: 'geometry', displayName: 'geometry' },
|
||||
{ name: 'hierarchyid', displayName: 'hierarchyid' },
|
||||
{ name: 'int', displayName: 'int' },
|
||||
{ name: 'money', displayName: 'money' },
|
||||
{ name: 'nchar(10)', displayName: 'nchar(10)' },
|
||||
{ name: 'ntext', displayName: 'ntext' },
|
||||
{ name: 'numeric(18, 0)', displayName: 'numeric(18, 0)' },
|
||||
{ name: 'nvarchar(50)', displayName: 'nvarchar(50)' },
|
||||
{ name: 'nvarchar(MAX)', displayName: 'nvarchar(MAX)' },
|
||||
{ name: 'real', displayName: 'real' },
|
||||
{ name: 'smalldatetime', displayName: 'smalldatetime' },
|
||||
{ name: 'smallint', displayName: 'smallint' },
|
||||
{ name: 'smallmoney', displayName: 'smallmoney' },
|
||||
{ name: 'sql_variant', displayName: 'sql_variant' },
|
||||
{ name: 'text', displayName: 'text' },
|
||||
{ name: 'time(7)', displayName: 'time(7)' },
|
||||
{ name: 'timestamp', displayName: 'timestamp' },
|
||||
{ name: 'tinyint', displayName: 'tinyint' },
|
||||
{ name: 'uniqueidentifier', displayName: 'uniqueidentifier' },
|
||||
{ name: 'varbinary(50)', displayName: 'varbinary(50)' },
|
||||
{ name: 'varbinary(MAX)', displayName: 'varbinary(MAX)' },
|
||||
{ name: 'varchar(50)', displayName: 'varchar(50)' },
|
||||
{ name: 'varchar(MAX)', displayName: 'varchar(MAX)' }
|
||||
];
|
||||
private table: sqlops.DeclarativeTableComponent;
|
||||
private loading: sqlops.LoadingComponent;
|
||||
private text: sqlops.TextComponent;
|
||||
private form: sqlops.FormContainer;
|
||||
|
||||
public constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||
super(instance, wizardPage, model, view, provider);
|
||||
}
|
||||
|
||||
|
||||
private static convertMetadata(column: ColumnMetadata): any[] {
|
||||
return [column.columnName, column.dataType, false, column.nullable];
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
this.loading = this.view.modelBuilder.loadingComponent().component();
|
||||
this.table = this.view.modelBuilder.declarativeTable().component();
|
||||
this.text = this.view.modelBuilder.text().component();
|
||||
|
||||
this.table.onDataChanged((e) => {
|
||||
this.model.proseColumns = [];
|
||||
this.table.data.forEach((row) => {
|
||||
this.model.proseColumns.push({
|
||||
columnName: row[0],
|
||||
dataType: row[1],
|
||||
primaryKey: row[2],
|
||||
nullable: row[3]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
this.form = this.view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[
|
||||
{
|
||||
component: this.text,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: this.table,
|
||||
title: ''
|
||||
}
|
||||
], {
|
||||
horizontal: false,
|
||||
componentWidth: '100%'
|
||||
}).component();
|
||||
|
||||
this.loading.component = this.form;
|
||||
await this.view.initializeModel(this.form);
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
this.loading.loading = true;
|
||||
await this.populateTable();
|
||||
this.instance.changeNextButtonLabel(localize('flatFileImport.importData', 'Import Data'));
|
||||
this.loading.loading = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageLeave(): Promise<boolean> {
|
||||
this.instance.changeNextButtonLabel(localize('flatFileImport.next', 'Next'));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async cleanup(): Promise<boolean> {
|
||||
delete this.model.proseColumns;
|
||||
this.instance.changeNextButtonLabel(localize('flatFileImport.next', 'Next'));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public setupNavigationValidator() {
|
||||
this.instance.registerNavigationValidator((info) => {
|
||||
return !this.loading.loading;
|
||||
});
|
||||
}
|
||||
|
||||
private async populateTable() {
|
||||
let data: any[][] = [];
|
||||
|
||||
this.model.proseColumns.forEach((column) => {
|
||||
data.push(ModifyColumnsPage.convertMetadata(column));
|
||||
});
|
||||
|
||||
this.table.updateProperties({
|
||||
height: 400,
|
||||
columns: [{
|
||||
displayName: localize('flatFileImport.columnName', 'Column Name'),
|
||||
valueType: sqlops.DeclarativeDataType.string,
|
||||
width: '150px',
|
||||
isReadOnly: false
|
||||
}, {
|
||||
displayName: localize('flatFileImport.dataType', 'Data Type'),
|
||||
valueType: sqlops.DeclarativeDataType.editableCategory,
|
||||
width: '150px',
|
||||
isReadOnly: false,
|
||||
categoryValues: this.categoryValues
|
||||
}, {
|
||||
displayName: localize('flatFileImport.primaryKey', 'Primary Key'),
|
||||
valueType: sqlops.DeclarativeDataType.boolean,
|
||||
width: '100px',
|
||||
isReadOnly: false
|
||||
}, {
|
||||
displayName: localize('flatFileImport.allowNulls', 'Allow Nulls'),
|
||||
valueType: sqlops.DeclarativeDataType.boolean,
|
||||
isReadOnly: false,
|
||||
width: '100px'
|
||||
}],
|
||||
data: data
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
123
extensions/import/src/wizard/pages/prosePreviewPage.ts
Normal file
123
extensions/import/src/wizard/pages/prosePreviewPage.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { ImportDataModel } from '../api/models';
|
||||
import { ImportPage } from '../api/importPage';
|
||||
import { FlatFileProvider } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class ProsePreviewPage extends ImportPage {
|
||||
private table: sqlops.TableComponent;
|
||||
private loading: sqlops.LoadingComponent;
|
||||
private form: sqlops.FormContainer;
|
||||
private refresh: sqlops.ButtonComponent;
|
||||
|
||||
public constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||
super(instance, wizardPage, model, view, provider);
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
this.table = this.view.modelBuilder.table().component();
|
||||
this.refresh = this.view.modelBuilder.button().withProperties({
|
||||
label: localize('flatFileImport.refresh', 'Refresh'),
|
||||
isFile: false
|
||||
}).component();
|
||||
|
||||
this.refresh.onDidClick(async () => {
|
||||
this.onPageEnter();
|
||||
});
|
||||
|
||||
this.loading = this.view.modelBuilder.loadingComponent().component();
|
||||
|
||||
this.form = this.view.modelBuilder.formContainer().withFormItems([
|
||||
{
|
||||
component: this.table,
|
||||
title: localize('flatFileImport.prosePreviewMessage', 'This operation analyzed the input file structure to generate the preview below for up to the first 50 rows.'),
|
||||
actions: [this.refresh]
|
||||
}
|
||||
]).component();
|
||||
|
||||
this.loading.component = this.form;
|
||||
|
||||
await this.view.initializeModel(this.loading);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
this.loading.loading = true;
|
||||
await this.handleProse();
|
||||
await this.populateTable(this.model.proseDataPreview, this.model.proseColumns.map(c => c.columnName));
|
||||
this.loading.loading = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageLeave(): Promise<boolean> {
|
||||
await this.emptyTable();
|
||||
return true;
|
||||
}
|
||||
|
||||
async cleanup(): Promise<boolean> {
|
||||
delete this.model.proseDataPreview;
|
||||
return true;
|
||||
}
|
||||
|
||||
public setupNavigationValidator() {
|
||||
this.instance.registerNavigationValidator((info) => {
|
||||
return !this.loading.loading;
|
||||
});
|
||||
}
|
||||
|
||||
private async handleProse() {
|
||||
await this.provider.sendPROSEDiscoveryRequest({
|
||||
filePath: this.model.filePath,
|
||||
tableName: this.model.table,
|
||||
schemaName: this.model.schema,
|
||||
fileType: this.model.fileType
|
||||
}).then((result) => {
|
||||
this.model.proseDataPreview = result.dataPreview;
|
||||
this.model.proseColumns = [];
|
||||
result.columnInfo.forEach((column) => {
|
||||
this.model.proseColumns.push({
|
||||
columnName: column.name,
|
||||
dataType: column.sqlType,
|
||||
primaryKey: false,
|
||||
nullable: column.isNullable
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async populateTable(tableData: string[][], columnHeaders: string[]) {
|
||||
let rows;
|
||||
let rowsLength = tableData.length;
|
||||
|
||||
if (rowsLength > 50) {
|
||||
rows = tableData;
|
||||
}
|
||||
else {
|
||||
rows = tableData.slice(0, rowsLength);
|
||||
}
|
||||
|
||||
this.table.updateProperties({
|
||||
data: rows,
|
||||
columns: columnHeaders,
|
||||
height: 400,
|
||||
width: '700',
|
||||
});
|
||||
}
|
||||
|
||||
private async emptyTable() {
|
||||
this.table.updateProperties([]);
|
||||
}
|
||||
|
||||
}
|
||||
173
extensions/import/src/wizard/pages/summaryPage.ts
Normal file
173
extensions/import/src/wizard/pages/summaryPage.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
import { ImportDataModel } from '../api/models';
|
||||
import { ImportPage } from '../api/importPage';
|
||||
import { FlatFileProvider, InsertDataResponse } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
export class SummaryPage extends ImportPage {
|
||||
private table: sqlops.TableComponent;
|
||||
private statusText: sqlops.TextComponent;
|
||||
private loading: sqlops.LoadingComponent;
|
||||
private form: sqlops.FormContainer;
|
||||
|
||||
public constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||
super(instance, wizardPage, model, view, provider);
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
this.table = this.view.modelBuilder.table().component();
|
||||
this.statusText = this.view.modelBuilder.text().component();
|
||||
this.loading = this.view.modelBuilder.loadingComponent().withItem(this.statusText).component();
|
||||
|
||||
this.form = this.view.modelBuilder.formContainer().withFormItems(
|
||||
[
|
||||
{
|
||||
component: this.table,
|
||||
title: localize('flatFileImport.importInformation', 'Import information')
|
||||
},
|
||||
{
|
||||
component: this.loading,
|
||||
title: localize('flatFileImport.importStatus', 'Import status')
|
||||
}
|
||||
]
|
||||
).component();
|
||||
|
||||
await this.view.initializeModel(this.form);
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
this.loading.loading = true;
|
||||
this.populateTable();
|
||||
await this.handleImport();
|
||||
this.loading.loading = false;
|
||||
this.instance.setImportAnotherFileVisibility(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageLeave(): Promise<boolean> {
|
||||
this.instance.setImportAnotherFileVisibility(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
public setupNavigationValidator() {
|
||||
this.instance.registerNavigationValidator((info) => {
|
||||
return !this.loading.loading;
|
||||
});
|
||||
}
|
||||
|
||||
private populateTable() {
|
||||
this.table.updateProperties({
|
||||
data: [
|
||||
[localize('flatFileImport.serverName', 'Server name'), this.model.server.providerName],
|
||||
[localize('flatFileImport.databaseName', 'Database name'), this.model.database],
|
||||
[localize('flatFileImport.tableName', 'Table name'), this.model.table],
|
||||
[localize('flatFileImport.tableSchema', 'Table schema'), this.model.schema],
|
||||
[localize('flatFileImport.fileImport', 'File to be imported'), this.model.filePath]],
|
||||
columns: ['Object type', 'Name'],
|
||||
width: 600,
|
||||
height: 200
|
||||
});
|
||||
}
|
||||
|
||||
private async handleImport(): Promise<boolean> {
|
||||
let changeColumnResults = [];
|
||||
this.model.proseColumns.forEach((val, i, arr) => {
|
||||
let columnChangeParams = {
|
||||
index: i,
|
||||
newName: val.columnName,
|
||||
newDataType: val.dataType,
|
||||
newNullable: val.nullable,
|
||||
newInPrimaryKey: val.primaryKey
|
||||
};
|
||||
changeColumnResults.push(this.provider.sendChangeColumnSettingsRequest(columnChangeParams));
|
||||
});
|
||||
|
||||
let result: InsertDataResponse;
|
||||
let err;
|
||||
try {
|
||||
result = await this.provider.sendInsertDataRequest({
|
||||
connectionString: await this.getConnectionString(),
|
||||
//TODO check what SSMS uses as batch size
|
||||
batchSize: 500
|
||||
});
|
||||
} catch (e) {
|
||||
err = e.toString();
|
||||
}
|
||||
|
||||
let updateText: string;
|
||||
if (!result || !result.result.success) {
|
||||
updateText = '✗ ';
|
||||
if (!result) {
|
||||
updateText += err;
|
||||
} else {
|
||||
updateText += result.result.errorMessage;
|
||||
}
|
||||
} else {
|
||||
// TODO: When sql statements are in, implement this.
|
||||
//let rows = await this.getCountRowsInserted();
|
||||
//if (rows < 0) {
|
||||
updateText = localize('flatFileImport.success.norows', '✔ You have successfully inserted the data into a table.');
|
||||
//} else {
|
||||
//updateText = localize('flatFileImport.success.rows', '✔ You have successfully inserted {0} rows.', rows);
|
||||
//}
|
||||
}
|
||||
this.statusText.updateProperties({
|
||||
value: updateText
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the connection string to send to the middleware
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
private async getConnectionString(): Promise<string> {
|
||||
let options = this.model.server.options;
|
||||
let connectionString: string;
|
||||
|
||||
if (options.authenticationType === 'Integrated') {
|
||||
connectionString = `Data Source=${options.server + (options.port ? `,${options.port}` : '')};Initial Catalog=${this.model.database};Integrated Security=True`;
|
||||
} else {
|
||||
let credentials = await sqlops.connection.getCredentials(this.model.server.connectionId);
|
||||
connectionString = `Data Source=${options.server + (options.port ? `,${options.port}` : '')};Initial Catalog=${this.model.database};Integrated Security=False;User Id=${options.user};Password=${credentials.password}`;
|
||||
}
|
||||
|
||||
// TODO: Fix this, it's returning undefined string.
|
||||
//await sqlops.connection.getConnectionString(this.model.server.connectionId, true);
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
// private async getCountRowsInserted(): Promise<Number> {
|
||||
// let connectionUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||
// let queryProvider = sqlops.dataprotocol.getProvider<sqlops.QueryProvider>(this.model.server.providerName, sqlops.DataProviderType.QueryProvider);
|
||||
// try {
|
||||
// let query = sqlstring.format('USE ?; SELECT COUNT(*) FROM ?', [this.model.database, this.model.table]);
|
||||
// let results = await queryProvider.runQueryAndReturn(connectionUri, query);
|
||||
// let cell = results.rows[0][0];
|
||||
// if (!cell || cell.isNull) {
|
||||
// return -1;
|
||||
// }
|
||||
// let numericCell = Number(cell.displayValue);
|
||||
// if (isNaN(numericCell)) {
|
||||
// return -1;
|
||||
// }
|
||||
// return numericCell;
|
||||
// } catch (e) {
|
||||
// return -1;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
19
extensions/import/tsconfig.json
Normal file
19
extensions/import/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "./out",
|
||||
"lib": [
|
||||
"es6", "es2015.promise"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"declaration": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
409
extensions/import/yarn.lock
Normal file
409
extensions/import/yarn.lock
Normal file
@@ -0,0 +1,409 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
agent-base@4, agent-base@^4.1.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
|
||||
dependencies:
|
||||
es6-promisify "^5.0.0"
|
||||
|
||||
applicationinsights@0.15.6:
|
||||
version "0.15.6"
|
||||
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-0.15.6.tgz#201a0682c0704fe4bdd9a92d0b2cbe34d2ae5972"
|
||||
|
||||
base64-js@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
|
||||
|
||||
bl@^1.0.0:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
|
||||
dependencies:
|
||||
readable-stream "^2.3.5"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
buffer-alloc-unsafe@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
|
||||
|
||||
buffer-alloc@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
||||
dependencies:
|
||||
buffer-alloc-unsafe "^1.1.0"
|
||||
buffer-fill "^1.0.0"
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
|
||||
buffer-fill@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
||||
|
||||
buffer@^3.0.1:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
|
||||
dependencies:
|
||||
base64-js "0.0.8"
|
||||
ieee754 "^1.1.4"
|
||||
isarray "^1.0.0"
|
||||
|
||||
commander@~2.8.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
|
||||
dependencies:
|
||||
graceful-readlink ">= 1.0.0"
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
|
||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.7":
|
||||
version "0.2.6"
|
||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/85653d8b305af8aef334728d71f07bdc240dfcb7"
|
||||
dependencies:
|
||||
vscode-languageclient "3.5.1"
|
||||
|
||||
debug@3.1.0, debug@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
|
||||
dependencies:
|
||||
file-type "^5.2.0"
|
||||
is-stream "^1.1.0"
|
||||
tar-stream "^1.5.2"
|
||||
|
||||
decompress-tarbz2@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
|
||||
dependencies:
|
||||
decompress-tar "^4.1.0"
|
||||
file-type "^6.1.0"
|
||||
is-stream "^1.1.0"
|
||||
seek-bzip "^1.0.5"
|
||||
unbzip2-stream "^1.0.9"
|
||||
|
||||
decompress-targz@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee"
|
||||
dependencies:
|
||||
decompress-tar "^4.1.1"
|
||||
file-type "^5.2.0"
|
||||
is-stream "^1.1.0"
|
||||
|
||||
decompress-unzip@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
|
||||
dependencies:
|
||||
file-type "^3.8.0"
|
||||
get-stream "^2.2.0"
|
||||
pify "^2.3.0"
|
||||
yauzl "^2.4.2"
|
||||
|
||||
decompress@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d"
|
||||
dependencies:
|
||||
decompress-tar "^4.0.0"
|
||||
decompress-tarbz2 "^4.0.0"
|
||||
decompress-targz "^4.0.0"
|
||||
decompress-unzip "^4.0.1"
|
||||
graceful-fs "^4.1.10"
|
||||
make-dir "^1.0.0"
|
||||
pify "^2.3.0"
|
||||
strip-dirs "^2.0.0"
|
||||
|
||||
end-of-stream@^1.0.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
|
||||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
es6-promise@^4.0.3:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29"
|
||||
|
||||
es6-promisify@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
|
||||
dependencies:
|
||||
es6-promise "^4.0.3"
|
||||
|
||||
eventemitter2@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
|
||||
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
file-type@^3.8.0:
|
||||
version "3.9.0"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
|
||||
|
||||
file-type@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
|
||||
|
||||
file-type@^6.1.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
|
||||
|
||||
fs-constants@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||
|
||||
get-stream@^2.2.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
|
||||
dependencies:
|
||||
object-assign "^4.0.1"
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
graceful-fs@^4.1.10:
|
||||
version "4.1.11"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
|
||||
|
||||
"graceful-readlink@>= 1.0.0":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
||||
|
||||
http-proxy-agent@^2.0.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:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
||||
dependencies:
|
||||
agent-base "^4.1.0"
|
||||
debug "^3.1.0"
|
||||
|
||||
ieee754@^1.1.4:
|
||||
version "1.1.12"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
|
||||
|
||||
inherits@~2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
|
||||
is-natural-number@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
|
||||
|
||||
is-stream@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||
|
||||
isarray@^1.0.0, isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
|
||||
make-dir@^1.0.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
||||
dependencies:
|
||||
pify "^3.0.0"
|
||||
|
||||
minimist@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
|
||||
mkdirp@^0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
|
||||
object-assign@^4.0.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
|
||||
once@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
opener@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
|
||||
|
||||
os-tmpdir@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
|
||||
pify@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||
|
||||
pify@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
|
||||
|
||||
pinkie-promise@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
||||
dependencies:
|
||||
pinkie "^2.0.0"
|
||||
|
||||
pinkie@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
||||
|
||||
readable-stream@^2.3.0, readable-stream@^2.3.5:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.3"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~2.0.0"
|
||||
safe-buffer "~5.1.1"
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
|
||||
seek-bzip@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
|
||||
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"
|
||||
dependencies:
|
||||
decompress "^4.2.0"
|
||||
eventemitter2 "^5.0.1"
|
||||
http-proxy-agent "^2.0.0"
|
||||
https-proxy-agent "^2.1.1"
|
||||
mkdirp "^0.5.1"
|
||||
tmp "^0.0.33"
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
strip-dirs@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
|
||||
dependencies:
|
||||
is-natural-number "^4.0.1"
|
||||
|
||||
tar-stream@^1.5.2:
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395"
|
||||
dependencies:
|
||||
bl "^1.0.0"
|
||||
buffer-alloc "^1.1.0"
|
||||
end-of-stream "^1.0.0"
|
||||
fs-constants "^1.0.0"
|
||||
readable-stream "^2.3.0"
|
||||
to-buffer "^1.1.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
through@^2.3.6:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
|
||||
tmp@^0.0.33:
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||
dependencies:
|
||||
os-tmpdir "~1.0.2"
|
||||
|
||||
to-buffer@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
|
||||
|
||||
unbzip2-stream@^1.0.9:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz#73a033a567bbbde59654b193c44d48a7e4f43c47"
|
||||
dependencies:
|
||||
buffer "^3.0.1"
|
||||
through "^2.3.6"
|
||||
|
||||
util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
|
||||
vscode-extension-telemetry@^0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.5.tgz#21e2abb4cbce3326e469ddbb322123b3702f3f85"
|
||||
dependencies:
|
||||
applicationinsights "0.15.6"
|
||||
winreg "0.0.13"
|
||||
|
||||
vscode-jsonrpc@3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa"
|
||||
|
||||
vscode-languageclient@3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98"
|
||||
dependencies:
|
||||
vscode-languageserver-protocol "3.5.1"
|
||||
|
||||
vscode-languageserver-protocol@3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
|
||||
dependencies:
|
||||
vscode-jsonrpc "3.5.0"
|
||||
vscode-languageserver-types "3.5.0"
|
||||
|
||||
vscode-languageserver-types@3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
|
||||
|
||||
vscode-nls@^3.2.1:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398"
|
||||
|
||||
winreg@0.0.13:
|
||||
version "0.0.13"
|
||||
resolved "https://registry.yarnpkg.com/winreg/-/winreg-0.0.13.tgz#76bfe02e1dd0c9c8275fb9fdf17a9f36846e3483"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
|
||||
xtend@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
|
||||
yauzl@^2.4.2:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
4
extensions/insights-default/yarn.lock
Normal file
4
extensions/insights-default/yarn.lock
Normal file
@@ -0,0 +1,4 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
4
extensions/markdown-basics/yarn.lock
Normal file
4
extensions/markdown-basics/yarn.lock
Normal file
@@ -0,0 +1,4 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
@@ -17,6 +17,14 @@
|
||||
"compile": "gulp compile-extension:mssql-client",
|
||||
"update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-mssql syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.7",
|
||||
"opener": "^1.4.3",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.4",
|
||||
"vscode-extension-telemetry": "^0.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
},
|
||||
"contributes": {
|
||||
"languages": [
|
||||
{
|
||||
@@ -656,19 +664,5 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.1.9",
|
||||
"opener": "^1.4.3",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.2",
|
||||
"vscode-extension-telemetry": "^0.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
},
|
||||
"resolutions": {
|
||||
"vscode-jsonrpc": "3.5.0",
|
||||
"vscode-languageclient": "3.5.0",
|
||||
"vscode-languageserver-protocol": "3.5.0",
|
||||
"vscode-languageserver-types": "3.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "1.5.0-alpha.9",
|
||||
"version": "1.5.0-alpha.28",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-netcoreapp2.1.zip",
|
||||
"Windows_64": "win-x64-netcoreapp2.1.zip",
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
|
||||
agent-base@4, agent-base@^4.1.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce"
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
|
||||
dependencies:
|
||||
es6-promisify "^5.0.0"
|
||||
|
||||
@@ -27,10 +27,25 @@ bl@^1.0.0:
|
||||
readable-stream "^2.3.5"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
buffer-alloc-unsafe@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
|
||||
|
||||
buffer-alloc@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
||||
dependencies:
|
||||
buffer-alloc-unsafe "^1.1.0"
|
||||
buffer-fill "^1.0.0"
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
|
||||
buffer-fill@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
||||
|
||||
buffer@^3.0.1:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
|
||||
@@ -49,11 +64,11 @@ core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
|
||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.1.9":
|
||||
version "0.1.9"
|
||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/a1a79895cb79658b75d78aa5cfd745855019148d"
|
||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.7":
|
||||
version "0.2.6"
|
||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/85653d8b305af8aef334728d71f07bdc240dfcb7"
|
||||
dependencies:
|
||||
vscode-languageclient "3.5.0"
|
||||
vscode-languageclient "3.5.1"
|
||||
|
||||
debug@3.1.0, debug@^3.1.0:
|
||||
version "3.1.0"
|
||||
@@ -139,9 +154,9 @@ eventemitter2@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
|
||||
|
||||
fd-slicer@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
@@ -157,6 +172,10 @@ file-type@^6.1.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
|
||||
|
||||
fs-constants@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||
|
||||
get-stream@^2.2.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
|
||||
@@ -180,15 +199,15 @@ http-proxy-agent@^2.0.0:
|
||||
debug "3.1.0"
|
||||
|
||||
https-proxy-agent@^2.1.1:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.0.tgz#7fbba856be8cd677986f42ebd3664f6317257887"
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
||||
dependencies:
|
||||
agent-base "^4.1.0"
|
||||
debug "^3.1.0"
|
||||
|
||||
ieee754@^1.1.4:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455"
|
||||
version "1.1.12"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
|
||||
|
||||
inherits@~2.0.3:
|
||||
version "2.0.3"
|
||||
@@ -207,8 +226,8 @@ isarray@^1.0.0, isarray@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
|
||||
make-dir@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.2.0.tgz#6d6a49eead4aae296c53bbf3a1a008bd6c89469b"
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
||||
dependencies:
|
||||
pify "^3.0.0"
|
||||
|
||||
@@ -270,21 +289,21 @@ process-nextick-args@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
||||
|
||||
readable-stream@^2.0.0, readable-stream@^2.3.5:
|
||||
version "2.3.5"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d"
|
||||
readable-stream@^2.3.0, readable-stream@^2.3.5:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.3"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~2.0.0"
|
||||
safe-buffer "~5.1.1"
|
||||
string_decoder "~1.0.3"
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
|
||||
seek-bzip@^1.0.5:
|
||||
version "1.0.5"
|
||||
@@ -296,9 +315,9 @@ 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.2":
|
||||
version "0.1.2"
|
||||
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/2aa9b336b6442e17e24693ddc907030575539798"
|
||||
"service-downloader@github:anthonydresser/service-downloader#0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/3c0abdf8603aca85d2eacfac3c547173e41bf0c7"
|
||||
dependencies:
|
||||
decompress "^4.2.0"
|
||||
eventemitter2 "^5.0.1"
|
||||
@@ -307,9 +326,9 @@ semver@^5.3.0:
|
||||
mkdirp "^0.5.1"
|
||||
tmp "^0.0.33"
|
||||
|
||||
string_decoder@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
@@ -320,12 +339,15 @@ strip-dirs@^2.0.0:
|
||||
is-natural-number "^4.0.1"
|
||||
|
||||
tar-stream@^1.5.2:
|
||||
version "1.5.5"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.5.tgz#5cad84779f45c83b1f2508d96b09d88c7218af55"
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395"
|
||||
dependencies:
|
||||
bl "^1.0.0"
|
||||
buffer-alloc "^1.1.0"
|
||||
end-of-stream "^1.0.0"
|
||||
readable-stream "^2.0.0"
|
||||
fs-constants "^1.0.0"
|
||||
readable-stream "^2.3.0"
|
||||
to-buffer "^1.1.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
through@^2.3.6:
|
||||
@@ -338,6 +360,10 @@ tmp@^0.0.33:
|
||||
dependencies:
|
||||
os-tmpdir "~1.0.2"
|
||||
|
||||
to-buffer@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
|
||||
|
||||
unbzip2-stream@^1.0.9:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz#73a033a567bbbde59654b193c44d48a7e4f43c47"
|
||||
@@ -355,24 +381,24 @@ vscode-extension-telemetry@^0.0.15:
|
||||
dependencies:
|
||||
applicationinsights "1.0.1"
|
||||
|
||||
vscode-jsonrpc@3.5.0, vscode-jsonrpc@^3.5.0:
|
||||
vscode-jsonrpc@3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa"
|
||||
|
||||
vscode-languageclient@3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.0.tgz#36d02cc186a8365a4467719a290fb200a9ae490a"
|
||||
vscode-languageclient@3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98"
|
||||
dependencies:
|
||||
vscode-languageserver-protocol "^3.5.0"
|
||||
vscode-languageserver-protocol "3.5.1"
|
||||
|
||||
vscode-languageserver-protocol@3.5.0, vscode-languageserver-protocol@^3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.0.tgz#067c5cbe27709795398d119692c97ebba1452209"
|
||||
vscode-languageserver-protocol@3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
|
||||
dependencies:
|
||||
vscode-jsonrpc "^3.5.0"
|
||||
vscode-languageserver-types "^3.5.0"
|
||||
vscode-jsonrpc "3.5.0"
|
||||
vscode-languageserver-types "3.5.0"
|
||||
|
||||
vscode-languageserver-types@3.5.0, vscode-languageserver-types@^3.5.0:
|
||||
vscode-languageserver-types@3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
|
||||
|
||||
@@ -385,11 +411,11 @@ xtend@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
|
||||
yauzl@^2.4.2:
|
||||
version "2.9.1"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.9.1.tgz#a81981ea70a57946133883f029c5821a89359a7f"
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.0.1"
|
||||
fd-slicer "~1.1.0"
|
||||
|
||||
zone.js@0.7.6:
|
||||
version "0.7.6"
|
||||
|
||||
@@ -10,6 +10,17 @@ Common SQL Profiler use-cases taken from https://docs.microsoft.com/en-us/sql/to
|
||||
- Monitoring the performance of SQL Server to tune workloads.
|
||||
- Correlating performance counters to diagnose problems.
|
||||
|
||||
## Getting Started:
|
||||
To launch SQL Server Profiler, you have to first make a connection to a server.
|
||||
|
||||
Open Profiler by pressing **Alt+P** on Windows, and **Ctrl+Alt+P** on macOS.
|
||||
|
||||
To Start/Stop Profiler, click the Start button or press **Alt+S** on Windows, or **Ctrl+Alt+S** on macOS.
|
||||
|
||||
Otherwise, open the command palette and type 'Profiler.'
|
||||
|
||||
For more info, [check out our documentation.](https://docs.microsoft.com/en-us/sql/sql-operations-studio/sql-server-profiler-extension?view=sql-server-2017)
|
||||
|
||||
## SQL Server Profiler 0.1.1 Release
|
||||
The SQL Server Profiler for SQL Operations Studio *Preview* extension is now available. This is the initial preview release for a new lightweight XEvent-based profiler. The SQL Server Profiler extension tries to make it simple to quickly trace server activity for troubleshooting and monitoring.
|
||||
|
||||
|
||||
26
extensions/profiler/client/src/data/createSessionData.ts
Normal file
26
extensions/profiler/client/src/data/createSessionData.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
export class CreateSessionData {
|
||||
public ownerUri: string;
|
||||
public sessionName: string;
|
||||
public templates: Array<sqlops.ProfilerSessionTemplate> = new Array<sqlops.ProfilerSessionTemplate>();
|
||||
|
||||
constructor(ownerUri: string, templates: Array<sqlops.ProfilerSessionTemplate>) {
|
||||
this.ownerUri = ownerUri;
|
||||
this.templates = templates;
|
||||
}
|
||||
|
||||
public getTemplateNames(): string[] {
|
||||
return this.templates.map(e => e.name);
|
||||
}
|
||||
|
||||
public selectTemplate(name: string): sqlops.ProfilerSessionTemplate {
|
||||
return this.templates.find((t) => { return t.name === name; });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { CreateSessionData } from '../data/createSessionData';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class CreateSessionDialog {
|
||||
|
||||
// Top level
|
||||
private readonly DialogTitle: string = localize('createSessionDialog.newSession', 'New Session');
|
||||
private readonly CancelButtonText: string = localize('createSessionDialog.cancel', 'Cancel');
|
||||
private readonly CreateButtonText: string = localize('createSessionDialog.create', 'Create');
|
||||
private readonly DialogTitleText: string = localize('createSessionDialog.title', 'Create New Profiler Session');
|
||||
|
||||
// UI Components
|
||||
private dialog: sqlops.window.modelviewdialog.Dialog;
|
||||
private templatesBox: sqlops.DropDownComponent;
|
||||
private sessionNameBox: sqlops.InputBoxComponent;
|
||||
|
||||
private model: CreateSessionData;
|
||||
|
||||
private _onSuccess: vscode.EventEmitter<CreateSessionData> = new vscode.EventEmitter<CreateSessionData>();
|
||||
public readonly onSuccess: vscode.Event<CreateSessionData> = this._onSuccess.event;
|
||||
|
||||
|
||||
constructor(ownerUri: string, templates: Array<sqlops.ProfilerSessionTemplate>) {
|
||||
if (typeof (templates) === 'undefined' || templates === null) {
|
||||
throw new Error(localize('createSessionDialog.templatesInvalid', "Invalid templates list, cannot open dialog"));
|
||||
}
|
||||
if (typeof (ownerUri) === 'undefined' || ownerUri === null) {
|
||||
throw new Error(localize('createSessionDialog.dialogOwnerInvalid', "Invalid dialog owner, cannot open dialog"));
|
||||
}
|
||||
this.model = new CreateSessionData(ownerUri, templates);
|
||||
}
|
||||
|
||||
public async showDialog(): Promise<void> {
|
||||
this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
|
||||
this.initializeContent();
|
||||
this.dialog.okButton.onClick(() => this.execute());
|
||||
this.dialog.cancelButton.onClick(() => { });
|
||||
this.dialog.okButton.label = this.CreateButtonText;
|
||||
this.dialog.cancelButton.label = this.CancelButtonText;
|
||||
|
||||
sqlops.window.modelviewdialog.openDialog(this.dialog);
|
||||
}
|
||||
|
||||
private initializeContent(): void {
|
||||
this.dialog.registerContent(async view => {
|
||||
this.templatesBox = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
values: []
|
||||
}).component();
|
||||
|
||||
this.sessionNameBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
required: true,
|
||||
multiline: false,
|
||||
value: ''
|
||||
}).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
components: [{
|
||||
component: this.templatesBox,
|
||||
title: localize('createSessionDialog.selectTemplates', "Select session template:")
|
||||
},
|
||||
{
|
||||
component: this.sessionNameBox,
|
||||
|
||||
title: localize('createSessionDialog.enterSessionName', "Enter session name:")
|
||||
}],
|
||||
title: this.DialogTitleText
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
|
||||
if (this.model.templates) {
|
||||
this.templatesBox.values = this.model.getTemplateNames();
|
||||
}
|
||||
|
||||
this.sessionNameBox.onTextChanged(() => {
|
||||
if (this.sessionNameBox.value.length > 0) {
|
||||
this.model.sessionName = this.sessionNameBox.value;
|
||||
this.dialog.okButton.enabled = true;
|
||||
} else {
|
||||
this.dialog.okButton.enabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async execute(): Promise<void> {
|
||||
let currentConnection = await sqlops.connection.getCurrentConnection();
|
||||
let profilerService = sqlops.dataprotocol.getProvider<sqlops.ProfilerProvider>(currentConnection.providerName, sqlops.DataProviderType.ProfilerProvider);
|
||||
|
||||
let name = this.sessionNameBox.value;
|
||||
let selected = this.templatesBox.value.toString();
|
||||
let temp = this.model.selectTemplate(selected);
|
||||
profilerService.createSession(this.model.ownerUri, this.sessionNameBox.value, temp);
|
||||
}
|
||||
}
|
||||
@@ -5,28 +5,33 @@
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as data from 'sqlops';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { ApiWrapper } from './apiWrapper';
|
||||
import { CreateSessionDialog } from './dialogs/profilerCreateSessionDialog';
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
*/
|
||||
export class MainController {
|
||||
protected _apiWrapper: ApiWrapper;
|
||||
protected _context: vscode.ExtensionContext;
|
||||
export class MainController {
|
||||
protected _apiWrapper: ApiWrapper;
|
||||
protected _context: vscode.ExtensionContext;
|
||||
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
public constructor(context: vscode.ExtensionContext, apiWrapper?: ApiWrapper) {
|
||||
this._apiWrapper = apiWrapper || new ApiWrapper();
|
||||
this._context = context;
|
||||
}
|
||||
// PUBLIC METHODS
|
||||
public constructor(context: vscode.ExtensionContext, apiWrapper?: ApiWrapper) {
|
||||
this._apiWrapper = apiWrapper || new ApiWrapper();
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates the extension
|
||||
*/
|
||||
public deactivate(): void {
|
||||
}
|
||||
public deactivate(): void {
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
}
|
||||
public activate(): void {
|
||||
vscode.commands.registerCommand('profiler.openCreateSessionDialog', (ownerUri: string, templates: Array<sqlops.ProfilerSessionTemplate>) => {
|
||||
let dialog = new CreateSessionDialog(ownerUri, templates);
|
||||
dialog.showDialog();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,5 @@
|
||||
|
||||
/// <reference path='../../../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference path='../../../../../src/sql/sqlops.d.ts'/>
|
||||
/// <reference path='../../../../../src/sql/sqlops.proposed.d.ts'/>
|
||||
/// <reference types='@types/node'/>
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "profiler",
|
||||
"displayName": "SQL Server Profiler",
|
||||
"description": "SQL Server Profiler for SQL Operations Studio",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.5",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt",
|
||||
@@ -27,7 +27,7 @@
|
||||
],
|
||||
"contributes": {
|
||||
|
||||
"commands": [
|
||||
"commands": [
|
||||
{
|
||||
"command": "profiler.newProfiler",
|
||||
"title": "New Profiler",
|
||||
@@ -42,12 +42,20 @@
|
||||
"command": "profiler.stop",
|
||||
"title": "Stop",
|
||||
"category": "Profiler"
|
||||
},
|
||||
{
|
||||
"command": "profiler.openCreateSessionDialog",
|
||||
"title": "Create Profiler Session",
|
||||
"category": "Profiler"
|
||||
}
|
||||
],
|
||||
"outputChannels": [
|
||||
"sqlprofiler"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-nls": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vscode": "1.0.1"
|
||||
}
|
||||
|
||||
@@ -2035,6 +2035,10 @@ vinyl@~2.0.1:
|
||||
remove-trailing-separator "^1.0.1"
|
||||
replace-ext "^1.0.0"
|
||||
|
||||
vscode-nls@^3.2.1:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398"
|
||||
|
||||
vscode@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.0.1.tgz#3d161200615fe2af1d92ddc650751159411a513b"
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["'", "'"]
|
||||
{ "open": "\"", "close": "\"", "notIn": ["string"] },
|
||||
{ "open": "N'", "close": "'", "notIn": ["string", "comment"] },
|
||||
{ "open": "'", "close": "'", "notIn": ["string", "comment"] }
|
||||
],
|
||||
"surroundingPairs": [
|
||||
["{", "}"],
|
||||
|
||||
@@ -232,11 +232,11 @@
|
||||
},
|
||||
"_db_light": {
|
||||
"fontCharacter": "\\E01C",
|
||||
"fontColor": "#dd4b78"
|
||||
"fontColor": "#bfc2c1"
|
||||
},
|
||||
"_db": {
|
||||
"fontCharacter": "\\E01C",
|
||||
"fontColor": "#f55385"
|
||||
"fontColor": "#d4d7d6"
|
||||
},
|
||||
"_default_light": {
|
||||
"fontCharacter": "\\E01D",
|
||||
@@ -936,11 +936,11 @@
|
||||
},
|
||||
"_shell_light": {
|
||||
"fontCharacter": "\\E073",
|
||||
"fontColor": "#455155"
|
||||
"fontColor": "#bfc2c1"
|
||||
},
|
||||
"_shell": {
|
||||
"fontCharacter": "\\E073",
|
||||
"fontColor": "#4d5a5e"
|
||||
"fontColor": "#d4d7d6"
|
||||
},
|
||||
"_slim_light": {
|
||||
"fontCharacter": "\\E074",
|
||||
@@ -1374,7 +1374,9 @@
|
||||
"cmakelists.txt": "_makefile_3",
|
||||
"procfile": "_heroku",
|
||||
"todo": "_todo",
|
||||
"npm-debug.log": "_npm_ignored"
|
||||
"npm-debug.log": "_npm_ignored",
|
||||
"dashboard": "_shell",
|
||||
"profiler": "_csv"
|
||||
},
|
||||
"languageIds": {
|
||||
"bat": "_windows",
|
||||
@@ -1660,7 +1662,9 @@
|
||||
"omakefile": "_makefile_2_light",
|
||||
"cmakelists.txt": "_makefile_3_light",
|
||||
"procfile": "_heroku_light",
|
||||
"npm-debug.log": "_npm_ignored_light"
|
||||
"npm-debug.log": "_npm_ignored_light",
|
||||
"dashboard": "_shell_light",
|
||||
"profiler": "_csv_light"
|
||||
}
|
||||
},
|
||||
"version": "https://github.com/jesseweed/seti-ui/commit/188dda34a56b9555c7d363771264c24f4693983d"
|
||||
|
||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sqlops",
|
||||
"version": "0.31.3",
|
||||
"version": "0.32.6",
|
||||
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
@@ -34,7 +34,7 @@
|
||||
"@angular/router": "~4.1.3",
|
||||
"@angular/upgrade": "~4.1.3",
|
||||
"angular2-grid": "2.0.6",
|
||||
"angular2-slickgrid": "git://github.com/Microsoft/angular2-slickgrid.git#1.3.11",
|
||||
"angular2-slickgrid": "github:Microsoft/angular2-slickgrid#1.4.4",
|
||||
"applicationinsights": "0.18.0",
|
||||
"chart.js": "^2.6.0",
|
||||
"fast-plist": "0.1.2",
|
||||
@@ -43,8 +43,8 @@
|
||||
"getmac": "1.0.7",
|
||||
"graceful-fs": "4.1.11",
|
||||
"html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.4",
|
||||
"http-proxy-agent": "0.2.7",
|
||||
"https-proxy-agent": "0.3.6",
|
||||
"http-proxy-agent": "^2.1.0",
|
||||
"https-proxy-agent": "^2.2.1",
|
||||
"iconv-lite": "0.4.19",
|
||||
"jquery": "3.1.0",
|
||||
"jschardet": "1.6.0",
|
||||
@@ -60,7 +60,7 @@
|
||||
"reflect-metadata": "^0.1.8",
|
||||
"rxjs": "5.4.0",
|
||||
"semver": "4.3.6",
|
||||
"slickgrid": "github:anthonydresser/SlickGrid#2.3.23",
|
||||
"slickgrid": "github:anthonydresser/SlickGrid#2.3.25",
|
||||
"spdlog": "0.6.0",
|
||||
"sudo-prompt": "^8.0.0",
|
||||
"svg.js": "^2.2.5",
|
||||
@@ -127,7 +127,7 @@
|
||||
"istanbul": "^0.3.17",
|
||||
"jsdom-no-contextify": "^3.1.0",
|
||||
"lazy.js": "^0.4.2",
|
||||
"mime": "1.2.11",
|
||||
"mime": "^1.4.1",
|
||||
"minimatch": "^2.0.10",
|
||||
"mkdirp": "^0.5.0",
|
||||
"mocha": "^2.2.5",
|
||||
@@ -149,7 +149,7 @@
|
||||
"underscore": "^1.8.3",
|
||||
"vinyl": "^0.4.5",
|
||||
"vinyl-fs": "^2.4.3",
|
||||
"vsce": "1.33.2",
|
||||
"vsce": "1.46.0",
|
||||
"vscode-nls-dev": "3.0.7"
|
||||
},
|
||||
"repository": {
|
||||
|
||||
@@ -30,10 +30,12 @@
|
||||
"gettingStartedUrl": "https://go.microsoft.com/fwlink/?linkid=862039",
|
||||
"releaseNotesUrl": "https://go.microsoft.com/fwlink/?linkid=875578",
|
||||
"documentationUrl": "https://go.microsoft.com/fwlink/?linkid=862277",
|
||||
"vscodeVersion": "1.23.1",
|
||||
"commit": "9ca6200018fc206d67a47229f991901a8a453781",
|
||||
"date": "2017-12-15T12:00:00.000Z",
|
||||
"recommendedExtensions": [
|
||||
"Microsoft.agent",
|
||||
"Microsoft.import",
|
||||
"Microsoft.profiler",
|
||||
"Microsoft.server-report",
|
||||
"Microsoft.whoisactive",
|
||||
@@ -42,4 +44,4 @@
|
||||
"extensionsGallery": {
|
||||
"serviceUrl": "https://sqlopsextensions.blob.core.windows.net/marketplace/v1/extensionsGallery.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[Desktop Entry]
|
||||
Name=@@NAME_LONG@@
|
||||
Comment=SQL Operations Studio
|
||||
Comment=Data Management Tool that enables you to work with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux.
|
||||
GenericName=Text Editor
|
||||
Exec=/usr/share/@@NAME@@/@@NAME@@ --unity-launch %F
|
||||
Icon=@@ICON@@
|
||||
|
||||
2568
resources/xlf/sqlops-core/sql.xlf
Normal file
2568
resources/xlf/sqlops-core/sql.xlf
Normal file
File diff suppressed because it is too large
Load Diff
589
resources/xlf/vscode-extensions/agent.xlf
Normal file
589
resources/xlf/vscode-extensions/agent.xlf
Normal file
@@ -0,0 +1,589 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<file original="extensions/agent/out/dialogs/scheduleDialog" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="scheduleDialog.newSchedule">
|
||||
<source xml:lang="en">New Schedule</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="scheduleDialog.ok">
|
||||
<source xml:lang="en">OK</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="scheduleDialog.cancel">
|
||||
<source xml:lang="en">Cancel</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="scheduleDialog.scheduleName">
|
||||
<source xml:lang="en">Schedule Name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="scheduleDialog.schedules">
|
||||
<source xml:lang="en">Schedules</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
<file original="extensions/agent/out/dialogs/proxyDialog" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="createProxy.createProxy">
|
||||
<source xml:lang="en">Create Proxy</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.editProxy">
|
||||
<source xml:lang="en">Edit Proxy</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.General">
|
||||
<source xml:lang="en">General</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.ProxyName">
|
||||
<source xml:lang="en">Proxy name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.CredentialName">
|
||||
<source xml:lang="en">Credential name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.Description">
|
||||
<source xml:lang="en">Description</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.SubsystemName">
|
||||
<source xml:lang="en">Subsystem</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.OperatingSystem">
|
||||
<source xml:lang="en">Operating system (CmdExec)</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.ReplicationSnapshot">
|
||||
<source xml:lang="en">Replication Snapshot</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.ReplicationTransactionLog">
|
||||
<source xml:lang="en">Replication Transaction-Log Reader</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.ReplicationDistributor">
|
||||
<source xml:lang="en">Replication Distributor</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.ReplicationMerge">
|
||||
<source xml:lang="en">Replication Merge</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.ReplicationQueueReader">
|
||||
<source xml:lang="en">Replication Queue Reader</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.SSASQueryLabel">
|
||||
<source xml:lang="en">SQL Server Analysis Services Query</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.SSASCommandLabel">
|
||||
<source xml:lang="en">SQL Server Analysis Services Command</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.SSISPackage">
|
||||
<source xml:lang="en">SQL Server Integration Services Package</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.PowerShell">
|
||||
<source xml:lang="en">PowerShell</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createProxy.subSystemHeading">
|
||||
<source xml:lang="en">Active to the following subsytems</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
<file original="extensions/agent/out/dialogs/pickScheduleDialog" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="pickSchedule.jobSchedules">
|
||||
<source xml:lang="en">Job Schedules</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pickSchedule.ok">
|
||||
<source xml:lang="en">OK</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pickSchedule.cancel">
|
||||
<source xml:lang="en">Cancel</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pickSchedule.scheduleName">
|
||||
<source xml:lang="en">Schedule Name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="pickSchedule.schedules">
|
||||
<source xml:lang="en">Schedules</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
<file original="extensions/agent/out/dialogs/operatorDialog" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="createOperator.createOperator">
|
||||
<source xml:lang="en">Create Operator</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.editOperator">
|
||||
<source xml:lang="en">Edit Operator</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.General">
|
||||
<source xml:lang="en">General</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.Notifications">
|
||||
<source xml:lang="en">Notifications</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.Name">
|
||||
<source xml:lang="en">Name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.Enabled">
|
||||
<source xml:lang="en">Enabled</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.EmailName">
|
||||
<source xml:lang="en">E-mail Name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.PagerEmailName">
|
||||
<source xml:lang="en">Pager E-mail Name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.PagerMondayCheckBox">
|
||||
<source xml:lang="en">Monday</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.PagerTuesdayCheckBox">
|
||||
<source xml:lang="en">Tuesday</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.PagerWednesdayCheckBox">
|
||||
<source xml:lang="en">Wednesday</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.PagerThursdayCheckBox">
|
||||
<source xml:lang="en">Thursday</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.PagerFridayCheckBox">
|
||||
<source xml:lang="en">Friday </source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.PagerSaturdayCheckBox">
|
||||
<source xml:lang="en">Saturday</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.PagerSundayCheckBox">
|
||||
<source xml:lang="en">Sunday</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.workdayBegin">
|
||||
<source xml:lang="en">Workday begin</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.workdayEnd">
|
||||
<source xml:lang="en">Workday end</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.PagerDutySchedule">
|
||||
<source xml:lang="en">Pager on duty schdule</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.AlertListHeading">
|
||||
<source xml:lang="en">Alert list</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.AlertNameColumnLabel">
|
||||
<source xml:lang="en">Alert name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.AlertEmailColumnLabel">
|
||||
<source xml:lang="en">E-mail</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createOperator.AlertPagerColumnLabel">
|
||||
<source xml:lang="en">Pager</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
<file original="extensions/agent/out/dialogs/jobStepDialog" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="jobStepDialog.newJobStep">
|
||||
<source xml:lang="en">New Job Step</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.fileBrowserTitle">
|
||||
<source xml:lang="en">Locate Database Files - </source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.ok">
|
||||
<source xml:lang="en">OK</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.cancel">
|
||||
<source xml:lang="en">Cancel</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.general">
|
||||
<source xml:lang="en">General</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.advanced">
|
||||
<source xml:lang="en">Advanced</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.open">
|
||||
<source xml:lang="en">Open...</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.parse">
|
||||
<source xml:lang="en">Parse</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.next">
|
||||
<source xml:lang="en">Next</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.previous">
|
||||
<source xml:lang="en">Previous</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.successParse">
|
||||
<source xml:lang="en">The command was successfully parsed.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.failParse">
|
||||
<source xml:lang="en">The command failed.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.blankStepName">
|
||||
<source xml:lang="en">The step name cannot be left blank</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.stepNameLabel">
|
||||
<source xml:lang="en">Step Name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.typeLabel">
|
||||
<source xml:lang="en">Type</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.runAsLabel">
|
||||
<source xml:lang="en">Run as</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.databaseLabel">
|
||||
<source xml:lang="en">Database</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.commandLabel">
|
||||
<source xml:lang="en">Command</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.successAction">
|
||||
<source xml:lang="en">On success action</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.failureAction">
|
||||
<source xml:lang="en">On failure action</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.runAsUser">
|
||||
<source xml:lang="en">Run as user</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.retryAttempts">
|
||||
<source xml:lang="en">Retry Attempts</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.retryInterval">
|
||||
<source xml:lang="en">Retry Interval (minutes)</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.logToTable">
|
||||
<source xml:lang="en">Log to table</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.appendExistingTableEntry">
|
||||
<source xml:lang="en">Append output to exisiting entry in table</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.includeStepOutputHistory">
|
||||
<source xml:lang="en">Include step output in history</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.outputFile">
|
||||
<source xml:lang="en">Output File</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.appendOutputToFile">
|
||||
<source xml:lang="en">Append output to existing file</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.selectedPath">
|
||||
<source xml:lang="en">Selected path</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.filesOfType">
|
||||
<source xml:lang="en">Files of type</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.fileName">
|
||||
<source xml:lang="en">File name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.allFiles">
|
||||
<source xml:lang="en">All Files (*)</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.TSQL">
|
||||
<source xml:lang="en">Transact-SQL script (T-SQL)</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.agentServiceAccount">
|
||||
<source xml:lang="en">SQL Server Agent Service Account</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.nextStep">
|
||||
<source xml:lang="en">Go to the next step</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.quitJobSuccess">
|
||||
<source xml:lang="en">Quit the job reporting success</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobStepDialog.quitJobFailure">
|
||||
<source xml:lang="en">Quit the job reporting failure</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
<file original="extensions/agent/out/dialogs/jobDialog" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="jobDialog.general">
|
||||
<source xml:lang="en">General</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.steps">
|
||||
<source xml:lang="en">Steps</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.schedules">
|
||||
<source xml:lang="en">Schedules</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.alerts">
|
||||
<source xml:lang="en">Alerts</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.notifications">
|
||||
<source xml:lang="en">Notifications</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.blankJobNameError">
|
||||
<source xml:lang="en">The name of the job cannot be blank.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.name">
|
||||
<source xml:lang="en">Name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.owner">
|
||||
<source xml:lang="en">Owner</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.category">
|
||||
<source xml:lang="en">Category</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.description">
|
||||
<source xml:lang="en">Description</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.enabled">
|
||||
<source xml:lang="en">Enabled</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.jobStepList">
|
||||
<source xml:lang="en">Job step list</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.step">
|
||||
<source xml:lang="en">Step</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.type">
|
||||
<source xml:lang="en">Type</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.onSuccess">
|
||||
<source xml:lang="en">On Success</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.onFailure">
|
||||
<source xml:lang="en">On Failure</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.new">
|
||||
<source xml:lang="en">New...</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.edit">
|
||||
<source xml:lang="en">Edit</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.delete">
|
||||
<source xml:lang="en">Delete</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.moveUp">
|
||||
<source xml:lang="en">Move Step Up</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.moveDown">
|
||||
<source xml:lang="en">Move Step Up</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.notificationsTabTop">
|
||||
<source xml:lang="en">Actions to perform when the job completes</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.email">
|
||||
<source xml:lang="en">Email</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.page">
|
||||
<source xml:lang="en">Page</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.eventLogCheckBoxLabel">
|
||||
<source xml:lang="en">Write to the Windows Application event log</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.deleteJobLabel">
|
||||
<source xml:lang="en">Automatically delete job</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.schedulesaLabel">
|
||||
<source xml:lang="en">Schedules list</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.pickSchedule">
|
||||
<source xml:lang="en">Pick Schedule</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.scheduleNameLabel">
|
||||
<source xml:lang="en">Schedule Name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.alertsList">
|
||||
<source xml:lang="en">Alerts list</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.newAlert">
|
||||
<source xml:lang="en">New Alert</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.alertNameLabel">
|
||||
<source xml:lang="en">Alert Name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.newJob">
|
||||
<source xml:lang="en">New Job</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobDialog.editJob">
|
||||
<source xml:lang="en">Edit Job</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
<file original="extensions/agent/out/dialogs/alertDialog" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="alertDialog.createAlert">
|
||||
<source xml:lang="en">Create Alert</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.editAlert">
|
||||
<source xml:lang="en">Edit Alert</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.General">
|
||||
<source xml:lang="en">General</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Response">
|
||||
<source xml:lang="en">Response</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Options">
|
||||
<source xml:lang="en">Options</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.eventAlert">
|
||||
<source xml:lang="en">Event alert definition</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Name">
|
||||
<source xml:lang="en">Name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Type">
|
||||
<source xml:lang="en">Type</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Enabled">
|
||||
<source xml:lang="en">Enabled</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.DatabaseName">
|
||||
<source xml:lang="en">Database name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.ErrorNumber">
|
||||
<source xml:lang="en">Error number</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity">
|
||||
<source xml:lang="en">Severity</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.RaiseAlertContains">
|
||||
<source xml:lang="en">Raise alert when message contains</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.MessageText">
|
||||
<source xml:lang="en">Message text</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity001">
|
||||
<source xml:lang="en">001 - Miscellaneous System Information</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity002">
|
||||
<source xml:lang="en">002 - Reserved</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity003">
|
||||
<source xml:lang="en">003 - Reserved</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity004">
|
||||
<source xml:lang="en">004 - Reserved</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity005">
|
||||
<source xml:lang="en">005 - Reserved</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity006">
|
||||
<source xml:lang="en">006 - Reserved</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity007">
|
||||
<source xml:lang="en">007 - Notification: Status Information</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity008">
|
||||
<source xml:lang="en">008 - Notification: User Intervention Required</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity009">
|
||||
<source xml:lang="en">009 - User Defined</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity010">
|
||||
<source xml:lang="en">010 - Information</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity011">
|
||||
<source xml:lang="en">011 - Specified Database Object Not Found</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity012">
|
||||
<source xml:lang="en">012 - Unused</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity013">
|
||||
<source xml:lang="en">013 - User Transaction Syntax Error</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity014">
|
||||
<source xml:lang="en">014 - Insufficient Permission</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity015">
|
||||
<source xml:lang="en">015 - Syntax Error in SQL Statements</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity016">
|
||||
<source xml:lang="en">016 - Miscellaneous User Error</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity017">
|
||||
<source xml:lang="en">017 - Insufficient Resources</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity018">
|
||||
<source xml:lang="en">018 - Nonfatal Internal Error</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity019">
|
||||
<source xml:lang="en">019 - Fatal Error in Resource</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity020">
|
||||
<source xml:lang="en">020 - Fatal Error in Current Process</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity021">
|
||||
<source xml:lang="en">021 - Fatal Error in Database Processes</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity022">
|
||||
<source xml:lang="en">022 - Fatal Error: Table Integrity Suspect</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity023">
|
||||
<source xml:lang="en">023 - Fatal Error: Database Integrity Suspect</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity024">
|
||||
<source xml:lang="en">024 - Fatal Error: Hardware Error</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.Severity025">
|
||||
<source xml:lang="en">025 - Fatal Error</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.AllDatabases">
|
||||
<source xml:lang="en"><all databases></source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.ExecuteJob">
|
||||
<source xml:lang="en">Execute Job</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.ExecuteJobName">
|
||||
<source xml:lang="en">Job Name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.NotifyOperators">
|
||||
<source xml:lang="en">Notify Operators</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.NewJob">
|
||||
<source xml:lang="en">New Job</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.OperatorList">
|
||||
<source xml:lang="en">Operator List</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.OperatorName">
|
||||
<source xml:lang="en">Operator</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.OperatorEmail">
|
||||
<source xml:lang="en">E-mail</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.OperatorPager">
|
||||
<source xml:lang="en">Pager</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.NewOperator">
|
||||
<source xml:lang="en">New Operator</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.IncludeErrorInEmail">
|
||||
<source xml:lang="en">Include alert error text in e-mail</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.IncludeErrorInPager">
|
||||
<source xml:lang="en">Include alert error text in pager</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.AdditionalNotification">
|
||||
<source xml:lang="en">Additional notification message to send</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.DelayBetweenResponse">
|
||||
<source xml:lang="en">Delay between responses</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.DelayMinutes">
|
||||
<source xml:lang="en">Delay Minutes</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.DelaySeconds">
|
||||
<source xml:lang="en">Delay Seconds</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
<file original="extensions/agent/out/dialogs/agentDialog" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="agentDialog.OK">
|
||||
<source xml:lang="en">OK</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="agentDialog.Cancel">
|
||||
<source xml:lang="en">Cancel</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
<file original="extensions/agent/out/data/jobData" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="jobData.whenJobCompletes">
|
||||
<source xml:lang="en">When the job completes</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobData.whenJobFails">
|
||||
<source xml:lang="en">When the job fails</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobData.whenJobSucceeds">
|
||||
<source xml:lang="en">When the job succeeds</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobData.jobNameRequired">
|
||||
<source xml:lang="en">Job name must be provided</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="jobData.saveErrorMessage">
|
||||
<source xml:lang="en">Job update failed '{0}'</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
<file original="extensions/agent/out/data/alertData" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="alertData.saveErrorMessage">
|
||||
<source xml:lang="en">Alert update failed '{0}'</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertData.DefaultAlertTypString">
|
||||
<source xml:lang="en">SQL Server event alert</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.PerformanceCondition">
|
||||
<source xml:lang="en">SQL Server performance condition alert</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="alertDialog.WmiEvent">
|
||||
<source xml:lang="en">WMI event alert</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
<file original="extensions/agent/out/mainController" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="mainController.notImplemented">
|
||||
<source xml:lang="en">This feature is under development. Check-out the latest insiders build if you'd like to try out the most recent changes!</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
</xliff>
|
||||
140
resources/xlf/vscode-extensions/import.xlf
Normal file
140
resources/xlf/vscode-extensions/import.xlf
Normal file
@@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<file original="extensions/import/out/wizard/pages/summaryPage" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="flatFileImport.importInformation">
|
||||
<source xml:lang="en">Import information</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.importStatus">
|
||||
<source xml:lang="en">Import status</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.serverName">
|
||||
<source xml:lang="en">Server name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.databaseName">
|
||||
<source xml:lang="en">Database name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.tableName">
|
||||
<source xml:lang="en">Table name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.tableSchema">
|
||||
<source xml:lang="en">Table schema</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.fileImport">
|
||||
<source xml:lang="en">File to be imported</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.success.norows">
|
||||
<source xml:lang="en">✔ You have successfully inserted the data into a table.</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
<file original="extensions/import/out/wizard/pages/prosePreviewPage" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="flatFileImport.refresh">
|
||||
<source xml:lang="en">Refresh</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.prosePreviewMessage">
|
||||
<source xml:lang="en">This operation analyzed the input file structure to generate the preview below for up to the first 50 rows.</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
<file original="extensions/import/out/wizard/pages/modifyColumnsPage" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="flatFileImport.importData">
|
||||
<source xml:lang="en">Import Data</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.next">
|
||||
<source xml:lang="en">Next</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.columnName">
|
||||
<source xml:lang="en">Column Name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.dataType">
|
||||
<source xml:lang="en">Data Type</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.primaryKey">
|
||||
<source xml:lang="en">Primary Key</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.allowNulls">
|
||||
<source xml:lang="en">Allow Nulls</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
<file original="extensions/import/out/wizard/pages/fileConfigPage" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="flatFileImport.serverDropdownTitle">
|
||||
<source xml:lang="en">Server the database is in</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.databaseDropdownTitle">
|
||||
<source xml:lang="en">Database the table is created in</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.browseFiles">
|
||||
<source xml:lang="en">Browse</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.openFile">
|
||||
<source xml:lang="en">Open</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.fileTextboxTitle">
|
||||
<source xml:lang="en">Location of the file to be imported</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.tableTextboxTitle">
|
||||
<source xml:lang="en">New table name</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.schemaTextboxTitle">
|
||||
<source xml:lang="en">Table schema</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
<file original="extensions/import/out/services/telemetry" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="import.serviceCrashButton">
|
||||
<source xml:lang="en">Give Feedback</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="serviceCrashMessage">
|
||||
<source xml:lang="en">service component could not start</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
<file original="extensions/import/out/services/serviceClient" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="serviceStarted">
|
||||
<source xml:lang="en">Service Started</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="serviceStarting">
|
||||
<source xml:lang="en">Starting service</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.serviceStartFailed">
|
||||
<source xml:lang="en">Failed to start Import service{0}</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="installingServiceDetailed">
|
||||
<source xml:lang="en">Installing {0} service to {1}</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="installingService">
|
||||
<source xml:lang="en">Installing Service</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="serviceInstalled">
|
||||
<source xml:lang="en">Installed</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="downloadingService">
|
||||
<source xml:lang="en">Downloading {0}</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="downloadingServiceStatus">
|
||||
<source xml:lang="en">Downloading Service</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="downloadingServiceComplete">
|
||||
<source xml:lang="en">Done!</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
<file original="extensions/import/out/wizard/flatFileWizard" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="import.needConnection">
|
||||
<source xml:lang="en">Please connect to a server before using this wizard.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.wizardName">
|
||||
<source xml:lang="en">Import flat file wizard</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.page1Name">
|
||||
<source xml:lang="en">Specify Input File</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.page2Name">
|
||||
<source xml:lang="en">Preview Data</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.page3Name">
|
||||
<source xml:lang="en">Modify Columns</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.page4Name">
|
||||
<source xml:lang="en">Summary</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="flatFileImport.importNewFile">
|
||||
<source xml:lang="en">Import new file</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
</xliff>
|
||||
29
resources/xlf/vscode-extensions/profiler.xlf
Normal file
29
resources/xlf/vscode-extensions/profiler.xlf
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<file original="extensions/profiler/client\out/dialogs/profilerCreateSessionDialog" source-language="en" datatype="plaintext"><body>
|
||||
<trans-unit id="createSessionDialog.newSession">
|
||||
<source xml:lang="en">New Session</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createSessionDialog.cancel">
|
||||
<source xml:lang="en">Cancel</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createSessionDialog.create">
|
||||
<source xml:lang="en">Create</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createSessionDialog.title">
|
||||
<source xml:lang="en">Create New Profiler Session</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createSessionDialog.templatesInvalid">
|
||||
<source xml:lang="en">Invalid templates list, cannot open dialog</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createSessionDialog.dialogOwnerInvalid">
|
||||
<source xml:lang="en">Invalid dialog owner, cannot open dialog</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createSessionDialog.selectTemplates">
|
||||
<source xml:lang="en">Select session template:</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="createSessionDialog.enterSessionName">
|
||||
<source xml:lang="en">Enter session name:</source>
|
||||
</trans-unit>
|
||||
</body></file>
|
||||
</xliff>
|
||||
1553
samples/extensionSamples/package-lock.json
generated
1553
samples/extensionSamples/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -172,8 +172,7 @@
|
||||
"dependencies": {
|
||||
"fs-extra": "^5.0.0",
|
||||
"handlebars": "^4.0.11",
|
||||
"vscode-nls": "2.0.2",
|
||||
"sqlops": "github:anthonydresser/sqlops-extension-sqlops"
|
||||
"vscode-nls": "2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/handlebars": "^4.0.11",
|
||||
@@ -192,6 +191,7 @@
|
||||
"typemoq": "^2.1.0",
|
||||
"typescript": "^2.6.1",
|
||||
"vscode": "^1.1.6",
|
||||
"sqlops": "github:anthonydresser/sqlops-extension-sqlops",
|
||||
"vsce": "1.36.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@ See [Paul Randal's wait types library] for more information about each wait type
|
||||
|
||||
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
|
||||
* flyfishingdba for Add square brackets for ms_foreachdb call (#1023)
|
||||
* Peter-Schneider for Changed the stored procedure call to work on case sensitive instances (#1809)
|
||||
|
||||
## What's new in Server Reports v1.3?
|
||||
* Changed the stored procedure call to work on case sensitive instances
|
||||
|
||||
## What's new in Server Reports v1.2?
|
||||
* Created left nav bar and added 2 categories for insight widgets: monitor and performance
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "server-report",
|
||||
"displayName": "Server Reports",
|
||||
"description": "Server Reports",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"engines": {
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
"command": "sqlservices.openDialog",
|
||||
"title": "sqlservices.openDialog"
|
||||
},
|
||||
{
|
||||
"command": "sqlservices.openConnectionDialog",
|
||||
"title": "sqlservices.openConnectionDialog"
|
||||
},
|
||||
{
|
||||
"command": "sqlservices.openEditor",
|
||||
"title": "sqlservices.openEditor"
|
||||
@@ -100,7 +104,7 @@
|
||||
"sqlops": "github:anthonydresser/sqlops-extension-sqlops",
|
||||
"tslint": "^3.14.0",
|
||||
"typescript": "^2.6.1",
|
||||
"vscode": "^1.1.14",
|
||||
"vscode": "^1.1.18",
|
||||
"@types/handlebars": "^4.0.11",
|
||||
"vsce": "1.36.2"
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ import * as vscode from 'vscode';
|
||||
import SplitPropertiesPanel from './splitPropertiesPanel';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import {TreeNode, TreeDataProvider} from './treeDataProvider';
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
@@ -48,6 +49,13 @@ export default class MainController implements vscode.Disposable {
|
||||
this.openDialog();
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('sqlservices.openConnectionDialog', async () => {
|
||||
let connection = await sqlops.connection.openConnectionDialog();
|
||||
if (connection) {
|
||||
console.info('Connection Opened: ' + connection.options['server']);
|
||||
}
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('sqlservices.openEditor', () => {
|
||||
this.openEditor();
|
||||
});
|
||||
@@ -67,12 +75,83 @@ export default class MainController implements vscode.Disposable {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private async getTab3Content(view: sqlops.ModelView): Promise<void> {
|
||||
let treeData = {
|
||||
label: '1',
|
||||
children: [
|
||||
{
|
||||
label: '11',
|
||||
id: '11',
|
||||
children: [
|
||||
{
|
||||
label: '111',
|
||||
id: '111',
|
||||
checked: false
|
||||
},
|
||||
{
|
||||
label: '112',
|
||||
id: '112',
|
||||
children: [
|
||||
{
|
||||
label: '1121',
|
||||
id: '1121',
|
||||
checked: true
|
||||
},
|
||||
{
|
||||
label: '1122',
|
||||
id: '1122',
|
||||
checked: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '12',
|
||||
id: '12',
|
||||
checked: true
|
||||
}
|
||||
],
|
||||
id: '1'
|
||||
};
|
||||
let root = TreeNode.createTree(treeData);
|
||||
|
||||
let treeDataProvider = new TreeDataProvider(root);
|
||||
|
||||
let tree: sqlops.TreeComponent<TreeNode> = view.modelBuilder.tree<TreeNode>().withProperties({
|
||||
'withCheckbox': true
|
||||
}).component();
|
||||
let treeView = tree.registerDataProvider(treeDataProvider);
|
||||
treeView.onNodeCheckedChanged(item => {
|
||||
if (item && item.element) {
|
||||
item.element.changeNodeCheckedState(item.checked);
|
||||
}
|
||||
});
|
||||
treeView.onDidChangeSelection(selectedNodes => {
|
||||
selectedNodes.forEach(node => {
|
||||
console.info('tree node selected: ' + node.label);
|
||||
});
|
||||
});
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: tree,
|
||||
title: 'Tree'
|
||||
}], {
|
||||
horizontal: false,
|
||||
componentWidth: 800,
|
||||
componentHeight: 800
|
||||
}).component();
|
||||
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
||||
formWrapper.loading = false;
|
||||
|
||||
await view.initializeModel(formWrapper);
|
||||
}
|
||||
private async getTabContent(view: sqlops.ModelView, customButton1: sqlops.window.modelviewdialog.Button, customButton2: sqlops.window.modelviewdialog.Button, componentWidth: number | string): Promise<void> {
|
||||
let inputBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
multiline: true,
|
||||
height: 100
|
||||
}).component();
|
||||
.withProperties({
|
||||
multiline: true,
|
||||
height: 100
|
||||
}).component();
|
||||
let inputBoxWrapper = view.modelBuilder.loadingComponent().withItem(inputBox).component();
|
||||
inputBoxWrapper.loading = false;
|
||||
customButton1.onClick(() => {
|
||||
@@ -88,7 +167,7 @@ export default class MainController implements vscode.Disposable {
|
||||
})
|
||||
.component();
|
||||
checkbox.onChanged(e => {
|
||||
console.info("inputBox.enabled " + inputBox.enabled);
|
||||
console.info('inputBox.enabled ' + inputBox.enabled);
|
||||
inputBox.enabled = !inputBox.enabled;
|
||||
});
|
||||
let button = view.modelBuilder.button()
|
||||
@@ -145,8 +224,8 @@ export default class MainController implements vscode.Disposable {
|
||||
component: inputBox4,
|
||||
title: 'inputBox4'
|
||||
}], {
|
||||
horizontal: true
|
||||
}).component();
|
||||
horizontal: true
|
||||
}).component();
|
||||
let groupModel1 = view.modelBuilder.groupContainer()
|
||||
.withLayout({
|
||||
}).withItems([
|
||||
@@ -178,8 +257,8 @@ export default class MainController implements vscode.Disposable {
|
||||
}).component();
|
||||
|
||||
let declarativeTable = view.modelBuilder.declarativeTable()
|
||||
.withProperties({
|
||||
columns: [{
|
||||
.withProperties({
|
||||
columns: [{
|
||||
displayName: 'Column 1',
|
||||
valueType: sqlops.DeclarativeDataType.string,
|
||||
width: '20px',
|
||||
@@ -204,12 +283,12 @@ export default class MainController implements vscode.Disposable {
|
||||
{ name: 'options2', displayName: 'option 2' }
|
||||
]
|
||||
}
|
||||
],
|
||||
data: [
|
||||
['Data00', 'Data01', false, 'options2'],
|
||||
['Data10', 'Data11', true, 'options1']
|
||||
]
|
||||
}).component();
|
||||
],
|
||||
data: [
|
||||
['Data00', 'Data01', false, 'options2'],
|
||||
['Data10', 'Data11', true, 'options1']
|
||||
]
|
||||
}).component();
|
||||
|
||||
declarativeTable.onDataChanged(e => {
|
||||
inputBox2.value = e.row.toString() + ' ' + e.column.toString() + ' ' + e.value.toString();
|
||||
@@ -223,7 +302,7 @@ export default class MainController implements vscode.Disposable {
|
||||
height: 150
|
||||
}).withItems([
|
||||
radioButton, groupModel1, radioButton2]
|
||||
, { flex: '1 1 50%' }).component();
|
||||
, { flex: '1 1 50%' }).component();
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: inputBoxWrapper,
|
||||
@@ -254,9 +333,9 @@ export default class MainController implements vscode.Disposable {
|
||||
component: listBox,
|
||||
title: 'List Box'
|
||||
}], {
|
||||
horizontal: false,
|
||||
componentWidth: componentWidth
|
||||
}).component();
|
||||
horizontal: false,
|
||||
componentWidth: componentWidth
|
||||
}).component();
|
||||
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
||||
formWrapper.loading = false;
|
||||
customButton2.onClick(() => {
|
||||
@@ -271,8 +350,9 @@ export default class MainController implements vscode.Disposable {
|
||||
let tab1 = sqlops.window.modelviewdialog.createTab('Test tab 1');
|
||||
|
||||
let tab2 = sqlops.window.modelviewdialog.createTab('Test tab 2');
|
||||
let tab3 = sqlops.window.modelviewdialog.createTab('Test tab 3');
|
||||
tab2.content = 'sqlservices';
|
||||
dialog.content = [tab1, tab2];
|
||||
dialog.content = [tab1, tab2, tab3];
|
||||
dialog.okButton.onClick(() => console.log('ok clicked!'));
|
||||
dialog.cancelButton.onClick(() => console.log('cancel clicked!'));
|
||||
dialog.okButton.label = 'ok';
|
||||
@@ -285,6 +365,10 @@ export default class MainController implements vscode.Disposable {
|
||||
tab1.registerContent(async (view) => {
|
||||
await this.getTabContent(view, customButton1, customButton2, 400);
|
||||
});
|
||||
|
||||
tab3.registerContent(async (view) => {
|
||||
await this.getTab3Content(view);
|
||||
});
|
||||
sqlops.window.modelviewdialog.openDialog(dialog);
|
||||
}
|
||||
|
||||
@@ -301,6 +385,20 @@ export default class MainController implements vscode.Disposable {
|
||||
page1.registerContent(async (view) => {
|
||||
await this.getTabContent(view, customButton1, customButton2, 800);
|
||||
});
|
||||
|
||||
wizard.registerOperation({
|
||||
displayName: 'test task',
|
||||
description: 'task description',
|
||||
isCancelable: true,
|
||||
connection: undefined,
|
||||
operation: op => {
|
||||
op.updateStatus(sqlops.TaskStatus.InProgress);
|
||||
op.updateStatus(sqlops.TaskStatus.InProgress, 'Task is running');
|
||||
setTimeout(() => {
|
||||
op.updateStatus(sqlops.TaskStatus.Succeeded);
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
wizard.pages = [page1, page2];
|
||||
wizard.open();
|
||||
}
|
||||
@@ -343,15 +441,32 @@ export default class MainController implements vscode.Disposable {
|
||||
webview2.message = count;
|
||||
});
|
||||
|
||||
let flexModel = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'column',
|
||||
alignItems: 'flex-start',
|
||||
height: 500
|
||||
}).withItems([
|
||||
webview1, webview2
|
||||
], { flex: '1 1 50%' })
|
||||
let editor1 = view.modelBuilder.editor()
|
||||
.withProperties({
|
||||
content: 'select * from sys.tables'
|
||||
})
|
||||
.component();
|
||||
|
||||
let editor2 = view.modelBuilder.editor()
|
||||
.withProperties({
|
||||
content: 'print("Hello World !")',
|
||||
languageMode: 'python'
|
||||
})
|
||||
.component();
|
||||
|
||||
let flexModel = view.modelBuilder.flexContainer().component();
|
||||
flexModel.addItem(editor1, { flex: '1' });
|
||||
flexModel.addItem(editor2, { flex: '1' });
|
||||
flexModel.setLayout({
|
||||
flexFlow: 'column',
|
||||
alignItems: 'stretch',
|
||||
height: '100%'
|
||||
});
|
||||
|
||||
view.onClosed((params) => {
|
||||
vscode.window.showInformationMessage('editor1: language: ' + editor1.languageMode + ' Content1: ' + editor1.content);
|
||||
vscode.window.showInformationMessage('editor2: language: ' + editor2.languageMode + ' Content2: ' + editor2.content);
|
||||
});
|
||||
await view.initializeModel(flexModel);
|
||||
});
|
||||
editor.openEditor();
|
||||
@@ -378,13 +493,14 @@ export default class MainController implements vscode.Disposable {
|
||||
let monitorLightPath = vscode.Uri.file(path.join(__dirname, '..', 'media', 'monitor.svg'));
|
||||
let monitorIcon = {
|
||||
light: monitorLightPath,
|
||||
dark: path.join(__dirname, '..', 'media', 'monitor_inverse.svg') };
|
||||
dark: path.join(__dirname, '..', 'media', 'monitor_inverse.svg')
|
||||
};
|
||||
|
||||
let monitorButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: 'Monitor',
|
||||
iconPath: monitorIcon
|
||||
}).component();
|
||||
.withProperties({
|
||||
label: 'Monitor',
|
||||
iconPath: monitorIcon
|
||||
}).component();
|
||||
let toolbarModel = view.modelBuilder.toolbarContainer()
|
||||
.withToolbarItems([{
|
||||
component: inputBox,
|
||||
|
||||
303
samples/sqlservices/src/controllers/treeDataProvider.ts
Normal file
303
samples/sqlservices/src/controllers/treeDataProvider.ts
Normal file
@@ -0,0 +1,303 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as path from 'path';
|
||||
|
||||
export enum TreeCheckboxState {
|
||||
Intermediate = 0,
|
||||
Checked = 1,
|
||||
Unchecked = 2
|
||||
}
|
||||
|
||||
export interface TreeComponentDataModel {
|
||||
label?: string;
|
||||
children?: TreeComponentDataModel[];
|
||||
id?: string;
|
||||
checked?: boolean;
|
||||
}
|
||||
|
||||
export class TreeNode implements sqlops.TreeComponentItem {
|
||||
private _onNodeChange = new vscode.EventEmitter<void>();
|
||||
private _onTreeChange = new vscode.EventEmitter<TreeNode>();
|
||||
private _data: TreeComponentDataModel;
|
||||
private _parent?: TreeNode;
|
||||
private _root: TreeNode;
|
||||
private _isAlwaysLeaf: boolean;
|
||||
private _nodeMap: Map<string, TreeNode>;
|
||||
private _children: TreeNode[];
|
||||
|
||||
public readonly onNodeChange: vscode.Event<void> = this._onNodeChange.event;
|
||||
public readonly onTreeChange: vscode.Event<TreeNode> = this._onTreeChange.event;
|
||||
|
||||
|
||||
/**
|
||||
* Creates new instance of tree node
|
||||
* @param data the underlining data that's bind to the tree node, any change in the tree will affect the same node in data
|
||||
* @param root the root node of the tree. If passed null, the current node will be the root
|
||||
*/
|
||||
constructor(data: TreeComponentDataModel, root: TreeNode) {
|
||||
if (!data) {
|
||||
throw new Error(`Invalid tree node data`);
|
||||
}
|
||||
if (root === undefined) {
|
||||
root = this;
|
||||
root._nodeMap = new Map<string, TreeNode>();
|
||||
}
|
||||
|
||||
this._root = root;
|
||||
if (this.findNode(data.id)) {
|
||||
throw new Error(`tree node with id: '${data.id}' already exists`);
|
||||
}
|
||||
this._data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* id for TreeNode
|
||||
*/
|
||||
public get id(): string {
|
||||
return this.data.id;
|
||||
}
|
||||
|
||||
public set id(value: string) {
|
||||
this.data.id = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Label to display to the user, describing this node
|
||||
*/
|
||||
public set label(value: string) {
|
||||
this.data.label = value;
|
||||
}
|
||||
|
||||
public get collapsibleState(): vscode.TreeItemCollapsibleState {
|
||||
if (!this._isAlwaysLeaf) {
|
||||
return vscode.TreeItemCollapsibleState.Expanded;
|
||||
} else {
|
||||
vscode.TreeItemCollapsibleState.None;
|
||||
}
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this.data.label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a leaf node (in which case no children can be generated) or is it expandable?
|
||||
*/
|
||||
public get isAlwaysLeaf(): boolean {
|
||||
return this._isAlwaysLeaf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parent of this node
|
||||
*/
|
||||
public get parent(): TreeNode {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
public get root(): TreeNode {
|
||||
return this._root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path identifying this node
|
||||
*/
|
||||
public get nodePath(): string {
|
||||
return `${this.parent ? this.parent.nodePath + '-' : ''}${this.id}`;
|
||||
}
|
||||
|
||||
public get data(): TreeComponentDataModel {
|
||||
if (this._data === undefined) {
|
||||
this._data = {
|
||||
label: undefined
|
||||
};
|
||||
}
|
||||
return this._data;
|
||||
}
|
||||
|
||||
public changeNodeCheckedState(value: boolean, fromParent?: boolean): void {
|
||||
if (value !== this.checked) {
|
||||
if (value !== undefined && this.children) {
|
||||
this.children.forEach(child => {
|
||||
child.changeNodeCheckedState(value, true);
|
||||
});
|
||||
}
|
||||
|
||||
this.checked = value;
|
||||
if (!fromParent && this.parent) {
|
||||
this.parent.refreshState();
|
||||
}
|
||||
|
||||
this.onValueChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public set checked(value: boolean) {
|
||||
this.data.checked = value;
|
||||
}
|
||||
|
||||
public refreshState(): void {
|
||||
if (this.hasChildren) {
|
||||
if (this.children.every(c => c.checked)) {
|
||||
this.changeNodeCheckedState(true);
|
||||
} else if (this.children.every(c => c.checked !== undefined && !c.checked)) {
|
||||
this.changeNodeCheckedState(false);
|
||||
} else {
|
||||
this.changeNodeCheckedState(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get hasChildren(): boolean {
|
||||
return this.children !== undefined && this.children.length > 0;
|
||||
}
|
||||
|
||||
public get checked(): boolean {
|
||||
return this.data.checked;
|
||||
}
|
||||
|
||||
private onValueChanged(): void {
|
||||
this._onNodeChange.fire();
|
||||
if (this.root) {
|
||||
this.root._onTreeChange.fire(this);
|
||||
}
|
||||
}
|
||||
|
||||
public get checkboxState(): TreeCheckboxState {
|
||||
if (this.checked === undefined) {
|
||||
return TreeCheckboxState.Intermediate;
|
||||
} else {
|
||||
return this.checked ? TreeCheckboxState.Checked : TreeCheckboxState.Unchecked;
|
||||
}
|
||||
}
|
||||
|
||||
public findNode(id: string): TreeNode {
|
||||
if (this.id === id) {
|
||||
return this;
|
||||
} else if (this.root) {
|
||||
return this.root._nodeMap.has(id) ? this.root._nodeMap.get(id) : undefined;
|
||||
} else {
|
||||
let node: TreeNode;
|
||||
if (this.children) {
|
||||
this.children.forEach(child => {
|
||||
node = child.findNode(id);
|
||||
if (node) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Children of this node
|
||||
*/
|
||||
public get children(): TreeNode[] {
|
||||
return this._children;
|
||||
}
|
||||
|
||||
public addChildNode(node: TreeNode): void {
|
||||
|
||||
if (node) {
|
||||
if (!node.root) {
|
||||
node._root = this.root;
|
||||
}
|
||||
if (!node.parent) {
|
||||
node._parent = this;
|
||||
}
|
||||
if (node.root) {
|
||||
node.root._nodeMap.set(node.id, node);
|
||||
}
|
||||
this._children.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
public static createNode(nodeData: TreeComponentDataModel, parent?: TreeNode, root?: TreeNode): TreeNode {
|
||||
let rootNode = root || (parent !== undefined ? parent.root : undefined);
|
||||
let treeNode = new TreeNode(nodeData, rootNode);
|
||||
|
||||
treeNode._parent = parent;
|
||||
return treeNode;
|
||||
}
|
||||
|
||||
public static createTree(nodeData: TreeComponentDataModel, parent?: TreeNode, root?: TreeNode): TreeNode {
|
||||
if (nodeData) {
|
||||
let treeNode = TreeNode.createNode(nodeData, parent, root);
|
||||
|
||||
if (nodeData.children && nodeData.children.length > 0) {
|
||||
treeNode._isAlwaysLeaf = false;
|
||||
treeNode._children = [];
|
||||
nodeData.children.forEach(childNode => {
|
||||
if (childNode) {
|
||||
let childTreeNode = TreeNode.createTree(childNode, treeNode, root || treeNode.root);
|
||||
treeNode.addChildNode(childTreeNode);
|
||||
}
|
||||
});
|
||||
treeNode.refreshState();
|
||||
} else {
|
||||
treeNode._isAlwaysLeaf = true;
|
||||
}
|
||||
return treeNode;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class TreeDataProvider implements sqlops.TreeComponentDataProvider<TreeNode> {
|
||||
private _onDidChangeTreeData = new vscode.EventEmitter<TreeNode>();
|
||||
constructor(private _root: TreeNode) {
|
||||
if(this._root) {
|
||||
this._root.onTreeChange(node => {
|
||||
this._onDidChangeTreeData.fire(node);
|
||||
});
|
||||
}
|
||||
}
|
||||
onDidChangeTreeData?: vscode.Event<TreeNode | undefined | null> = this._onDidChangeTreeData.event ;
|
||||
|
||||
/**
|
||||
* Get [TreeItem](#TreeItem) representation of the `element`
|
||||
*
|
||||
* @param element The element for which [TreeItem](#TreeItem) representation is asked for.
|
||||
* @return [TreeItem](#TreeItem) representation of the element
|
||||
*/
|
||||
getTreeItem(element: TreeNode): sqlops.TreeComponentItem | Thenable<sqlops.TreeComponentItem> {
|
||||
let item: sqlops.TreeComponentItem = {};
|
||||
item.label = element.label;
|
||||
item.checked = element.checked;
|
||||
item.collapsibleState = element.collapsibleState;
|
||||
item.iconPath = vscode.Uri.file(path.join(__dirname, '..', 'media', 'monitor.svg'));
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the children of `element` or root if no element is passed.
|
||||
*
|
||||
* @param element The element from which the provider gets children. Can be `undefined`.
|
||||
* @return Children of `element` or root if no element is passed.
|
||||
*/
|
||||
getChildren(element?: TreeNode): vscode.ProviderResult<TreeNode[]> {
|
||||
if (element) {
|
||||
return Promise.resolve(element.children);
|
||||
} else {
|
||||
return Promise.resolve(this._root.children);
|
||||
}
|
||||
}
|
||||
|
||||
getParent(element?: TreeNode): vscode.ProviderResult<TreeNode> {
|
||||
if (element) {
|
||||
return Promise.resolve(element.parent);
|
||||
} else {
|
||||
return Promise.resolve(this._root);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export class Checkbox implements OnInit, OnChanges {
|
||||
@Input() label: string;
|
||||
@Input() enabled = true;
|
||||
@Input() checked = true;
|
||||
@Input() private ariaLabel: string;
|
||||
@Input('aria-label') private ariaLabel: string;
|
||||
|
||||
@Output() onChange = new EventEmitter<boolean>();
|
||||
|
||||
|
||||
@@ -233,8 +233,8 @@ export class Dropdown extends Disposable {
|
||||
this._layoutTree();
|
||||
return { dispose: () => { } };
|
||||
},
|
||||
onDOMEvent: (e, activeElement) => {
|
||||
if (!DOM.isAncestor(activeElement, this.$el.getHTMLElement()) && !DOM.isAncestor(activeElement, this.$treeContainer.getHTMLElement())) {
|
||||
onDOMEvent: e => {
|
||||
if (!DOM.isAncestor(e.srcElement, this.$el.getHTMLElement()) && !DOM.isAncestor(e.srcElement, this.$treeContainer.getHTMLElement())) {
|
||||
this._input.validate();
|
||||
this._onBlur.fire();
|
||||
this._contextView.hide();
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import {
|
||||
Component, Inject, forwardRef, ElementRef, OnInit, Input,
|
||||
Output, OnChanges, SimpleChanges, EventEmitter
|
||||
} from '@angular/core';
|
||||
|
||||
import { Dropdown, IDropdownOptions } from 'sql/base/browser/ui/editableDropdown/dropdown';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { attachEditableDropdownStyler } from 'sql/common/theme/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
@Component({
|
||||
selector: 'editable-select-box',
|
||||
template: ''
|
||||
})
|
||||
export class EditableDropDown extends AngularDisposable implements OnInit, OnChanges {
|
||||
private _selectbox: Dropdown;
|
||||
|
||||
@Input() options: string[];
|
||||
@Input() selectedOption: string;
|
||||
@Input() onlyEmitOnChange = false;
|
||||
|
||||
@Output() onDidSelect = new EventEmitter<string>();
|
||||
|
||||
private _previousVal: string;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(IThemeService) private themeService: IThemeService,
|
||||
@Inject(IContextViewService) private contextViewService: IContextViewService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
let dropdownOptions: IDropdownOptions = {
|
||||
values: [],
|
||||
strictSelection: false,
|
||||
placeholder: '',
|
||||
maxHeight: 125,
|
||||
ariaLabel: '',
|
||||
actionLabel: ''
|
||||
};
|
||||
this._selectbox = new Dropdown(this._el.nativeElement, this.contextViewService, this.themeService, dropdownOptions);
|
||||
this._selectbox.values = this.options;
|
||||
this._selectbox.value = this.selectedOption;
|
||||
|
||||
this._selectbox.onValueChange(e => {
|
||||
if (this.onlyEmitOnChange) {
|
||||
if (this._previousVal !== e) {
|
||||
this.onDidSelect.emit(e);
|
||||
this._previousVal = e;
|
||||
}
|
||||
} else {
|
||||
this.onDidSelect.emit(e);
|
||||
}
|
||||
});
|
||||
this._register(attachEditableDropdownStyler(this._selectbox, this.themeService));
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
}
|
||||
|
||||
public get value(): string {
|
||||
return this._selectbox.value;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
|
||||
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
|
||||
@Component({
|
||||
@@ -29,7 +28,7 @@ export class InputBox extends AngularDisposable implements OnInit, OnChanges {
|
||||
@Input() max: string;
|
||||
@Input() type: string;
|
||||
@Input() placeholder: string;
|
||||
@Input() ariaLabel: string;
|
||||
@Input('aria-label') ariaLabel: string;
|
||||
@Input() value: string;
|
||||
|
||||
@Output() onDidChange = new EventEmitter<string | number>();
|
||||
|
||||
@@ -33,7 +33,7 @@ export function appendRowLink(container: Builder, label: string, labelClass: str
|
||||
container.element('tr', {}, (rowContainer) => {
|
||||
rowContainer.element('td', { class: labelClass }, (labelCellContainer) => {
|
||||
labelCellContainer.div({}, (labelContainer) => {
|
||||
labelContainer.innerHtml(label);
|
||||
labelContainer.text(label);
|
||||
});
|
||||
});
|
||||
rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => {
|
||||
|
||||
@@ -135,7 +135,7 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
private _name: string,
|
||||
private _partService: IPartService,
|
||||
private _telemetryService: ITelemetryService,
|
||||
private _contextKeyService: IContextKeyService,
|
||||
_contextKeyService: IContextKeyService,
|
||||
options?: IModalOptions
|
||||
) {
|
||||
super();
|
||||
@@ -228,7 +228,7 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
}
|
||||
|
||||
// The builder builds the dialog. It append header, body and footer sections.
|
||||
this._builder = $().div({ class: builderClass, 'role': 'dialog' }, (dialogContainer) => {
|
||||
this._builder = $().div({ class: builderClass, 'role': 'dialog', 'aria-label': this._title }, (dialogContainer) => {
|
||||
this._modalDialog = dialogContainer.div({ class: 'modal-dialog ', role: 'document' }, (modalDialog) => {
|
||||
modalDialog.div({ class: 'modal-content' }, (modelContent) => {
|
||||
parts.forEach((part) => {
|
||||
@@ -357,6 +357,21 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a footer button matching the provided label
|
||||
* @param label Label to show on the button
|
||||
* @param onSelect The callback to call when the button is selected
|
||||
*/
|
||||
protected findFooterButton(label: string): Button {
|
||||
return this._footerButtons.find(e => {
|
||||
try {
|
||||
return e && e.element.innerText === label;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an error in the error message element
|
||||
* @param err Text to show in the error message
|
||||
|
||||
@@ -150,8 +150,8 @@ export class OptionsDialog extends Modal {
|
||||
|
||||
private onOptionLinkClicked(optionName: string): void {
|
||||
var option = this._optionElements[optionName].option;
|
||||
this._optionTitle.innerHtml(option.displayName);
|
||||
this._optionDescription.innerHtml(option.description);
|
||||
this._optionTitle.text(option.displayName);
|
||||
this._optionDescription.text(option.description);
|
||||
}
|
||||
|
||||
private fillInOptions(container: Builder, options: sqlops.ServiceOption[]): void {
|
||||
|
||||
@@ -49,7 +49,7 @@ export function createOptionElement(option: sqlops.ServiceOption, rowContainer:
|
||||
optionWidget.value = optionValue;
|
||||
inputElement = findElement(rowContainer, 'input');
|
||||
} else if (option.valueType === ServiceOptionType.category || option.valueType === ServiceOptionType.boolean) {
|
||||
optionWidget = new SelectBox(possibleInputs, optionValue.toString(), contextViewService);
|
||||
optionWidget = new SelectBox(possibleInputs, optionValue.toString(), contextViewService, undefined, { ariaLabel: option.displayName });
|
||||
DialogHelper.appendInputSelectBox(rowContainer, optionWidget);
|
||||
inputElement = findElement(rowContainer, 'monaco-select-box');
|
||||
} else if (option.valueType === ServiceOptionType.string || option.valueType === ServiceOptionType.password) {
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IThemable } from 'vs/platform/theme/common/styler';
|
||||
import * as objects from 'sql/base/common/objects';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { Dimension, EventType } from 'vs/base/browser/dom';
|
||||
import { $, Builder } from 'vs/base/browser/builder';
|
||||
import { EventType } from 'vs/base/browser/dom';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IActionOptions, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
@@ -17,12 +15,16 @@ import './panelStyles';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface IPanelStyles {
|
||||
}
|
||||
|
||||
export interface IPanelOptions {
|
||||
showHeaderWhenSingleView?: boolean;
|
||||
}
|
||||
|
||||
export interface IPanelView {
|
||||
render(container: HTMLElement): void;
|
||||
layout(dimension: Dimension): void;
|
||||
remove?(): void;
|
||||
}
|
||||
|
||||
export interface IPanelTab {
|
||||
@@ -36,6 +38,10 @@ interface IInternalPanelTab extends IPanelTab {
|
||||
label: Builder;
|
||||
}
|
||||
|
||||
const defaultOptions: IPanelOptions = {
|
||||
showHeaderWhenSingleView: true
|
||||
};
|
||||
|
||||
export type PanelTabIdentifier = string;
|
||||
|
||||
export class TabbedPanel extends Disposable implements IThemable {
|
||||
@@ -49,11 +55,12 @@ export class TabbedPanel extends Disposable implements IThemable {
|
||||
private _actionbar: ActionBar;
|
||||
private _currentDimensions: Dimension;
|
||||
private _collapsed = false;
|
||||
private _headerVisible: boolean;
|
||||
|
||||
private _onTabChange = new Emitter<PanelTabIdentifier>();
|
||||
public onTabChange: Event<PanelTabIdentifier> = this._onTabChange.event;
|
||||
|
||||
constructor(private container: HTMLElement) {
|
||||
constructor(private container: HTMLElement, private options: IPanelOptions = defaultOptions) {
|
||||
super();
|
||||
this.$parent = this._register($('.tabbedPanel'));
|
||||
this.$parent.appendTo(container);
|
||||
@@ -65,7 +72,12 @@ export class TabbedPanel extends Disposable implements IThemable {
|
||||
let actionbarcontainer = $('.title-actions');
|
||||
this._actionbar = new ActionBar(actionbarcontainer.getHTMLElement());
|
||||
this.$header.append(actionbarcontainer);
|
||||
this.$parent.append(this.$header);
|
||||
if (options.showHeaderWhenSingleView) {
|
||||
this._headerVisible = true;
|
||||
this.$parent.append(this.$header);
|
||||
} else {
|
||||
this._headerVisible = false;
|
||||
}
|
||||
this.$body = $('tabBody');
|
||||
this.$body.attr('role', 'tabpanel');
|
||||
this.$body.attr('tabindex', '0');
|
||||
@@ -73,12 +85,16 @@ export class TabbedPanel extends Disposable implements IThemable {
|
||||
}
|
||||
|
||||
public pushTab(tab: IPanelTab): PanelTabIdentifier {
|
||||
let internalTab = objects.clone(tab) as IInternalPanelTab;
|
||||
let internalTab = tab as IInternalPanelTab;
|
||||
this._tabMap.set(tab.identifier, internalTab);
|
||||
this._createTab(internalTab);
|
||||
if (!this._shownTab) {
|
||||
this.showTab(tab.identifier);
|
||||
}
|
||||
if (this._tabMap.size > 1 && !this._headerVisible) {
|
||||
this.$parent.append(this.$header, 0);
|
||||
this._headerVisible = true;
|
||||
}
|
||||
return tab.identifier as PanelTabIdentifier;
|
||||
}
|
||||
|
||||
@@ -139,6 +155,11 @@ export class TabbedPanel extends Disposable implements IThemable {
|
||||
}
|
||||
|
||||
public removeTab(tab: PanelTabIdentifier) {
|
||||
let actualTab = this._tabMap.get(tab);
|
||||
actualTab.header.destroy();
|
||||
if (actualTab.view.remove) {
|
||||
actualTab.view.remove();
|
||||
}
|
||||
this._tabMap.get(tab).header.destroy();
|
||||
this._tabMap.delete(tab);
|
||||
}
|
||||
@@ -151,8 +172,9 @@ export class TabbedPanel extends Disposable implements IThemable {
|
||||
this._currentDimensions = dimension;
|
||||
this.$header.style('width', dimension.width + 'px');
|
||||
this.$body.style('width', dimension.width + 'px');
|
||||
this.$body.style('height', (dimension.height - this.headersize) + 'px');
|
||||
this._layoutCurrentTab(new Dimension(dimension.width, dimension.height - this.headersize));
|
||||
const bodyHeight = dimension.height - (this._headerVisible ? this.headersize : 0);
|
||||
this.$body.style('height', bodyHeight + 'px');
|
||||
this._layoutCurrentTab(new Dimension(dimension.width, bodyHeight));
|
||||
}
|
||||
|
||||
private _layoutCurrentTab(dimension: Dimension): void {
|
||||
|
||||
@@ -62,7 +62,7 @@ export class TabHeaderComponent extends Disposable implements AfterContentInit,
|
||||
tabLabelcontainer.classList.add(this.tab.iconClass);
|
||||
} else {
|
||||
tabLabelcontainer.className = 'tabLabel';
|
||||
tabLabelcontainer.innerHTML = this.tab.title;
|
||||
tabLabelcontainer.textContent = this.tab.title;
|
||||
}
|
||||
tabLabelcontainer.title = this.tab.title;
|
||||
}
|
||||
|
||||
212
src/sql/base/browser/ui/scrollableSplitview/heightMap.ts
Normal file
212
src/sql/base/browser/ui/scrollableSplitview/heightMap.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { INextIterator } from 'vs/base/common/iterator';
|
||||
|
||||
export interface IView {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface IViewItem {
|
||||
view: IView;
|
||||
top: number;
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export class HeightMap {
|
||||
|
||||
private heightMap: IViewItem[];
|
||||
private indexes: { [item: string]: number; };
|
||||
|
||||
constructor() {
|
||||
this.heightMap = [];
|
||||
this.indexes = {};
|
||||
}
|
||||
|
||||
public getContentHeight(): number {
|
||||
let last = this.heightMap[this.heightMap.length - 1];
|
||||
return !last ? 0 : last.top + last.height;
|
||||
}
|
||||
|
||||
public onInsertItems(iterator: INextIterator<IViewItem>, afterItemId: string = null): number {
|
||||
let viewItem: IViewItem;
|
||||
let i: number, j: number;
|
||||
let totalSize: number;
|
||||
let sizeDiff = 0;
|
||||
|
||||
if (afterItemId === null) {
|
||||
i = 0;
|
||||
totalSize = 0;
|
||||
} else {
|
||||
i = this.indexes[afterItemId] + 1;
|
||||
viewItem = this.heightMap[i - 1];
|
||||
|
||||
if (!viewItem) {
|
||||
console.error('view item doesnt exist');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
totalSize = viewItem.top + viewItem.height;
|
||||
}
|
||||
|
||||
let boundSplice = this.heightMap.splice.bind(this.heightMap, i, 0);
|
||||
|
||||
let itemsToInsert: IViewItem[] = [];
|
||||
|
||||
while (viewItem = iterator.next()) {
|
||||
viewItem.top = totalSize + sizeDiff;
|
||||
|
||||
this.indexes[viewItem.view.id] = i++;
|
||||
itemsToInsert.push(viewItem);
|
||||
sizeDiff += viewItem.height;
|
||||
}
|
||||
|
||||
boundSplice.apply(this.heightMap, itemsToInsert);
|
||||
|
||||
for (j = i; j < this.heightMap.length; j++) {
|
||||
viewItem = this.heightMap[j];
|
||||
viewItem.top += sizeDiff;
|
||||
this.indexes[viewItem.view.id] = j;
|
||||
}
|
||||
|
||||
for (j = itemsToInsert.length - 1; j >= 0; j--) {
|
||||
this.onInsertItem(itemsToInsert[j]);
|
||||
}
|
||||
|
||||
for (j = this.heightMap.length - 1; j >= i; j--) {
|
||||
this.onRefreshItem(this.heightMap[j]);
|
||||
}
|
||||
|
||||
return sizeDiff;
|
||||
}
|
||||
|
||||
public onInsertItem(item: IViewItem): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
// Contiguous items
|
||||
public onRemoveItems(iterator: INextIterator<string>): void {
|
||||
let itemId: string;
|
||||
let viewItem: IViewItem;
|
||||
let startIndex: number = null;
|
||||
let i: number;
|
||||
let sizeDiff = 0;
|
||||
|
||||
while (itemId = iterator.next()) {
|
||||
i = this.indexes[itemId];
|
||||
viewItem = this.heightMap[i];
|
||||
|
||||
if (!viewItem) {
|
||||
console.error('view item doesnt exist');
|
||||
return;
|
||||
}
|
||||
|
||||
sizeDiff -= viewItem.height;
|
||||
delete this.indexes[itemId];
|
||||
this.onRemoveItem(viewItem);
|
||||
|
||||
if (startIndex === null) {
|
||||
startIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (sizeDiff === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.heightMap.splice(startIndex, i - startIndex + 1);
|
||||
|
||||
for (i = startIndex; i < this.heightMap.length; i++) {
|
||||
viewItem = this.heightMap[i];
|
||||
viewItem.top += sizeDiff;
|
||||
this.indexes[viewItem.view.id] = i;
|
||||
this.onRefreshItem(viewItem);
|
||||
}
|
||||
}
|
||||
|
||||
public onRemoveItem(item: IViewItem): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
public onRefreshItem(item: IViewItem, needsRender: boolean = false): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
protected updateSize(item: string, size: number): void {
|
||||
let i = this.indexes[item];
|
||||
|
||||
let viewItem = this.heightMap[i];
|
||||
|
||||
viewItem.height = size;
|
||||
}
|
||||
|
||||
protected updateTop(item: string, top: number): void {
|
||||
let i = this.indexes[item];
|
||||
|
||||
let viewItem = this.heightMap[i];
|
||||
|
||||
viewItem.top = top;
|
||||
}
|
||||
|
||||
public itemsCount(): number {
|
||||
return this.heightMap.length;
|
||||
}
|
||||
|
||||
public itemAt(position: number): string {
|
||||
return this.heightMap[this.indexAt(position)].view.id;
|
||||
}
|
||||
|
||||
public withItemsInRange(start: number, end: number, fn: (item: string) => void): void {
|
||||
start = this.indexAt(start);
|
||||
end = this.indexAt(end);
|
||||
for (let i = start; i <= end; i++) {
|
||||
fn(this.heightMap[i].view.id);
|
||||
}
|
||||
}
|
||||
|
||||
public indexAt(position: number): number {
|
||||
let left = 0;
|
||||
let right = this.heightMap.length;
|
||||
let center: number;
|
||||
let item: IViewItem;
|
||||
|
||||
// Binary search
|
||||
while (left < right) {
|
||||
center = Math.floor((left + right) / 2);
|
||||
item = this.heightMap[center];
|
||||
|
||||
if (position < item.top) {
|
||||
right = center;
|
||||
} else if (position >= item.top + item.height) {
|
||||
if (left === center) {
|
||||
break;
|
||||
}
|
||||
left = center;
|
||||
} else {
|
||||
return center;
|
||||
}
|
||||
}
|
||||
|
||||
return this.heightMap.length;
|
||||
}
|
||||
|
||||
public indexAfter(position: number): number {
|
||||
return Math.min(this.indexAt(position) + 1, this.heightMap.length);
|
||||
}
|
||||
|
||||
public itemAtIndex(index: number): IViewItem {
|
||||
return this.heightMap[index];
|
||||
}
|
||||
|
||||
public itemAfter(item: IViewItem): IViewItem {
|
||||
return this.heightMap[this.indexes[item.view.id] + 1] || null;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.heightMap = null;
|
||||
this.indexes = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-scroll-split-view {
|
||||
position: relative;
|
||||
}
|
||||
@@ -0,0 +1,622 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./scrollableSplitview';
|
||||
import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { mapEvent, Emitter, Event, debounceEvent } from 'vs/base/common/event';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { clamp } from 'vs/base/common/numbers';
|
||||
import { range, firstIndex } from 'vs/base/common/arrays';
|
||||
import { Sash, Orientation, ISashEvent as IBaseSashEvent } from 'vs/base/browser/ui/sash/sash';
|
||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { HeightMap, IView as HeightIView, IViewItem as HeightIViewItem } from './heightMap';
|
||||
import { ArrayIterator } from 'vs/base/common/iterator';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
export { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
|
||||
export interface ISplitViewOptions {
|
||||
orientation?: Orientation; // default Orientation.VERTICAL
|
||||
enableResizing?: boolean;
|
||||
}
|
||||
|
||||
const defaultOptions: ISplitViewOptions = {
|
||||
enableResizing: true
|
||||
};
|
||||
|
||||
export interface IView extends HeightIView {
|
||||
readonly minimumSize: number;
|
||||
readonly maximumSize: number;
|
||||
readonly onDidChange: Event<number | undefined>;
|
||||
render(container: HTMLElement, orientation: Orientation): void;
|
||||
layout(size: number, orientation: Orientation): void;
|
||||
}
|
||||
|
||||
interface ISashEvent {
|
||||
sash: Sash;
|
||||
start: number;
|
||||
current: number;
|
||||
}
|
||||
|
||||
interface IViewItem extends HeightIViewItem {
|
||||
view: IView;
|
||||
size: number;
|
||||
container: HTMLElement;
|
||||
disposable: IDisposable;
|
||||
layout(): void;
|
||||
}
|
||||
|
||||
interface ISashItem {
|
||||
sash: Sash;
|
||||
disposable: IDisposable;
|
||||
}
|
||||
|
||||
interface ISashDragState {
|
||||
index: number;
|
||||
start: number;
|
||||
sizes: number[];
|
||||
}
|
||||
|
||||
enum State {
|
||||
Idle,
|
||||
Busy
|
||||
}
|
||||
|
||||
function pushToEnd<T>(arr: T[], value: T): T[] {
|
||||
let didFindValue = false;
|
||||
|
||||
const result = arr.filter(v => {
|
||||
if (v === value) {
|
||||
didFindValue = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (didFindValue) {
|
||||
result.push(value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export class ScrollableSplitView extends HeightMap implements IDisposable {
|
||||
|
||||
private orientation: Orientation;
|
||||
private el: HTMLElement;
|
||||
private size = 0;
|
||||
private contentSize = 0;
|
||||
private viewItems: IViewItem[] = [];
|
||||
private sashItems: ISashItem[] = [];
|
||||
private sashDragState: ISashDragState;
|
||||
private state: State = State.Idle;
|
||||
private scrollable: ScrollableElement;
|
||||
|
||||
private options: ISplitViewOptions;
|
||||
|
||||
private dirtyState = false;
|
||||
|
||||
private lastRenderTop: number;
|
||||
private lastRenderHeight: number;
|
||||
|
||||
private _onDidSashChange = new Emitter<void>();
|
||||
readonly onDidSashChange = this._onDidSashChange.event;
|
||||
private _onDidSashReset = new Emitter<void>();
|
||||
readonly onDidSashReset = this._onDidSashReset.event;
|
||||
|
||||
get length(): number {
|
||||
return this.viewItems.length;
|
||||
}
|
||||
|
||||
constructor(container: HTMLElement, options: ISplitViewOptions = {}) {
|
||||
super();
|
||||
this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
|
||||
|
||||
this.options = mixin(options, defaultOptions, false);
|
||||
|
||||
this.el = document.createElement('div');
|
||||
this.scrollable = new ScrollableElement(this.el, {});
|
||||
debounceEvent(this.scrollable.onScroll, (l, e) => e, 25)(e => {
|
||||
this.render(e.scrollTop, e.height);
|
||||
this.relayout();
|
||||
});
|
||||
let domNode = this.scrollable.getDomNode();
|
||||
dom.addClass(this.el, 'monaco-scroll-split-view');
|
||||
dom.addClass(domNode, 'monaco-split-view2');
|
||||
dom.addClass(domNode, this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal');
|
||||
container.appendChild(domNode);
|
||||
}
|
||||
|
||||
addViews(views: IView[], sizes: number[], index = this.viewItems.length): void {
|
||||
if (this.state !== State.Idle) {
|
||||
throw new Error('Cant modify splitview');
|
||||
}
|
||||
|
||||
this.state = State.Busy;
|
||||
|
||||
for (let i = 0; i < views.length; i++) {
|
||||
let view = views[i], size = sizes[i];
|
||||
|
||||
// Add view
|
||||
const container = dom.$('.split-view-view');
|
||||
|
||||
const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size));
|
||||
const containerDisposable = toDisposable(() => {
|
||||
if (container.parentElement) {
|
||||
this.el.removeChild(container);
|
||||
}
|
||||
this.onRemoveItems(new ArrayIterator([item.view.id]));
|
||||
});
|
||||
const disposable = combinedDisposable([onChangeDisposable, containerDisposable]);
|
||||
|
||||
const layoutContainer = this.orientation === Orientation.VERTICAL
|
||||
? size => item.container.style.height = `${item.size}px`
|
||||
: size => item.container.style.width = `${item.size}px`;
|
||||
|
||||
const layout = () => {
|
||||
layoutContainer(item.size);
|
||||
item.view.layout(item.size, this.orientation);
|
||||
};
|
||||
|
||||
size = Math.round(size);
|
||||
const item: IViewItem = { view, container, size, layout, disposable, height: size, top: 0, width: 0 };
|
||||
this.viewItems.splice(index, 0, item);
|
||||
|
||||
this.onInsertItems(new ArrayIterator([item]), index > 0 ? this.viewItems[index - 1].view.id : undefined);
|
||||
|
||||
// Add sash
|
||||
if (this.options.enableResizing && this.viewItems.length > 1) {
|
||||
const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
|
||||
const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) };
|
||||
const sash = new Sash(this.el, layoutProvider, { orientation });
|
||||
const sashEventMapper = this.orientation === Orientation.VERTICAL
|
||||
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY })
|
||||
: (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX });
|
||||
|
||||
const onStart = mapEvent(sash.onDidStart, sashEventMapper);
|
||||
const onStartDisposable = onStart(this.onSashStart, this);
|
||||
const onChange = mapEvent(sash.onDidChange, sashEventMapper);
|
||||
const onSashChangeDisposable = onChange(this.onSashChange, this);
|
||||
const onEnd = mapEvent<void, void>(sash.onDidEnd, () => null);
|
||||
const onEndDisposable = onEnd(() => this._onDidSashChange.fire());
|
||||
const onDidReset = mapEvent<void, void>(sash.onDidReset, () => null);
|
||||
const onDidResetDisposable = onDidReset(() => this._onDidSashReset.fire());
|
||||
|
||||
const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, onEndDisposable, onDidResetDisposable, sash]);
|
||||
const sashItem: ISashItem = { sash, disposable };
|
||||
|
||||
this.sashItems.splice(index - 1, 0, sashItem);
|
||||
}
|
||||
|
||||
view.render(container, this.orientation);
|
||||
}
|
||||
|
||||
this.relayout(index);
|
||||
this.state = State.Idle;
|
||||
}
|
||||
|
||||
addView(view: IView, size: number, index = this.viewItems.length): void {
|
||||
if (this.state !== State.Idle) {
|
||||
throw new Error('Cant modify splitview');
|
||||
}
|
||||
|
||||
this.state = State.Busy;
|
||||
|
||||
// Add view
|
||||
const container = dom.$('.split-view-view');
|
||||
|
||||
const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size));
|
||||
const containerDisposable = toDisposable(() => {
|
||||
if (container.parentElement) {
|
||||
this.el.removeChild(container);
|
||||
}
|
||||
this.onRemoveItems(new ArrayIterator([item.view.id]));
|
||||
});
|
||||
const disposable = combinedDisposable([onChangeDisposable, containerDisposable]);
|
||||
|
||||
const layoutContainer = this.orientation === Orientation.VERTICAL
|
||||
? size => item.container.style.height = `${item.size}px`
|
||||
: size => item.container.style.width = `${item.size}px`;
|
||||
|
||||
const layout = () => {
|
||||
layoutContainer(item.size);
|
||||
item.view.layout(item.size, this.orientation);
|
||||
};
|
||||
|
||||
size = Math.round(size);
|
||||
const item: IViewItem = { view, container, size, layout, disposable, height: size, top: 0, width: 0 };
|
||||
this.viewItems.splice(index, 0, item);
|
||||
|
||||
this.onInsertItems(new ArrayIterator([item]), index > 0 ? this.viewItems[index - 1].view.id : undefined);
|
||||
|
||||
// Add sash
|
||||
if (this.options.enableResizing && this.viewItems.length > 1) {
|
||||
const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
|
||||
const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) };
|
||||
const sash = new Sash(this.el, layoutProvider, { orientation });
|
||||
const sashEventMapper = this.orientation === Orientation.VERTICAL
|
||||
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY })
|
||||
: (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX });
|
||||
|
||||
const onStart = mapEvent(sash.onDidStart, sashEventMapper);
|
||||
const onStartDisposable = onStart(this.onSashStart, this);
|
||||
const onChange = mapEvent(sash.onDidChange, sashEventMapper);
|
||||
const onSashChangeDisposable = onChange(this.onSashChange, this);
|
||||
const onEnd = mapEvent<void, void>(sash.onDidEnd, () => null);
|
||||
const onEndDisposable = onEnd(() => this._onDidSashChange.fire());
|
||||
const onDidReset = mapEvent<void, void>(sash.onDidReset, () => null);
|
||||
const onDidResetDisposable = onDidReset(() => this._onDidSashReset.fire());
|
||||
|
||||
const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, onEndDisposable, onDidResetDisposable, sash]);
|
||||
const sashItem: ISashItem = { sash, disposable };
|
||||
|
||||
sash.hide();
|
||||
this.sashItems.splice(index - 1, 0, sashItem);
|
||||
}
|
||||
|
||||
view.render(container, this.orientation);
|
||||
this.relayout(index);
|
||||
this.state = State.Idle;
|
||||
}
|
||||
|
||||
removeView(index: number): void {
|
||||
if (this.state !== State.Idle) {
|
||||
throw new Error('Cant modify splitview');
|
||||
}
|
||||
|
||||
this.state = State.Busy;
|
||||
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove view
|
||||
const viewItem = this.viewItems.splice(index, 1)[0];
|
||||
viewItem.disposable.dispose();
|
||||
|
||||
// Remove sash
|
||||
if (this.options.enableResizing && this.viewItems.length >= 1) {
|
||||
const sashIndex = Math.max(index - 1, 0);
|
||||
const sashItem = this.sashItems.splice(sashIndex, 1)[0];
|
||||
sashItem.disposable.dispose();
|
||||
} else {
|
||||
this.lastRenderHeight = NaN, this.lastRenderTop = NaN;
|
||||
}
|
||||
|
||||
this.relayout();
|
||||
this.state = State.Idle;
|
||||
}
|
||||
|
||||
moveView(from: number, to: number): void {
|
||||
if (this.state !== State.Idle) {
|
||||
throw new Error('Cant modify splitview');
|
||||
}
|
||||
|
||||
this.state = State.Busy;
|
||||
|
||||
if (from < 0 || from >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (to < 0 || to >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (from === to) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewItem = this.viewItems.splice(from, 1)[0];
|
||||
this.viewItems.splice(to, 0, viewItem);
|
||||
|
||||
if (to + 1 < this.viewItems.length) {
|
||||
this.el.insertBefore(viewItem.container, this.viewItems[to + 1].container);
|
||||
} else {
|
||||
this.el.appendChild(viewItem.container);
|
||||
}
|
||||
|
||||
this.layoutViews();
|
||||
this.state = State.Idle;
|
||||
}
|
||||
|
||||
private relayout(lowPriorityIndex?: number): void {
|
||||
const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndex);
|
||||
}
|
||||
|
||||
layout(size: number): void {
|
||||
const previousSize = Math.max(this.size, this.contentSize);
|
||||
this.size = size;
|
||||
this.resize(this.viewItems.length - 1, size - previousSize);
|
||||
}
|
||||
|
||||
private render(scrollTop: number, viewHeight: number): void {
|
||||
let i: number;
|
||||
let stop: number;
|
||||
|
||||
let renderTop = scrollTop;
|
||||
let renderBottom = scrollTop + viewHeight;
|
||||
let thisRenderBottom = this.lastRenderTop + this.lastRenderHeight;
|
||||
|
||||
// when view scrolls down, start rendering from the renderBottom
|
||||
for (i = this.indexAfter(renderBottom) - 1, stop = this.indexAt(Math.max(thisRenderBottom, renderTop)); i >= stop; i--) {
|
||||
if (this.insertItemInDOM(<IViewItem>this.itemAtIndex(i))) {
|
||||
this.dirtyState = true;
|
||||
}
|
||||
}
|
||||
|
||||
// when view scrolls up, start rendering from either this.renderTop or renderBottom
|
||||
for (i = Math.min(this.indexAt(this.lastRenderTop), this.indexAfter(renderBottom)) - 1, stop = this.indexAt(renderTop); i >= stop; i--) {
|
||||
if (this.insertItemInDOM(<IViewItem>this.itemAtIndex(i))) {
|
||||
this.dirtyState = true;
|
||||
}
|
||||
}
|
||||
|
||||
// when view scrolls down, start unrendering from renderTop
|
||||
for (i = this.indexAt(this.lastRenderTop), stop = Math.min(this.indexAt(renderTop), this.indexAfter(thisRenderBottom)); i < stop; i++) {
|
||||
if (this.removeItemFromDOM(<IViewItem>this.itemAtIndex(i))) {
|
||||
this.dirtyState = true;
|
||||
}
|
||||
}
|
||||
|
||||
// when view scrolls up, start unrendering from either renderBottom this.renderTop
|
||||
for (i = Math.max(this.indexAfter(renderBottom), this.indexAt(this.lastRenderTop)), stop = this.indexAfter(thisRenderBottom); i < stop; i++) {
|
||||
if (this.removeItemFromDOM(<IViewItem>this.itemAtIndex(i))) {
|
||||
this.dirtyState = true;
|
||||
}
|
||||
}
|
||||
|
||||
let topItem = this.itemAtIndex(this.indexAt(renderTop));
|
||||
|
||||
if (topItem) {
|
||||
this.el.style.top = (topItem.top - renderTop) + 'px';
|
||||
}
|
||||
|
||||
this.lastRenderTop = renderTop;
|
||||
this.lastRenderHeight = renderBottom - renderTop;
|
||||
}
|
||||
|
||||
private onSashStart({ sash, start }: ISashEvent): void {
|
||||
const index = firstIndex(this.sashItems, item => item.sash === sash);
|
||||
const sizes = this.viewItems.map(i => i.size);
|
||||
|
||||
// const upIndexes = range(index, -1);
|
||||
// const collapseUp = upIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
|
||||
// const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
|
||||
|
||||
// const downIndexes = range(index + 1, this.viewItems.length);
|
||||
// const collapseDown = downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
|
||||
// const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
|
||||
|
||||
// const minDelta = -Math.min(collapseUp, expandDown);
|
||||
// const maxDelta = Math.min(collapseDown, expandUp);
|
||||
|
||||
this.sashDragState = { start, index, sizes };
|
||||
}
|
||||
|
||||
private onSashChange({ sash, current }: ISashEvent): void {
|
||||
const { index, start, sizes } = this.sashDragState;
|
||||
const delta = current - start;
|
||||
|
||||
this.resize(index, delta, sizes);
|
||||
}
|
||||
|
||||
private onViewChange(item: IViewItem, size: number | undefined): void {
|
||||
const index = this.viewItems.indexOf(item);
|
||||
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
size = typeof size === 'number' ? size : item.size;
|
||||
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
||||
item.size = size;
|
||||
this.relayout(index);
|
||||
}
|
||||
|
||||
resizeView(index: number, size: number): void {
|
||||
if (this.state !== State.Idle) {
|
||||
throw new Error('Cant modify splitview');
|
||||
}
|
||||
|
||||
this.state = State.Busy;
|
||||
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const item = this.viewItems[index];
|
||||
size = Math.round(size);
|
||||
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
||||
let delta = size - item.size;
|
||||
|
||||
if (delta !== 0 && index < this.viewItems.length - 1) {
|
||||
const downIndexes = range(index + 1, this.viewItems.length);
|
||||
const collapseDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
|
||||
const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
|
||||
const deltaDown = clamp(delta, -expandDown, collapseDown);
|
||||
|
||||
this.resize(index, deltaDown);
|
||||
delta -= deltaDown;
|
||||
}
|
||||
|
||||
if (delta !== 0 && index > 0) {
|
||||
const upIndexes = range(index - 1, -1);
|
||||
const collapseUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
|
||||
const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
|
||||
const deltaUp = clamp(-delta, -collapseUp, expandUp);
|
||||
|
||||
this.resize(index - 1, deltaUp);
|
||||
}
|
||||
|
||||
this.state = State.Idle;
|
||||
}
|
||||
|
||||
// DOM changes
|
||||
|
||||
private insertItemInDOM(item: IViewItem): boolean {
|
||||
if (item.container.parentElement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let elementAfter: HTMLElement = null;
|
||||
let itemAfter = <IViewItem>this.itemAfter(item);
|
||||
|
||||
if (itemAfter && itemAfter.container) {
|
||||
elementAfter = itemAfter.container;
|
||||
}
|
||||
|
||||
if (elementAfter === null) {
|
||||
this.el.appendChild(item.container);
|
||||
} else {
|
||||
try {
|
||||
this.el.insertBefore(item.container, elementAfter);
|
||||
} catch (e) {
|
||||
// console.warn('Failed to locate previous tree element');
|
||||
this.el.appendChild(item.container);
|
||||
}
|
||||
}
|
||||
|
||||
item.layout();
|
||||
return true;
|
||||
}
|
||||
|
||||
private removeItemFromDOM(item: IViewItem): boolean {
|
||||
if (!item || !item.container || !item.container.parentElement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.el.removeChild(item.container);
|
||||
return true;
|
||||
}
|
||||
|
||||
getViewSize(index: number): number {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return this.viewItems[index].size;
|
||||
}
|
||||
|
||||
private resize(index: number, delta: number, sizes = this.viewItems.map(i => i.size), lowPriorityIndex?: number): void {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (delta !== 0) {
|
||||
let upIndexes = range(index, -1);
|
||||
let downIndexes = range(index + 1, this.viewItems.length);
|
||||
|
||||
if (typeof lowPriorityIndex === 'number') {
|
||||
upIndexes = pushToEnd(upIndexes, lowPriorityIndex);
|
||||
downIndexes = pushToEnd(downIndexes, lowPriorityIndex);
|
||||
}
|
||||
|
||||
const upItems = upIndexes.map(i => this.viewItems[i]);
|
||||
const upSizes = upIndexes.map(i => sizes[i]);
|
||||
|
||||
const downItems = downIndexes.map(i => this.viewItems[i]);
|
||||
const downSizes = downIndexes.map(i => sizes[i]);
|
||||
|
||||
for (let i = 0, deltaUp = delta; deltaUp !== 0 && i < upItems.length; i++) {
|
||||
const item = upItems[i];
|
||||
const size = clamp(upSizes[i] + deltaUp, item.view.minimumSize, item.view.maximumSize);
|
||||
const viewDelta = size - upSizes[i];
|
||||
|
||||
deltaUp -= viewDelta;
|
||||
item.size = size;
|
||||
}
|
||||
|
||||
for (let i = 0, deltaDown = delta; deltaDown !== 0 && i < downItems.length; i++) {
|
||||
const item = downItems[i];
|
||||
const size = clamp(downSizes[i] - deltaDown, item.view.minimumSize, item.view.maximumSize);
|
||||
const viewDelta = size - downSizes[i];
|
||||
|
||||
deltaDown += viewDelta;
|
||||
item.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
let contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
let emptyDelta = this.size - contentSize;
|
||||
|
||||
for (let i = this.viewItems.length - 1; emptyDelta > 0 && i >= 0; i--) {
|
||||
const item = this.viewItems[i];
|
||||
const size = clamp(item.size + emptyDelta, item.view.minimumSize, item.view.maximumSize);
|
||||
const viewDelta = size - item.size;
|
||||
|
||||
emptyDelta -= viewDelta;
|
||||
item.size = size;
|
||||
}
|
||||
|
||||
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
|
||||
this.scrollable.setScrollDimensions({
|
||||
scrollHeight: this.contentSize,
|
||||
height: this.size
|
||||
});
|
||||
|
||||
this.layoutViews();
|
||||
}
|
||||
|
||||
private layoutViews(): void {
|
||||
if (this.dirtyState) {
|
||||
for (let i = this.indexAt(this.lastRenderTop); i <= this.indexAfter(this.lastRenderTop + this.lastRenderHeight) - 1; i++) {
|
||||
this.viewItems[i].layout();
|
||||
if (this.options.enableResizing) {
|
||||
this.sashItems[i].sash.layout();
|
||||
}
|
||||
}
|
||||
this.dirtyState = false;
|
||||
}
|
||||
|
||||
// Update sashes enablement
|
||||
// let previous = false;
|
||||
// const collapsesDown = this.viewItems.map(i => previous = (i.size - i.view.minimumSize > 0) || previous);
|
||||
|
||||
// previous = false;
|
||||
// const expandsDown = this.viewItems.map(i => previous = (i.view.maximumSize - i.size > 0) || previous);
|
||||
|
||||
// const reverseViews = [...this.viewItems].reverse();
|
||||
// previous = false;
|
||||
// const collapsesUp = reverseViews.map(i => previous = (i.size - i.view.minimumSize > 0) || previous).reverse();
|
||||
|
||||
// previous = false;
|
||||
// const expandsUp = reverseViews.map(i => previous = (i.view.maximumSize - i.size > 0) || previous).reverse();
|
||||
|
||||
// this.sashItems.forEach((s, i) => {
|
||||
// if ((collapsesDown[i] && expandsUp[i + 1]) || (expandsDown[i] && collapsesUp[i + 1])) {
|
||||
// s.sash.enable();
|
||||
// } else {
|
||||
// s.sash.disable();
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
private getSashPosition(sash: Sash): number {
|
||||
let position = 0;
|
||||
|
||||
for (let i = 0; i < this.sashItems.length; i++) {
|
||||
position += this.viewItems[i].size;
|
||||
|
||||
if (this.sashItems[i].sash === sash) {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.viewItems.forEach(i => i.disposable.dispose());
|
||||
this.viewItems = [];
|
||||
|
||||
this.sashItems.forEach(i => i.disposable.dispose());
|
||||
this.sashItems = [];
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ export class SelectBox extends AngularDisposable implements OnInit, OnChanges {
|
||||
@Input() options: string[];
|
||||
@Input() selectedOption: string;
|
||||
@Input() onlyEmitOnChange = false;
|
||||
@Input('aria-label') ariaLabel: string;
|
||||
|
||||
@Output() onDidSelect = new EventEmitter<ISelectData>();
|
||||
|
||||
@@ -42,7 +43,7 @@ export class SelectBox extends AngularDisposable implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._selectbox = new vsSelectBox(this.options, this.selectedOption, this.contextViewService);
|
||||
this._selectbox = new vsSelectBox(this.options, this.selectedOption, this.contextViewService, undefined, { ariaLabel: this.ariaLabel });
|
||||
this._selectbox.render(this._el.nativeElement);
|
||||
this._selectbox.onDidSelect(e => {
|
||||
if (this.onlyEmitOnChange) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import { SelectBox as vsSelectBox, ISelectBoxStyles as vsISelectBoxStyles } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { SelectBox as vsSelectBox, ISelectBoxStyles as vsISelectBoxStyles, ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { IContextViewProvider, AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
@@ -46,8 +46,8 @@ export class SelectBox extends vsSelectBox {
|
||||
private inputValidationErrorBackground: Color;
|
||||
private element: HTMLElement;
|
||||
|
||||
constructor(options: string[], selectedOption: string, contextViewProvider: IContextViewProvider, container?: HTMLElement) {
|
||||
super(options, 0, contextViewProvider);
|
||||
constructor(options: string[], selectedOption: string, contextViewProvider: IContextViewProvider, container?: HTMLElement, selectBoxOptions?: ISelectBoxOptions) {
|
||||
super(options, 0, contextViewProvider, undefined, selectBoxOptions);
|
||||
this._optionsDictionary = new Array();
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
this._optionsDictionary[options[i]] = i;
|
||||
@@ -84,6 +84,7 @@ export class SelectBox extends vsSelectBox {
|
||||
this.inputValidationWarningBackground = styles.inputValidationWarningBackground;
|
||||
this.inputValidationErrorBorder = styles.inputValidationErrorBorder;
|
||||
this.inputValidationErrorBackground = styles.inputValidationErrorBackground;
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public selectWithOptionName(optionName: string): void {
|
||||
|
||||
215
src/sql/base/browser/ui/table/asyncDataView.ts
Normal file
215
src/sql/base/browser/ui/table/asyncDataView.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export interface IObservableCollection<T> {
|
||||
getLength(): number;
|
||||
at(index: number): T;
|
||||
getRange(start: number, end: number): T[];
|
||||
setCollectionChangedCallback(callback: (change: CollectionChange, startIndex: number, count: number) => void): void;
|
||||
}
|
||||
|
||||
export interface IGridDataRow {
|
||||
row?: number;
|
||||
values: any[];
|
||||
}
|
||||
|
||||
export enum CollectionChange {
|
||||
ItemsReplaced
|
||||
}
|
||||
|
||||
class LoadCancellationToken {
|
||||
isCancelled: boolean;
|
||||
}
|
||||
|
||||
class DataWindow<TData> {
|
||||
private _dataSourceLength: number;
|
||||
private _data: TData[];
|
||||
private _length: number = 0;
|
||||
private _offsetFromDataSource: number = -1;
|
||||
|
||||
private loadFunction: (offset: number, count: number) => Thenable<TData[]>;
|
||||
private lastLoadCancellationToken: LoadCancellationToken;
|
||||
private loadCompleteCallback: (start: number, end: number) => void;
|
||||
private placeholderItemGenerator: (index: number) => TData;
|
||||
|
||||
constructor(dataSourceLength: number,
|
||||
loadFunction: (offset: number, count: number) => Thenable<TData[]>,
|
||||
placeholderItemGenerator: (index: number) => TData,
|
||||
loadCompleteCallback: (start: number, end: number) => void) {
|
||||
this._dataSourceLength = dataSourceLength;
|
||||
this.loadFunction = loadFunction;
|
||||
this.placeholderItemGenerator = placeholderItemGenerator;
|
||||
this.loadCompleteCallback = loadCompleteCallback;
|
||||
}
|
||||
|
||||
getStartIndex(): number {
|
||||
return this._offsetFromDataSource;
|
||||
}
|
||||
|
||||
getEndIndex(): number {
|
||||
return this._offsetFromDataSource + this._length;
|
||||
}
|
||||
|
||||
contains(dataSourceIndex: number): boolean {
|
||||
return dataSourceIndex >= this.getStartIndex() && dataSourceIndex < this.getEndIndex();
|
||||
}
|
||||
|
||||
getItem(index: number): TData {
|
||||
if (!this._data) {
|
||||
return this.placeholderItemGenerator(index);
|
||||
}
|
||||
return this._data[index - this._offsetFromDataSource];
|
||||
}
|
||||
|
||||
positionWindow(offset: number, length: number): void {
|
||||
this._offsetFromDataSource = offset;
|
||||
this._length = length;
|
||||
this._data = undefined;
|
||||
|
||||
if (this.lastLoadCancellationToken) {
|
||||
this.lastLoadCancellationToken.isCancelled = true;
|
||||
}
|
||||
|
||||
if (length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cancellationToken = new LoadCancellationToken();
|
||||
this.lastLoadCancellationToken = cancellationToken;
|
||||
this.loadFunction(offset, length).then(data => {
|
||||
if (!cancellationToken.isCancelled) {
|
||||
this._data = data;
|
||||
this.loadCompleteCallback(this._offsetFromDataSource, this._offsetFromDataSource + this._length);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class VirtualizedCollection<TData> implements IObservableCollection<TData> {
|
||||
|
||||
private _length: number;
|
||||
private _windowSize: number;
|
||||
private _bufferWindowBefore: DataWindow<TData>;
|
||||
private _window: DataWindow<TData>;
|
||||
private _bufferWindowAfter: DataWindow<TData>;
|
||||
|
||||
private collectionChangedCallback: (change: CollectionChange, startIndex: number, count: number) => void;
|
||||
|
||||
constructor(windowSize: number,
|
||||
length: number,
|
||||
loadFn: (offset: number, count: number) => Thenable<TData[]>,
|
||||
private _placeHolderGenerator: (index: number) => TData) {
|
||||
this._windowSize = windowSize;
|
||||
this._length = length;
|
||||
|
||||
let loadCompleteCallback = (start: number, end: number) => {
|
||||
if (this.collectionChangedCallback) {
|
||||
this.collectionChangedCallback(CollectionChange.ItemsReplaced, start, end - start);
|
||||
}
|
||||
};
|
||||
|
||||
this._bufferWindowBefore = new DataWindow(length, loadFn, _placeHolderGenerator, loadCompleteCallback);
|
||||
this._window = new DataWindow(length, loadFn, _placeHolderGenerator, loadCompleteCallback);
|
||||
this._bufferWindowAfter = new DataWindow(length, loadFn, _placeHolderGenerator, loadCompleteCallback);
|
||||
}
|
||||
|
||||
setCollectionChangedCallback(callback: (change: CollectionChange, startIndex: number, count: number) => void): void {
|
||||
this.collectionChangedCallback = callback;
|
||||
}
|
||||
|
||||
getLength(): number {
|
||||
return this._length;
|
||||
}
|
||||
|
||||
at(index: number): TData {
|
||||
return this.getRange(index, index + 1)[0];
|
||||
}
|
||||
|
||||
getRange(start: number, end: number): TData[] {
|
||||
|
||||
// current data may contain placeholders
|
||||
let currentData = this.getRangeFromCurrent(start, end);
|
||||
|
||||
// only shift window and make promise of refreshed data in following condition:
|
||||
if (start < this._bufferWindowBefore.getStartIndex() || end > this._bufferWindowAfter.getEndIndex()) {
|
||||
// jump, reset
|
||||
this.resetWindowsAroundIndex(start);
|
||||
} else if (end <= this._bufferWindowBefore.getEndIndex()) {
|
||||
// scroll up, shift up
|
||||
let windowToRecycle = this._bufferWindowAfter;
|
||||
this._bufferWindowAfter = this._window;
|
||||
this._window = this._bufferWindowBefore;
|
||||
this._bufferWindowBefore = windowToRecycle;
|
||||
let newWindowOffset = Math.max(0, this._window.getStartIndex() - this._windowSize);
|
||||
|
||||
this._bufferWindowBefore.positionWindow(newWindowOffset, this._window.getStartIndex() - newWindowOffset);
|
||||
} else if (start >= this._bufferWindowAfter.getStartIndex()) {
|
||||
// scroll down, shift down
|
||||
let windowToRecycle = this._bufferWindowBefore;
|
||||
this._bufferWindowBefore = this._window;
|
||||
this._window = this._bufferWindowAfter;
|
||||
this._bufferWindowAfter = windowToRecycle;
|
||||
let newWindowOffset = Math.min(this._window.getStartIndex() + this._windowSize, this._length);
|
||||
let newWindowLength = Math.min(this._length - newWindowOffset, this._windowSize);
|
||||
|
||||
this._bufferWindowAfter.positionWindow(newWindowOffset, newWindowLength);
|
||||
}
|
||||
|
||||
return currentData;
|
||||
}
|
||||
|
||||
private getRangeFromCurrent(start: number, end: number): TData[] {
|
||||
let currentData = [];
|
||||
for (let i = 0; i < end - start; i++) {
|
||||
currentData.push(this.getDataFromCurrent(start + i));
|
||||
}
|
||||
|
||||
return currentData;
|
||||
}
|
||||
|
||||
private getDataFromCurrent(index: number): TData {
|
||||
if (this._bufferWindowBefore.contains(index)) {
|
||||
return this._bufferWindowBefore.getItem(index);
|
||||
} else if (this._bufferWindowAfter.contains(index)) {
|
||||
return this._bufferWindowAfter.getItem(index);
|
||||
} else if (this._window.contains(index)) {
|
||||
return this._window.getItem(index);
|
||||
}
|
||||
|
||||
return this._placeHolderGenerator(index);
|
||||
}
|
||||
|
||||
private resetWindowsAroundIndex(index: number): void {
|
||||
|
||||
let bufferWindowBeforeStart = Math.max(0, index - this._windowSize * 1.5);
|
||||
let bufferWindowBeforeEnd = Math.max(0, index - this._windowSize / 2);
|
||||
this._bufferWindowBefore.positionWindow(bufferWindowBeforeStart, bufferWindowBeforeEnd - bufferWindowBeforeStart);
|
||||
|
||||
let mainWindowStart = bufferWindowBeforeEnd;
|
||||
let mainWindowEnd = Math.min(mainWindowStart + this._windowSize, this._length);
|
||||
this._window.positionWindow(mainWindowStart, mainWindowEnd - mainWindowStart);
|
||||
|
||||
let bufferWindowAfterStart = mainWindowEnd;
|
||||
let bufferWindowAfterEnd = Math.min(bufferWindowAfterStart + this._windowSize, this._length);
|
||||
this._bufferWindowAfter.positionWindow(bufferWindowAfterStart, bufferWindowAfterEnd - bufferWindowAfterStart);
|
||||
}
|
||||
}
|
||||
|
||||
export class AsyncDataProvider<TData extends IGridDataRow> implements Slick.DataProvider<TData> {
|
||||
|
||||
constructor(private dataRows: IObservableCollection<TData>) { }
|
||||
|
||||
public getLength(): number {
|
||||
return this.dataRows ? this.dataRows.getLength() : 0;
|
||||
}
|
||||
|
||||
public getItem(index: number): TData {
|
||||
return !this.dataRows ? undefined : this.dataRows.at(index);
|
||||
}
|
||||
|
||||
public getRange(start: number, end: number): TData[] {
|
||||
return !this.dataRows ? undefined : this.dataRows.getRange(start, end);
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,8 @@
|
||||
background-image: url('down.svg');
|
||||
}
|
||||
|
||||
.vs-dark .slick-header-menubutton {
|
||||
.vs-dark .slick-header-menubutton,
|
||||
.hc-black .slick-header-menubutton {
|
||||
background-image: url('down-inverse.svg');
|
||||
}
|
||||
|
||||
@@ -54,7 +55,8 @@
|
||||
background-image: url('filter.svg');
|
||||
}
|
||||
|
||||
.vs-dark .slick-header-menubutton.filtered {
|
||||
.vs-dark .slick-header-menubutton.filtered,
|
||||
.hc-black .slick-header-menubutton.filtered {
|
||||
background-image: url('filter_inverse.svg');
|
||||
}
|
||||
|
||||
@@ -74,6 +76,10 @@
|
||||
background: none repeat scroll 0 0 #333333;
|
||||
}
|
||||
|
||||
.hc-black .slick-header-menu {
|
||||
background: none repeat scroll 0 0 #000000;
|
||||
}
|
||||
|
||||
.slick-header-menu a.monaco-button.monaco-text-button {
|
||||
width: 60px;
|
||||
margin: 6px 6px 6px 6px;
|
||||
@@ -144,4 +150,8 @@ label {
|
||||
|
||||
.vs-dark .slick-header-menu > input.input {
|
||||
color: #4a4a4a;
|
||||
}
|
||||
|
||||
.hc-black .slick-header-menu > input.input {
|
||||
color: #000000;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
|
||||
/**
|
||||
* Implements the various additional navigation keybindings we want out of slickgrid
|
||||
*/
|
||||
export class AdditionalKeyBindings<T> implements Slick.Plugin<T> {
|
||||
private grid: Slick.Grid<T>;
|
||||
private handler = new Slick.EventHandler();
|
||||
|
||||
public init(grid: Slick.Grid<T>) {
|
||||
this.grid = grid;
|
||||
this.handler.subscribe(this.grid.onKeyDown, (e, args) => this.handleKeyDown(e, args));
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.handler.unsubscribeAll();
|
||||
}
|
||||
|
||||
private handleKeyDown(e: KeyboardEvent, args: Slick.OnKeyDownEventArgs<T>): void {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
let handled = true;
|
||||
|
||||
if (event.equals(KeyCode.RightArrow | KeyMod.CtrlCmd)) {
|
||||
this.grid.setActiveCell(args.row, this.grid.getColumns().length - 1);
|
||||
} else if (event.equals(KeyCode.LeftArrow | KeyMod.CtrlCmd)) {
|
||||
// account for row column
|
||||
if (this.grid.canCellBeActive(args.row, 0)) {
|
||||
this.grid.setActiveCell(args.row, 0);
|
||||
} else {
|
||||
this.grid.setActiveCell(args.row, 1);
|
||||
}
|
||||
} else if (event.equals(KeyCode.UpArrow | KeyMod.CtrlCmd)) {
|
||||
this.grid.setActiveCell(0, args.cell);
|
||||
} else if (event.equals(KeyCode.DownArrow | KeyMod.CtrlCmd)) {
|
||||
this.grid.setActiveCell(this.grid.getDataLength() - 1, args.cell);
|
||||
} else if (event.equals(KeyCode.Home | KeyMod.CtrlCmd)) {
|
||||
// account for row column
|
||||
if (this.grid.canCellBeActive(0, 0)) {
|
||||
this.grid.setActiveCell(0, 0);
|
||||
} else {
|
||||
this.grid.setActiveCell(0, 1);
|
||||
}
|
||||
} else if (event.equals(KeyCode.End | KeyMod.CtrlCmd)) {
|
||||
this.grid.setActiveCell(this.grid.getDataLength() - 1, this.grid.getColumns().length - 1);
|
||||
} else {
|
||||
handled = false;
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export class AutoColumnSize<T> implements Slick.Plugin<T> {
|
||||
private _context: CanvasRenderingContext2D;
|
||||
private _options: IAutoColumnSizeOptions;
|
||||
|
||||
constructor(options: IAutoColumnSizeOptions) {
|
||||
constructor(options: IAutoColumnSizeOptions = defaultOptions) {
|
||||
this._options = mixin(options, defaultOptions, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
// Drag select selection model gist taken from https://gist.github.com/skoon/5312536
|
||||
// heavily modified
|
||||
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
require.__$__nodeRequire('slickgrid/plugins/slick.cellrangedecorator');
|
||||
require.__$__nodeRequire('slickgrid/plugins/slick.cellrangeselector');
|
||||
|
||||
export interface ICellRangeSelector<T> extends Slick.Plugin<T> {
|
||||
onCellRangeSelected: Slick.Event<{ range: Slick.Range }>;
|
||||
onBeforeCellRangeSelected: Slick.Event<Slick.Cell>;
|
||||
}
|
||||
|
||||
export interface ICellSelectionModelOptions {
|
||||
cellRangeSelector?: any;
|
||||
selectActiveCell?: boolean;
|
||||
}
|
||||
|
||||
const defaults: ICellSelectionModelOptions = {
|
||||
selectActiveCell: true
|
||||
};
|
||||
|
||||
export class CellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slick.Range>> {
|
||||
private grid: Slick.Grid<T>;
|
||||
private selector: ICellRangeSelector<T>;
|
||||
private ranges: Array<Slick.Range> = [];
|
||||
|
||||
public onSelectedRangesChanged = new Slick.Event<Array<Slick.Range>>();
|
||||
|
||||
constructor(private options: ICellSelectionModelOptions = defaults) {
|
||||
this.options = mixin(this.options, defaults, false);
|
||||
|
||||
if (this.options.cellRangeSelector) {
|
||||
this.selector = this.options.cellRangeSelector;
|
||||
} else {
|
||||
// this is added by the noderequires above
|
||||
this.selector = new (<any>Slick).CellRangeSelector({ selectionCss: { 'border': '2px dashed grey' } });
|
||||
}
|
||||
}
|
||||
|
||||
public init(grid: Slick.Grid<T>) {
|
||||
this.grid = grid;
|
||||
this.grid.onActiveCellChanged.subscribe((e, args) => this.handleActiveCellChange(e, args));
|
||||
this.grid.onKeyDown.subscribe(e => this.handleKeyDown(e));
|
||||
this.grid.onHeaderClick.subscribe((e: MouseEvent, args) => this.handleHeaderClick(e, args));
|
||||
this.grid.registerPlugin(this.selector);
|
||||
this.selector.onCellRangeSelected.subscribe((e, args) => this.handleCellRangeSelected(e, args));
|
||||
this.selector.onBeforeCellRangeSelected.subscribe((e, args) => this.handleBeforeCellRangeSelected(e, args));
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.grid.onActiveCellChanged.unsubscribe((e, args) => this.handleActiveCellChange(e, args));
|
||||
this.grid.onKeyDown.unsubscribe(e => this.handleKeyDown(e));
|
||||
this.selector.onCellRangeSelected.unsubscribe((e, args) => this.handleCellRangeSelected(e, args));
|
||||
this.selector.onBeforeCellRangeSelected.unsubscribe((e, args) => this.handleBeforeCellRangeSelected(e, args));
|
||||
this.grid.unregisterPlugin(this.selector);
|
||||
}
|
||||
|
||||
private removeInvalidRanges(ranges: Array<Slick.Range>): Array<Slick.Range> {
|
||||
let result: Array<Slick.Range> = [];
|
||||
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
let r = ranges[i];
|
||||
if (this.grid.canCellBeSelected(r.fromRow, r.fromCell) && this.grid.canCellBeSelected(r.toRow, r.toCell)) {
|
||||
result.push(r);
|
||||
} else if (this.grid.canCellBeSelected(r.fromRow, r.fromCell + 1) && this.grid.canCellBeSelected(r.toRow, r.toCell)) {
|
||||
// account for number row
|
||||
result.push(new Slick.Range(r.fromRow, r.fromCell + 1, r.toRow, r.toCell));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public setSelectedRanges(ranges: Array<Slick.Range>): void {
|
||||
// simple check for: empty selection didn't change, prevent firing onSelectedRangesChanged
|
||||
if ((!this.ranges || this.ranges.length === 0) && (!ranges || ranges.length === 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ranges = this.removeInvalidRanges(ranges);
|
||||
this.onSelectedRangesChanged.notify(this.ranges);
|
||||
}
|
||||
|
||||
public getSelectedRanges() {
|
||||
return this.ranges;
|
||||
}
|
||||
|
||||
private handleBeforeCellRangeSelected(e, args: Slick.Cell) {
|
||||
if (this.grid.getEditorLock().isActive()) {
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private handleCellRangeSelected(e, args: { range: Slick.Range }) {
|
||||
this.grid.setActiveCell(args.range.fromRow, args.range.fromCell, false, false, true);
|
||||
this.setSelectedRanges([args.range]);
|
||||
}
|
||||
|
||||
private handleActiveCellChange(e, args) {
|
||||
if (this.options.selectActiveCell && !isUndefinedOrNull(args.row) && !isUndefinedOrNull(args.cell)) {
|
||||
this.setSelectedRanges([new Slick.Range(args.row, args.cell)]);
|
||||
} else if (!this.options.selectActiveCell) {
|
||||
// clear the previous selection once the cell changes
|
||||
this.setSelectedRanges([]);
|
||||
}
|
||||
}
|
||||
|
||||
private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) {
|
||||
if (!isUndefinedOrNull(args.column)) {
|
||||
let columnIndex = this.grid.getColumnIndex(args.column.id);
|
||||
if (this.grid.canCellBeSelected(0, columnIndex)) {
|
||||
let ranges: Array<Slick.Range>;
|
||||
if (e.shiftKey) {
|
||||
ranges = this.getSelectedRanges();
|
||||
ranges.push(new Slick.Range(0, columnIndex, this.grid.getDataLength() - 1, columnIndex));
|
||||
} else {
|
||||
ranges = [new Slick.Range(0, columnIndex, this.grid.getDataLength() - 1, columnIndex)];
|
||||
}
|
||||
this.grid.setActiveCell(0, columnIndex);
|
||||
this.setSelectedRanges(ranges);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleKeyDown(e) {
|
||||
/***
|
||||
* Кey codes
|
||||
* 37 left
|
||||
* 38 up
|
||||
* 39 right
|
||||
* 40 down
|
||||
*/
|
||||
let ranges, last;
|
||||
let active = this.grid.getActiveCell();
|
||||
let metaKey = e.ctrlKey || e.metaKey;
|
||||
|
||||
if (active && e.shiftKey && !metaKey && !e.altKey &&
|
||||
(e.which === 37 || e.which === 39 || e.which === 38 || e.which === 40)) {
|
||||
|
||||
ranges = this.getSelectedRanges();
|
||||
if (!ranges.length) {
|
||||
ranges.push(new Slick.Range(active.row, active.cell));
|
||||
}
|
||||
|
||||
// keyboard can work with last range only
|
||||
last = ranges.pop();
|
||||
|
||||
// can't handle selection out of active cell
|
||||
if (!last.contains(active.row, active.cell)) {
|
||||
last = new Slick.Range(active.row, active.cell);
|
||||
}
|
||||
|
||||
let dRow = last.toRow - last.fromRow,
|
||||
dCell = last.toCell - last.fromCell,
|
||||
// walking direction
|
||||
dirRow = active.row === last.fromRow ? 1 : -1,
|
||||
dirCell = active.cell === last.fromCell ? 1 : -1;
|
||||
|
||||
if (e.which === 37) {
|
||||
dCell -= dirCell;
|
||||
} else if (e.which === 39) {
|
||||
dCell += dirCell;
|
||||
} else if (e.which === 38) {
|
||||
dRow -= dirRow;
|
||||
} else if (e.which === 40) {
|
||||
dRow += dirRow;
|
||||
}
|
||||
|
||||
// define new selection range
|
||||
let new_last = new Slick.Range(active.row, active.cell, active.row + dirRow * dRow, active.cell + dirCell * dCell);
|
||||
if (this.removeInvalidRanges([new_last]).length) {
|
||||
ranges.push(new_last);
|
||||
let viewRow = dirRow > 0 ? new_last.toRow : new_last.fromRow;
|
||||
let viewCell = dirCell > 0 ? new_last.toCell : new_last.fromCell;
|
||||
this.grid.scrollRowIntoView(viewRow, false);
|
||||
this.grid.scrollCellIntoView(viewRow, viewCell, false);
|
||||
} else {
|
||||
ranges.push(last);
|
||||
}
|
||||
|
||||
this.setSelectedRanges(ranges);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,364 +0,0 @@
|
||||
// Drag select selection model gist taken from https://gist.github.com/skoon/5312536
|
||||
// heavily modified
|
||||
|
||||
import { clone } from 'sql/base/common/objects';
|
||||
|
||||
export class DragCellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slick.Range>> {
|
||||
private readonly keyColResizeIncr = 5;
|
||||
|
||||
private _grid: Slick.Grid<T>;
|
||||
private _ranges: Array<Slick.Range> = [];
|
||||
private _dragging = false;
|
||||
private _handler = new Slick.EventHandler();
|
||||
|
||||
public onSelectedRangesChanged = new Slick.Event<Slick.Range[]>();
|
||||
|
||||
public init(grid: Slick.Grid<T>): void {
|
||||
this._grid = grid;
|
||||
this._handler.subscribe(this._grid.onActiveCellChanged, (e: Event, data: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(e, data));
|
||||
this._handler.subscribe(this._grid.onKeyDown, (e: JQueryInputEventObject) => this.handleKeyDown(e));
|
||||
this._handler.subscribe(this._grid.onClick, (e: MouseEvent) => this.handleClick(e));
|
||||
this._handler.subscribe(this._grid.onDrag, (e: MouseEvent) => this.handleDrag(e));
|
||||
this._handler.subscribe(this._grid.onDragInit, (e: MouseEvent) => this.handleDragInit(e));
|
||||
this._handler.subscribe(this._grid.onDragStart, (e: MouseEvent) => this.handleDragStart(e));
|
||||
this._handler.subscribe(this._grid.onDragEnd, (e: MouseEvent) => this.handleDragEnd(e));
|
||||
this._handler.subscribe(this._grid.onHeaderClick, (e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e, args));
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this._handler.unsubscribeAll();
|
||||
}
|
||||
|
||||
private rangesToRows(ranges: Array<Slick.Range>): Array<number> {
|
||||
let rows = [];
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
for (let j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
|
||||
rows.push(j);
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
private rowsToRanges(rows: Array<number>): Array<Slick.Range> {
|
||||
let ranges = [];
|
||||
let lastCell = this._grid.getColumns().length - 1;
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
public getSelectedRows(): Array<number> {
|
||||
return this.rangesToRows(this._ranges);
|
||||
}
|
||||
|
||||
public setSelectedRows(rows: Array<number>) {
|
||||
this.setSelectedRanges(this.rowsToRanges(rows));
|
||||
}
|
||||
|
||||
public setSelectedRanges(ranges: Array<Slick.Range>) {
|
||||
this._ranges = ranges;
|
||||
this.onSelectedRangesChanged.notify(this._ranges);
|
||||
}
|
||||
|
||||
public getSelectedRanges(): Array<Slick.Range> {
|
||||
return this._ranges;
|
||||
}
|
||||
|
||||
private handleActiveCellChange(e: Event, data: Slick.OnActiveCellChangedEventArgs<T>) { }
|
||||
|
||||
private isNavigationKey(e: BaseJQueryEventObject) {
|
||||
// Nave keys (home, end, arrows) are all in sequential order so use a
|
||||
switch (e.which) {
|
||||
case $.ui.keyCode.HOME:
|
||||
case $.ui.keyCode.END:
|
||||
case $.ui.keyCode.LEFT:
|
||||
case $.ui.keyCode.UP:
|
||||
case $.ui.keyCode.RIGHT:
|
||||
case $.ui.keyCode.DOWN:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private navigateLeft(e: JQueryInputEventObject, activeCell: Slick.Cell) {
|
||||
if (activeCell.cell > 1) {
|
||||
let isHome = e.which === $.ui.keyCode.HOME;
|
||||
let newActiveCellColumn = isHome ? 1 : activeCell.cell - 1;
|
||||
// Unsure why but for range, must record 1 index less than expected
|
||||
let newRangeColumn = newActiveCellColumn - 1;
|
||||
|
||||
if (e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
|
||||
// If we are on the rightmost edge of the range and we navigate left,
|
||||
// we want to deselect the rightmost cell
|
||||
if (last.fromCell <= newRangeColumn) { last.toCell -= 1; }
|
||||
|
||||
let fromRow = Math.min(activeCell.row, last.fromRow);
|
||||
let fromCell = Math.min(newRangeColumn, last.fromCell);
|
||||
let toRow = Math.max(activeCell.row, last.toRow);
|
||||
let toCell = Math.max(newRangeColumn, last.toCell);
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(activeCell.row, newRangeColumn, activeCell.row, newRangeColumn)];
|
||||
}
|
||||
|
||||
this._grid.setActiveCell(activeCell.row, newActiveCellColumn);
|
||||
this.setSelectedRanges(this._ranges);
|
||||
}
|
||||
}
|
||||
|
||||
private navigateRight(e: JQueryInputEventObject, activeCell: Slick.Cell) {
|
||||
let columnLength = this._grid.getColumns().length;
|
||||
if (activeCell.cell < columnLength) {
|
||||
let isEnd = e.which === $.ui.keyCode.END;
|
||||
let newActiveCellColumn = isEnd ? columnLength : activeCell.cell + 1;
|
||||
// Unsure why but for range, must record 1 index less than expected
|
||||
let newRangeColumn = newActiveCellColumn - 1;
|
||||
if (e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
|
||||
// If we are on the leftmost edge of the range and we navigate right,
|
||||
// we want to deselect the leftmost cell
|
||||
if (newRangeColumn <= last.toCell) { last.fromCell += 1; }
|
||||
|
||||
let fromRow = Math.min(activeCell.row, last.fromRow);
|
||||
let fromCell = Math.min(newRangeColumn, last.fromCell);
|
||||
let toRow = Math.max(activeCell.row, last.toRow);
|
||||
let toCell = Math.max(newRangeColumn, last.toCell);
|
||||
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(activeCell.row, newRangeColumn, activeCell.row, newRangeColumn)];
|
||||
}
|
||||
this._grid.setActiveCell(activeCell.row, newActiveCellColumn);
|
||||
this.setSelectedRanges(this._ranges);
|
||||
}
|
||||
}
|
||||
|
||||
private handleKeyDown(e: JQueryInputEventObject) {
|
||||
let activeCell = this._grid.getActiveCell();
|
||||
|
||||
if (activeCell) {
|
||||
// navigation keys
|
||||
if (this.isNavigationKey(e)) {
|
||||
e.stopImmediatePropagation();
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
let event = new CustomEvent('gridnav', {
|
||||
detail: {
|
||||
which: e.which,
|
||||
ctrlKey: e.ctrlKey,
|
||||
metaKey: e.metaKey,
|
||||
shiftKey: e.shiftKey,
|
||||
altKey: e.altKey
|
||||
}
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
return;
|
||||
}
|
||||
// end key
|
||||
if (e.which === $.ui.keyCode.END) {
|
||||
this.navigateRight(e, activeCell);
|
||||
}
|
||||
// home key
|
||||
if (e.which === $.ui.keyCode.HOME) {
|
||||
this.navigateLeft(e, activeCell);
|
||||
}
|
||||
// left arrow
|
||||
if (e.which === $.ui.keyCode.LEFT) {
|
||||
// column resize
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
|
||||
let allColumns = clone(this._grid.getColumns());
|
||||
allColumns[activeCell.cell - 1].width = allColumns[activeCell.cell - 1].width - this.keyColResizeIncr;
|
||||
this._grid.setColumnWidths(allColumns);
|
||||
} else {
|
||||
this.navigateLeft(e, activeCell);
|
||||
}
|
||||
// up arrow
|
||||
} else if (e.which === $.ui.keyCode.UP && activeCell.row > 0) {
|
||||
if (e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
|
||||
// If we are on the bottommost edge of the range and we navigate up,
|
||||
// we want to deselect the bottommost row
|
||||
let newRangeRow = activeCell.row - 1;
|
||||
if (last.fromRow <= newRangeRow) { last.toRow -= 1; }
|
||||
|
||||
let fromRow = Math.min(activeCell.row - 1, last.fromRow);
|
||||
let fromCell = Math.min(activeCell.cell - 1, last.fromCell);
|
||||
let toRow = Math.max(newRangeRow, last.toRow);
|
||||
let toCell = Math.max(activeCell.cell - 1, last.toCell);
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(activeCell.row - 1, activeCell.cell - 1, activeCell.row - 1, activeCell.cell - 1)];
|
||||
}
|
||||
this._grid.setActiveCell(activeCell.row - 1, activeCell.cell);
|
||||
this.setSelectedRanges(this._ranges);
|
||||
// right arrow
|
||||
} else if (e.which === $.ui.keyCode.RIGHT) {
|
||||
// column resize
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
|
||||
let allColumns = clone(this._grid.getColumns());
|
||||
allColumns[activeCell.cell - 1].width = allColumns[activeCell.cell - 1].width + this.keyColResizeIncr;
|
||||
this._grid.setColumnWidths(allColumns);
|
||||
} else {
|
||||
this.navigateRight(e, activeCell);
|
||||
}
|
||||
// down arrow
|
||||
} else if (e.which === $.ui.keyCode.DOWN && activeCell.row < this._grid.getDataLength() - 1) {
|
||||
if (e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
|
||||
// If we are on the topmost edge of the range and we navigate down,
|
||||
// we want to deselect the topmost row
|
||||
let newRangeRow = activeCell.row + 1;
|
||||
if (newRangeRow <= last.toRow) { last.fromRow += 1; }
|
||||
|
||||
let fromRow = Math.min(activeCell.row + 1, last.fromRow);
|
||||
let fromCell = Math.min(activeCell.cell - 1, last.fromCell);
|
||||
let toRow = Math.max(activeCell.row + 1, last.toRow);
|
||||
let toCell = Math.max(activeCell.cell - 1, last.toCell);
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(activeCell.row + 1, activeCell.cell - 1, activeCell.row + 1, activeCell.cell - 1)];
|
||||
}
|
||||
this._grid.setActiveCell(activeCell.row + 1, activeCell.cell);
|
||||
this.setSelectedRanges(this._ranges);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) {
|
||||
let columnIndex = this._grid.getColumnIndex(args.column.id);
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
this._ranges.push(new Slick.Range(0, columnIndex, this._grid.getDataLength() - 1, columnIndex));
|
||||
this._grid.setActiveCell(0, columnIndex + 1);
|
||||
} else if (e.shiftKey && this._ranges.length) {
|
||||
let last = this._ranges.pop().fromCell;
|
||||
let from = Math.min(columnIndex, last);
|
||||
let to = Math.max(columnIndex, last);
|
||||
this._ranges = [];
|
||||
for (let i = from; i <= to; i++) {
|
||||
if (i !== last) {
|
||||
this._ranges.push(new Slick.Range(0, i, this._grid.getDataLength() - 1, i));
|
||||
}
|
||||
}
|
||||
this._ranges.push(new Slick.Range(0, last, this._grid.getDataLength() - 1, last));
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(0, columnIndex, this._grid.getDataLength() - 1, columnIndex)];
|
||||
this._grid.resetActiveCell();
|
||||
}
|
||||
this.setSelectedRanges(this._ranges);
|
||||
e.stopImmediatePropagation();
|
||||
return true;
|
||||
}
|
||||
|
||||
private handleClick(e: MouseEvent) {
|
||||
let cell = this._grid.getCellFromEvent(e);
|
||||
if (!cell || !this._grid.canCellBeActive(cell.row, cell.cell)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!e.ctrlKey && !e.shiftKey && !e.metaKey) {
|
||||
if (cell.cell !== 0) {
|
||||
this._ranges = [new Slick.Range(cell.row, cell.cell - 1, cell.row, cell.cell - 1)];
|
||||
this.setSelectedRanges(this._ranges);
|
||||
this._grid.setActiveCell(cell.row, cell.cell);
|
||||
return true;
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(cell.row, 0, cell.row, this._grid.getColumns().length - 1)];
|
||||
this.setSelectedRanges(this._ranges);
|
||||
this._grid.setActiveCell(cell.row, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (this._grid.getOptions().multiSelect) {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
if (cell.cell === 0) {
|
||||
this._ranges.push(new Slick.Range(cell.row, 0, cell.row, this._grid.getColumns().length - 1));
|
||||
this._grid.setActiveCell(cell.row, 1);
|
||||
} else {
|
||||
this._ranges.push(new Slick.Range(cell.row, cell.cell - 1, cell.row, cell.cell - 1));
|
||||
this._grid.setActiveCell(cell.row, cell.cell);
|
||||
}
|
||||
} else if (this._ranges.length && e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
if (cell.cell === 0) {
|
||||
let fromRow = Math.min(cell.row, last.fromRow);
|
||||
let toRow = Math.max(cell.row, last.fromRow);
|
||||
this._ranges = [new Slick.Range(fromRow, 0, toRow, this._grid.getColumns().length - 1)];
|
||||
} else {
|
||||
let fromRow = Math.min(cell.row, last.fromRow);
|
||||
let fromCell = Math.min(cell.cell - 1, last.fromCell);
|
||||
let toRow = Math.max(cell.row, last.toRow);
|
||||
let toCell = Math.max(cell.cell - 1, last.toCell);
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setSelectedRanges(this._ranges);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private handleDragInit(e: MouseEvent) {
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
private handleDragStart(e: MouseEvent) {
|
||||
let cell = this._grid.getCellFromEvent(e);
|
||||
e.stopImmediatePropagation();
|
||||
this._dragging = true;
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
this._ranges.push(new Slick.Range(cell.row, cell.cell));
|
||||
this._grid.setActiveCell(cell.row, cell.cell);
|
||||
} else if (this._ranges.length && e.shiftKey) {
|
||||
let last = this._ranges.pop();
|
||||
let fromRow = Math.min(cell.row, last.fromRow);
|
||||
let fromCell = Math.min(cell.cell - 1, last.fromCell);
|
||||
let toRow = Math.max(cell.row, last.toRow);
|
||||
let toCell = Math.max(cell.cell - 1, last.toCell);
|
||||
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
|
||||
} else {
|
||||
this._ranges = [new Slick.Range(cell.row, cell.cell)];
|
||||
this._grid.setActiveCell(cell.row, cell.cell);
|
||||
}
|
||||
this.setSelectedRanges(this._ranges);
|
||||
}
|
||||
|
||||
private handleDrag(e: MouseEvent) {
|
||||
if (this._dragging) {
|
||||
let cell = this._grid.getCellFromEvent(e);
|
||||
let activeCell = this._grid.getActiveCell();
|
||||
if (!cell || !this._grid.canCellBeActive(cell.row, cell.cell)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._ranges.pop();
|
||||
|
||||
if (activeCell.cell === 0) {
|
||||
let lastCell = this._grid.getColumns().length - 1;
|
||||
let firstRow = Math.min(cell.row, activeCell.row);
|
||||
let lastRow = Math.max(cell.row, activeCell.row);
|
||||
this._ranges.push(new Slick.Range(firstRow, 0, lastRow, lastCell));
|
||||
} else {
|
||||
let firstRow = Math.min(cell.row, activeCell.row);
|
||||
let lastRow = Math.max(cell.row, activeCell.row);
|
||||
let firstColumn = Math.min(cell.cell - 1, activeCell.cell - 1);
|
||||
let lastColumn = Math.max(cell.cell - 1, activeCell.cell - 1);
|
||||
this._ranges.push(new Slick.Range(firstRow, firstColumn, lastRow, lastColumn));
|
||||
}
|
||||
this.setSelectedRanges(this._ranges);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private handleDragEnd(e: MouseEvent) {
|
||||
this._dragging = false;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { mixin } from 'vs/base/common/objects';
|
||||
import { SlickGrid } from 'angular2-slickgrid';
|
||||
import { Button } from '../../button/button';
|
||||
import { attachButtonStyler } from 'sql/common/theme/styler';
|
||||
import { escape } from 'sql/base/common/strings';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export class HeaderFilter {
|
||||
@@ -174,7 +175,7 @@ export class HeaderFilter {
|
||||
if (filterItems[i] && filterItems[i].indexOf('Error:') < 0) {
|
||||
filterOptions += '<label><input type="checkbox" value="' + i + '"'
|
||||
+ (filtered ? ' checked="checked"' : '')
|
||||
+ '/>' + filterItems[i] + '</label>';
|
||||
+ '/>' + escape(filterItems[i]) + '</label>';
|
||||
}
|
||||
}
|
||||
let $filter = $('<div class="filter">')
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { StandardMouseWheelEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
|
||||
const SCROLL_WHEEL_SENSITIVITY = 50;
|
||||
|
||||
export interface IMouseWheelSupportOptions {
|
||||
scrollSpeed?: number;
|
||||
}
|
||||
|
||||
const defaultOptions: IMouseWheelSupportOptions = {
|
||||
scrollSpeed: SCROLL_WHEEL_SENSITIVITY
|
||||
};
|
||||
|
||||
export class MouseWheelSupport implements Slick.Plugin<any> {
|
||||
|
||||
private viewport: HTMLElement;
|
||||
private canvas: HTMLElement;
|
||||
private options: IMouseWheelSupportOptions;
|
||||
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
constructor(options: IMouseWheelSupportOptions = {}) {
|
||||
this.options = mixin(options, defaultOptions);
|
||||
}
|
||||
|
||||
public init(grid: Slick.Grid<any>): void {
|
||||
this.canvas = grid.getCanvasNode();
|
||||
this.viewport = this.canvas.parentElement;
|
||||
let onMouseWheel = (browserEvent: MouseWheelEvent) => {
|
||||
let e = new StandardMouseWheelEvent(browserEvent);
|
||||
this._onMouseWheel(e);
|
||||
};
|
||||
this._disposables.push(DOM.addDisposableListener(this.viewport, 'mousewheel', onMouseWheel));
|
||||
this._disposables.push(DOM.addDisposableListener(this.viewport, 'DOMMouseScroll', onMouseWheel));
|
||||
}
|
||||
|
||||
private _onMouseWheel(event: StandardMouseWheelEvent) {
|
||||
const scrollHeight = this.canvas.clientHeight;
|
||||
const height = this.viewport.clientHeight;
|
||||
const scrollDown = Math.sign(event.deltaY) === -1;
|
||||
if (scrollDown) {
|
||||
if ((this.viewport.scrollTop - (event.deltaY * this.options.scrollSpeed)) + height > scrollHeight) {
|
||||
this.viewport.scrollTop = scrollHeight - height;
|
||||
this.viewport.dispatchEvent(new Event('scroll'));
|
||||
} else {
|
||||
this.viewport.scrollTop = this.viewport.scrollTop - (event.deltaY * this.options.scrollSpeed);
|
||||
this.viewport.dispatchEvent(new Event('scroll'));
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
} else {
|
||||
if ((this.viewport.scrollTop - (event.deltaY * this.options.scrollSpeed)) < 0) {
|
||||
this.viewport.scrollTop = 0;
|
||||
this.viewport.dispatchEvent(new Event('scroll'));
|
||||
} else {
|
||||
this.viewport.scrollTop = this.viewport.scrollTop - (event.deltaY * this.options.scrollSpeed);
|
||||
this.viewport.dispatchEvent(new Event('scroll'));
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
dispose(this._disposables);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// Adopted and converted to typescript from https://github.com/6pac/SlickGrid/blob/master/plugins/slick.rowdetailview.js
|
||||
// heavily modified
|
||||
import { escape } from 'sql/base/common/strings';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
@@ -354,7 +355,7 @@ export class RowDetailView {
|
||||
html.push("style='height:", dataContext._height, "px;"); //set total height of padding
|
||||
html.push("top:", rowHeight, "px'>"); //shift detail below 1st row
|
||||
html.push("<div id='detailViewContainer_", dataContext.id, "' class='detail-container' style='max-height:" + (dataContext._height - rowHeight + bottomMargin) + "px'>"); //sub ctr for custom styling
|
||||
html.push("<div id='innerDetailView_", dataContext.id, "'>", dataContext._detailContent, "</div></div>");
|
||||
html.push("<div id='innerDetailView_", dataContext.id, "'>", escape(dataContext._detailContent), "</div></div>");
|
||||
//&omit a final closing detail container </div> that would come next
|
||||
|
||||
return html.join('');
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { range } from 'vs/base/common/arrays';
|
||||
|
||||
export interface IRowNumberColumnOptions {
|
||||
numberOfRows: number;
|
||||
cssClass?: string;
|
||||
}
|
||||
|
||||
const sizePerDigit = 15;
|
||||
|
||||
export class RowNumberColumn<T> implements Slick.Plugin<T> {
|
||||
private handler = new Slick.EventHandler();
|
||||
private grid: Slick.Grid<T>;
|
||||
|
||||
|
||||
constructor(private options: IRowNumberColumnOptions) {
|
||||
}
|
||||
|
||||
public init(grid: Slick.Grid<T>) {
|
||||
this.grid = grid;
|
||||
this.handler
|
||||
.subscribe(this.grid.onClick, (e, args) => this.handleClick(e, args))
|
||||
.subscribe(this.grid.onHeaderClick, (e, args) => this.handleHeaderClick(e, args));
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.handler.unsubscribeAll();
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
public getColumnDefinition(): Slick.Column<T> {
|
||||
return {
|
||||
id: 'rowNumber',
|
||||
name: '',
|
||||
field: 'rowNumber',
|
||||
width: this.options.numberOfRows.toString().length * sizePerDigit,
|
||||
resizable: false,
|
||||
cssClass: this.options.cssClass,
|
||||
focusable: false,
|
||||
selectable: false,
|
||||
formatter: (r, c, v, cd, dc) => this.formatter(r, c, v, cd, dc)
|
||||
};
|
||||
}
|
||||
|
||||
private formatter(row, cell, value, columnDef: Slick.Column<T>, dataContext): string {
|
||||
if (dataContext) {
|
||||
// row is zero-based, we need make it 1 based for display in the result grid
|
||||
//
|
||||
return `<span>${row + 1}</span>`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,17 @@ import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { isArray, isBoolean } from 'vs/base/common/types';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { range } from 'vs/base/common/arrays';
|
||||
|
||||
export interface ITableContextMenuEvent {
|
||||
anchor: HTMLElement | { x: number, y: number };
|
||||
cell?: { row: number, cell: number };
|
||||
}
|
||||
|
||||
export interface ITableStyles extends IListStyles {
|
||||
tableHeaderBackground?: Color;
|
||||
@@ -27,30 +35,46 @@ function getDefaultOptions<T>(): Slick.GridOptions<T> {
|
||||
};
|
||||
}
|
||||
|
||||
export class Table<T extends Slick.SlickData> extends Widget implements IThemable {
|
||||
export interface ITableSorter<T> {
|
||||
sort(args: Slick.OnSortEventArgs<T>);
|
||||
}
|
||||
|
||||
export interface ITableConfiguration<T> {
|
||||
dataProvider?: Slick.DataProvider<T> | Array<T>;
|
||||
columns?: Slick.Column<T>[];
|
||||
sorter?: ITableSorter<T>;
|
||||
}
|
||||
|
||||
export class Table<T extends Slick.SlickData> extends Widget implements IThemable, IDisposable {
|
||||
private styleElement: HTMLStyleElement;
|
||||
private idPrefix: string;
|
||||
|
||||
private _grid: Slick.Grid<T>;
|
||||
private _columns: Slick.Column<T>[];
|
||||
private _data: TableDataView<T>;
|
||||
private _data: Slick.DataProvider<T>;
|
||||
private _sorter: ITableSorter<T>;
|
||||
|
||||
private _autoscroll: boolean;
|
||||
private _onRowCountChangeListener: IDisposable;
|
||||
private _container: HTMLElement;
|
||||
private _tableContainer: HTMLElement;
|
||||
|
||||
private _classChangeTimeout: number;
|
||||
|
||||
constructor(parent: HTMLElement, data?: Array<T> | TableDataView<T>, columns?: Slick.Column<T>[], options?: Slick.GridOptions<T>) {
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
private _onContextMenu = new Emitter<ITableContextMenuEvent>();
|
||||
public readonly onContextMenu: Event<ITableContextMenuEvent> = this._onContextMenu.event;
|
||||
|
||||
constructor(parent: HTMLElement, configuration?: ITableConfiguration<T>, options?: Slick.GridOptions<T>) {
|
||||
super();
|
||||
if (data instanceof TableDataView) {
|
||||
this._data = data;
|
||||
if (!configuration || isArray(configuration.dataProvider)) {
|
||||
this._data = new TableDataView<T>(configuration && configuration.dataProvider as Array<T>);
|
||||
} else {
|
||||
this._data = new TableDataView<T>(data);
|
||||
this._data = configuration.dataProvider;
|
||||
}
|
||||
|
||||
if (columns) {
|
||||
this._columns = columns;
|
||||
if (configuration.columns) {
|
||||
this._columns = configuration.columns;
|
||||
} else {
|
||||
this._columns = new Array<Slick.Column<T>>();
|
||||
}
|
||||
@@ -81,15 +105,33 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
|
||||
this._grid = new Slick.Grid<T>(this._tableContainer, this._data, this._columns, newOptions);
|
||||
this.idPrefix = this._tableContainer.classList[0];
|
||||
DOM.addClass(this._container, this.idPrefix);
|
||||
this._onRowCountChangeListener = this._data.onRowCountChange(() => this._handleRowCountChange());
|
||||
this._grid.onSort.subscribe((e, args) => {
|
||||
this._data.sort(args);
|
||||
this._grid.invalidate();
|
||||
this._grid.render();
|
||||
if (configuration.sorter) {
|
||||
this._sorter = configuration.sorter;
|
||||
this._grid.onSort.subscribe((e, args) => {
|
||||
this._sorter.sort(args);
|
||||
this._grid.invalidate();
|
||||
this._grid.render();
|
||||
});
|
||||
}
|
||||
|
||||
this._grid.onContextMenu.subscribe((e: JQuery.Event) => {
|
||||
const originalEvent = e.originalEvent;
|
||||
const cell = this._grid.getCellFromEvent(originalEvent);
|
||||
const anchor = originalEvent instanceof MouseEvent ? { x: originalEvent.x, y: originalEvent.y } : originalEvent.srcElement as HTMLElement;
|
||||
this._onContextMenu.fire({ anchor, cell });
|
||||
});
|
||||
}
|
||||
|
||||
private _handleRowCountChange() {
|
||||
public dispose() {
|
||||
dispose(this._disposables);
|
||||
}
|
||||
|
||||
public invalidateRows(rows: number[], keepEditor: boolean) {
|
||||
this._grid.invalidateRows(rows, keepEditor);
|
||||
this._grid.render();
|
||||
}
|
||||
|
||||
public updateRowCount() {
|
||||
this._grid.updateRowCount();
|
||||
this._grid.render();
|
||||
if (this._autoscroll) {
|
||||
@@ -113,20 +155,22 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
|
||||
} else {
|
||||
this._data = new TableDataView<T>(data);
|
||||
}
|
||||
this._onRowCountChangeListener.dispose();
|
||||
this._grid.setData(this._data, true);
|
||||
this._onRowCountChangeListener = this._data.onRowCountChange(() => this._handleRowCountChange());
|
||||
}
|
||||
|
||||
get columns(): Slick.Column<T>[] {
|
||||
return this._grid.getColumns();
|
||||
}
|
||||
|
||||
setSelectedRows(rows: number[]) {
|
||||
this._grid.setSelectedRows(rows);
|
||||
public setSelectedRows(rows: number[] | boolean) {
|
||||
if (isBoolean(rows)) {
|
||||
this._grid.setSelectedRows(range(this._grid.getDataLength()));
|
||||
} else {
|
||||
this._grid.setSelectedRows(rows);
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedRows(): number[] {
|
||||
public getSelectedRows(): number[] {
|
||||
return this._grid.getSelectedRows();
|
||||
}
|
||||
|
||||
@@ -143,21 +187,6 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
|
||||
};
|
||||
}
|
||||
|
||||
onContextMenu(fn: (e: Slick.EventData, data: Slick.OnContextMenuEventArgs<T>) => any): IDisposable;
|
||||
onContextMenu(fn: (e: DOMEvent, data: Slick.OnContextMenuEventArgs<T>) => any): IDisposable;
|
||||
onContextMenu(fn: any): IDisposable {
|
||||
this._grid.onContextMenu.subscribe(fn);
|
||||
return {
|
||||
dispose: () => {
|
||||
this._grid.onContextMenu.unsubscribe(fn);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getCellFromEvent(e: DOMEvent): Slick.Cell {
|
||||
return this._grid.getCellFromEvent(e);
|
||||
}
|
||||
|
||||
setSelectionModel(model: Slick.SelectionModel<T, Array<Slick.Range>>) {
|
||||
this._grid.setSelectionModel(model);
|
||||
}
|
||||
@@ -194,7 +223,7 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
|
||||
this._tableContainer.style.width = sizing.width + 'px';
|
||||
this._tableContainer.style.height = sizing.height + 'px';
|
||||
} else {
|
||||
if (orientation === Orientation.HORIZONTAL) {
|
||||
if (orientation === Orientation.VERTICAL) {
|
||||
this._container.style.width = '100%';
|
||||
this._container.style.height = sizing + 'px';
|
||||
this._tableContainer.style.width = '100%';
|
||||
@@ -207,6 +236,7 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
|
||||
}
|
||||
}
|
||||
this.resizeCanvas();
|
||||
this.autosizeColumns();
|
||||
}
|
||||
|
||||
autosizeColumns() {
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface IFindPosition {
|
||||
row: number;
|
||||
}
|
||||
|
||||
export function defaultSort<T>(args: Slick.OnSortEventArgs<T>, data: Array<T>): Array<T> {
|
||||
function defaultSort<T>(args: Slick.OnSortEventArgs<T>, data: Array<T>): Array<T> {
|
||||
let field = args.sortCol.field;
|
||||
let sign = args.sortAsc ? 1 : -1;
|
||||
return data.sort((a, b) => (a[field] === b[field] ? 0 : (a[field] > b[field] ? 1 : -1)) * sign);
|
||||
|
||||
@@ -25,7 +25,7 @@ export class TableBasicView<T> extends View {
|
||||
super(undefined, viewOpts);
|
||||
this._container = document.createElement('div');
|
||||
this._container.className = 'table-view';
|
||||
this._table = new Table<T>(this._container, data, columns, tableOpts);
|
||||
this._table = new Table<T>(this._container, { dataProvider: data, columns }, tableOpts);
|
||||
}
|
||||
|
||||
public get table(): Table<T> {
|
||||
@@ -59,7 +59,7 @@ export class TableHeaderView<T> extends HeaderView {
|
||||
super(undefined, viewOpts);
|
||||
this._container = document.createElement('div');
|
||||
this._container.className = 'table-view';
|
||||
this._table = new Table<T>(this._container, data, columns, tableOpts);
|
||||
this._table = new Table<T>(this._container, { dataProvider: data, columns }, tableOpts);
|
||||
}
|
||||
|
||||
public get table(): Table<T> {
|
||||
@@ -76,7 +76,7 @@ export class TableHeaderView<T> extends HeaderView {
|
||||
}
|
||||
|
||||
protected layoutBody(size: number): void {
|
||||
this._table.layout(size, Orientation.HORIZONTAL);
|
||||
this._table.layout(size, Orientation.VERTICAL);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
@@ -99,7 +99,7 @@ export class TableCollapsibleView<T> extends AbstractCollapsibleView {
|
||||
super(undefined, viewOpts);
|
||||
this._container = document.createElement('div');
|
||||
this._container.className = 'table-view';
|
||||
this._table = new Table<T>(this._container, data, columns, tableOpts);
|
||||
this._table = new Table<T>(this._container, { dataProvider: data, columns }, tableOpts);
|
||||
}
|
||||
|
||||
public render(container: HTMLElement, orientation: Orientation): void {
|
||||
@@ -143,6 +143,6 @@ export class TableCollapsibleView<T> extends AbstractCollapsibleView {
|
||||
}
|
||||
|
||||
protected layoutBody(size: number): void {
|
||||
this._table.layout(size, Orientation.HORIZONTAL);
|
||||
this._table.layout(size, Orientation.VERTICAL);
|
||||
}
|
||||
}
|
||||
|
||||
1
src/sql/base/browser/ui/taskbar/media/add.svg
Normal file
1
src/sql/base/browser/ui/taskbar/media/add.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>add</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,6v4H10v4H6V10H2V6H6V2h4V6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,7V9H9v4H7V9H3V7H7V3H9V7Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 486 B |
@@ -18,6 +18,12 @@
|
||||
background-image: url('start.svg');
|
||||
}
|
||||
|
||||
.vs .icon.add,
|
||||
.vs-dark .icon.add,
|
||||
.hc-black .icon.add {
|
||||
background-image: url('add.svg');
|
||||
}
|
||||
|
||||
.vs .icon.stop,
|
||||
.vs-dark .icon.stop,
|
||||
.hc-black .icon.stop {
|
||||
|
||||
@@ -82,7 +82,7 @@ export class Taskbar {
|
||||
public static createTaskbarText(inputText: string): HTMLElement {
|
||||
let element = document.createElement('div');
|
||||
element.className = 'taskbarTextSeparator';
|
||||
element.innerHTML = inputText;
|
||||
element.textContent = inputText;
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export function escape(html: string): string {
|
||||
case '>': return '>';
|
||||
case '&': return '&';
|
||||
case '"': return '"';
|
||||
case '\'': return ''';
|
||||
default: return match;
|
||||
}
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user