mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-19 11:01:38 -05:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45e3c6ae49 | ||
|
|
d526fe0f7f | ||
|
|
5707b58fda | ||
|
|
1a9f72dfe0 | ||
|
|
078f3a2b54 | ||
|
|
5bcfb9ab32 | ||
|
|
9bd45cf66a | ||
|
|
89c48bbe75 | ||
|
|
6b549696c5 | ||
|
|
3aaf8a24bf | ||
|
|
41ffd6e8ae | ||
|
|
c0a6f3e012 | ||
|
|
eae8de0373 | ||
|
|
23ec6ac567 | ||
|
|
f4cfb4a5ef | ||
|
|
80a9c82813 | ||
|
|
bcd6178d67 | ||
|
|
f10e281ffc | ||
|
|
c2b32fd64a | ||
|
|
80f150dfb4 | ||
|
|
a5c5fcbde1 | ||
|
|
0d76e845d5 | ||
|
|
979d50eb0e |
15
CHANGELOG.md
15
CHANGELOG.md
@@ -1,5 +1,20 @@
|
||||
# Change Log
|
||||
|
||||
## Version 0.29.3
|
||||
* Release date: May 7, 2018
|
||||
* Release status: Public Preview
|
||||
|
||||
## What's new in this version
|
||||
The May release is focused on stabilization and bug fixes leading up to the Build conference. This build contains the following highlights.
|
||||
|
||||
* Announcing **Redgate SQL Search** extension available in Extension Manager
|
||||
* Community Localization available for 10 languages: **German, Spanish, French, Italian, Japanese, Korean, Portuguese, Russian, Simplified Chinese and Traditional Chinese!**
|
||||
* **GDPR-compliant** build has reduced telemetry collection, improved [opt-out](https://github.com/Microsoft/sqlopsstudio/wiki/How-to-Disable-Telemetry-Reporting) experience and in-product links to [Privacy Statement](https://privacy.microsoft.com/en-us/privacystatement)
|
||||
* Extension Manager has improved Marketplace experience to easily discover community extensions
|
||||
* SQL Agent extension Jobs and Job History view improvement
|
||||
* Updates for **whoisactive** and **Server Reports** extensions
|
||||
* Continue to fix GitHub issues
|
||||
|
||||
## Version 0.28.6
|
||||
* Release date: April 25, 2018
|
||||
* Release status: Public Preview
|
||||
|
||||
27
README.md
27
README.md
@@ -4,16 +4,16 @@
|
||||
|
||||
SQL Operations Studio is a data management tool that enables you to work with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux.
|
||||
|
||||
**Download SQL Operations Studio March Public Preview**
|
||||
**Download SQL Operations Studio May Public Preview**
|
||||
|
||||
Platform | Link
|
||||
-- | --
|
||||
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=872717
|
||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=872718
|
||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=872719
|
||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=872720
|
||||
Linux DEB | https://go.microsoft.com/fwlink/?linkid=872722
|
||||
Linux RPM | https://go.microsoft.com/fwlink/?linkid=872721
|
||||
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=873386
|
||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=873387
|
||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=873388
|
||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=873389
|
||||
Linux RPM | https://go.microsoft.com/fwlink/?linkid=873390
|
||||
Linux DEB | https://go.microsoft.com/fwlink/?linkid=873391
|
||||
|
||||
Go to our [download page](https://aka.ms/sqlopsstudio) for more specific instructions.
|
||||
|
||||
@@ -74,7 +74,18 @@ We would like to thank all our users who raised issues, and in particular the fo
|
||||
* SebastianPfliegel `Remove sqlExtensionHelp (#312)`
|
||||
* olljanat for `Implemented npm version check (#314)`
|
||||
* Adam Mechanic for helping with the `whoisactive` extension
|
||||
* All community localization contributors *(will get list of individuals next month)*
|
||||
* All community localization contributors
|
||||
* French: Adrien Clerbois, ANAS BELABBES, Antoine Griffard, Arian Papillon, Eric Macarez, Eric Van Thorre, Jérémy LANDON, Matthias GROSPERRIN, Maxime COQUEREL, Olivier Guinart, thierry DEMAN-BARCELÒ, Thomas Potier
|
||||
* Italian: Aldo Donetti, Alessandro Alpi, Andrea Dottor, Bruni Luca, Gianluca Hotz, Luca Nardi, Luigi Bruno, Marco Dal Pino, Mirco Vanini, Pasquale Ceglie, Riccardo Cappello, Sergio Govoni, Stefano Demiliani
|
||||
* German: Anna Henke-Gunvaldson, Ben Weissman, David Ullmer, J.M. ., Kai Modo, Konstantin Staschill, Kostja Klein, Lennart Trunk, Markus Ehrenmüller-Jensen, Mascha Kroenlein, Matthias Knoll, Mourad Louha, Thomas Hütter, Wolfgang Straßer
|
||||
* Spanish: Alberto Poblacion, Andy Gonzalez, Carlos Mendible, Christian Araujo, Daniel D, Eickhel Mendoza, Ernesto Cardenas, Ivan Toledo Ivanovic, Fran Diaz, JESUS GIL, Jorge Serrano Pérez, José Saturnino Pimentel Juárez, Mauricio Hidalgo, Pablo Iglesias, Rikhardo Estrada Rdez, Thierry DEMAN, YOLANDA CUESTA ALTIERI
|
||||
* Japanese: Fujio Kojima, Kazushi KAMEGAWA, Masayoshi Yamada, Masayuki Ozawa , Seiji Momoto, Takashi Kanai, Takayoshi Tanaka, Yoshihisa Ozaki, 庄垣内治
|
||||
* Chinese (simplified): DAN YE, Joel Yang, Lynne Dong, Ryan(Yu) Zhang, Sheng Jiang, Wei Zhang, Zhiliang Xu
|
||||
* 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
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@@ -37,9 +37,8 @@ exports.defaultLanguages = [
|
||||
// languages requested by the community to non-stable builds
|
||||
exports.extraLanguages = [
|
||||
{ id: 'pt-br', folderName: 'ptb' },
|
||||
// {{SQL CARBON EDIT}}
|
||||
// { id: 'hu', folderName: 'hun' },
|
||||
// { id: 'tr', folderName: 'trk' }
|
||||
{ id: 'hu', folderName: 'hun' },
|
||||
{ id: 'tr', folderName: 'trk' }
|
||||
];
|
||||
// non built-in extensions also that are transifex and need to be part of the language packs
|
||||
var externalExtensionsWithTranslations = {
|
||||
|
||||
@@ -658,7 +658,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.1.5",
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.1.7",
|
||||
"opener": "^1.4.3",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.2",
|
||||
"vscode-extension-telemetry": "^0.0.15"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "1.4.0-alpha.30",
|
||||
"version": "1.4.0-alpha.31",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-netcoreapp2.1.zip",
|
||||
"Windows_64": "win-x64-netcoreapp2.1.zip",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -73,7 +73,7 @@
|
||||
"panel.background": "#FFFFFE",
|
||||
"panel.border": "#C8C8C8",
|
||||
"panelTitle.activeForeground": "#212121",
|
||||
"panelTitle.inactiveForeground": "#888888"
|
||||
"panelTitle.inactiveForeground": "#757575"
|
||||
},
|
||||
"tokenColors": [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sqlops",
|
||||
"version": "0.29.3",
|
||||
"version": "0.30.1",
|
||||
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
|
||||
@@ -38,6 +38,9 @@ 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)
|
||||
|
||||
## What's new in Server Reports v1.2?
|
||||
* Created left nav bar and added 2 categories for insight widgets: monitor and performance
|
||||
|
||||
## What's new in Server Reports v1.1?
|
||||
* Fixed DB Space Usage where it threw an error when database names contain special characters
|
||||
* Changed DB Space Usage and DB Buffer Usage to show only top 10 data
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 52 KiB |
2
samples/serverReports/package-lock.json
generated
2
samples/serverReports/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "server-report",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "server-report",
|
||||
"displayName": "Server Reports",
|
||||
"description": "Server Reports",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"engines": {
|
||||
@@ -30,7 +30,30 @@
|
||||
"title": "Server Reports",
|
||||
"description": "This extension shows useful reports for a server.",
|
||||
"container": {
|
||||
"server-insights1": null
|
||||
"nav-section": [
|
||||
{
|
||||
"id": "server-reports-monitoring",
|
||||
"title": "Monitor",
|
||||
"icon": {
|
||||
"light": "./out/src/media/monitor.svg",
|
||||
"dark": "./out/src/media/monitor_inverse.svg"
|
||||
},
|
||||
"container": {
|
||||
"server-reports-monitoring-container": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "server-reports-performance",
|
||||
"title": "Performance",
|
||||
"icon": {
|
||||
"light": "./out/src/media/performance.svg",
|
||||
"dark": "./out/src/media/performance_inverse.svg"
|
||||
},
|
||||
"container": {
|
||||
"server-reports-performance-container": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -122,7 +145,7 @@
|
||||
],
|
||||
"dashboard.containers": [
|
||||
{
|
||||
"id": "server-insights1",
|
||||
"id": "server-reports-monitoring-container",
|
||||
"container": {
|
||||
"widgets-container": [
|
||||
{
|
||||
@@ -164,7 +187,14 @@
|
||||
"widget": {
|
||||
"extension-backup-growth-trend": {}
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "server-reports-performance-container",
|
||||
"container": {
|
||||
"widgets-container": [
|
||||
{
|
||||
"name": "Wait Counts by Paul Randal",
|
||||
"gridItemConfig": {
|
||||
|
||||
1
samples/serverReports/src/media/monitor.svg
Normal file
1
samples/serverReports/src/media/monitor.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>usage</title><path d="M7,7H0A6.88,6.88,0,0,1,.25,5.14,7,7,0,0,1,5.14.25,6.92,6.92,0,0,1,7,0ZM6.75,8,1.88,12.87a6.91,6.91,0,0,1-1.4-2.26A7.33,7.33,0,0,1,0,8ZM6,1.08a6,6,0,0,0-1.77.6A6,6,0,0,0,1.68,4.23,6,6,0,0,0,1.08,6H6ZM1.08,9a6.63,6.63,0,0,0,.32,1.23A5.58,5.58,0,0,0,2,11.37L4.34,9ZM8,1l.89,0a8.52,8.52,0,0,1,.86.09,8.14,8.14,0,0,1,.84.18,6.66,6.66,0,0,1,.84.29,7.51,7.51,0,0,1,4.25,4.73,7.58,7.58,0,0,1,.06,4.18A7.51,7.51,0,0,1,15,12.29,7.49,7.49,0,0,1,12.29,15a7.5,7.5,0,0,1-1.79.75,7.63,7.63,0,0,1-4.87-.3A7.53,7.53,0,0,1,3.2,13.8l-.35-.35L8,8.29Zm.5,14a6.31,6.31,0,0,0,1.73-.23,6.66,6.66,0,0,0,1.55-.66,6.5,6.5,0,0,0,2.33-2.33,6.64,6.64,0,0,0,.66-1.55,6.47,6.47,0,0,0,0-3.36,6.69,6.69,0,0,0-.59-1.49,6.45,6.45,0,0,0-.93-1.29,6.63,6.63,0,0,0-1.21-1,6.49,6.49,0,0,0-1.44-.71A6.32,6.32,0,0,0,9,2v6.7L4.27,13.44a6.39,6.39,0,0,0,2,1.16A6.47,6.47,0,0,0,8.5,15Z"/></svg>
|
||||
|
After Width: | Height: | Size: 969 B |
1
samples/serverReports/src/media/monitor_inverse.svg
Normal file
1
samples/serverReports/src/media/monitor_inverse.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>usage_inverse</title><path class="cls-1" d="M7,7H0A6.88,6.88,0,0,1,.25,5.14,7,7,0,0,1,5.14.25,6.92,6.92,0,0,1,7,0ZM6.75,8,1.88,12.87a6.91,6.91,0,0,1-1.4-2.26A7.33,7.33,0,0,1,0,8ZM6,1.08a6,6,0,0,0-1.77.6A6,6,0,0,0,1.68,4.23,6,6,0,0,0,1.08,6H6ZM1.08,9a6.63,6.63,0,0,0,.32,1.23A5.58,5.58,0,0,0,2,11.37L4.34,9ZM8,1l.89,0a8.52,8.52,0,0,1,.86.09,8.14,8.14,0,0,1,.84.18,6.66,6.66,0,0,1,.84.29,7.51,7.51,0,0,1,4.25,4.73,7.58,7.58,0,0,1,.06,4.18A7.51,7.51,0,0,1,15,12.29,7.49,7.49,0,0,1,12.29,15a7.5,7.5,0,0,1-1.79.75,7.63,7.63,0,0,1-4.87-.3A7.53,7.53,0,0,1,3.2,13.8l-.35-.35L8,8.29Zm.5,14a6.31,6.31,0,0,0,1.73-.23,6.66,6.66,0,0,0,1.55-.66,6.5,6.5,0,0,0,2.33-2.33,6.64,6.64,0,0,0,.66-1.55,6.47,6.47,0,0,0,0-3.36,6.69,6.69,0,0,0-.59-1.49,6.45,6.45,0,0,0-.93-1.29,6.63,6.63,0,0,0-1.21-1,6.49,6.49,0,0,0-1.44-.71A6.32,6.32,0,0,0,9,2v6.7L4.27,13.44a6.39,6.39,0,0,0,2,1.16A6.47,6.47,0,0,0,8.5,15Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
1
samples/serverReports/src/media/performance.svg
Normal file
1
samples/serverReports/src/media/performance.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#101e23;}.cls-2{fill:#4bb8d1;}.cls-3{fill:#0c1011;}</style></defs><title>health</title><path class="cls-1" d="M12.58,1.51A6.36,6.36,0,0,0,8,3.9,6.32,6.32,0,0,0,3.41,1.51,3.81,3.81,0,0,0,0,5.35,5.7,5.7,0,0,0,.64,7.88h.72l0-.08A5.18,5.18,0,0,1,.64,5.39c.07-1.25.87-3.14,2.8-3.23h.12A5.81,5.81,0,0,1,7.73,4.63L8,5.06l.27-.43a5.72,5.72,0,0,1,4.28-2.47c1.93.09,2.73,2,2.8,3.23a5.15,5.15,0,0,1-.64,2.34l0,0a2.38,2.38,0,0,1-.34.68,19.45,19.45,0,0,1-6.57,6.06,11.11,11.11,0,0,1-1.25-.81c-.34-.25-.66-.52-1-.8h0a22.83,22.83,0,0,1-2.76-3H2a18.68,18.68,0,0,0,5.76,5.29h0l0,0h0c3.49-1.63,7-5.73,7.49-7.18V8A5.85,5.85,0,0,0,16,5.35,3.81,3.81,0,0,0,12.58,1.51Z"/><path class="cls-1" d="M1.41,8l-.1-.15h0Z"/><path class="cls-1" d="M7.79,15.22v0h0Z"/><path class="cls-1" d="M7.76,15.23h0v0Z"/><path class="cls-1" d="M14.72,7.73l0,0a.13.13,0,0,0,0,0Z"/><path class="cls-2" d="M12.62,8.7v.12a.48.48,0,0,1-.48.48H8.66l0,.07L7.38,12.65h0A.72.72,0,0,1,6,12.53V9.44H6V6.6L5,9.05H5a.56.56,0,0,1-.52.37H.92V8.36H4.13l0-.07L5.41,5h0a.72.72,0,0,1,1.42.12V8.22h0v2.84L7.77,8.6h0a.56.56,0,0,1,.52-.37h3.84A.48.48,0,0,1,12.62,8.7Z"/><path class="cls-3" d="M12.62,8.7v.12a.48.48,0,0,1-.48.48H8.66l0,.07L7.38,12.65h0A.72.72,0,0,1,6,12.53V9.44H6V6.6L5,9.05H5a.56.56,0,0,1-.52.37H.92V8.36H4.13l0-.07L5.41,5h0a.72.72,0,0,1,1.42.12V8.22h0v2.84L7.77,8.6h0a.56.56,0,0,1,.52-.37h3.84A.48.48,0,0,1,12.62,8.7Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1
samples/serverReports/src/media/performance_inverse.svg
Normal file
1
samples/serverReports/src/media/performance_inverse.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#101e23;}.cls-3{fill:#4bb8d1;}</style></defs><title>health_inverse</title><path class="cls-1" d="M12.58,1.51A6.36,6.36,0,0,0,8,3.9,6.32,6.32,0,0,0,3.41,1.51,3.81,3.81,0,0,0,0,5.35,5.7,5.7,0,0,0,.64,7.88h.72l0-.08A5.18,5.18,0,0,1,.64,5.39c.07-1.25.87-3.14,2.8-3.23h.12A5.81,5.81,0,0,1,7.73,4.63L8,5.06l.27-.43a5.72,5.72,0,0,1,4.28-2.47c1.93.09,2.73,2,2.8,3.23a5.15,5.15,0,0,1-.64,2.34l0,0a2.38,2.38,0,0,1-.34.68,19.45,19.45,0,0,1-6.57,6.06,11.11,11.11,0,0,1-1.25-.81c-.34-.25-.66-.52-1-.8h0a22.83,22.83,0,0,1-2.76-3H2a18.68,18.68,0,0,0,5.76,5.29h0l0,0h0c3.49-1.63,7-5.73,7.49-7.18V8A5.85,5.85,0,0,0,16,5.35,3.81,3.81,0,0,0,12.58,1.51Z"/><path class="cls-2" d="M1.41,8l-.1-.15h0Z"/><path class="cls-2" d="M7.79,15.22v0h0Z"/><path class="cls-2" d="M7.76,15.23h0v0Z"/><path class="cls-2" d="M14.72,7.73l0,0a.13.13,0,0,0,0,0Z"/><path class="cls-3" d="M12.62,8.7v.12a.48.48,0,0,1-.48.48H8.66l0,.07L7.38,12.65h0A.72.72,0,0,1,6,12.53V9.44H6V6.6L5,9.05H5a.56.56,0,0,1-.52.37H.92V8.36H4.13l0-.07L5.41,5h0a.72.72,0,0,1,1.42.12V8.22h0v2.84L7.77,8.6h0a.56.56,0,0,1,.52-.37h3.84A.48.48,0,0,1,12.62,8.7Z"/><path class="cls-1" d="M12.62,8.7v.12a.48.48,0,0,1-.48.48H8.66l0,.07L7.38,12.65h0A.72.72,0,0,1,6,12.53V9.44H6V6.6L5,9.05H5a.56.56,0,0,1-.52.37H.92V8.36H4.13l0-.07L5.41,5h0a.72.72,0,0,1,1.42.12V8.22h0v2.84L7.77,8.6h0a.56.56,0,0,1,.52-.37h3.84A.48.48,0,0,1,12.62,8.7Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -16,6 +16,11 @@
|
||||
],
|
||||
"main": "./out/src/extension",
|
||||
"contributes": {
|
||||
|
||||
"commands": [{
|
||||
"command": "mssql.openDialog",
|
||||
"title": "mssql.openDialog"
|
||||
}],
|
||||
"dashboard.tabs": [
|
||||
{
|
||||
"id": "sqlservices.tab",
|
||||
@@ -62,7 +67,9 @@
|
||||
"postinstall": "node ./node_modules/vscode/bin/install && node ./node_modules/sqlops/bin/install && gulp copytypings"
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-nls": "^3.2.2"
|
||||
"vscode-nls": "^3.2.2",
|
||||
"fs-extra": "^5.0.0",
|
||||
"handlebars": "^4.0.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^7.0.43",
|
||||
@@ -76,6 +83,7 @@
|
||||
"sqlops": "github:anthonydresser/sqlops-extension-sqlops",
|
||||
"tslint": "^3.14.0",
|
||||
"typescript": "^2.6.1",
|
||||
"vscode": "^1.1.14"
|
||||
"vscode": "^1.1.14",
|
||||
"@types/handlebars": "^4.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,11 +40,110 @@ export default class MainController implements vscode.Disposable {
|
||||
vscode.window.showInformationMessage(`Clicked from profile ${profile.serverName}.${profile.databaseName}`);
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('mssql.openDialog', () => {
|
||||
this.openDialog();
|
||||
});
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private openDialog(): void {
|
||||
let dialog = sqlops.window.modelviewdialog.createDialog('Test dialog');
|
||||
let tab1 = sqlops.window.modelviewdialog.createTab('Test tab 1');
|
||||
|
||||
let tab2 = sqlops.window.modelviewdialog.createTab('Test tab 2');
|
||||
tab2.content = 'sqlservices';
|
||||
dialog.content = [tab1, tab2];
|
||||
dialog.okButton.onClick(() => console.log('ok clicked!'));
|
||||
dialog.cancelButton.onClick(() => console.log('cancel clicked!'));
|
||||
dialog.okButton.label = 'ok';
|
||||
dialog.cancelButton.label = 'no';
|
||||
let customButton1 = sqlops.window.modelviewdialog.createButton('Test button 1');
|
||||
customButton1.onClick(() => console.log('button 1 clicked!'));
|
||||
let customButton2 = sqlops.window.modelviewdialog.createButton('Test button 2');
|
||||
customButton2.onClick(() => console.log('button 2 clicked!'));
|
||||
dialog.customButtons = [customButton1, customButton2];
|
||||
tab1.registerContent(async (view) => {
|
||||
let inputBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
//width: 300
|
||||
})
|
||||
.component();
|
||||
let inputBox2 = view.modelBuilder.inputBox()
|
||||
.component();
|
||||
|
||||
let inputBox3 = view.modelBuilder.inputBox()
|
||||
.component();
|
||||
|
||||
let checkbox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: 'Copy-only backup'
|
||||
})
|
||||
.component();
|
||||
checkbox.onChanged(e => {
|
||||
console.info("inputBox.enabled " + inputBox.enabled);
|
||||
inputBox.enabled = !inputBox.enabled;
|
||||
});
|
||||
let button = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: '+'
|
||||
}).component();
|
||||
let button3 = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: '-'
|
||||
|
||||
}).component();
|
||||
let button2 = view.modelBuilder.button()
|
||||
.component();
|
||||
button.onDidClick(e => {
|
||||
inputBox2.value = 'Button clicked';
|
||||
});
|
||||
let dropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: 'Full',
|
||||
values: ['Full', 'Differential', 'Transaction Log']
|
||||
})
|
||||
.component();
|
||||
let f = 0;
|
||||
inputBox.onTextChanged((params) => {
|
||||
vscode.window.showInformationMessage(inputBox.value);
|
||||
f = f + 1;
|
||||
inputBox2.value=f.toString();
|
||||
});
|
||||
dropdown.onValueChanged((params) => {
|
||||
vscode.window.showInformationMessage(inputBox2.value);
|
||||
inputBox.value = dropdown.value;
|
||||
});
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: inputBox,
|
||||
title: 'Backup name'
|
||||
}, {
|
||||
component: inputBox2,
|
||||
title: 'Recovery model'
|
||||
}, {
|
||||
component:dropdown,
|
||||
title: 'Backup type'
|
||||
}, {
|
||||
component: checkbox,
|
||||
title: ''
|
||||
}, {
|
||||
component: inputBox2,
|
||||
title: 'Backup files',
|
||||
actions: [button, button3]
|
||||
}], {
|
||||
horizontal:false,
|
||||
width: 500,
|
||||
componentWidth: 400
|
||||
}).component();
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
|
||||
sqlops.window.modelviewdialog.openDialog(dialog);
|
||||
}
|
||||
|
||||
private registerSqlServicesModelView(): void {
|
||||
sqlops.dashboard.registerModelViewProvider('sqlservices', async (view) => {
|
||||
sqlops.ui.registerModelViewProvider('sqlservices', async (view) => {
|
||||
let flexModel = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
@@ -62,7 +161,7 @@ export default class MainController implements vscode.Disposable {
|
||||
.withProperties<sqlops.CardProperties>({
|
||||
label: 'label1',
|
||||
value: 'value1',
|
||||
actions: [{ label: 'action', taskId: 'sqlservices.clickTask' }]
|
||||
actions: [{ label: 'action' }]
|
||||
})
|
||||
.component()
|
||||
]).component(),
|
||||
@@ -74,7 +173,7 @@ export default class MainController implements vscode.Disposable {
|
||||
.withProperties<sqlops.CardProperties>({
|
||||
label: 'label2',
|
||||
value: 'value2',
|
||||
actions: [{ label: 'action', taskId: 'sqlservices.clickTask' }]
|
||||
actions: [{ label: 'action' }]
|
||||
})
|
||||
.component()
|
||||
]).component()
|
||||
@@ -85,7 +184,7 @@ export default class MainController implements vscode.Disposable {
|
||||
}
|
||||
|
||||
private registerSplitPanelModelView(): void {
|
||||
sqlops.dashboard.registerModelViewProvider('splitPanel', async (view) => {
|
||||
sqlops.ui.registerModelViewProvider('splitPanel', async (view) => {
|
||||
let numPanels = 3;
|
||||
let splitPanel = new SplitPropertiesPanel(view, numPanels);
|
||||
await view.initializeModel(splitPanel.modelBase);
|
||||
|
||||
@@ -21,6 +21,12 @@ panel {
|
||||
.tabbedPanel .composite.title {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tabbedPanel .tabContainer {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tabbedPanel .tabList {
|
||||
@@ -31,6 +37,7 @@ panel {
|
||||
line-height: 35px;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tabbedPanel .tabList .tab {
|
||||
@@ -65,15 +72,6 @@ panel {
|
||||
line-height: 45px;
|
||||
}
|
||||
|
||||
.tabbedPanel .tabList .tab:hover .tabLabel,
|
||||
.tabbedPanel .tabList .tab .tabLabel.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tabbedPanel .tabList .tab .tabLabel {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.tabbedPanel .tabList .tab .tabLabel.icon {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
@@ -97,17 +95,13 @@ panel {
|
||||
}
|
||||
|
||||
.composite.title .title-actions {
|
||||
flex: 1 1 auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.tab > .tabLabel.active {
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
|
||||
.composite.title ~ tab.fullsize > :first-child {
|
||||
height: calc(100% - 38px);
|
||||
}
|
||||
|
||||
.tabbedPanel .title-actions .panel-actions .actions-container {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ import { Component, ContentChildren, QueryList, AfterContentInit, Inject, forwar
|
||||
|
||||
import { TabComponent } from './tab.component';
|
||||
import { TabHeaderComponent } from './tabHeader.component';
|
||||
import { ScrollableDirective } from 'sql/base/browser/ui/scrollable/scrollable.directive';
|
||||
import './panelStyles';
|
||||
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
@@ -46,12 +46,13 @@ let idPool = 0;
|
||||
@Component({
|
||||
selector: 'panel',
|
||||
template: `
|
||||
|
||||
<div class="tabbedPanel fullsize" #tabbedPanel>
|
||||
<div *ngIf="!options.showTabsWhenOne ? _tabs.length !== 1 : true" class="composite title" #titleContainer>
|
||||
<div class="tabList" #tabList role="tablist">
|
||||
<div *ngFor="let tab of _tabs">
|
||||
<tab-header [tab]="tab" [showIcon]="options.showIcon" (onSelectTab)='selectTab($event)' (onCloseTab)='closeTab($event)'> </tab-header>
|
||||
<div class="tabbedPanel fullsize" [ngClass]="options.layout === NavigationBarLayout.vertical ? 'vertical' : 'horizontal'">
|
||||
<div *ngIf="!options.showTabsWhenOne ? _tabs.length !== 1 : true" class="composite title">
|
||||
<div class="tabContainer">
|
||||
<div class="tabList" role="tablist" scrollable [horizontalScroll]="ScrollbarVisibility.Auto" [verticalScroll]="ScrollbarVisibility.Hidden" [scrollYToX]="true">
|
||||
<div *ngFor="let tab of _tabs">
|
||||
<tab-header [active]="_activeTab === tab" [tab]="tab" [showIcon]="options.showIcon" (onSelectTab)='selectTab($event)' (onCloseTab)='closeTab($event)'> </tab-header>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="title-actions">
|
||||
@@ -67,11 +68,12 @@ let idPool = 0;
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class PanelComponent extends Disposable implements AfterContentInit, OnInit, OnChanges, OnDestroy, AfterViewInit {
|
||||
export class PanelComponent extends Disposable {
|
||||
@Input() public options: IPanelOptions;
|
||||
@Input() public actions: Array<Action>;
|
||||
@ContentChildren(TabComponent) private _tabs: QueryList<TabComponent>;
|
||||
@ViewChildren(TabHeaderComponent) private _headerTabs: QueryList<TabHeaderComponent>;
|
||||
@ViewChild(ScrollableDirective) private scrollable: ScrollableDirective;
|
||||
|
||||
@Output() public onTabChange = new EventEmitter<TabComponent>();
|
||||
@Output() public onTabClose = new EventEmitter<TabComponent>();
|
||||
@@ -79,12 +81,11 @@ export class PanelComponent extends Disposable implements AfterContentInit, OnIn
|
||||
private _activeTab: TabComponent;
|
||||
private _actionbar: ActionBar;
|
||||
private _mru: TabComponent[];
|
||||
private _scrollableElement: ScrollableElement;
|
||||
|
||||
private ScrollbarVisibility = ScrollbarVisibility;
|
||||
private NavigationBarLayout = NavigationBarLayout;
|
||||
|
||||
@ViewChild('panelActionbar', { read: ElementRef }) private _actionbarRef: ElementRef;
|
||||
@ViewChild('tabbedPanel', { read: ElementRef }) private _tabbedPanelRef: ElementRef;
|
||||
@ViewChild('titleContainer', { read: ElementRef }) private _titleContainer: ElementRef;
|
||||
@ViewChild('tabList', { read: ElementRef }) private _tabList: ElementRef;
|
||||
constructor( @Inject(forwardRef(() => NgZone)) private _zone: NgZone) {
|
||||
super();
|
||||
}
|
||||
@@ -96,51 +97,14 @@ export class PanelComponent extends Disposable implements AfterContentInit, OnIn
|
||||
|
||||
ngAfterContentInit(): void {
|
||||
if (this._tabs && this._tabs.length > 0) {
|
||||
this._activeTab = this._tabs.first;
|
||||
this._activeTab.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
if (!this.options.showTabsWhenOne ? this._tabs.length !== 1 : true) {
|
||||
let container = this._titleContainer.nativeElement as HTMLElement;
|
||||
let tabList = this._tabList.nativeElement as HTMLElement;
|
||||
container.removeChild(tabList);
|
||||
|
||||
this._scrollableElement = new ScrollableElement(tabList, {
|
||||
horizontal: ScrollbarVisibility.Auto,
|
||||
vertical: ScrollbarVisibility.Hidden,
|
||||
scrollYToX: true,
|
||||
useShadows: false,
|
||||
horizontalScrollbarSize: 3
|
||||
this.selectTab(this._tabs.first);
|
||||
} else {
|
||||
const sub = this._tabs.changes.subscribe(() => {
|
||||
if (this._tabs && this._tabs.length > 0) {
|
||||
this.selectTab(this._tabs.first);
|
||||
sub.unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
this._scrollableElement.onScroll(e => {
|
||||
tabList.scrollLeft = e.scrollLeft;
|
||||
});
|
||||
|
||||
container.insertBefore(this._scrollableElement.getDomNode(), container.firstChild);
|
||||
|
||||
this._scrollableElement.setScrollDimensions({
|
||||
width: tabList.offsetWidth,
|
||||
scrollWidth: tabList.scrollWidth
|
||||
});
|
||||
|
||||
this._register(addDisposableListener(window, EventType.RESIZE, () => {
|
||||
// Todo: Need to set timeout because we have to make sure that the grids have already rearraged before the getContentHeight gets called.
|
||||
setTimeout(() => {
|
||||
this._scrollableElement.setScrollDimensions({
|
||||
width: tabList.offsetWidth,
|
||||
scrollWidth: tabList.scrollWidth
|
||||
});
|
||||
}, 100);
|
||||
}));
|
||||
|
||||
if (this.options.layout === NavigationBarLayout.horizontal) {
|
||||
(<HTMLElement>this._tabbedPanelRef.nativeElement).classList.add(horizontalLayout);
|
||||
} else {
|
||||
(<HTMLElement>this._tabbedPanelRef.nativeElement).classList.add(verticalLayout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +118,17 @@ export class PanelComponent extends Disposable implements AfterContentInit, OnIn
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this._tabs.changes.subscribe(() => {
|
||||
if (this.scrollable) {
|
||||
this.scrollable.layout();
|
||||
}
|
||||
});
|
||||
if (this.scrollable) {
|
||||
this.scrollable.layout();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this._actionbar) {
|
||||
this._actionbar.dispose();
|
||||
@@ -210,12 +185,6 @@ export class PanelComponent extends Disposable implements AfterContentInit, OnIn
|
||||
this.setMostRecentlyUsed(tab);
|
||||
this._activeTab.active = true;
|
||||
|
||||
// Make the tab header focus on the new selected tab
|
||||
let activeTabHeader = this._headerTabs.find(i => i.tab === this._activeTab);
|
||||
if (activeTabHeader) {
|
||||
activeTabHeader.focusOnTabHeader();
|
||||
}
|
||||
|
||||
this.onTabChange.emit(tab);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,9 +9,11 @@ import { TabComponent } from './tab.component';
|
||||
import { TabHeaderComponent } from './tabHeader.component';
|
||||
import { PanelComponent } from './panel.component';
|
||||
|
||||
import { ScrollableModule } from 'sql/base/browser/ui/scrollable/scrollable.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
imports: [CommonModule, ScrollableModule],
|
||||
exports: [TabComponent, PanelComponent],
|
||||
declarations: [TabComponent, TabHeaderComponent, PanelComponent]
|
||||
})
|
||||
export class PanelModule { }
|
||||
export class PanelModule { }
|
||||
|
||||
@@ -5,33 +5,33 @@
|
||||
import 'vs/css!./media/panel';
|
||||
|
||||
import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER } from 'vs/workbench/common/theme';
|
||||
import { PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
|
||||
// Title Active
|
||||
const titleActive = theme.getColor(TAB_ACTIVE_FOREGROUND);
|
||||
const titleActive = theme.getColor(PANEL_ACTIVE_TITLE_FOREGROUND);
|
||||
const titleActiveBorder = theme.getColor(PANEL_ACTIVE_TITLE_BORDER);
|
||||
if (titleActive || titleActiveBorder) {
|
||||
collector.addRule(`
|
||||
.tabbedPanel > .title > .tabList .tab:hover .tabLabel,
|
||||
.tabbedPanel > .title > .tabList .tab .tabLabel.active {
|
||||
.tabbedPanel > .title .tabList .tab:hover .tabLabel,
|
||||
.tabbedPanel > .title .tabList .tab .tabLabel.active {
|
||||
color: ${titleActive};
|
||||
border-bottom-color: ${titleActiveBorder};
|
||||
}
|
||||
|
||||
.tabbedPanel > .title > .tabList .tab-header.active {
|
||||
.tabbedPanel > .title .tabList .tab-header.active {
|
||||
outline: none;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Title Inactive
|
||||
const titleInactive = theme.getColor(TAB_INACTIVE_FOREGROUND);
|
||||
const titleInactive = theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND);
|
||||
if (titleInactive) {
|
||||
collector.addRule(`
|
||||
.tabbedPanel > .title > .tabList .tab .tabLabel {
|
||||
.tabbedPanel > .title .tabList .tab .tabLabel {
|
||||
color: ${titleInactive};
|
||||
}
|
||||
`);
|
||||
@@ -41,7 +41,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const focusBorderColor = theme.getColor(focusBorder);
|
||||
if (focusBorderColor) {
|
||||
collector.addRule(`
|
||||
.tabbedPanel > .title > .tabList .tab .tabLabel:focus {
|
||||
.tabbedPanel > .title .tabList .tab .tabLabel:focus {
|
||||
color: ${titleActive};
|
||||
border-bottom-color: ${focusBorderColor} !important;
|
||||
border-bottom: 1px solid;
|
||||
@@ -54,8 +54,8 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const outline = theme.getColor(activeContrastBorder);
|
||||
if (outline) {
|
||||
collector.addRule(`
|
||||
.tabbedPanel > .title > .tabList .tab-header.active,
|
||||
.tabbedPanel > .title > .tabList .tab-header:hover {
|
||||
.tabbedPanel > .title .tabList .tab-header.active,
|
||||
.tabbedPanel > .title .tabList .tab-header:hover {
|
||||
outline-color: ${outline};
|
||||
outline-width: 1px;
|
||||
outline-style: solid;
|
||||
@@ -63,7 +63,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
outline-offset: -5px;
|
||||
}
|
||||
|
||||
.tabbedPanel > .title > .tabList .tab-header:hover:not(.active) {
|
||||
.tabbedPanel > .title .tabList .tab-header:hover:not(.active) {
|
||||
outline-style: dashed;
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { Component, Input, ContentChild, OnDestroy } from '@angular/core';
|
||||
import { Component, Input, ContentChild, OnDestroy, TemplateRef, ChangeDetectorRef, forwardRef, Inject } from '@angular/core';
|
||||
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
|
||||
@@ -13,13 +13,14 @@ export abstract class TabChild {
|
||||
@Component({
|
||||
selector: 'tab',
|
||||
template: `
|
||||
<div class="visibility" [class.hidden]="!active && visibilityType == 'visibility'" *ngIf="visibilityType == 'visibility' || active" class="fullsize">
|
||||
<ng-content class="body fullsize"></ng-content>
|
||||
<div class="visibility" [class.hidden]="shouldBeHidden()" *ngIf="shouldBeIfed()" class="fullsize">
|
||||
<ng-container *ngTemplateOutlet="templateRef"></ng-container>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class TabComponent implements OnDestroy {
|
||||
@ContentChild(TabChild) private _child: TabChild;
|
||||
@ContentChild(TemplateRef) templateRef;
|
||||
@Input() public title: string;
|
||||
@Input() public canClose: boolean;
|
||||
@Input() public actions: Array<Action>;
|
||||
@@ -27,9 +28,18 @@ export class TabComponent implements OnDestroy {
|
||||
public _active = false;
|
||||
@Input() public identifier: string;
|
||||
@Input() private visibilityType: 'if' | 'visibility' = 'if';
|
||||
private rendered = false;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef
|
||||
) { }
|
||||
|
||||
public set active(val: boolean) {
|
||||
this._active = val;
|
||||
if (this.active) {
|
||||
this.rendered = true;
|
||||
}
|
||||
this._cd.detectChanges();
|
||||
if (this.active && this._child) {
|
||||
this._child.layout();
|
||||
}
|
||||
@@ -45,4 +55,21 @@ export class TabComponent implements OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
shouldBeIfed(): boolean {
|
||||
if (this.active) {
|
||||
return true;
|
||||
} else if (this.visibilityType === 'visibility' && this.rendered) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
shouldBeHidden(): boolean {
|
||||
if (this.visibilityType === 'visibility' && !this.active) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import { CloseTabAction } from './tabActions';
|
||||
@Component({
|
||||
selector: 'tab-header',
|
||||
template: `
|
||||
<div #actionHeader class="tab-header" style="flex: 0 0; flex-direction: row;" [class.active]="tab.active" tabindex="0" (keyup)="onKey($event)">
|
||||
<div #actionHeader class="tab-header" style="flex: 0 0; flex-direction: row; height: 100%" [class.active]="tab.active" tabindex="0" (keyup)="onKey($event)">
|
||||
<span class="tab" (click)="selectTab(tab)" role="tab" [attr.aria-selected]="tab.active" [attr.aria-label]="tab.title">
|
||||
<a class="tabLabel" [class.active]="tab.active" #tabLabel>
|
||||
</a>
|
||||
@@ -31,6 +31,7 @@ import { CloseTabAction } from './tabActions';
|
||||
export class TabHeaderComponent extends Disposable implements AfterContentInit, OnDestroy {
|
||||
@Input() public tab: TabComponent;
|
||||
@Input() public showIcon: boolean;
|
||||
@Input() public active: boolean;
|
||||
@Output() public onSelectTab: EventEmitter<TabComponent> = new EventEmitter<TabComponent>();
|
||||
@Output() public onCloseTab: EventEmitter<TabComponent> = new EventEmitter<TabComponent>();
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Directive, Inject, forwardRef, ElementRef } from '@angular/core';
|
||||
import { Directive, Inject, forwardRef, ElementRef, Input } from '@angular/core';
|
||||
|
||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { getContentHeight, addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { getContentHeight, addDisposableListener, EventType, getContentWidth } from 'vs/base/browser/dom';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
|
||||
@Directive({
|
||||
@@ -17,33 +17,45 @@ export class ScrollableDirective extends AngularDisposable {
|
||||
private scrollableElement: ScrollableElement;
|
||||
private parent: HTMLElement;
|
||||
private scrolled: HTMLElement;
|
||||
@Input() horizontalScroll: ScrollbarVisibility;
|
||||
@Input() verticalScroll: ScrollbarVisibility;
|
||||
@Input() useShadow = false;
|
||||
@Input() scrollYToX = false;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.scrolled = this._el.nativeElement as HTMLElement;
|
||||
this.parent = this.scrolled.parentElement;
|
||||
const next = this.scrolled.nextSibling;
|
||||
this.parent.removeChild(this.scrolled);
|
||||
|
||||
this.scrolled.style.position = 'relative';
|
||||
|
||||
this.scrollableElement = new ScrollableElement(this.scrolled, {
|
||||
horizontal: ScrollbarVisibility.Hidden,
|
||||
vertical: ScrollbarVisibility.Auto,
|
||||
useShadows: false
|
||||
horizontal: this.horizontalScroll,
|
||||
vertical: this.verticalScroll,
|
||||
useShadows: this.useShadow,
|
||||
scrollYToX: this.scrollYToX,
|
||||
horizontalScrollbarSize: 3
|
||||
});
|
||||
|
||||
this.scrollableElement.onScroll(e => {
|
||||
this.scrolled.style.bottom = e.scrollTop + 'px';
|
||||
if (this.verticalScroll === ScrollbarVisibility.Auto) {
|
||||
this.scrolled.style.bottom = e.scrollTop + 'px';
|
||||
} else if (this.horizontalScroll === ScrollbarVisibility.Auto) {
|
||||
this.scrolled.scrollLeft = e.scrollLeft;
|
||||
}
|
||||
});
|
||||
|
||||
this.parent.appendChild(this.scrollableElement.getDomNode());
|
||||
this.parent.insertBefore(this.scrollableElement.getDomNode(), next);
|
||||
const initialHeight = getContentHeight(this.scrolled);
|
||||
this.scrollableElement.setScrollDimensions({
|
||||
scrollHeight: getContentHeight(this.scrolled),
|
||||
height: getContentHeight(this.parent)
|
||||
});
|
||||
const initalWidth = getContentWidth(this.scrolled);
|
||||
this.resetScrollDimensions();
|
||||
|
||||
this._register(addDisposableListener(window, EventType.RESIZE, () => {
|
||||
this.resetScrollDimensions();
|
||||
@@ -52,24 +64,23 @@ export class ScrollableDirective extends AngularDisposable {
|
||||
// unforunately because of angular rendering behavior we need to do a double check to make sure nothing changed after this point
|
||||
setTimeout(() => {
|
||||
let currentheight = getContentHeight(this.scrolled);
|
||||
if (initialHeight !== currentheight) {
|
||||
this.scrollableElement.setScrollDimensions({
|
||||
scrollHeight: currentheight,
|
||||
height: getContentHeight(this.parent)
|
||||
});
|
||||
let currentWidth = getContentWidth(this.scrolled);
|
||||
if (initialHeight !== currentheight || initalWidth !== currentWidth) {
|
||||
this.resetScrollDimensions();
|
||||
}
|
||||
}, 200);
|
||||
|
||||
}
|
||||
|
||||
private resetScrollDimensions() {
|
||||
this.scrollableElement.setScrollDimensions({
|
||||
scrollHeight: getContentHeight(this.scrolled),
|
||||
height: getContentHeight(this.parent)
|
||||
scrollHeight: this.verticalScroll === ScrollbarVisibility.Auto ? getContentHeight(this.scrolled) : undefined,
|
||||
height: this.verticalScroll === ScrollbarVisibility.Auto ? getContentHeight(this.parent) : undefined,
|
||||
scrollWidth: this.horizontalScroll === ScrollbarVisibility.Auto ? this.scrolled.scrollWidth : undefined,
|
||||
width: this.horizontalScroll === ScrollbarVisibility.Auto ? this.scrolled.offsetWidth : undefined
|
||||
});
|
||||
}
|
||||
|
||||
public layout() {
|
||||
|
||||
this.resetScrollDimensions();
|
||||
}
|
||||
}
|
||||
|
||||
15
src/sql/base/browser/ui/scrollable/scrollable.module.ts
Normal file
15
src/sql/base/browser/ui/scrollable/scrollable.module.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { ScrollableDirective } from './scrollable.directive';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
exports: [ScrollableDirective],
|
||||
declarations: [ScrollableDirective]
|
||||
})
|
||||
export class ScrollableModule { }
|
||||
@@ -131,6 +131,14 @@
|
||||
background: url("filter_inverse.svg") center center no-repeat !important;
|
||||
}
|
||||
|
||||
.vs .icon.filterLabel {
|
||||
background-image: url("filter.svg");
|
||||
}
|
||||
|
||||
.vs-dark .icon.filterLabel,
|
||||
.hc-black .icon.filterLabel {
|
||||
background-image: url("filter_inverse.svg");
|
||||
}
|
||||
|
||||
.vs .icon.warning-badge,
|
||||
.vs-dark .icon.warning-badge,
|
||||
|
||||
@@ -34,20 +34,28 @@ import { AccountProviderAddedEventParams, UpdateAccountListEventParams } from 's
|
||||
import { FixedListView } from 'sql/platform/views/fixedListView';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
export interface IProviderViewUiComponent {
|
||||
view: FixedListView<sqlops.Account>;
|
||||
addAccountAction: AddAccountAction;
|
||||
}
|
||||
|
||||
export class AccountDialog extends Modal {
|
||||
public static ACCOUNTLIST_HEIGHT = 77;
|
||||
|
||||
public viewModel: AccountViewModel;
|
||||
|
||||
// MEMBER VARIABLES ////////////////////////////////////////////////////
|
||||
private _providerViews: { [providerId: string]: FixedListView<sqlops.Account> } = {};
|
||||
private _providerViews: { [providerId: string]: IProviderViewUiComponent } = {};
|
||||
|
||||
private _closeButton: Button;
|
||||
private _addAccountButton: Button;
|
||||
private _delegate: AccountListDelegate;
|
||||
private _accountRenderer: AccountListRenderer;
|
||||
private _actionRunner: ActionRunner;
|
||||
private _splitView: SplitView;
|
||||
private _container: HTMLElement;
|
||||
private _splitViewContainer: HTMLElement;
|
||||
private _noaccountViewContainer: HTMLElement;
|
||||
|
||||
// EVENTING ////////////////////////////////////////////////////////////
|
||||
private _onAddAccountErrorEmitter: Emitter<string>;
|
||||
@@ -89,6 +97,14 @@ export class AccountDialog extends Modal {
|
||||
this.viewModel.addProviderEvent(arg => { self.addProvider(arg); });
|
||||
this.viewModel.removeProviderEvent(arg => { self.removeProvider(arg); });
|
||||
this.viewModel.updateAccountListEvent(arg => { self.updateProviderAccounts(arg); });
|
||||
|
||||
// Load the initial contents of the view model
|
||||
this.viewModel.initialize()
|
||||
.then(addedProviders => {
|
||||
for (let addedProvider of addedProviders) {
|
||||
self.addProvider(addedProvider);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// MODAL OVERRIDE METHODS //////////////////////////////////////////////
|
||||
@@ -104,26 +120,35 @@ export class AccountDialog extends Modal {
|
||||
attachModalDialogStyler(this, this._themeService);
|
||||
this._closeButton = this.addFooterButton(localize('accountDialog.close', 'Close'), () => this.close());
|
||||
this.registerListeners();
|
||||
|
||||
// Load the initial contents of the view model
|
||||
this.viewModel.initialize()
|
||||
.then(addedProviders => {
|
||||
for (let addedProvider of addedProviders) {
|
||||
self.addProvider(addedProvider);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement) {
|
||||
this._container = container;
|
||||
let viewBody = DOM.$('div.account-view');
|
||||
DOM.append(container, viewBody);
|
||||
this._splitView = new SplitView(viewBody);
|
||||
this._splitViewContainer = DOM.$('div.account-view');
|
||||
DOM.append(container, this._splitViewContainer);
|
||||
this._splitView = new SplitView(this._splitViewContainer);
|
||||
|
||||
this._noaccountViewContainer = DOM.$('div.no-account-view');
|
||||
let noAccountTitle = DOM.append(this._noaccountViewContainer, DOM.$('.no-account-view-label'));
|
||||
let noAccountLabel = localize('accountDialog.noAccountLabel', 'There is no linked account. Please add an acount.');
|
||||
noAccountTitle.innerHTML = noAccountLabel;
|
||||
|
||||
// Show the add account button for the first provider
|
||||
// Todo: If we have more than 1 provider, need to show all add account buttons for all providers
|
||||
let buttonSection = DOM.append(this._noaccountViewContainer, DOM.$('div.button-section'));
|
||||
this._addAccountButton = new Button(buttonSection);
|
||||
this._addAccountButton.label = localize('accountDialog.addConnection', 'Add an account');
|
||||
this._register(this._addAccountButton.onDidClick(() => {
|
||||
(<IProviderViewUiComponent>Object.values(this._providerViews)[0]).addAccountAction.run();
|
||||
}));
|
||||
|
||||
DOM.append(container, this._noaccountViewContainer);
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
// Theme styler
|
||||
this._register(attachButtonStyler(this._closeButton, this._themeService));
|
||||
this._register(attachButtonStyler(this._addAccountButton, this._themeService));
|
||||
}
|
||||
|
||||
/* Overwrite escape key behavior */
|
||||
@@ -143,12 +168,48 @@ export class AccountDialog extends Modal {
|
||||
|
||||
public open() {
|
||||
this.show();
|
||||
if (!this.isEmptyLinkedAccount()) {
|
||||
this.showSplitView();
|
||||
} else {
|
||||
this._splitViewContainer.hidden = true;
|
||||
this._noaccountViewContainer.hidden = false;
|
||||
this._addAccountButton.focus();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private showSplitView() {
|
||||
this._splitViewContainer.hidden = false;
|
||||
this._noaccountViewContainer.hidden = true;
|
||||
let views = this._splitView.getViews();
|
||||
if (views && views.length > 0) {
|
||||
let firstView = views[0];
|
||||
if (firstView instanceof FixedListView) {
|
||||
firstView.list.setSelection([0]);
|
||||
firstView.list.domFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isEmptyLinkedAccount(): boolean {
|
||||
for (var providerId in this._providerViews) {
|
||||
var listView = this._providerViews[providerId].view;
|
||||
if (listView && listView.list.length > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
for (let key in this._providerViews) {
|
||||
this._providerViews[key].dispose();
|
||||
if (this._providerViews[key].addAccountAction) {
|
||||
this._providerViews[key].addAccountAction.dispose();
|
||||
}
|
||||
if (this._providerViews[key].view) {
|
||||
this._providerViews[key].view.dispose();
|
||||
}
|
||||
delete this._providerViews[key];
|
||||
}
|
||||
}
|
||||
@@ -199,21 +260,26 @@ export class AccountDialog extends Modal {
|
||||
|
||||
// Set the initial items of the list
|
||||
providerView.updateList(newProvider.initialAccounts);
|
||||
|
||||
if (newProvider.initialAccounts.length > 0 && this._splitViewContainer.hidden) {
|
||||
this.showSplitView();
|
||||
}
|
||||
|
||||
this.layout();
|
||||
|
||||
// Store the view for the provider
|
||||
this._providerViews[newProvider.addedProvider.id] = providerView;
|
||||
// Store the view for the provider and action
|
||||
this._providerViews[newProvider.addedProvider.id] = { view: providerView, addAccountAction: addAccountAction };
|
||||
}
|
||||
|
||||
private removeProvider(removedProvider: sqlops.AccountProviderMetadata) {
|
||||
// Skip removing the provider if it doesn't exist
|
||||
let providerView = this._providerViews[removedProvider.id];
|
||||
if (!providerView) {
|
||||
if (!providerView || !providerView.view) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the list view from the split view
|
||||
this._splitView.removeView(providerView);
|
||||
this._splitView.removeView(providerView.view);
|
||||
this._splitView.layout(DOM.getContentHeight(this._container));
|
||||
|
||||
// Remove the list view from our internal map
|
||||
@@ -223,10 +289,15 @@ export class AccountDialog extends Modal {
|
||||
|
||||
private updateProviderAccounts(args: UpdateAccountListEventParams) {
|
||||
let providerMapping = this._providerViews[args.providerId];
|
||||
if (!providerMapping) {
|
||||
if (!providerMapping || !providerMapping.view) {
|
||||
return;
|
||||
}
|
||||
providerMapping.updateList(args.accountList);
|
||||
providerMapping.view.updateList(args.accountList);
|
||||
|
||||
if (args.accountList.length > 0 && this._splitViewContainer.hidden) {
|
||||
this.showSplitView();
|
||||
}
|
||||
|
||||
this.layout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,49 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
.no-account-view {
|
||||
font-size: 12px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.account-view .monaco-split-view .split-view-view .header {
|
||||
.no-account-view .no-account-view-label {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.account-view .header {
|
||||
position: relative;
|
||||
line-height: 22px;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
padding-left: 20px;
|
||||
padding-right: 12px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.account-view .header .title {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.account-view .header .actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.account-view .header .actions .action-item .action-label {
|
||||
width: 30px;
|
||||
background-size: 16px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
margin-right: 0;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.account-view .header .count-badge-wrapper {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.account-view .provider-view .list-row {
|
||||
@@ -58,24 +98,4 @@
|
||||
.account-view .provider-view .monaco-list .monaco-list-row.selected .list-row .actions-container,
|
||||
.account-view .provider-view .monaco-list .monaco-list-row.focused .list-row .actions-container{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.account-view .split-view-view .header .title {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.account-view .split-view-view .header .actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.account-view .split-view-view .header .actions .action-item .action-label {
|
||||
width: 30px;
|
||||
background-size: 16px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
margin-right: 0;
|
||||
height: 22px;
|
||||
}
|
||||
@@ -6,23 +6,25 @@
|
||||
-->
|
||||
<panel class="dashboard-panel" (onTabChange)="handleTabChange($event)" (onTabClose)="handleTabClose($event)" [actions]="panelActions">
|
||||
<tab [visibilityType]="'visibility'" *ngFor="let tab of tabs" [title]="tab.title" class="fullsize" [identifier]="tab.id" [canClose]="tab.canClose" [actions]="tab.actions">
|
||||
<dashboard-home-container *ngIf="tab.id === 'homeTab'; else not_home" [properties]="propertiesWidget" [tab]="tab">
|
||||
</dashboard-home-container>
|
||||
<ng-template #not_home>
|
||||
<dashboard-webview-container *ngIf="getContentType(tab) === 'webview-container'" [tab]="tab">
|
||||
</dashboard-webview-container>
|
||||
<dashboard-widget-container *ngIf="getContentType(tab) === 'widgets-container'" [tab]="tab">
|
||||
</dashboard-widget-container>
|
||||
<dashboard-modelview-container *ngIf="getContentType(tab) === 'modelview-container'" [tab]="tab">
|
||||
</dashboard-modelview-container>
|
||||
<dashboard-controlhost-container *ngIf="getContentType(tab) === 'controlhost-container'" [tab]="tab">
|
||||
</dashboard-controlhost-container>
|
||||
<dashboard-nav-section *ngIf="getContentType(tab) === 'nav-section'" [tab]="tab">
|
||||
</dashboard-nav-section>
|
||||
<dashboard-grid-container *ngIf="getContentType(tab) === 'grid-container'" [tab]="tab">
|
||||
</dashboard-grid-container>
|
||||
<dashboard-error-container *ngIf="getContentType(tab) === 'error-container'" [tab]="tab">
|
||||
</dashboard-error-container>
|
||||
<ng-template>
|
||||
<dashboard-home-container *ngIf="tab.id === 'homeTab'; else not_home" [properties]="propertiesWidget" [tab]="tab">
|
||||
</dashboard-home-container>
|
||||
<ng-template #not_home>
|
||||
<dashboard-webview-container *ngIf="getContentType(tab) === 'webview-container'" [tab]="tab">
|
||||
</dashboard-webview-container>
|
||||
<dashboard-widget-container *ngIf="getContentType(tab) === 'widgets-container'" [tab]="tab">
|
||||
</dashboard-widget-container>
|
||||
<dashboard-modelview-container *ngIf="getContentType(tab) === 'modelview-container'" [tab]="tab">
|
||||
</dashboard-modelview-container>
|
||||
<dashboard-controlhost-container *ngIf="getContentType(tab) === 'controlhost-container'" [tab]="tab">
|
||||
</dashboard-controlhost-container>
|
||||
<dashboard-nav-section *ngIf="getContentType(tab) === 'nav-section'" [tab]="tab">
|
||||
</dashboard-nav-section>
|
||||
<dashboard-grid-container *ngIf="getContentType(tab) === 'grid-container'" [tab]="tab">
|
||||
</dashboard-grid-container>
|
||||
<dashboard-error-container *ngIf="getContentType(tab) === 'error-container'" [tab]="tab">
|
||||
</dashboard-error-container>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</tab>
|
||||
</panel>
|
||||
|
||||
@@ -17,7 +17,7 @@ import { IPropertiesConfig } from 'sql/parts/dashboard/pages/serverDashboardPage
|
||||
import { PanelComponent } from 'sql/base/browser/ui/panel/panel.component';
|
||||
import { IDashboardRegistry, Extensions as DashboardExtensions, IDashboardTab } from 'sql/platform/dashboard/common/dashboardRegistry';
|
||||
import { PinUnpinTabAction, AddFeatureTabAction } from './actions';
|
||||
import { TabComponent } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { TabComponent, TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { AngularEventType } from 'sql/services/angularEventing/angularEventingService';
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
@@ -61,7 +61,7 @@ export abstract class DashboardPage extends AngularDisposable {
|
||||
private _tabsDispose: Array<IDisposable> = [];
|
||||
private _tabSettingConfigs: Array<TabSettingConfig> = [];
|
||||
|
||||
@ViewChildren(DashboardTab) private _tabs: QueryList<DashboardTab>;
|
||||
@ViewChildren(TabChild) private _tabs: QueryList<DashboardTab>;
|
||||
@ViewChild(PanelComponent) private _panel: PanelComponent;
|
||||
|
||||
private _editEnabled = new Emitter<boolean>();
|
||||
@@ -142,7 +142,6 @@ export abstract class DashboardPage extends AngularDisposable {
|
||||
actions: []
|
||||
};
|
||||
this.addNewTab(homeTab);
|
||||
this._panel.selectTab(homeTab.id);
|
||||
|
||||
let allTabs = dashboardHelper.filterConfigs(dashboardRegistry.tabs, this.dashboardService);
|
||||
|
||||
@@ -254,7 +253,6 @@ export abstract class DashboardPage extends AngularDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private getContentType(tab: TabConfig): string {
|
||||
return tab.container ? Object.keys(tab.container)[0] : '';
|
||||
}
|
||||
@@ -310,10 +308,10 @@ export abstract class DashboardPage extends AngularDisposable {
|
||||
}
|
||||
|
||||
public handleTabChange(tab: TabComponent): void {
|
||||
this._cd.detectChanges();
|
||||
let localtab = this._tabs.find(i => i.id === tab.identifier);
|
||||
this._editEnabled.fire(localtab.editable);
|
||||
this._cd.detectChanges();
|
||||
localtab.layout();
|
||||
}
|
||||
|
||||
public handleTabClose(tab: TabComponent): void {
|
||||
|
||||
@@ -15,22 +15,22 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const tabActiveForeground = theme.getColor(TAB_ACTIVE_FOREGROUND);
|
||||
if (tabActiveBackground || tabActiveForeground) {
|
||||
collector.addRule(`
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .monaco-scrollable-element > .tabList .tab:hover .tabLabel,
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .monaco-scrollable-element > .tabList .tab .tabLabel.active {
|
||||
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab:hover .tabLabel,
|
||||
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab .tabLabel.active {
|
||||
color: ${tabActiveForeground};
|
||||
border-bottom: 0px solid;
|
||||
}
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .monaco-scrollable-element > .tabList .tab-header.active {
|
||||
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab-header.active {
|
||||
background-color: ${tabActiveBackground};
|
||||
outline-color: ${tabActiveBackground};
|
||||
}
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel.horizontal > .title > .monaco-scrollable-element > .tabList .tab-header.active {
|
||||
panel.dashboard-panel > .tabbedPanel.horizontal > .title .tabList .tab-header.active {
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel.vertical > .title > .monaco-scrollable-element > .tabList .tab-header.active {
|
||||
panel.dashboard-panel > .tabbedPanel.vertical > .title .tabList .tab-header.active {
|
||||
border-right-color: transparent;
|
||||
}
|
||||
`);
|
||||
@@ -39,7 +39,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const activeTabBorderColor = theme.getColor(TAB_ACTIVE_BORDER);
|
||||
if (activeTabBorderColor) {
|
||||
collector.addRule(`
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .monaco-scrollable-element > .tabList .tab-header.active {
|
||||
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab-header.active {
|
||||
box-shadow: ${activeTabBorderColor} 0 -1px inset;
|
||||
}
|
||||
`);
|
||||
@@ -50,11 +50,11 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const tabInactiveForeground = theme.getColor(TAB_INACTIVE_FOREGROUND);
|
||||
if (tabInactiveBackground || tabInactiveForeground) {
|
||||
collector.addRule(`
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .monaco-scrollable-element > .tabList .tab .tabLabel {
|
||||
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab .tabLabel {
|
||||
color: ${tabInactiveForeground};
|
||||
}
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .monaco-scrollable-element > .tabList .tab-header {
|
||||
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab-header {
|
||||
background-color: ${tabInactiveBackground};
|
||||
}
|
||||
`);
|
||||
@@ -71,12 +71,12 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
}
|
||||
|
||||
// Panel title background
|
||||
const tabBoarder = theme.getColor(TAB_BORDER);
|
||||
if (tabBoarder) {
|
||||
const tabBorder = theme.getColor(TAB_BORDER);
|
||||
if (tabBorder) {
|
||||
collector.addRule(`
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .monaco-scrollable-element > .tabList .tab-header {
|
||||
border-right-color: ${tabBoarder};
|
||||
border-bottom-color: ${tabBoarder};
|
||||
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab-header {
|
||||
border-right-color: ${tabBorder};
|
||||
border-bottom-color: ${tabBorder};
|
||||
}
|
||||
`);
|
||||
}
|
||||
@@ -86,13 +86,13 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
if (outline) {
|
||||
collector.addRule(`
|
||||
panel.dashboard-panel > .tabbedPanel > .title {
|
||||
border-bottom-color: ${tabBoarder};
|
||||
border-bottom-color: ${tabBorder};
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel.vertical > .title {
|
||||
border-right-color: ${tabBoarder};
|
||||
border-right-color: ${tabBorder};
|
||||
border-right-width: 1px;
|
||||
border-right-style: solid;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { OnDestroy } from '@angular/core';
|
||||
|
||||
import Event from 'vs/base/common/event';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
|
||||
export enum Conditional {
|
||||
'equals',
|
||||
@@ -17,7 +21,7 @@ export enum Conditional {
|
||||
'always'
|
||||
}
|
||||
|
||||
export abstract class DashboardTab extends AngularDisposable {
|
||||
export abstract class DashboardTab extends TabChild implements OnDestroy {
|
||||
public abstract layout(): void;
|
||||
public abstract readonly id: string;
|
||||
public abstract readonly editable: boolean;
|
||||
@@ -26,4 +30,23 @@ export abstract class DashboardTab extends AngularDisposable {
|
||||
public enableEdit(): void {
|
||||
// no op
|
||||
}
|
||||
|
||||
private _toDispose: IDisposable[] = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
|
||||
protected _register<T extends IDisposable>(t: T): T {
|
||||
this._toDispose.push(t);
|
||||
return t;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,17 @@
|
||||
import 'vs/css!./dashboardControlHostContainer';
|
||||
|
||||
import { Component, forwardRef, Input, AfterContentInit, ViewChild, OnChanges } from '@angular/core';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { TabConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { ControlHostContent } from 'sql/parts/dashboard/contents/controlHostContent.component';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
|
||||
@Component({
|
||||
selector: 'dashboard-controlhost-container',
|
||||
providers: [{ provide: DashboardTab, useExisting: forwardRef(() => DashboardControlHostContainer) }],
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => DashboardControlHostContainer) }],
|
||||
template: `
|
||||
<controlhost-content [webviewId]="tab.id">
|
||||
</controlhost-content>
|
||||
|
||||
@@ -9,13 +9,14 @@ import { Component, Inject, Input, forwardRef, ViewChild, ElementRef, ChangeDete
|
||||
|
||||
import { TabConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
@Component({
|
||||
selector: 'dashboard-error-container',
|
||||
providers: [{ provide: DashboardTab, useExisting: forwardRef(() => DashboardErrorContainer) }],
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => DashboardErrorContainer) }],
|
||||
template: `
|
||||
<div class="error-container">
|
||||
<div class="icon globalError">
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'vs/css!./dashboardGridContainer';
|
||||
|
||||
import { Component, Inject, Input, forwardRef, ViewChild, ElementRef, ViewChildren, QueryList, OnDestroy, ChangeDetectorRef, EventEmitter } from '@angular/core';
|
||||
import { NgGridConfig, NgGrid, NgGridItem } from 'angular2-grid';
|
||||
import { concat } from 'rxjs/operator/concat';
|
||||
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
@@ -14,13 +15,13 @@ import { TabConfig, WidgetConfig } from 'sql/parts/dashboard/common/dashboardWid
|
||||
import { DashboardWidgetWrapper } from 'sql/parts/dashboard/contents/dashboardWidgetWrapper.component';
|
||||
import { subscriptionToDisposable } from 'sql/base/common/lifecycle';
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { WebviewContent } from 'sql/parts/dashboard/contents/webviewContent.component';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { concat } from 'rxjs/operator/concat';
|
||||
import { WebviewContent } from 'sql/parts/dashboard/contents/webviewContent.component';
|
||||
|
||||
export interface GridCellConfig {
|
||||
id?: string;
|
||||
@@ -42,7 +43,7 @@ export interface GridWebviewConfig extends GridCellConfig {
|
||||
@Component({
|
||||
selector: 'dashboard-grid-container',
|
||||
templateUrl: decodeURI(require.toUrl('sql/parts/dashboard/containers/dashboardGridContainer.component.html')),
|
||||
providers: [{ provide: DashboardTab, useExisting: forwardRef(() => DashboardGridContainer) }]
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => DashboardGridContainer) }]
|
||||
})
|
||||
export class DashboardGridContainer extends DashboardTab implements OnDestroy {
|
||||
@Input() private tab: TabConfig;
|
||||
|
||||
@@ -14,15 +14,18 @@ import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboar
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { AngularEventType } from 'sql/services/angularEventing/angularEventingService';
|
||||
import { DashboardWidgetWrapper } from 'sql/parts/dashboard/contents/dashboardWidgetWrapper.component';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { ScrollableDirective } from 'sql/base/browser/ui/scrollable/scrollable.directive';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
|
||||
@Component({
|
||||
selector: 'dashboard-home-container',
|
||||
providers: [{ provide: DashboardTab, useExisting: forwardRef(() => DashboardHomeContainer) }],
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => DashboardHomeContainer) }],
|
||||
template: `
|
||||
<div class="fullsize" style="display: flex; flex-direction: column">
|
||||
<div scrollable>
|
||||
<div scrollable [horizontalScroll]="ScrollbarVisibility.Hidden" [verticalScroll]="ScrollbarVisibility.Auto">
|
||||
<dashboard-widget-wrapper #propertiesClass *ngIf="properties" [collapsable]="true" [_config]="properties"
|
||||
style="padding-left: 10px; padding-right: 10px; display: block; flex: 0" [style.height.px]="_propertiesClass?.collapsed ? '30' : '90'">
|
||||
</dashboard-widget-wrapper>
|
||||
@@ -37,6 +40,8 @@ export class DashboardHomeContainer extends DashboardWidgetContainer {
|
||||
@ViewChild('propertiesClass') private _propertiesClass: DashboardWidgetWrapper;
|
||||
@ContentChild(ScrollableDirective) private _scrollable;
|
||||
|
||||
private ScrollbarVisibility = ScrollbarVisibility;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) _cd: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) protected dashboardService: DashboardServiceInterface
|
||||
|
||||
@@ -11,10 +11,11 @@ import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { TabConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
|
||||
@Component({
|
||||
selector: 'dashboard-modelview-container',
|
||||
providers: [{ provide: DashboardTab, useExisting: forwardRef(() => DashboardModelViewContainer) }],
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => DashboardModelViewContainer) }],
|
||||
template: `
|
||||
<modelview-content [modelViewId]="tab.id">
|
||||
</modelview-content>
|
||||
|
||||
@@ -4,18 +4,20 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
<panel [options]="panelOpt" style="flex: 1 1 auto;" class="dashboard-panel" (onTabChange)="handleTabChange($event)">
|
||||
<panel [options]="panelOpt" style="flex: 1 1 auto;" class="dashboard-panel">
|
||||
<tab [visibilityType]="'visibility'" *ngFor="let tab of tabs" [title]="tab.title" class="fullsize"
|
||||
[identifier]="tab.id" [canClose]="tab.canClose" [actions]="tab.actions" [iconClass]="tab.iconClass">
|
||||
<dashboard-webview-container *ngIf="getContentType(tab) === 'webview-container'" [tab]="tab">
|
||||
</dashboard-webview-container>
|
||||
<dashboard-widget-container *ngIf="getContentType(tab) === 'widgets-container'" [tab]="tab">
|
||||
</dashboard-widget-container>
|
||||
<dashboard-modelview-container *ngIf="getContentType(tab) === 'modelview-container'" [tab]="tab">
|
||||
</dashboard-modelview-container>
|
||||
<dashboard-grid-container *ngIf="getContentType(tab) === 'grid-container'" [tab]="tab">
|
||||
</dashboard-grid-container>
|
||||
<dashboard-error-container *ngIf="getContentType(tab) === 'error-container'" [tab]="tab">
|
||||
</dashboard-error-container>
|
||||
<ng-template>
|
||||
<dashboard-webview-container *ngIf="getContentType(tab) === 'webview-container'" [tab]="tab">
|
||||
</dashboard-webview-container>
|
||||
<dashboard-widget-container *ngIf="getContentType(tab) === 'widgets-container'" [tab]="tab">
|
||||
</dashboard-widget-container>
|
||||
<dashboard-modelview-container *ngIf="getContentType(tab) === 'modelview-container'" [tab]="tab">
|
||||
</dashboard-modelview-container>
|
||||
<dashboard-grid-container *ngIf="getContentType(tab) === 'grid-container'" [tab]="tab">
|
||||
</dashboard-grid-container>
|
||||
<dashboard-error-container *ngIf="getContentType(tab) === 'error-container'" [tab]="tab">
|
||||
</dashboard-error-container>
|
||||
</ng-template>
|
||||
</tab>
|
||||
</panel>
|
||||
@@ -11,7 +11,7 @@ import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboar
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { WidgetConfig, TabConfig, NavSectionConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { PanelComponent, IPanelOptions, NavigationBarLayout } from 'sql/base/browser/ui/panel/panel.component';
|
||||
import { TabComponent } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { TabComponent, TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { WIDGETS_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution';
|
||||
import { GRID_CONTAINER } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution';
|
||||
@@ -23,7 +23,7 @@ import * as nls from 'vs/nls';
|
||||
|
||||
@Component({
|
||||
selector: 'dashboard-nav-section',
|
||||
providers: [{ provide: DashboardTab, useExisting: forwardRef(() => DashboardNavSection) }],
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => DashboardNavSection) }],
|
||||
templateUrl: decodeURI(require.toUrl('sql/parts/dashboard/containers/dashboardNavSection.component.html'))
|
||||
})
|
||||
export class DashboardNavSection extends DashboardTab implements OnDestroy, OnChanges, AfterContentInit {
|
||||
@@ -51,7 +51,7 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh
|
||||
dashboardHelper.validateGridConfig
|
||||
];
|
||||
|
||||
@ViewChildren(DashboardTab) private _tabs: QueryList<DashboardTab>;
|
||||
@ViewChildren(TabChild) private _tabs: QueryList<DashboardTab>;
|
||||
@ViewChild(PanelComponent) private _panel: PanelComponent;
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) protected dashboardService: CommonServiceInterface,
|
||||
@@ -124,11 +124,6 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh
|
||||
this.addNewTab(config);
|
||||
return config;
|
||||
});
|
||||
|
||||
// put this immediately on the stack so that is ran *after* the tab is rendered
|
||||
setTimeout(() => {
|
||||
this._panel.selectTab(selectedTabs[0].id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,10 +169,4 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public handleTabChange(tab: TabComponent): void {
|
||||
let localtab = this._tabs.find(i => i.id === tab.identifier);
|
||||
this._cd.detectChanges();
|
||||
localtab.layout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,11 @@ import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { TabConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { WebviewContent } from 'sql/parts/dashboard/contents/webviewContent.component';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
|
||||
@Component({
|
||||
selector: 'dashboard-webview-container',
|
||||
providers: [{ provide: DashboardTab, useExisting: forwardRef(() => DashboardWebviewContainer) }],
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => DashboardWebviewContainer) }],
|
||||
template: `
|
||||
<webview-content [webviewId]="tab.id">
|
||||
</webview-content>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { DashboardWidgetWrapper } from 'sql/parts/dashboard/contents/dashboardWi
|
||||
import { subscriptionToDisposable } from 'sql/base/common/lifecycle';
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { WidgetContent } from 'sql/parts/dashboard/contents/widgetContent.component';
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
@@ -22,7 +23,7 @@ import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
@Component({
|
||||
selector: 'dashboard-widget-container',
|
||||
providers: [{ provide: DashboardTab, useExisting: forwardRef(() => DashboardWidgetContainer) }],
|
||||
providers: [{ provide: TabChild, useExisting: forwardRef(() => DashboardWidgetContainer) }],
|
||||
template: `
|
||||
<widget-content [widgets]="widgets" [originalConfig]="tab.originalConfig" [context]="tab.context">
|
||||
</widget-content>
|
||||
|
||||
@@ -10,7 +10,6 @@ import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { TabConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
|
||||
@@ -13,7 +13,6 @@ import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { TabConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
@@ -37,15 +36,13 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo
|
||||
private _onMessageDisposable: IDisposable;
|
||||
private _webview: Webview;
|
||||
private _html: string;
|
||||
private _dashboardService: DashboardServiceInterface;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private commonService: CommonServiceInterface,
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _dashboardService: DashboardServiceInterface,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef
|
||||
) {
|
||||
super();
|
||||
this._dashboardService = commonService as DashboardServiceInterface;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
@@ -56,12 +56,13 @@ import { JobHistoryComponent } from 'sql/parts/jobManagement/views/jobHistory.co
|
||||
let baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer,
|
||||
DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, ModelViewContent, WebviewContent, WidgetContent,
|
||||
ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer,
|
||||
JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent, DashboardModelViewContainer, ModelComponentWrapper,
|
||||
ScrollableDirective];
|
||||
JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent, DashboardModelViewContainer, ModelComponentWrapper];
|
||||
|
||||
/* Panel */
|
||||
import { PanelModule } from 'sql/base/browser/ui/panel/panel.module';
|
||||
|
||||
import { ScrollableModule } from 'sql/base/browser/ui/scrollable/scrollable.module';
|
||||
|
||||
/* Pages */
|
||||
import { ServerDashboardPage } from 'sql/parts/dashboard/pages/serverDashboardPage.component';
|
||||
import { DatabaseDashboardPage } from 'sql/parts/dashboard/pages/databaseDashboardPage.component';
|
||||
@@ -75,7 +76,6 @@ import { TasksWidget } from 'sql/parts/dashboard/widgets/tasks/tasksWidget.compo
|
||||
import { InsightsWidget } from 'sql/parts/dashboard/widgets/insights/insightsWidget.component';
|
||||
import { WebviewWidget } from 'sql/parts/dashboard/widgets/webview/webviewWidget.component';
|
||||
import { JobStepsViewComponent } from '../jobManagement/views/jobStepsView.component';
|
||||
import { ScrollableDirective } from 'sql/base/browser/ui/scrollable/scrollable.directive';
|
||||
|
||||
let widgetComponents = [
|
||||
PropertiesWidgetComponent,
|
||||
@@ -126,7 +126,8 @@ const appRoutes: Routes = [
|
||||
NgGridModule,
|
||||
ChartsModule,
|
||||
RouterModule.forRoot(appRoutes),
|
||||
PanelModule
|
||||
PanelModule,
|
||||
ScrollableModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: APP_BASE_HREF, useValue: '/' },
|
||||
|
||||
@@ -6,24 +6,31 @@ import { Component, Input, Inject, ChangeDetectorRef, forwardRef, ElementRef, On
|
||||
|
||||
import { getContentHeight, getContentWidth } from 'vs/base/browser/dom';
|
||||
import { Dimension } from 'vs/base/browser/builder';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
import { IInsightsView, IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
||||
import { DragCellSelectionModel } from '../../../../../base/browser/ui/table/plugins/dragCellSelectionModel.plugin';
|
||||
import { DragCellSelectionModel } from 'sql/base/browser/ui/table/plugins/dragCellSelectionModel.plugin';
|
||||
import { attachTableStyler} from 'sql/common/theme/styler';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
|
||||
@Component({
|
||||
template: '<span></span>'
|
||||
})
|
||||
export default class TableInsight implements IInsightsView, OnInit {
|
||||
export default class TableInsight extends Disposable implements IInsightsView, OnInit {
|
||||
private table: Table<any>;
|
||||
private dataView: TableDataView<any>;
|
||||
private columns: Slick.Column<any>[];
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => ElementRef)) private _elementRef: ElementRef
|
||||
) { }
|
||||
@Inject(forwardRef(() => ElementRef)) private _elementRef: ElementRef,
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrap: CommonServiceInterface,
|
||||
) {
|
||||
super();
|
||||
this._elementRef.nativeElement.className = 'slickgridContainer';
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.createTable();
|
||||
@@ -58,13 +65,14 @@ export default class TableInsight implements IInsightsView, OnInit {
|
||||
if (!this.table) {
|
||||
this.table = new Table(this._elementRef.nativeElement, this.dataView, this.columns, { showRowNumber: true });
|
||||
this.table.setSelectionModel(new DragCellSelectionModel());
|
||||
this._register(attachTableStyler(this.table, this._bootstrap.themeService));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function transformData(rows: string[][], columns: string[]): {[key: string]: string}[] {
|
||||
function transformData(rows: string[][], columns: string[]): { [key: string]: string }[] {
|
||||
return rows.map(row => {
|
||||
let object: {[key: string]: string} = {};
|
||||
let object: { [key: string]: string } = {};
|
||||
row.forEach((val, index) => {
|
||||
object[columns[index]] = val;
|
||||
});
|
||||
|
||||
@@ -4,19 +4,21 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { EditorInput, EditorModel } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, EditorModel, ConfirmResult, EncodingMode } from 'vs/workbench/common/editor';
|
||||
import { IConnectionManagementService, IConnectableInput, INewConnectionParams } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { EditSessionReadyParams } from 'sqlops';
|
||||
import { EditSessionReadyParams, ISelectionData } from 'sqlops';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import nls = require('vs/nls');
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput';
|
||||
|
||||
/**
|
||||
* Input for the EditDataEditor. This input is simply a wrapper around a QueryResultsInput for the QueryResultsEditor
|
||||
* Input for the EditDataEditor.
|
||||
*/
|
||||
export class EditDataInput extends EditorInput implements IConnectableInput {
|
||||
public static ID: string = 'workbench.editorinputs.editDataInput';
|
||||
@@ -25,15 +27,23 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
|
||||
private _editorContainer: HTMLElement;
|
||||
private _updateTaskbar: Emitter<EditDataInput>;
|
||||
private _editorInitializing: Emitter<boolean>;
|
||||
private _showTableView: Emitter<EditDataInput>;
|
||||
private _showResultsEditor: Emitter<EditDataInput>;
|
||||
private _refreshButtonEnabled: boolean;
|
||||
private _stopButtonEnabled: boolean;
|
||||
private _setup: boolean;
|
||||
private _toDispose: IDisposable[];
|
||||
private _rowLimit: number;
|
||||
private _objectType: string;
|
||||
private _css: HTMLStyleElement;
|
||||
private _useQueryFilter: boolean;
|
||||
|
||||
constructor(private _uri: URI, private _schemaName, private _tableName,
|
||||
constructor(
|
||||
private _uri: URI,
|
||||
private _schemaName,
|
||||
private _tableName,
|
||||
private _sql: UntitledEditorInput,
|
||||
private _queryString: string,
|
||||
private _results: EditDataResultsInput,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@INotificationService private notificationService: INotificationService
|
||||
@@ -42,12 +52,20 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
|
||||
this._visible = false;
|
||||
this._hasBootstrapped = false;
|
||||
this._updateTaskbar = new Emitter<EditDataInput>();
|
||||
this._showTableView = new Emitter<EditDataInput>();
|
||||
this._showResultsEditor = new Emitter<EditDataInput>();
|
||||
this._editorInitializing = new Emitter<boolean>();
|
||||
this._setup = false;
|
||||
this._stopButtonEnabled = false;
|
||||
this._refreshButtonEnabled = false;
|
||||
this._toDispose = [];
|
||||
this._useQueryFilter = false;
|
||||
|
||||
// re-emit sql editor events through this editor if it exists
|
||||
if (this._sql) {
|
||||
this._toDispose.push(this._sql.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
|
||||
this._sql.disableSaving();
|
||||
}
|
||||
this.disableSaving();
|
||||
|
||||
//TODO determine is this is a table or a view
|
||||
this._objectType = 'TABLE';
|
||||
@@ -79,27 +97,45 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
|
||||
public get tableName(): string { return this._tableName; }
|
||||
public get schemaName(): string { return this._schemaName; }
|
||||
public get uri(): string { return this._uri.toString(); }
|
||||
public get updateTaskbar(): Event<EditDataInput> { return this._updateTaskbar.event; }
|
||||
public get editorInitializing(): Event<boolean> { return this._editorInitializing.event; }
|
||||
public get showTableView(): Event<EditDataInput> { return this._showTableView.event; }
|
||||
public get sql(): UntitledEditorInput { return this._sql; }
|
||||
public get results(): EditDataResultsInput { return this._results; }
|
||||
public getResultsInputResource(): string { return this._results.uri; }
|
||||
public get updateTaskbarEvent(): Event<EditDataInput> { return this._updateTaskbar.event; }
|
||||
public get editorInitializingEvent(): Event<boolean> { return this._editorInitializing.event; }
|
||||
public get showResultsEditorEvent(): Event<EditDataInput> { return this._showResultsEditor.event; }
|
||||
public get stopButtonEnabled(): boolean { return this._stopButtonEnabled; }
|
||||
public get refreshButtonEnabled(): boolean { return this._refreshButtonEnabled; }
|
||||
public get container(): HTMLElement { return this._editorContainer; }
|
||||
public get hasBootstrapped(): boolean { return this._hasBootstrapped; }
|
||||
public get visible(): boolean { return this._visible; }
|
||||
public get setup(): boolean { return this._setup; }
|
||||
public get rowLimit(): number { return this._rowLimit; }
|
||||
public get objectType(): string { return this._objectType; }
|
||||
public showResultsEditor(): void { this._showResultsEditor.fire(); }
|
||||
public isDirty(): boolean { return false; }
|
||||
public save(): TPromise<boolean> { return TPromise.as(false); }
|
||||
public confirmSave(): TPromise<ConfirmResult> { return TPromise.wrap(ConfirmResult.DONT_SAVE); }
|
||||
public getTypeId(): string { return EditDataInput.ID; }
|
||||
public setVisibleTrue(): void { this._visible = true; }
|
||||
public setBootstrappedTrue(): void { this._hasBootstrapped = true; }
|
||||
public getResource(): URI { return this._uri; }
|
||||
public getName(): string { return this._uri.path; }
|
||||
public supportsSplitEditor(): boolean { return false; }
|
||||
public setupComplete() { this._setup = true; }
|
||||
public set container(container: HTMLElement) {
|
||||
this._disposeContainer();
|
||||
this._editorContainer = container;
|
||||
public get queryString(): string {
|
||||
return this._queryString;
|
||||
}
|
||||
public set queryString(queryString: string) {
|
||||
this._queryString = queryString;
|
||||
}
|
||||
public get css(): HTMLStyleElement {
|
||||
return this._css;
|
||||
}
|
||||
public set css(css: HTMLStyleElement) {
|
||||
this._css = css;
|
||||
}
|
||||
public get queryPaneEnabled(): boolean {
|
||||
return this._useQueryFilter;
|
||||
}
|
||||
public set queryPaneEnabled(useQueryFilter: boolean) {
|
||||
this._useQueryFilter = useQueryFilter;
|
||||
}
|
||||
|
||||
// State Update Callbacks
|
||||
@@ -111,13 +147,9 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
|
||||
}
|
||||
|
||||
public initEditEnd(result: EditSessionReadyParams): void {
|
||||
if (result.success) {
|
||||
this._refreshButtonEnabled = true;
|
||||
this._stopButtonEnabled = false;
|
||||
} else {
|
||||
this._refreshButtonEnabled = false;
|
||||
this._stopButtonEnabled = false;
|
||||
|
||||
this._refreshButtonEnabled = true;
|
||||
this._stopButtonEnabled = false;
|
||||
if (!result.success) {
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: result.message
|
||||
@@ -142,8 +174,16 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
|
||||
}
|
||||
|
||||
public onConnectSuccess(params?: INewConnectionParams): void {
|
||||
this._queryModelService.initializeEdit(this.uri, this.schemaName, this.tableName, this._objectType, this._rowLimit);
|
||||
this._showTableView.fire(this);
|
||||
let rowLimit: number = undefined;
|
||||
let queryString: string = undefined;
|
||||
if (this._useQueryFilter) {
|
||||
queryString = this._queryString;
|
||||
} else {
|
||||
rowLimit = this._rowLimit;
|
||||
}
|
||||
|
||||
this._queryModelService.initializeEdit(this.uri, this.schemaName, this.tableName, this._objectType, rowLimit, queryString);
|
||||
this.showResultsEditor();
|
||||
}
|
||||
|
||||
public onDisconnect(): void {
|
||||
@@ -157,27 +197,19 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
|
||||
// Boiler Plate Functions
|
||||
public matches(otherInput: any): boolean {
|
||||
if (otherInput instanceof EditDataInput) {
|
||||
return (this.uri === otherInput.uri);
|
||||
return this._sql.matches(otherInput.sql);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public resolve(refresh?: boolean): TPromise<EditorModel> {
|
||||
return TPromise.as(null);
|
||||
return this._sql.matches(otherInput);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._queryModelService.disposeQuery(this.uri);
|
||||
this._sql.dispose();
|
||||
this._results.dispose();
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
this._disposeContainer();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private _disposeContainer() {
|
||||
if (this._editorContainer && this._editorContainer.parentElement) {
|
||||
this._editorContainer.parentElement.removeChild(this._editorContainer);
|
||||
this._editorContainer = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
@@ -186,11 +218,22 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
|
||||
return this._connectionManagementService.disconnectEditor(this, true);
|
||||
}).then(() => {
|
||||
this.dispose();
|
||||
super.close();
|
||||
});
|
||||
}
|
||||
|
||||
public get tabColor(): string {
|
||||
return this._connectionManagementService.getTabColorForUri(this.uri);
|
||||
}
|
||||
|
||||
public get onDidModelChangeContent(): Event<void> { return this._sql.onDidModelChangeContent; }
|
||||
public get onDidModelChangeEncoding(): Event<void> { return this._sql.onDidModelChangeEncoding; }
|
||||
public resolve(refresh?: boolean): TPromise<EditorModel> { return this._sql.resolve(); }
|
||||
public getEncoding(): string { return this._sql.getEncoding(); }
|
||||
public suggestFileName(): string { return this._sql.suggestFileName(); }
|
||||
public getName(): string { return this._sql.getName(); }
|
||||
public get hasAssociatedFilePath(): boolean { return this._sql.hasAssociatedFilePath; }
|
||||
|
||||
public setEncoding(encoding: string, mode: EncodingMode /* ignored, we only have Encode */): void {
|
||||
this._sql.setEncoding(encoding, mode);
|
||||
}
|
||||
}
|
||||
|
||||
105
src/sql/parts/editData/common/editDataResultsInput.ts
Normal file
105
src/sql/parts/editData/common/editDataResultsInput.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { localize } from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { EditorInput } from 'vs/workbench/common/editor';
|
||||
|
||||
/**
|
||||
* Input for the EditDataResultsEditor. This input helps with logic for the viewing and editing of
|
||||
* data in the results grid.
|
||||
*/
|
||||
export class EditDataResultsInput extends EditorInput {
|
||||
|
||||
// Tracks if the editor that holds this input should be visible (i.e. true if a query has been run)
|
||||
private _visible: boolean;
|
||||
|
||||
// Tracks if the editor has holds this input has has bootstrapped angular yet
|
||||
private _hasBootstrapped: boolean;
|
||||
|
||||
// Holds the HTML content for the editor when the editor discards this input and loads another
|
||||
private _editorContainer: HTMLElement;
|
||||
public css: HTMLStyleElement;
|
||||
|
||||
constructor(private _uri: string) {
|
||||
super();
|
||||
this._visible = false;
|
||||
this._hasBootstrapped = false;
|
||||
}
|
||||
|
||||
getTypeId(): string {
|
||||
return EditDataResultsInput.ID;
|
||||
}
|
||||
|
||||
matches(other: any): boolean {
|
||||
if (other instanceof EditDataResultsInput) {
|
||||
return (other._uri === this._uri);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
resolve(refresh?: boolean): TPromise<any> {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
supportsSplitEditor(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public setBootstrappedTrue(): void {
|
||||
this._hasBootstrapped = true;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._disposeContainer();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private _disposeContainer() {
|
||||
if (!this._editorContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parentContainer = this._editorContainer.parentNode;
|
||||
if (parentContainer) {
|
||||
parentContainer.removeChild(this._editorContainer);
|
||||
this._editorContainer = null;
|
||||
}
|
||||
}
|
||||
|
||||
//// Properties
|
||||
|
||||
static get ID() {
|
||||
return 'workbench.editorinputs.editDataResultsInput';
|
||||
}
|
||||
|
||||
set container(container: HTMLElement) {
|
||||
this._disposeContainer();
|
||||
this._editorContainer = container;
|
||||
}
|
||||
|
||||
get container(): HTMLElement {
|
||||
return this._editorContainer;
|
||||
}
|
||||
|
||||
get hasBootstrapped(): boolean {
|
||||
return this._hasBootstrapped;
|
||||
}
|
||||
|
||||
get visible(): boolean {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
set visible(visible: boolean) {
|
||||
this._visible = visible;
|
||||
}
|
||||
|
||||
get uri(): string {
|
||||
return this._uri;
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,14 @@
|
||||
import 'vs/css!sql/parts/query/editor/media/queryEditor';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Builder, Dimension } from 'vs/base/browser/builder';
|
||||
import { Builder, Dimension, withElementById } from 'vs/base/browser/builder';
|
||||
|
||||
import { EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { EditorOptions, EditorInput } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { Position } from 'vs/platform/editor/common/editor';
|
||||
import { Position, IEditorControl, IEditor } from 'vs/platform/editor/common/editor';
|
||||
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
@@ -21,6 +22,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { EditDataInput } from 'sql/parts/editData/common/editDataInput';
|
||||
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import * as queryContext from 'sql/parts/query/common/queryContext';
|
||||
import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
@@ -29,13 +31,22 @@ import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import { IEditorDescriptorService } from 'sql/parts/query/editor/editorDescriptorService';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import {
|
||||
RefreshTableAction, StopRefreshTableAction,
|
||||
ChangeMaxRowsAction, ChangeMaxRowsActionItem
|
||||
RefreshTableAction, StopRefreshTableAction, ChangeMaxRowsAction, ChangeMaxRowsActionItem, ShowQueryPaneAction
|
||||
} from 'sql/parts/editData/execution/editDataActions';
|
||||
import { EditDataModule } from 'sql/parts/grid/views/editData/editData.module';
|
||||
import { IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { EDITDATA_SELECTOR } from 'sql/parts/grid/views/editData/editData.component';
|
||||
import { EditDataComponentParams } from 'sql/services/bootstrap/bootstrapParams';
|
||||
import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
|
||||
import { CodeEditor } from 'vs/editor/browser/codeEditor';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ISelectionData } from 'sqlops';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IFlexibleSash, VerticalFlexibleSash, HorizontalFlexibleSash } from 'sql/parts/query/views/flexibleSash';
|
||||
import { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
import { EditDataResultsEditor } from 'sql/parts/editData/editor/editDataResultsEditor';
|
||||
import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput';
|
||||
|
||||
/**
|
||||
* Editor that hosts an action bar and a resultSetInput for an edit data session
|
||||
@@ -44,17 +55,34 @@ export class EditDataEditor extends BaseEditor {
|
||||
|
||||
public static ID: string = 'workbench.editor.editDataEditor';
|
||||
|
||||
// The height of the tabs above the editor
|
||||
private readonly _tabHeight: number = 35;
|
||||
|
||||
// The minimum width/height of the editors hosted in the QueryEditor
|
||||
private readonly _minEditorSize: number = 220;
|
||||
|
||||
private _sash: IFlexibleSash;
|
||||
private _dimension: Dimension;
|
||||
private _container: HTMLElement;
|
||||
|
||||
private _resultsEditor: EditDataResultsEditor;
|
||||
private _resultsEditorContainer: HTMLElement;
|
||||
|
||||
private _sqlEditor: TextResourceEditor;
|
||||
private _sqlEditorContainer: HTMLElement;
|
||||
|
||||
private _taskbar: Taskbar;
|
||||
private _taskbarContainer: HTMLElement;
|
||||
private _changeMaxRowsActionItem: ChangeMaxRowsActionItem;
|
||||
private _stopRefreshTableAction: StopRefreshTableAction;
|
||||
private _refreshTableAction: RefreshTableAction;
|
||||
private _changeMaxRowsAction: ChangeMaxRowsAction;
|
||||
private _showQueryPaneAction: ShowQueryPaneAction;
|
||||
private _spinnerElement: HTMLElement;
|
||||
private _initialized: boolean = false;
|
||||
|
||||
private _queryEditorVisible: IContextKey<boolean>;
|
||||
private hideQueryResultsView = false;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService _telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@@ -62,19 +90,61 @@ export class EditDataEditor extends BaseEditor {
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
|
||||
@IContextMenuService private _contextMenuService: IContextMenuService,
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@IEditorDescriptorService private _editorDescriptorService: IEditorDescriptorService,
|
||||
@IEditorGroupService private _editorGroupService: IEditorGroupService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IBootstrapService private _bootstrapService: IBootstrapService
|
||||
) {
|
||||
super(EditDataEditor.ID, _telemetryService, themeService);
|
||||
|
||||
if (contextKeyService) {
|
||||
this._queryEditorVisible = queryContext.QueryEditorVisibleContext.bindTo(contextKeyService);
|
||||
}
|
||||
}
|
||||
|
||||
// PUBLIC METHODS ////////////////////////////////////////////////////////////
|
||||
|
||||
// Getters and Setters
|
||||
public get editDataInput(): EditDataInput { return <EditDataInput>this.input; }
|
||||
public get uri(): string { return this.input ? this.editDataInput.uri.toString() : undefined; }
|
||||
public get tableName(): string { return this.editDataInput.tableName; }
|
||||
public get uri(): string { return this.input ? this.editDataInput.uri.toString() : undefined; }
|
||||
public set resultsEditorVisibility(isVisible: boolean) {
|
||||
let input: EditDataInput = <EditDataInput>this.input;
|
||||
input.results.visible = isVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the position of the editor.
|
||||
*/
|
||||
public changePosition(position: Position): void {
|
||||
if (this._resultsEditor) {
|
||||
this._resultsEditor.changePosition(position);
|
||||
}
|
||||
if (this._sqlEditor) {
|
||||
this._sqlEditor.changePosition(position);
|
||||
}
|
||||
super.changePosition(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to indicate to the editor that the input should be cleared and resources associated with the
|
||||
* input should be freed.
|
||||
*/
|
||||
public clearInput(): void {
|
||||
if (this._resultsEditor) {
|
||||
this._resultsEditor.clearInput();
|
||||
}
|
||||
if (this._sqlEditor) {
|
||||
this._sqlEditor.clearInput();
|
||||
}
|
||||
this._disposeEditors();
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.editDataInput.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to create the editor in the parent builder.
|
||||
@@ -83,9 +153,82 @@ export class EditDataEditor extends BaseEditor {
|
||||
const parentElement = parent.getHTMLElement();
|
||||
DOM.addClass(parentElement, 'side-by-side-editor');
|
||||
this._createTaskbar(parentElement);
|
||||
this._container = document.createElement('div');
|
||||
this._container.style.height = 'calc(100% - 28px)';
|
||||
DOM.append(parentElement, this._container);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._disposeEditors();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets focus on this editor. Specifically, it sets the focus on the hosted text editor.
|
||||
*/
|
||||
public focus(): void {
|
||||
if (this._sqlEditor) {
|
||||
this._sqlEditor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public getControl(): IEditorControl {
|
||||
if (this._sqlEditor) {
|
||||
return this._sqlEditor.getControl();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public getEditorText(): string {
|
||||
if (this._sqlEditor && this._sqlEditor.getControl()) {
|
||||
let control = this._sqlEditor.getControl();
|
||||
let codeEditor: CodeEditor = <CodeEditor>control;
|
||||
|
||||
if (codeEditor) {
|
||||
let value = codeEditor.getModel().getValue();
|
||||
if (value !== undefined && value.length > 0) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the spinner element to show that something was happening, hidden by default
|
||||
*/
|
||||
public hideSpinner(): void {
|
||||
this._spinnerElement.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal variable keeping track of the editor's size, and re-calculates the sash position.
|
||||
* To be called when the container of this editor changes size.
|
||||
*/
|
||||
public layout(dimension: Dimension): void {
|
||||
this._dimension = dimension;
|
||||
|
||||
if (this._sash) {
|
||||
this._setSashDimension();
|
||||
this._sash.layout();
|
||||
}
|
||||
|
||||
this._doLayout();
|
||||
this._resizeGridContents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this editor and the sub-editors to visible.
|
||||
*/
|
||||
public setEditorVisible(visible: boolean, position: Position): void {
|
||||
if (this._resultsEditor) {
|
||||
this._resultsEditor.setVisible(visible, position);
|
||||
}
|
||||
if (this._sqlEditor) {
|
||||
this._sqlEditor.setVisible(visible, position);
|
||||
}
|
||||
|
||||
super.setEditorVisible(visible, position);
|
||||
|
||||
// Note: must update after calling super.setEditorVisible so that the accurate count is handled
|
||||
this._updateQueryEditorVisible(visible);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,9 +238,9 @@ export class EditDataEditor extends BaseEditor {
|
||||
let oldInput = <EditDataInput>this.input;
|
||||
if (!newInput.setup) {
|
||||
this._initialized = false;
|
||||
this._register(newInput.updateTaskbar((owner) => this._updateTaskbar(owner)));
|
||||
this._register(newInput.editorInitializing((initializing) => this.onEditorInitializingChanged(initializing)));
|
||||
this._register(newInput.showTableView(() => this._showTableView()));
|
||||
this._register(newInput.updateTaskbarEvent((owner) => this._updateTaskbar(owner)));
|
||||
this._register(newInput.editorInitializingEvent((initializing) => this._onEditorInitializingChanged(initializing)));
|
||||
this._register(newInput.showResultsEditorEvent(() => this._showResultsEditor()));
|
||||
newInput.onRowDropDownSet(this._changeMaxRowsActionItem.defaultRowCount);
|
||||
newInput.setupComplete();
|
||||
}
|
||||
@@ -106,15 +249,6 @@ export class EditDataEditor extends BaseEditor {
|
||||
.then(() => this._updateInput(oldInput, newInput, options));
|
||||
}
|
||||
|
||||
private onEditorInitializingChanged(initializing: boolean): void {
|
||||
if (initializing) {
|
||||
this.showSpinner();
|
||||
} else {
|
||||
this._initialized = true;
|
||||
this.hideSpinner();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the spinner element that shows something is happening, hidden by default
|
||||
*/
|
||||
@@ -126,92 +260,74 @@ export class EditDataEditor extends BaseEditor {
|
||||
}, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the spinner element to show that something was happening, hidden by default
|
||||
*/
|
||||
public hideSpinner(): void {
|
||||
this._spinnerElement.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this editor and the sub-editors to visible.
|
||||
*/
|
||||
public setEditorVisible(visible: boolean, position: Position): void {
|
||||
super.setEditorVisible(visible, position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the position of the editor.
|
||||
*/
|
||||
public changePosition(position: Position): void {
|
||||
super.changePosition(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to indicate to the editor that the input should be cleared and resources associated with the
|
||||
* input should be freed.
|
||||
*/
|
||||
public clearInput(): void {
|
||||
this._disposeEditors();
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal variable keeping track of the editor's size, and re-calculates the sash position.
|
||||
* To be called when the container of this editor changes size.
|
||||
*/
|
||||
public layout(dimension: Dimension): void {
|
||||
this._dimension = dimension;
|
||||
let input: EditDataInput = <EditDataInput>this.input;
|
||||
if (input) {
|
||||
let uri: string = input.uri;
|
||||
if (uri) {
|
||||
this._queryModelService.resizeResultsets(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._disposeEditors();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.input.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the results table for the current edit data session is visible
|
||||
* Public for testing only.
|
||||
*/
|
||||
public _isResultsEditorVisible(): boolean {
|
||||
if (!this.editDataInput) {
|
||||
return false;
|
||||
}
|
||||
return this.editDataInput.visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes visible the results table for the current edit data session
|
||||
*/
|
||||
private _showTableView(): void {
|
||||
if (this._isResultsEditorVisible()) {
|
||||
public toggleResultsEditorVisibility(): void {
|
||||
let input = <EditDataInput>this.input;
|
||||
let hideResults = this.hideQueryResultsView;
|
||||
this.hideQueryResultsView = !this.hideQueryResultsView;
|
||||
if (!input.results) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._createTableViewContainer();
|
||||
this._setTableViewVisible();
|
||||
this.setInput(this.editDataInput, this.options);
|
||||
this.resultsEditorVisibility = hideResults;
|
||||
this._doLayout();
|
||||
}
|
||||
|
||||
// PRIVATE METHODS ////////////////////////////////////////////////////////////
|
||||
private _updateTaskbar(owner: EditDataInput): void {
|
||||
// Update the taskbar if the owner of this call is being presented
|
||||
if (owner.matches(this.editDataInput)) {
|
||||
this._refreshTableAction.enabled = owner.refreshButtonEnabled;
|
||||
this._stopRefreshTableAction.enabled = owner.stopButtonEnabled;
|
||||
this._changeMaxRowsActionItem.setCurrentOptionIndex = owner.rowLimit;
|
||||
private _createEditor(editorInput: EditorInput, container: HTMLElement): TPromise<BaseEditor> {
|
||||
const descriptor = this._editorDescriptorService.getEditor(editorInput);
|
||||
if (!descriptor) {
|
||||
return TPromise.wrapError(new Error(strings.format('Can not find a registered editor for the input {0}', editorInput)));
|
||||
}
|
||||
|
||||
let editor = descriptor.instantiate(this._instantiationService);
|
||||
editor.create(new Builder(container));
|
||||
editor.setVisible(this.isVisible(), this.position);
|
||||
return TPromise.as(editor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the HTML for the EditDataResultsEditor to the EditDataEditor. If the HTML has not yet been
|
||||
* created, it creates it and appends it. If it has already been created, it locates it and
|
||||
* appends it.
|
||||
*/
|
||||
private _createResultsEditorContainer() {
|
||||
this._createSash();
|
||||
|
||||
const parentElement = this.getContainer().getHTMLElement();
|
||||
let input = <EditDataInput>this.input;
|
||||
|
||||
if (!input.results.container) {
|
||||
this._resultsEditorContainer = DOM.append(parentElement, DOM.$('.editDataContainer-horizontal'));
|
||||
this._resultsEditorContainer.style.position = 'absolute';
|
||||
|
||||
input.results.container = this._resultsEditorContainer;
|
||||
} else {
|
||||
this._resultsEditorContainer = DOM.append(parentElement, input.results.container);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the sash with the requested orientation and registers sash callbacks
|
||||
*/
|
||||
private _createSash(): void {
|
||||
if (!this._sash) {
|
||||
let parentElement: HTMLElement = this.getContainer().getHTMLElement();
|
||||
|
||||
this._sash = this._register(new HorizontalFlexibleSash(parentElement, this._minEditorSize));
|
||||
this._setSashDimension();
|
||||
|
||||
this._register(this._sash.onPositionChange(position => this._doLayout()));
|
||||
}
|
||||
|
||||
this._sash.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the HTML for the SQL editor. Creates new HTML every time.
|
||||
*/
|
||||
private _createSqlEditorContainer() {
|
||||
const parentElement = this.getContainer().getHTMLElement();
|
||||
this._sqlEditorContainer = DOM.append(parentElement, DOM.$('.details-editor-container'));
|
||||
this._sqlEditorContainer.style.position = 'absolute';
|
||||
}
|
||||
|
||||
private _createTaskbar(parentElement: HTMLElement): void {
|
||||
@@ -222,22 +338,25 @@ export class EditDataEditor extends BaseEditor {
|
||||
});
|
||||
|
||||
// Create Actions for the toolbar
|
||||
this._stopRefreshTableAction = this._instantiationService.createInstance(StopRefreshTableAction, this);
|
||||
this._refreshTableAction = this._instantiationService.createInstance(RefreshTableAction, this);
|
||||
this._stopRefreshTableAction = this._instantiationService.createInstance(StopRefreshTableAction, this);
|
||||
this._changeMaxRowsAction = this._instantiationService.createInstance(ChangeMaxRowsAction, this);
|
||||
this._showQueryPaneAction = this._instantiationService.createInstance(ShowQueryPaneAction, this);
|
||||
|
||||
// Create HTML Elements for the taskbar
|
||||
let separator = Taskbar.createTaskbarSeparator();
|
||||
let textSeperator = Taskbar.createTaskbarText(nls.localize('maxRowTaskbar', 'Max Rows:'));
|
||||
let textSeparator = Taskbar.createTaskbarText(nls.localize('maxRowTaskbar', 'Max Rows:'));
|
||||
|
||||
this._spinnerElement = Taskbar.createTaskbarSpinner();
|
||||
|
||||
// Set the content in the order we desire
|
||||
let content: ITaskbarContent[] = [
|
||||
{ action: this._stopRefreshTableAction },
|
||||
{ action: this._refreshTableAction },
|
||||
{ action: this._stopRefreshTableAction },
|
||||
{ element: separator },
|
||||
{ element: textSeperator },
|
||||
{ element: textSeparator },
|
||||
{ action: this._changeMaxRowsAction },
|
||||
{ action: this._showQueryPaneAction },
|
||||
{ element: this._spinnerElement }
|
||||
];
|
||||
this._taskbar.setContent(content);
|
||||
@@ -258,32 +377,181 @@ export class EditDataEditor extends BaseEditor {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles setting input for this editor. If this new input does not match the old input (e.g. a new file
|
||||
* has been opened with the same editor, or we are opening the editor for the first time).
|
||||
*/
|
||||
private _updateInput(oldInput: EditDataInput, newInput: EditDataInput, options?: EditorOptions): TPromise<void> {
|
||||
let returnValue: TPromise<void>;
|
||||
|
||||
if (!newInput.matches(oldInput)) {
|
||||
this._disposeEditors();
|
||||
|
||||
if (this._isResultsEditorVisible()) {
|
||||
this._createTableViewContainer();
|
||||
let uri: string = newInput.uri;
|
||||
if (uri) {
|
||||
this._queryModelService.refreshResultsets(uri);
|
||||
}
|
||||
}
|
||||
|
||||
returnValue = this._setNewInput(newInput, options);
|
||||
} else {
|
||||
this._setNewInput(newInput, options);
|
||||
returnValue = TPromise.as(null);
|
||||
private _disposeEditors(): void {
|
||||
if (this._sqlEditor) {
|
||||
this._sqlEditor.dispose();
|
||||
this._sqlEditor = null;
|
||||
}
|
||||
if (this._resultsEditor) {
|
||||
this._resultsEditor.dispose();
|
||||
this._resultsEditor = null;
|
||||
}
|
||||
|
||||
this._updateTaskbar(newInput);
|
||||
return returnValue;
|
||||
let thisEditorParent: HTMLElement = this.getContainer().getHTMLElement();
|
||||
|
||||
if (this._sqlEditorContainer) {
|
||||
let sqlEditorParent: HTMLElement = this._sqlEditorContainer.parentElement;
|
||||
if (sqlEditorParent && sqlEditorParent === thisEditorParent) {
|
||||
this._sqlEditorContainer.parentElement.removeChild(this._sqlEditorContainer);
|
||||
}
|
||||
this._sqlEditorContainer = null;
|
||||
}
|
||||
|
||||
if (this._resultsEditorContainer) {
|
||||
let resultsEditorParent: HTMLElement = this._resultsEditorContainer.parentElement;
|
||||
if (resultsEditorParent && resultsEditorParent === thisEditorParent) {
|
||||
this._resultsEditorContainer.parentElement.removeChild(this._resultsEditorContainer);
|
||||
}
|
||||
this._resultsEditorContainer = null;
|
||||
this.hideQueryResultsView = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _doLayout(skipResizeGridContent: boolean = false): void {
|
||||
if (!this._isResultsEditorVisible() && this._sqlEditor) {
|
||||
this._doLayoutSql();
|
||||
return;
|
||||
}
|
||||
if (!this._sqlEditor || !this._resultsEditor || !this._dimension || !this._sash) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._doLayoutHorizontal();
|
||||
|
||||
if (!skipResizeGridContent) {
|
||||
this._resizeGridContents();
|
||||
}
|
||||
}
|
||||
|
||||
private _doLayoutHorizontal(): void {
|
||||
let splitPointTop: number = this._sash.getSplitPoint();
|
||||
let parent: ClientRect = this.getContainer().getHTMLElement().getBoundingClientRect();
|
||||
|
||||
let sqlEditorHeight: number;
|
||||
let sqlEditorTop: number;
|
||||
let resultsEditorHeight: number;
|
||||
let resultsEditorTop: number;
|
||||
|
||||
let editorTopOffset = parent.top + this._getTaskBarHeight();
|
||||
|
||||
this._resultsEditorContainer.hidden = false;
|
||||
|
||||
let titleBar = withElementById('workbench.parts.titlebar');
|
||||
if (this.queryPaneEnabled()) {
|
||||
this._sqlEditorContainer.hidden = false;
|
||||
|
||||
sqlEditorTop = editorTopOffset;
|
||||
sqlEditorHeight = splitPointTop - sqlEditorTop;
|
||||
|
||||
resultsEditorTop = splitPointTop;
|
||||
resultsEditorHeight = parent.bottom - resultsEditorTop;
|
||||
|
||||
if (titleBar) {
|
||||
sqlEditorHeight += DOM.getContentHeight(titleBar.getHTMLElement());
|
||||
}
|
||||
} else {
|
||||
this._sqlEditorContainer.hidden = true;
|
||||
|
||||
sqlEditorTop = editorTopOffset;
|
||||
sqlEditorHeight = 0;
|
||||
|
||||
resultsEditorTop = editorTopOffset;
|
||||
resultsEditorHeight = parent.bottom - resultsEditorTop;
|
||||
|
||||
if (titleBar) {
|
||||
resultsEditorHeight += DOM.getContentHeight(titleBar.getHTMLElement());
|
||||
}
|
||||
}
|
||||
|
||||
this._sqlEditorContainer.style.height = `${sqlEditorHeight}px`;
|
||||
this._sqlEditorContainer.style.width = `${this._dimension.width}px`;
|
||||
this._sqlEditorContainer.style.top = `${sqlEditorTop}px`;
|
||||
|
||||
this._resultsEditorContainer.style.height = `${resultsEditorHeight}px`;
|
||||
this._resultsEditorContainer.style.width = `${this._dimension.width}px`;
|
||||
this._resultsEditorContainer.style.top = `${resultsEditorTop}px`;
|
||||
|
||||
this._sqlEditor.layout(new Dimension(this._dimension.width, sqlEditorHeight));
|
||||
this._resultsEditor.layout(new Dimension(this._dimension.width, resultsEditorHeight));
|
||||
}
|
||||
|
||||
private _doLayoutSql() {
|
||||
if (this._resultsEditorContainer) {
|
||||
this._resultsEditorContainer.style.width = '0px';
|
||||
this._resultsEditorContainer.style.height = '0px';
|
||||
this._resultsEditorContainer.style.left = '0px';
|
||||
this._resultsEditorContainer.hidden = true;
|
||||
}
|
||||
|
||||
if (this._dimension) {
|
||||
let sqlEditorHeight: number;
|
||||
|
||||
if (this.queryPaneEnabled()) {
|
||||
this._sqlEditorContainer.hidden = false;
|
||||
sqlEditorHeight = this._dimension.height - this._getTaskBarHeight();
|
||||
} else {
|
||||
this._sqlEditorContainer.hidden = true;
|
||||
sqlEditorHeight = 0;
|
||||
}
|
||||
|
||||
this._sqlEditorContainer.style.height = `${sqlEditorHeight}px`;
|
||||
this._sqlEditorContainer.style.width = `${this._dimension.width}px`;
|
||||
|
||||
this._sqlEditor.layout(new Dimension(this._dimension.width, sqlEditorHeight));
|
||||
}
|
||||
}
|
||||
|
||||
private _getTaskBarHeight(): number {
|
||||
let taskBarElement = this._taskbar.getContainer().getHTMLElement();
|
||||
return DOM.getContentHeight(taskBarElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the results table for the current edit data session is visible
|
||||
* Public for testing only.
|
||||
*/
|
||||
private _isResultsEditorVisible(): boolean {
|
||||
let input: EditDataInput = <EditDataInput>this.input;
|
||||
|
||||
if (!input) {
|
||||
return false;
|
||||
}
|
||||
return input.results.visible;
|
||||
}
|
||||
|
||||
private _onEditorInitializingChanged(initializing: boolean): void {
|
||||
if (initializing) {
|
||||
this.showSpinner();
|
||||
} else {
|
||||
this._initialized = true;
|
||||
this.hideSpinner();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets input for the results editor after it has been created.
|
||||
*/
|
||||
private _onResultsEditorCreated(resultsEditor: EditDataResultsEditor, resultsInput: EditDataResultsInput, options: EditorOptions): TPromise<void> {
|
||||
this._resultsEditor = resultsEditor;
|
||||
return this._resultsEditor.setInput(resultsInput, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets input for the SQL editor after it has been created.
|
||||
*/
|
||||
private _onSqlEditorCreated(sqlEditor: TextResourceEditor, sqlInput: UntitledEditorInput, options: EditorOptions): TPromise<void> {
|
||||
this._sqlEditor = sqlEditor;
|
||||
return this._sqlEditor.setInput(sqlInput, options);
|
||||
}
|
||||
|
||||
private _resizeGridContents(): void {
|
||||
if (this._isResultsEditorVisible()) {
|
||||
let queryInput: EditDataInput = <EditDataInput>this.input;
|
||||
let uri: string = queryInput.uri;
|
||||
if (uri) {
|
||||
this._queryModelService.resizeResultsets(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,68 +559,177 @@ export class EditDataEditor extends BaseEditor {
|
||||
* - Opened for the first time
|
||||
* - Opened with a new EditDataInput
|
||||
*/
|
||||
private _setNewInput(newInput: EditDataInput, options?: EditorOptions): TPromise<void> {
|
||||
private _setNewInput(newInput: EditDataInput, options?: EditorOptions): TPromise<any> {
|
||||
|
||||
// Promises that will ensure proper ordering of editor creation logic
|
||||
let createEditors: () => TPromise<any>;
|
||||
let onEditorsCreated: (result) => TPromise<any>;
|
||||
|
||||
// If both editors exist, create joined promises - one for each editor
|
||||
if (this._isResultsEditorVisible()) {
|
||||
// If both editors exist, create a joined promise and wait for both editors to be created
|
||||
return this._bootstrapAngularAndResults(newInput, options);
|
||||
}
|
||||
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
/**
|
||||
* Appends the HTML for the edit data table view
|
||||
*/
|
||||
private _createTableViewContainer() {
|
||||
if (!this.editDataInput.container) {
|
||||
this.editDataInput.container = DOM.append(this._container, DOM.$('.editDataContainer'));
|
||||
this.editDataInput.container.style.height = '100%';
|
||||
} else {
|
||||
DOM.append(this._container, this.editDataInput.container);
|
||||
}
|
||||
}
|
||||
|
||||
private _disposeEditors(): void {
|
||||
if (this._container) {
|
||||
new Builder(this._container).clearChildren();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the angular components and record for this input that we have done so
|
||||
*/
|
||||
private _bootstrapAngularAndResults(input: EditDataInput, options: EditorOptions): TPromise<void> {
|
||||
super.setInput(input, options);
|
||||
if (!input.hasBootstrapped) {
|
||||
let uri = this.editDataInput.uri;
|
||||
|
||||
// Pass the correct DataService to the new angular component
|
||||
let dataService = this._queryModelService.getDataService(uri);
|
||||
if (!dataService) {
|
||||
throw new Error('DataService not found for URI: ' + uri);
|
||||
}
|
||||
|
||||
// Mark that we have bootstrapped
|
||||
input.setBootstrappedTrue();
|
||||
|
||||
// Get the bootstrap params and perform the bootstrap
|
||||
// Note: pass in input so on disposal this is cleaned up.
|
||||
// Otherwise many components will be left around and be subscribed
|
||||
// to events from the backing data service
|
||||
const parent = this.editDataInput.container;
|
||||
let params: EditDataComponentParams = {
|
||||
dataService: dataService
|
||||
createEditors = () => {
|
||||
return TPromise.join([
|
||||
this._createEditor(<EditDataResultsInput>newInput.results, this._resultsEditorContainer),
|
||||
this._createEditor(<UntitledEditorInput>newInput.sql, this._sqlEditorContainer)
|
||||
]);
|
||||
};
|
||||
onEditorsCreated = (result: IEditor[]) => {
|
||||
return TPromise.join([
|
||||
this._onResultsEditorCreated(<EditDataResultsEditor>result[0], newInput.results, options),
|
||||
this._onSqlEditorCreated(<TextResourceEditor>result[1], newInput.sql, options)
|
||||
]);
|
||||
};
|
||||
|
||||
// If only the sql editor exists, create a promise and wait for the sql editor to be created
|
||||
} else {
|
||||
createEditors = () => {
|
||||
return this._createEditor(<UntitledEditorInput>newInput.sql, this._sqlEditorContainer);
|
||||
};
|
||||
onEditorsCreated = (result: TextResourceEditor) => {
|
||||
return this._onSqlEditorCreated(result, newInput.sql, options);
|
||||
};
|
||||
this._bootstrapService.bootstrap(
|
||||
EditDataModule,
|
||||
parent,
|
||||
EDITDATA_SELECTOR,
|
||||
params,
|
||||
this.editDataInput);
|
||||
}
|
||||
return TPromise.wrap<void>(null);
|
||||
|
||||
// Create a promise to re render the layout after the editor creation logic
|
||||
let doLayout: () => TPromise<any> = () => {
|
||||
this._doLayout();
|
||||
return TPromise.as(undefined);
|
||||
};
|
||||
|
||||
// Run all three steps synchronously
|
||||
return createEditors()
|
||||
.then(onEditorsCreated)
|
||||
.then(doLayout);
|
||||
}
|
||||
|
||||
private _setTableViewVisible(): void {
|
||||
this.editDataInput.setVisibleTrue();
|
||||
private _setSashDimension(): void {
|
||||
if (!this._dimension) {
|
||||
return;
|
||||
}
|
||||
this._sash.setDimenesion(this._dimension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes visible the results table for the current edit data session
|
||||
*/
|
||||
private _showResultsEditor(): void {
|
||||
if (this._isResultsEditorVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._editorGroupService.pinEditor(this.position, this.input);
|
||||
|
||||
let input = <EditDataInput>this.input;
|
||||
this._createResultsEditorContainer();
|
||||
|
||||
this._createEditor(<EditDataResultsInput>input.results, this._resultsEditorContainer)
|
||||
.then(result => {
|
||||
this._onResultsEditorCreated(<EditDataResultsEditor>result, input.results, this.options);
|
||||
this.resultsEditorVisibility = true;
|
||||
this.hideQueryResultsView = false;
|
||||
this._doLayout(true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles setting input for this editor. If this new input does not match the old input (e.g. a new file
|
||||
* has been opened with the same editor, or we are opening the editor for the first time).
|
||||
*/
|
||||
private _updateInput(oldInput: EditDataInput, newInput: EditDataInput, options?: EditorOptions): TPromise<void> {
|
||||
|
||||
if (this._sqlEditor) {
|
||||
this._sqlEditor.clearInput();
|
||||
}
|
||||
|
||||
if (oldInput) {
|
||||
this._disposeEditors();
|
||||
}
|
||||
|
||||
this._createSqlEditorContainer();
|
||||
if (this._isResultsEditorVisible()) {
|
||||
this._createResultsEditorContainer();
|
||||
|
||||
let uri: string = newInput.uri;
|
||||
if (uri) {
|
||||
this._queryModelService.refreshResultsets(uri);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._sash) {
|
||||
if (this._isResultsEditorVisible()) {
|
||||
this._sash.show();
|
||||
} else {
|
||||
this._sash.hide();
|
||||
}
|
||||
}
|
||||
|
||||
this._updateTaskbar(newInput);
|
||||
return this._setNewInput(newInput, options);
|
||||
}
|
||||
|
||||
private _updateQueryEditorVisible(currentEditorIsVisible: boolean): void {
|
||||
if (this._queryEditorVisible) {
|
||||
let visible = currentEditorIsVisible;
|
||||
if (!currentEditorIsVisible) {
|
||||
// Current editor is closing but still tracked as visible. Check if any other editor is visible
|
||||
const candidates = [...this._editorService.getVisibleEditors()].filter(e => {
|
||||
if (e && e.getId) {
|
||||
return e.getId() === EditDataEditor.ID;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
// Note: require 2 or more candidates since current is closing but still
|
||||
// counted as visible
|
||||
visible = candidates.length > 1;
|
||||
}
|
||||
this._queryEditorVisible.set(visible);
|
||||
}
|
||||
}
|
||||
|
||||
private _updateTaskbar(owner: EditDataInput): void {
|
||||
// Update the taskbar if the owner of this call is being presented
|
||||
if (owner.matches(this.editDataInput)) {
|
||||
this._refreshTableAction.enabled = owner.refreshButtonEnabled;
|
||||
this._stopRefreshTableAction.enabled = owner.stopButtonEnabled;
|
||||
this._changeMaxRowsActionItem.setCurrentOptionIndex = owner.rowLimit;
|
||||
this._showQueryPaneAction.queryPaneEnabled = owner.queryPaneEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the run method of this editor's RunQueryAction
|
||||
*/
|
||||
public runQuery(): void {
|
||||
this._refreshTableAction.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the run method of this editor's CancelQueryAction
|
||||
*/
|
||||
public cancelQuery(): void {
|
||||
this._stopRefreshTableAction.run();
|
||||
}
|
||||
|
||||
public toggleQueryPane(): void {
|
||||
this.editDataInput.queryPaneEnabled = !this.queryPaneEnabled();
|
||||
if (this.queryPaneEnabled()) {
|
||||
this._showQueryEditor();
|
||||
} else {
|
||||
this._hideQueryEditor();
|
||||
}
|
||||
this._doLayout(false);
|
||||
}
|
||||
|
||||
private _showQueryEditor(): void {
|
||||
this._sqlEditorContainer.hidden = false;
|
||||
this._changeMaxRowsActionItem.disable();
|
||||
}
|
||||
private _hideQueryEditor(): void {
|
||||
this._sqlEditorContainer.hidden = true;
|
||||
this._changeMaxRowsActionItem.enable();
|
||||
}
|
||||
|
||||
public queryPaneEnabled(): boolean {
|
||||
return this.editDataInput.queryPaneEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
113
src/sql/parts/editData/editor/editDataResultsEditor.ts
Normal file
113
src/sql/parts/editData/editor/editDataResultsEditor.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { getZoomLevel } from 'vs/base/browser/browser';
|
||||
import { Configuration } from 'vs/editor/browser/config/configuration';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import { IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { BareResultsGridInfo } from 'sql/parts/query/editor/queryResultsEditor';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { EditDataComponentParams } from 'sql/services/bootstrap/bootstrapParams';
|
||||
import { EditDataModule } from 'sql/parts/grid/views/editData/editData.module';
|
||||
import { EDITDATA_SELECTOR } from 'sql/parts/grid/views/editData/editData.component';
|
||||
import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput';
|
||||
|
||||
export class EditDataResultsEditor extends BaseEditor {
|
||||
|
||||
public static ID: string = 'workbench.editor.editDataResultsEditor';
|
||||
public static AngularSelectorString: string = 'slickgrid-container.slickgridContainer';
|
||||
protected _input: EditDataResultsInput;
|
||||
protected _rawOptions: BareResultsGridInfo;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@IBootstrapService private _bootstrapService: IBootstrapService,
|
||||
@IConfigurationService private _configurationService: IConfigurationService
|
||||
) {
|
||||
super(EditDataResultsEditor.ID, telemetryService, themeService);
|
||||
this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel());
|
||||
this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('resultsGrid')) {
|
||||
this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel());
|
||||
this._applySettings();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public get input(): EditDataResultsInput {
|
||||
return this._input;
|
||||
}
|
||||
|
||||
public createEditor(parent: Builder): void {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
}
|
||||
|
||||
public setInput(input: EditDataResultsInput, options: EditorOptions): TPromise<void> {
|
||||
super.setInput(input, options);
|
||||
this._applySettings();
|
||||
if (!input.hasBootstrapped) {
|
||||
this._bootstrapAngular();
|
||||
}
|
||||
return TPromise.wrap<void>(null);
|
||||
}
|
||||
|
||||
private _applySettings() {
|
||||
if (this.input && this.input.container) {
|
||||
Configuration.applyFontInfoSlow(this.getContainer().getHTMLElement(), this._rawOptions);
|
||||
if (!this.input.css) {
|
||||
this.input.css = dom.createStyleSheet(this.input.container);
|
||||
}
|
||||
let cssRuleText = '';
|
||||
if (types.isNumber(this._rawOptions.cellPadding)) {
|
||||
cssRuleText = this._rawOptions.cellPadding + 'px';
|
||||
} else {
|
||||
cssRuleText = this._rawOptions.cellPadding.join('px ') + 'px;';
|
||||
}
|
||||
let content = `.grid .slick-cell { padding: ${cssRuleText}; }`;
|
||||
this.input.css.innerHTML = content;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the angular components and record for this input that we have done so
|
||||
*/
|
||||
private _bootstrapAngular(): void {
|
||||
let input = <EditDataResultsInput>this.input;
|
||||
let uri = input.uri;
|
||||
|
||||
// Pass the correct DataService to the new angular component
|
||||
let dataService = this._queryModelService.getDataService(uri);
|
||||
if (!dataService) {
|
||||
throw new Error('DataService not found for URI: ' + uri);
|
||||
}
|
||||
|
||||
// Mark that we have bootstrapped
|
||||
input.setBootstrappedTrue();
|
||||
|
||||
// Get the bootstrap params and perform the bootstrap
|
||||
// Note: pass in input so on disposal this is cleaned up.
|
||||
// Otherwise many components will be left around and be subscribed
|
||||
// to events from the backing data service
|
||||
const parent = input.container;
|
||||
let params: EditDataComponentParams = { dataService: dataService };
|
||||
this._bootstrapService.bootstrap(
|
||||
EditDataModule,
|
||||
parent,
|
||||
EDITDATA_SELECTOR,
|
||||
params,
|
||||
input);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import { Action, IActionItem, IActionRunner } from 'vs/base/common/actions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { EventEmitter } from 'sql/base/common/eventEmitter';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { EditDataEditor } from 'sql/parts/editData/editor/editDataEditor';
|
||||
@@ -63,7 +63,7 @@ export abstract class EditDataAction extends Action {
|
||||
* Action class that refreshes the table for an edit data session
|
||||
*/
|
||||
export class RefreshTableAction extends EditDataAction {
|
||||
private static EnabledClass = 'refresh';
|
||||
private static EnabledClass = 'start';
|
||||
public static ID = 'refreshTableAction';
|
||||
|
||||
constructor(editor: EditDataEditor,
|
||||
@@ -72,14 +72,24 @@ export class RefreshTableAction extends EditDataAction {
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
) {
|
||||
super(editor, RefreshTableAction.ID, RefreshTableAction.EnabledClass, _connectionManagementService);
|
||||
this.label = nls.localize('editData.refresh', 'Refresh');
|
||||
this.label = nls.localize('editData.run', 'Run');
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
if (this.isConnected(this.editor)) {
|
||||
let input = this.editor.editDataInput;
|
||||
|
||||
let rowLimit: number = undefined;
|
||||
let queryString: string = undefined;
|
||||
if (input.queryPaneEnabled) {
|
||||
queryString = input.queryString = this.editor.getEditorText();
|
||||
} else {
|
||||
rowLimit = input.rowLimit;
|
||||
}
|
||||
|
||||
this._queryModelService.disposeEdit(input.uri).then((result) => {
|
||||
this._queryModelService.initializeEdit(input.uri, input.schemaName, input.tableName, input.objectType, input.rowLimit);
|
||||
this._queryModelService.initializeEdit(input.uri, input.schemaName, input.tableName, input.objectType, rowLimit, queryString);
|
||||
input.showResultsEditor();
|
||||
}, error => {
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
@@ -162,10 +172,12 @@ export class ChangeMaxRowsActionItem extends EventEmitter implements IActionItem
|
||||
this._options = ['200', '1000', '10000'];
|
||||
this._currentOptionsIndex = 0;
|
||||
this.toDispose = [];
|
||||
this.selectBox = new SelectBox([], -1, contextViewService);
|
||||
this.selectBox = new SelectBox(this._options, this._options[this._currentOptionsIndex], contextViewService);
|
||||
this._registerListeners();
|
||||
this._refreshOptions();
|
||||
this.defaultRowCount = Number(this._options[this._currentOptionsIndex]);
|
||||
|
||||
this.toDispose.push(attachSelectBoxStyler(this.selectBox, _themeService));
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
@@ -181,6 +193,14 @@ export class ChangeMaxRowsActionItem extends EventEmitter implements IActionItem
|
||||
return true;
|
||||
}
|
||||
|
||||
public enable(): void {
|
||||
this.selectBox.enable();
|
||||
}
|
||||
|
||||
public disable(): void {
|
||||
this.selectBox.disable();
|
||||
}
|
||||
|
||||
public set setCurrentOptionIndex(selection: number) {
|
||||
this._currentOptionsIndex = this._options.findIndex(x => x === selection.toString());
|
||||
this._refreshOptions();
|
||||
@@ -210,3 +230,40 @@ export class ChangeMaxRowsActionItem extends EventEmitter implements IActionItem
|
||||
this.toDispose.push(attachSelectBoxStyler(this.selectBox, this._themeService));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action class that is tied with toggling the Query editor
|
||||
*/
|
||||
export class ShowQueryPaneAction extends EditDataAction {
|
||||
|
||||
private static EnabledClass = 'filterLabel';
|
||||
public static ID = 'showQueryPaneAction';
|
||||
private readonly showSqlLabel = nls.localize('editData.showSql', 'Show SQL Pane');
|
||||
private readonly closeSqlLabel = nls.localize('editData.closeSql', 'Close SQL Pane');
|
||||
|
||||
constructor(editor: EditDataEditor,
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@IConnectionManagementService _connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(editor, ShowQueryPaneAction.ID, ShowQueryPaneAction.EnabledClass, _connectionManagementService);
|
||||
this.label = this.showSqlLabel;
|
||||
}
|
||||
|
||||
public set queryPaneEnabled(value: boolean) {
|
||||
this.updateLabel(value);
|
||||
}
|
||||
|
||||
private updateLabel(queryPaneEnabled: boolean): void {
|
||||
if (queryPaneEnabled) {
|
||||
this.label = this.closeSqlLabel;
|
||||
} else {
|
||||
this.label = this.showSqlLabel;
|
||||
}
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
this.editor.toggleQueryPane();
|
||||
this.updateLabel(this.editor.queryPaneEnabled());
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
@@ -17,4 +17,6 @@ export let SelectAllMessages = 'SelectAllMessages';
|
||||
export let SaveAsCsv = 'SaveAsCSV';
|
||||
export let SaveAsJSON = 'SaveAsJSON';
|
||||
export let SaveAsExcel = 'SaveAsExcel';
|
||||
export let ViewAsChart = 'ViewAsChart';
|
||||
export let GoToNextQueryOutputTab = 'GoToNextQueryOutputTab';
|
||||
export let GoToNextGrid = 'GoToNextGrid';
|
||||
|
||||
@@ -22,6 +22,8 @@ import { GridParentComponent } from 'sql/parts/grid/views/gridParentComponent';
|
||||
import { EditDataGridActionProvider } from 'sql/parts/grid/views/editData/editDataGridActions';
|
||||
import { error } from 'sql/base/common/log';
|
||||
import { clone } from 'sql/base/common/objects';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
|
||||
export const EDITDATA_SELECTOR: string = 'editdata-component';
|
||||
|
||||
@@ -40,7 +42,6 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
// All datasets
|
||||
private dataSet: IGridDataSet;
|
||||
private scrollTimeOut: number;
|
||||
private messagesAdded = false;
|
||||
private scrollEnabled = true;
|
||||
private firstRender = true;
|
||||
private totalElapsedTimeSpan: number;
|
||||
@@ -54,6 +55,8 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
private removingNewRow: boolean;
|
||||
private rowIdMappings: { [gridRowId: number]: number } = {};
|
||||
|
||||
private notificationService: INotificationService;
|
||||
|
||||
// Edit Data functions
|
||||
public onActiveCellChanged: (event: { row: number, column: number }) => void;
|
||||
public onCellEditEnd: (event: { row: number, column: number, newValue: any }) => void;
|
||||
@@ -75,6 +78,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
let editDataParameters: EditDataComponentParams = this._bootstrapService.getBootstrapParams(this._el.nativeElement.tagName);
|
||||
this.dataService = editDataParameters.dataService;
|
||||
this.actionProvider = this._bootstrapService.instantiationService.createInstance(EditDataGridActionProvider, this.dataService, this.onGridSelectAll(), this.onDeleteRow(), this.onRevertRow());
|
||||
this.notificationService = bootstrapService.notificationService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,7 +131,6 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
self.renderedDataSets = self.placeHolderDataSets;
|
||||
self.totalElapsedTimeSpan = undefined;
|
||||
self.complete = false;
|
||||
self.messagesAdded = false;
|
||||
|
||||
// Hooking up edit functions
|
||||
this.onIsCellEditValid = (row, column, value): boolean => {
|
||||
@@ -302,7 +305,6 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
handleComplete(self: EditDataComponent, event: any): void {
|
||||
self.totalElapsedTimeSpan = event.data;
|
||||
self.complete = true;
|
||||
self.messagesAdded = true;
|
||||
}
|
||||
|
||||
handleEditSessionReady(self, event): void {
|
||||
@@ -310,7 +312,12 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
}
|
||||
|
||||
handleMessage(self: EditDataComponent, event: any): void {
|
||||
// TODO: what do we do with messages?
|
||||
if (event.data && event.data.isError) {
|
||||
self.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: event.data.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleResultSet(self: EditDataComponent, event: any): void {
|
||||
@@ -360,7 +367,6 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
undefinedDataSet.dataRows = undefined;
|
||||
undefinedDataSet.resized = new EventEmitter();
|
||||
self.placeHolderDataSets.push(undefinedDataSet);
|
||||
self.messagesAdded = true;
|
||||
self.onScroll(0);
|
||||
|
||||
// Setup the state of the selected cell
|
||||
|
||||
@@ -25,7 +25,8 @@ export const MESSAGES_COPY_ID = 'grid.messages.copy';
|
||||
export const TOGGLERESULTS_ID = 'grid.toggleResultPane';
|
||||
export const TOGGLEMESSAGES_ID = 'grid.toggleMessagePane';
|
||||
export const GOTONEXTQUERYOUTPUTTAB_ID = 'query.goToNextQueryOutputTab';
|
||||
|
||||
export const GRID_VIEWASCHART_ID = 'grid.viewAsChart';
|
||||
export const GRID_GOTONEXTGRID_ID = 'grid.goToNextGrid';
|
||||
|
||||
export class GridActionProvider {
|
||||
|
||||
|
||||
@@ -77,4 +77,11 @@ export const selectAllMessages = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.SelectAllMessages);
|
||||
};
|
||||
|
||||
export const viewAsChart = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.ViewAsChart);
|
||||
};
|
||||
|
||||
export const goToNextGrid = (accessor: ServicesAccessor) => {
|
||||
runActionOnActiveResultsEditor(accessor, GridContentEvents.GoToNextGrid);
|
||||
};
|
||||
|
||||
|
||||
@@ -168,6 +168,12 @@ export abstract class GridParentComponent {
|
||||
case GridContentEvents.GoToNextQueryOutputTab:
|
||||
self.goToNextQueryOutputTab();
|
||||
break;
|
||||
case GridContentEvents.ViewAsChart:
|
||||
self.showChartForGrid(self.activeGrid);
|
||||
break;
|
||||
case GridContentEvents.GoToNextGrid:
|
||||
self.goToNextGrid();
|
||||
break;
|
||||
default:
|
||||
error('Unexpected grid content event type "' + type + '" sent');
|
||||
break;
|
||||
@@ -278,6 +284,22 @@ export abstract class GridParentComponent {
|
||||
protected goToNextQueryOutputTab(): void {
|
||||
}
|
||||
|
||||
protected showChartForGrid(index: number) {
|
||||
}
|
||||
|
||||
protected goToNextGrid() {
|
||||
if (this.renderedDataSets.length > 0) {
|
||||
let next = this.activeGrid + 1;
|
||||
if (next >= this.renderedDataSets.length) {
|
||||
next = 0;
|
||||
}
|
||||
this.navigateToGrid(next);
|
||||
}
|
||||
}
|
||||
|
||||
protected navigateToGrid(index: number) {
|
||||
}
|
||||
|
||||
private initShortcutsBase(): void {
|
||||
let shortcuts = {
|
||||
'ToggleResultPane': () => {
|
||||
|
||||
@@ -97,12 +97,12 @@ export class ChartViewerComponent implements OnInit, OnDestroy, IChartViewAction
|
||||
@Inject(BOOTSTRAP_SERVICE_ID) private _bootstrapService: IBootstrapService,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef
|
||||
) {
|
||||
this.setDefaultChartConfig();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.setDefaultChartConfig();
|
||||
this.legendOptions = Object.values(LegendPosition);
|
||||
this.initializeUI();
|
||||
this._initActionBar();
|
||||
}
|
||||
|
||||
private setDefaultChartConfig() {
|
||||
@@ -114,11 +114,6 @@ export class ChartViewerComponent implements OnInit, OnDestroy, IChartViewAction
|
||||
};
|
||||
}
|
||||
|
||||
private initializeUI() {
|
||||
// Initialize the taskbar
|
||||
this._initActionBar();
|
||||
}
|
||||
|
||||
private getDefaultChartType(): string {
|
||||
let defaultChartType = Constants.chartTypeHorizontalBar;
|
||||
if (this._bootstrapService.configurationService) {
|
||||
@@ -155,6 +150,10 @@ export class ChartViewerComponent implements OnInit, OnDestroy, IChartViewAction
|
||||
this.initChart();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.initChart();
|
||||
}
|
||||
|
||||
setConfigValue(key: string, value: any, refresh = true): void {
|
||||
this._chartConfig[key] = value;
|
||||
if (refresh) {
|
||||
@@ -316,7 +315,6 @@ export class ChartViewerComponent implements OnInit, OnDestroy, IChartViewAction
|
||||
this._executeResult.rows = dataSet.dataRows.getRange(0, dataSet.dataRows.getLength()).map(gridRow => {
|
||||
return gridRow.values.map(cell => cell.displayValue);
|
||||
});
|
||||
this.initChart();
|
||||
}
|
||||
|
||||
public initChart() {
|
||||
|
||||
@@ -589,7 +589,7 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
|
||||
});
|
||||
}
|
||||
|
||||
private showChartForGrid(index: number) {
|
||||
protected showChartForGrid(index: number) {
|
||||
if (this.renderedDataSets.length > index) {
|
||||
this.showChartRequested.emit(this.renderedDataSets[index]);
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
|
||||
<panel class="dashboard-panel" [options]="panelOpt">
|
||||
<tab [title]="jobsComponentTitle" class="fullsize" [identifier]="jobsTabIdentifier"
|
||||
[iconClass]="jobsIconClass">
|
||||
<div id="jobsDiv" class="fullsize" *ngIf="showHistory === false">
|
||||
<jobsview-component></jobsview-component>
|
||||
</div>
|
||||
<div id="historyDiv" class="fullsize" *ngIf="showHistory === true">
|
||||
<jobhistory-component></jobhistory-component>
|
||||
</div>
|
||||
[iconClass]="jobsIconClass">
|
||||
<ng-template>
|
||||
<div id="jobsDiv" class="fullsize" *ngIf="showHistory === false">
|
||||
<jobsview-component></jobsview-component>
|
||||
</div>
|
||||
<div id="historyDiv" class="fullsize" *ngIf="showHistory === true">
|
||||
<jobhistory-component></jobhistory-component>
|
||||
</div>
|
||||
</ng-template>
|
||||
</tab>
|
||||
</panel>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<!-- Overview -->
|
||||
<div class="overview-container">
|
||||
<div class="overview-tab" (click)='toggleCollapse()'>
|
||||
<div class="overview-tab" (click)='toggleCollapse()' tabindex="0">
|
||||
<input id="accordion" type="checkbox">
|
||||
<label for="accordion">
|
||||
<div class="resultsViewCollapsible collapsed" (click)='toggleCollapse()'></div>
|
||||
|
||||
@@ -148,6 +148,8 @@ export class JobHistoryComponent extends Disposable implements OnInit {
|
||||
if (this._jobCacheObject.prevJobID === this._agentViewComponent.jobId || jobHistories[0].jobId === this._agentViewComponent.jobId) {
|
||||
this._showPreviousRuns = true;
|
||||
this.buildHistoryTree(self, jobHistories);
|
||||
$('jobhistory-component .history-details .prev-run-list .monaco-tree').attr('tabIndex', '-1');
|
||||
$('jobhistory-component .history-details .prev-run-list .monaco-tree-row').attr('tabIndex', '0');
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
} else if (jobHistories && jobHistories.length === 0 ){
|
||||
|
||||
@@ -60,11 +60,13 @@ export class JobStepsViewComponent extends Disposable implements OnInit, AfterCo
|
||||
dataSource: this._treeDataSource,
|
||||
filter: this._treeFilter,
|
||||
renderer: this._treeRenderer
|
||||
}, {verticalScrollMode: ScrollbarVisibility.Visible});
|
||||
}, { verticalScrollMode: ScrollbarVisibility.Visible });
|
||||
this._register(attachListStyler(this._tree, this.bootstrapService.themeService));
|
||||
}
|
||||
this._tree.layout(JobStepsViewComponent._pageSize);
|
||||
this._tree.setInput(new JobStepsViewModel());
|
||||
$('jobstepsview-component .steps-tree .monaco-tree').attr('tabIndex', '-1');
|
||||
$('jobstepsview-component .steps-tree .monaco-tree-row').attr('tabIndex', '0');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
19
src/sql/parts/modelComponents/card.component.html
Normal file
19
src/sql/parts/modelComponents/card.component.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<div *ngIf="label" class="model-card">
|
||||
<span *ngIf="hasStatus" class="card-status">
|
||||
<div class="status-content" [style.backgroundColor]="statusColor"></div>
|
||||
</span>
|
||||
<div class="card-content">
|
||||
<h4 class="card-label">{{label}}</h4>
|
||||
<p class="card-value">{{value}}</p>
|
||||
<span *ngIf="actions">
|
||||
<table class="model-table">
|
||||
<tr *ngFor="let action of actions">
|
||||
<td class="table-row">{{action.label}}</td>
|
||||
<td *ngIf="action.actionTitle" class="table-row">
|
||||
<a class="pointer prominent" (click)="onDidActionClick(action)">{{action.actionTitle}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -9,29 +9,37 @@ import { Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFacto
|
||||
} from '@angular/core';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import * as colors from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
|
||||
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/parts/modelComponents/interfaces';
|
||||
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
|
||||
import { BOOTSTRAP_SERVICE_ID, IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { StatusIndicator, CardProperties, ActionDescriptor } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div *ngIf="label" class="model-card" >
|
||||
<h4 class="card-label">{{label}}</h4>
|
||||
<p class="card-value">{{value}}</p>
|
||||
</div>
|
||||
`
|
||||
templateUrl: decodeURI(require.toUrl('sql/parts/modelComponents/card.component.html'))
|
||||
})
|
||||
export default class CardComponent extends ComponentBase implements IComponent, OnDestroy {
|
||||
@Input() descriptor: IComponentDescriptor;
|
||||
@Input() modelStore: IModelStore;
|
||||
|
||||
constructor(@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef) {
|
||||
private backgroundColor: string;
|
||||
|
||||
constructor(@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(BOOTSTRAP_SERVICE_ID) private _bootstrapService: IBootstrapService
|
||||
) {
|
||||
super(changeRef);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.baseInit();
|
||||
this._register(this._bootstrapService.themeService.onDidColorThemeChange(this.updateTheme, this));
|
||||
this.updateTheme(this._bootstrapService.themeService.getColorTheme());
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -52,15 +60,45 @@ export default class CardComponent extends ComponentBase implements IComponent,
|
||||
// CSS-bound properties
|
||||
|
||||
public get label(): string {
|
||||
return this.getPropertyOrDefault<sqlops.CardProperties, string>((props) => props.label, '');
|
||||
return this.getPropertyOrDefault<CardProperties, string>((props) => props.label, '');
|
||||
}
|
||||
|
||||
public get value(): string {
|
||||
return this.getPropertyOrDefault<sqlops.CardProperties, string>((props) => props.value, '');
|
||||
return this.getPropertyOrDefault<CardProperties, string>((props) => props.value, '');
|
||||
}
|
||||
|
||||
public get actions(): sqlops.ActionDescriptor[] {
|
||||
return this.getPropertyOrDefault<sqlops.CardProperties, sqlops.ActionDescriptor[]>((props) => props.actions, []);
|
||||
public get actions(): ActionDescriptor[] {
|
||||
return this.getPropertyOrDefault<CardProperties, ActionDescriptor[]>((props) => props.actions, []);
|
||||
}
|
||||
|
||||
public hasStatus(): boolean {
|
||||
let status = this.getPropertyOrDefault<CardProperties, StatusIndicator>((props) => props.status, StatusIndicator.None);
|
||||
return status !== StatusIndicator.None;
|
||||
}
|
||||
|
||||
public get statusColor(): string {
|
||||
let status = this.getPropertyOrDefault<CardProperties, StatusIndicator>((props) => props.status, StatusIndicator.None);
|
||||
switch(status) {
|
||||
case StatusIndicator.Ok:
|
||||
return 'green';
|
||||
case StatusIndicator.Warning:
|
||||
return 'orange';
|
||||
case StatusIndicator.Error:
|
||||
return 'red';
|
||||
default:
|
||||
return this.backgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
private updateTheme(theme: IColorTheme) {
|
||||
this.backgroundColor = theme.getColor(colors.editorBackground, true).toString();
|
||||
}
|
||||
|
||||
private onDidActionClick(action: ActionDescriptor): void {
|
||||
this._onEventEmitter.fire({
|
||||
eventType: ComponentEventType.onDidClick,
|
||||
args: action
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,9 @@
|
||||
.model-card {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: auto;
|
||||
height: 90%;
|
||||
width: auto;
|
||||
margin: 15px;
|
||||
padding: 10px 45px 20px 45px;
|
||||
min-height: 30px;
|
||||
min-width: 30px;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: rgb(214, 214, 214);
|
||||
@@ -16,6 +13,16 @@
|
||||
box-shadow: rgba(120, 120, 120, 0.75) 0px 0px 6px;
|
||||
}
|
||||
|
||||
.model-card .card-content {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: auto;
|
||||
width: auto;
|
||||
padding: 10px 45px 20px 45px;
|
||||
min-height: 30px;
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
.model-card .card-label {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
@@ -24,4 +31,42 @@
|
||||
.model-card .card-value {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.model-card .card-status {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 5px;
|
||||
overflow: hidden;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.model-card .status-content {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.model-card .model-table {
|
||||
border-spacing: 5px;
|
||||
}
|
||||
|
||||
.model-table .table-row {
|
||||
width: auto;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.model-table .table-cell {
|
||||
vertical-align: top;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
.model-table a {
|
||||
cursor: pointer;
|
||||
text-decoration: underline
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { CommonServiceInterface } from 'sql/services/common/commonServiceInterfa
|
||||
import { attachInputBoxStyler, attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
|
||||
@Component({
|
||||
selector: 'inputBox',
|
||||
selector: 'checkbox',
|
||||
template: `
|
||||
<div #input style="width: 100%"></div>
|
||||
`
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import 'vs/css!./flexContainer';
|
||||
|
||||
import { Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
|
||||
import {
|
||||
Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
|
||||
ViewChild, ElementRef, Injector, OnDestroy, OnInit
|
||||
} from '@angular/core';
|
||||
|
||||
@@ -18,14 +19,17 @@ import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class ItemDescriptor<T> {
|
||||
constructor(public descriptor: IComponentDescriptor, public config: T) {}
|
||||
constructor(public descriptor: IComponentDescriptor, public config: T) { }
|
||||
}
|
||||
|
||||
export abstract class ComponentBase extends Disposable implements IComponent, OnDestroy, OnInit {
|
||||
protected properties: { [key: string]: any; } = {};
|
||||
constructor (
|
||||
private _valid: boolean = true;
|
||||
protected _validations: (() => boolean | Thenable<boolean>)[] = [];
|
||||
private _eventQueue: IComponentEventArgs[] = [];
|
||||
constructor(
|
||||
protected _changeRef: ChangeDetectorRef) {
|
||||
super();
|
||||
super();
|
||||
}
|
||||
|
||||
/// IComponent implementation
|
||||
@@ -41,6 +45,7 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
protected baseInit(): void {
|
||||
if (this.modelStore) {
|
||||
this.modelStore.registerComponent(this);
|
||||
this._validations.push(() => this.modelStore.validate(this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,13 +55,14 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
if (this.modelStore) {
|
||||
this.modelStore.unregisterComponent(this);
|
||||
}
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
abstract setLayout (layout: any): void;
|
||||
abstract setLayout(layout: any): void;
|
||||
|
||||
public setProperties(properties: { [key: string]: any; }): void {
|
||||
if (!properties) {
|
||||
@@ -64,6 +70,7 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
}
|
||||
this.properties = properties;
|
||||
this.layout();
|
||||
this.validate();
|
||||
}
|
||||
|
||||
protected getProperties<TPropertyBag>(): TPropertyBag {
|
||||
@@ -77,20 +84,54 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
|
||||
protected setPropertyFromUI<TPropertyBag, TValue>(propertySetter: (TPropertyBag, TValue) => void, value: TValue) {
|
||||
propertySetter(this.getProperties<TPropertyBag>(), value);
|
||||
this._onEventEmitter.fire({
|
||||
this.fireEvent({
|
||||
eventType: ComponentEventType.PropertiesChanged,
|
||||
args: this.getProperties()
|
||||
});
|
||||
this.validate();
|
||||
}
|
||||
|
||||
public get onEvent(): Event<IComponentEventArgs> {
|
||||
return this._onEventEmitter.event;
|
||||
}
|
||||
|
||||
public get title(): string {
|
||||
public get enabled(): boolean {
|
||||
let properties = this.getProperties();
|
||||
let title = properties['title'];
|
||||
return title ? <string>title : '';
|
||||
let enabled = properties['enabled'];
|
||||
return enabled !== undefined ? <boolean>enabled : true;
|
||||
}
|
||||
|
||||
public get valid(): boolean {
|
||||
return this._valid;
|
||||
}
|
||||
|
||||
public registerEventHandler(handler: (event: IComponentEventArgs) => void): IDisposable {
|
||||
if (this._eventQueue) {
|
||||
while (this._eventQueue.length > 0) {
|
||||
let event = this._eventQueue.pop();
|
||||
handler(event);
|
||||
}
|
||||
this._eventQueue = undefined;
|
||||
}
|
||||
return this._onEventEmitter.event(handler);
|
||||
}
|
||||
|
||||
private fireEvent(event: IComponentEventArgs) {
|
||||
this._onEventEmitter.fire(event);
|
||||
if (this._eventQueue) {
|
||||
this._eventQueue.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
public validate(): Thenable<boolean> {
|
||||
let validations = this._validations.map(validation => Promise.resolve(validation()));
|
||||
return Promise.all(validations).then(values => {
|
||||
let isValid = values.every(value => value === true);
|
||||
if (this._valid !== isValid) {
|
||||
this._valid = isValid;
|
||||
this.fireEvent({
|
||||
eventType: ComponentEventType.validityChanged,
|
||||
args: this._valid
|
||||
});
|
||||
}
|
||||
return isValid;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,11 +143,17 @@ export abstract class ContainerBase<T> extends ComponentBase {
|
||||
) {
|
||||
super(_changeRef);
|
||||
this.items = [];
|
||||
this._validations.push(() => this.items.every(item => this.modelStore.getComponent(item.descriptor.id).valid));
|
||||
}
|
||||
|
||||
/// IComponent container-related implementation
|
||||
public addToContainer(componentDescriptor: IComponentDescriptor, config: any): void {
|
||||
this.items.push(new ItemDescriptor(componentDescriptor, config));
|
||||
this.modelStore.eventuallyRunOnComponent(componentDescriptor.id, component => component.registerEventHandler(event => {
|
||||
if (event.eventType === ComponentEventType.validityChanged) {
|
||||
this.validate();
|
||||
}
|
||||
}));
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
@@ -115,5 +162,5 @@ export abstract class ContainerBase<T> extends ComponentBase {
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
abstract setLayout (layout: any): void;
|
||||
abstract setLayout(layout: any): void;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { attachEditableDropdownStyler } from 'sql/common/theme/styler';
|
||||
|
||||
@Component({
|
||||
selector: 'inputBox',
|
||||
selector: 'dropdown',
|
||||
template: `
|
||||
<div #input style="width: 100%"></div>
|
||||
`
|
||||
|
||||
@@ -22,6 +22,8 @@ export interface TitledFormItemLayout {
|
||||
actions?: string[];
|
||||
isFormComponent: Boolean;
|
||||
horizontal: boolean;
|
||||
width: number;
|
||||
componentWidth: number;
|
||||
}
|
||||
class FormItem {
|
||||
constructor(public descriptor: IComponentDescriptor, public config: TitledFormItemLayout) { }
|
||||
@@ -29,42 +31,43 @@ class FormItem {
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div #container *ngIf="items" class="form-table"
|
||||
[style.alignItems]="alignItems" [style.alignContent]="alignContent">
|
||||
<div *ngFor="let item of items" class="form-row">
|
||||
<div #container *ngIf="items" class="form-table" >
|
||||
<ng-container *ngFor="let item of items">
|
||||
<div class="form-row" [style.width]="getFormWidth(item)">
|
||||
<ng-container *ngIf="isFormComponent(item)">
|
||||
<ng-container *ngIf="isHorizontal(item)">
|
||||
<div class="form-cell">{{getItemTitle(item)}}</div>
|
||||
<div class="form-cell">
|
||||
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
|
||||
</model-component-wrapper>
|
||||
</div>
|
||||
<div *ngIf="itemHasActions(item)" class="form-cell">
|
||||
<div class="form-actions-table">
|
||||
<div *ngFor="let actionItem of getActionComponents(item)" class="form-cell" >
|
||||
<model-component-wrapper [descriptor]="actionItem.descriptor" [modelStore]="modelStore">
|
||||
<div class="form-component-container">
|
||||
<div [style.width]="getComponentWidth(item)" [ngClass]="{'form-input-flex': !getComponentWidth(item)}">
|
||||
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
|
||||
</model-component-wrapper>
|
||||
</div>
|
||||
<div *ngIf="itemHasActions(item)" class="form-component-actions">
|
||||
<ng-container *ngFor="let actionItem of getActionComponents(item)">
|
||||
<model-component-wrapper [descriptor]="actionItem.descriptor" [modelStore]="modelStore" >
|
||||
</model-component-wrapper>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="isVertical(item)">
|
||||
<div class="form-item-row form-item-title">{{getItemTitle(item)}}</div>
|
||||
<div class="form-item-row">
|
||||
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
|
||||
<div class="form-vertical-container" *ngIf="isVertical(item)">
|
||||
<div class="form-item-row">{{getItemTitle(item)}}</div>
|
||||
<div class="form-item-row" [style.width]="getComponentWidth(item)">
|
||||
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore" [style.width]="getComponentWidth(item)">
|
||||
</model-component-wrapper>
|
||||
</div>
|
||||
<div *ngIf="itemHasActions(item)" class="form-actions-table">
|
||||
|
||||
<div *ngFor="let actionItem of getActionComponents(item)" class="form-actions-cell" >
|
||||
<model-component-wrapper [descriptor]="actionItem.descriptor" [modelStore]="modelStore">
|
||||
</model-component-wrapper>
|
||||
<div *ngIf="itemHasActions(item)" class="form-item-row form-actions-table form-item-last-row">
|
||||
<div *ngFor="let actionItem of getActionComponents(item)" class="form-actions-cell" >
|
||||
<model-component-wrapper [descriptor]="actionItem.descriptor" [modelStore]="modelStore">
|
||||
</model-component-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
@@ -113,6 +116,16 @@ export default class FormContainer extends ContainerBase<FormItemLayout> impleme
|
||||
return this._alignContent;
|
||||
}
|
||||
|
||||
private getFormWidth(item: FormItem): string {
|
||||
let itemConfig = item.config;
|
||||
return itemConfig && itemConfig.width ? +itemConfig.width + 'px' : '100%';
|
||||
}
|
||||
|
||||
private getComponentWidth(item: FormItem): string {
|
||||
let itemConfig = item.config;
|
||||
return (itemConfig && itemConfig.componentWidth) ? itemConfig.componentWidth + 'px' : '';
|
||||
}
|
||||
|
||||
private getItemTitle(item: FormItem): string {
|
||||
let itemConfig = item.config;
|
||||
return itemConfig ? itemConfig.title : '';
|
||||
|
||||
@@ -1,42 +1,49 @@
|
||||
|
||||
.form-table {
|
||||
width:400px;
|
||||
display:table;
|
||||
padding: 30px;
|
||||
width: 100%;
|
||||
display: table;
|
||||
padding: 10px 30px 0px 30px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-actions-table {
|
||||
display:table;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: table-row;
|
||||
width: 100px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.form-item-row {
|
||||
padding-bottom: 5px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.form-item-title {
|
||||
|
||||
padding-top: 20px;
|
||||
.form-vertical-container {
|
||||
padding-bottom: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-cell {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
padding-right: 5px;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.form-component-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.form-input-flex {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-component-actions {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.form-actions-cell {
|
||||
padding-top: 5px;
|
||||
padding-right: 5px;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
.form-action {
|
||||
width: 20px;
|
||||
}
|
||||
@@ -10,10 +10,11 @@ import {
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
|
||||
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
|
||||
import { InputBox, IInputOptions } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { InputBox, IInputOptions, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { attachInputBoxStyler, attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
|
||||
@@ -44,10 +45,24 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
|
||||
if (this._inputContainer) {
|
||||
let inputOptions: IInputOptions = {
|
||||
placeholder: '',
|
||||
ariaLabel: ''
|
||||
ariaLabel: '',
|
||||
validationOptions: {
|
||||
validation: () => {
|
||||
if (this.valid) {
|
||||
return undefined;
|
||||
} else {
|
||||
return {
|
||||
content: this._input.inputElement.validationMessage || nls.localize('invalidValueError', 'Invalid value'),
|
||||
type: MessageType.ERROR
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
useDefaultValidation: true
|
||||
};
|
||||
|
||||
this._input = new InputBox(this._inputContainer.nativeElement, this._commonService.contextViewService, inputOptions);
|
||||
this._validations.push(() => !this._input.inputElement.validationMessage);
|
||||
|
||||
this._register(this._input);
|
||||
this._register(attachInputBoxStyler(this._input, this._commonService.themeService));
|
||||
@@ -61,6 +76,13 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
|
||||
}
|
||||
}
|
||||
|
||||
public validate(): Thenable<boolean> {
|
||||
return super.validate().then(valid => {
|
||||
this._input.validate();
|
||||
return valid;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.baseDestroy();
|
||||
}
|
||||
@@ -78,7 +100,19 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
|
||||
|
||||
public setProperties(properties: { [key: string]: any; }): void {
|
||||
super.setProperties(properties);
|
||||
this._input.inputElement.type = this.inputType;
|
||||
if (this.inputType === 'number') {
|
||||
this._input.inputElement.step = 'any';
|
||||
}
|
||||
this._input.value = this.value;
|
||||
this._input.setAriaLabel(this.ariaLabel);
|
||||
this._input.setPlaceHolder(this.placeHolder);
|
||||
this._input.setEnabled(this.enabled);
|
||||
if (this.width) {
|
||||
this._input.width = this.width;
|
||||
}
|
||||
this._input.inputElement.required = this.required;
|
||||
this.validate();
|
||||
}
|
||||
|
||||
// CSS-bound properties
|
||||
@@ -88,10 +122,54 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
|
||||
}
|
||||
|
||||
public set value(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.InputBoxProperties, string>(this.setInputBoxProperties, newValue);
|
||||
this.setPropertyFromUI<sqlops.InputBoxProperties, string>((props, value) => props.value = value, newValue);
|
||||
}
|
||||
|
||||
private setInputBoxProperties(properties: sqlops.InputBoxProperties, value: string): void {
|
||||
properties.value = value;
|
||||
public get ariaLabel(): string {
|
||||
return this.getPropertyOrDefault<sqlops.InputBoxProperties, string>((props) => props.ariaLabel, '');
|
||||
}
|
||||
|
||||
public set ariaLabel(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.InputBoxProperties, string>((props, value) => props.ariaLabel = value, newValue);
|
||||
}
|
||||
|
||||
public get placeHolder(): string {
|
||||
return this.getPropertyOrDefault<sqlops.InputBoxProperties, string>((props) => props.placeHolder, '');
|
||||
}
|
||||
|
||||
public set placeHolder(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.InputBoxProperties, string>((props, value) => props.placeHolder = value, newValue);
|
||||
}
|
||||
|
||||
public get height(): number {
|
||||
return this.getPropertyOrDefault<sqlops.InputBoxProperties, number>((props) => props.height, undefined);
|
||||
}
|
||||
|
||||
public set height(newValue: number) {
|
||||
this.setPropertyFromUI<sqlops.InputBoxProperties, number>((props, value) => props.height = value, newValue);
|
||||
}
|
||||
|
||||
public get width(): number {
|
||||
return this.getPropertyOrDefault<sqlops.InputBoxProperties, number>((props) => props.width, undefined);
|
||||
}
|
||||
|
||||
public set width(newValue: number) {
|
||||
this.setPropertyFromUI<sqlops.InputBoxProperties, number>((props, value) => props.width = value, newValue);
|
||||
}
|
||||
|
||||
public get inputType(): string {
|
||||
return this.getPropertyOrDefault<sqlops.InputBoxProperties, string>((props) => props.inputType, 'text');
|
||||
}
|
||||
|
||||
public set inputType(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.InputBoxProperties, string>((props, value) => props.inputType = value, newValue);
|
||||
}
|
||||
|
||||
public get required(): boolean {
|
||||
return this.getPropertyOrDefault<sqlops.InputBoxProperties, boolean>((props) => props.required, false);
|
||||
}
|
||||
|
||||
public set required(newValue: boolean) {
|
||||
this.setPropertyFromUI<sqlops.InputBoxProperties, boolean>((props, value) => props.required = value, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { InjectionToken } from '@angular/core';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
/**
|
||||
* An instance of a model-backed component. This will be a UI element
|
||||
@@ -17,12 +18,13 @@ export interface IComponent {
|
||||
descriptor: IComponentDescriptor;
|
||||
modelStore: IModelStore;
|
||||
layout();
|
||||
registerEventHandler(handler: (event: IComponentEventArgs) => void): IDisposable;
|
||||
clearContainer?: () => void;
|
||||
addToContainer?: (componentDescriptor: IComponentDescriptor, config: any) => void;
|
||||
setLayout?: (layout: any) => void;
|
||||
setProperties?: (properties: { [key: string]: any; }) => void;
|
||||
title?: string;
|
||||
onEvent?: Event<IComponentEventArgs>;
|
||||
readonly valid?: boolean;
|
||||
validate(): Thenable<boolean>;
|
||||
}
|
||||
|
||||
export const COMPONENT_CONFIG = new InjectionToken<IComponentConfig>('component_config');
|
||||
@@ -60,7 +62,8 @@ export interface IComponentEventArgs {
|
||||
export enum ComponentEventType {
|
||||
PropertiesChanged,
|
||||
onDidChange,
|
||||
onDidClick
|
||||
onDidClick,
|
||||
validityChanged
|
||||
}
|
||||
|
||||
export interface IModelStore {
|
||||
@@ -85,4 +88,12 @@ export interface IModelStore {
|
||||
* @memberof IModelStore
|
||||
*/
|
||||
eventuallyRunOnComponent<T>(componentId: string, action: (component: IComponent) => T): Promise<T>;
|
||||
/**
|
||||
* Register a callback that will validate components when given a component ID
|
||||
*/
|
||||
registerValidationCallback(callback: (componentId: string) => Thenable<boolean>): void;
|
||||
/**
|
||||
* Run all validations for the given component and return the new validation value
|
||||
*/
|
||||
validate(component: IComponent): Thenable<boolean>;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { IModelStore, IComponentDescriptor, IComponent } from './interfaces';
|
||||
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
|
||||
const componentRegistry = <IComponentRegistry> Registry.as(Extensions.ComponentContribution);
|
||||
const componentRegistry = <IComponentRegistry>Registry.as(Extensions.ComponentContribution);
|
||||
|
||||
|
||||
class ComponentDescriptor implements IComponentDescriptor {
|
||||
@@ -27,6 +27,7 @@ export class ModelStore implements IModelStore {
|
||||
private _descriptorMappings: { [x: string]: IComponentDescriptor } = {};
|
||||
private _componentMappings: { [x: string]: IComponent } = {};
|
||||
private _componentActions: { [x: string]: Deferred<IComponent> } = {};
|
||||
private _validationCallbacks: ((componentId: string) => Thenable<boolean>)[] = [];
|
||||
constructor() {
|
||||
}
|
||||
|
||||
@@ -66,6 +67,15 @@ export class ModelStore implements IModelStore {
|
||||
}
|
||||
}
|
||||
|
||||
registerValidationCallback(callback: (componentId: string) => Thenable<boolean>): void {
|
||||
this._validationCallbacks.push(callback);
|
||||
}
|
||||
|
||||
validate(component: IComponent): Thenable<boolean> {
|
||||
let componentId = Object.entries(this._componentMappings).find(([id, mappedComponent]) => component === mappedComponent)[0];
|
||||
return Promise.all(this._validationCallbacks.map(callback => callback(componentId))).then(validations => validations.every(validation => validation === true));
|
||||
}
|
||||
|
||||
private addPendingAction<T>(componentId: string, action: (component: IComponent) => T): Promise<T> {
|
||||
// We create a promise and chain it onto a tracking promise whose resolve method
|
||||
// will only be called once the component is created
|
||||
|
||||
@@ -11,9 +11,8 @@ import { Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import nls = require('vs/nls');
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { TabConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { IModelView } from 'sql/services/model/modelViewService';
|
||||
@@ -55,6 +54,11 @@ export class ModelViewContent extends ViewBase implements OnInit, IModelView {
|
||||
}));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._onDestroy.fire();
|
||||
super.ngOnDestroy();
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ const componentRegistry = <IComponentRegistry>Registry.as(Extensions.ComponentCo
|
||||
export abstract class ViewBase extends AngularDisposable implements IModelView {
|
||||
protected readonly modelStore: IModelStore;
|
||||
protected rootDescriptor: IComponentDescriptor;
|
||||
protected _onDestroy = new Emitter<void>();
|
||||
public readonly onDestroy = this._onDestroy.event;
|
||||
constructor(protected changeRef: ChangeDetectorRef) {
|
||||
super();
|
||||
this.modelStore = new ModelStore();
|
||||
@@ -39,9 +41,10 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
|
||||
private _onEventEmitter = new Emitter<any>();
|
||||
|
||||
|
||||
initializeModel(rootComponent: IComponentShape): void {
|
||||
initializeModel(rootComponent: IComponentShape, validationCallback: (componentId: string) => Thenable<boolean>): void {
|
||||
let descriptor = this.defineComponent(rootComponent);
|
||||
this.rootDescriptor = descriptor;
|
||||
this.modelStore.registerValidationCallback(validationCallback);
|
||||
// Kick off the build by detecting changes to the model
|
||||
this.changeRef.detectChanges();
|
||||
}
|
||||
@@ -99,16 +102,18 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
|
||||
|
||||
registerEvent(componentId: string) {
|
||||
this.queueAction(componentId, (component) => {
|
||||
if (component.onEvent) {
|
||||
this._register(component.onEvent(e => {
|
||||
e.componentId = componentId;
|
||||
this._onEventEmitter.fire(e);
|
||||
}));
|
||||
}
|
||||
this._register(component.registerEventHandler(e => {
|
||||
e.componentId = componentId;
|
||||
this._onEventEmitter.fire(e);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
public get onEvent(): Event<IComponentEventArgs> {
|
||||
return this._onEventEmitter.event;
|
||||
}
|
||||
|
||||
public validate(componentId: string): Thenable<boolean> {
|
||||
return new Promise(resolve => this.modelStore.eventuallyRunOnComponent(componentId, component => resolve(component.validate())));
|
||||
}
|
||||
}
|
||||
@@ -192,9 +192,10 @@ export class OEEditDataAction extends EditDataAction {
|
||||
id: string, label: string,
|
||||
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
|
||||
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
|
||||
@IScriptingService protected _scriptingService: IScriptingService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService
|
||||
) {
|
||||
super(id, label, _queryEditorService, _connectionManagementService);
|
||||
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService);
|
||||
}
|
||||
|
||||
public run(actionContext: any): TPromise<boolean> {
|
||||
|
||||
@@ -33,6 +33,8 @@ import { QueryPlanEditor } from 'sql/parts/queryPlan/queryPlanEditor';
|
||||
import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
|
||||
import * as Constants from 'sql/parts/query/common/constants';
|
||||
import { localize } from 'vs/nls';
|
||||
import { EditDataResultsEditor } from 'sql/parts/editData/editor/editDataResultsEditor';
|
||||
import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput';
|
||||
|
||||
const gridCommandsWeightBonus = 100; // give our commands a little bit more weight over other default list/tree commands
|
||||
|
||||
@@ -81,6 +83,16 @@ const editDataEditorDescriptor = new EditorDescriptor(
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
||||
.registerEditor(editDataEditorDescriptor, [new SyncDescriptor(EditDataInput)]);
|
||||
|
||||
// Editor
|
||||
const editDataResultsEditorDescriptor = new EditorDescriptor(
|
||||
EditDataResultsEditor,
|
||||
EditDataResultsEditor.ID,
|
||||
'EditDataResults'
|
||||
);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
||||
.registerEditor(editDataResultsEditorDescriptor, [new SyncDescriptor(EditDataResultsInput)]);
|
||||
|
||||
let actionRegistry = <IWorkbenchActionRegistry>Registry.as(Extensions.WorkbenchActions);
|
||||
|
||||
// Query Actions
|
||||
@@ -211,6 +223,22 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
handler: gridCommands.saveAsExcel
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: gridActions.GRID_VIEWASCHART_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(gridCommandsWeightBonus),
|
||||
when: ResultsGridFocusCondition,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_R, KeyMod.CtrlCmd | KeyCode.KEY_V),
|
||||
handler: gridCommands.viewAsChart
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: gridActions.GRID_GOTONEXTGRID_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(gridCommandsWeightBonus),
|
||||
when: ResultsGridFocusCondition,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_R, KeyMod.CtrlCmd | KeyCode.KEY_N),
|
||||
handler: gridCommands.goToNextGrid
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: gridActions.TOGGLERESULTS_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(gridCommandsWeightBonus),
|
||||
|
||||
@@ -30,7 +30,7 @@ export interface IQueryEditorService {
|
||||
newQueryPlanEditor(xmlShowPlan: string): Promise<any>;
|
||||
|
||||
// Creates new edit data session
|
||||
newEditDataEditor(schemaName: string, tableName: string): Promise<IConnectableInput>;
|
||||
newEditDataEditor(schemaName: string, tableName: string, queryString: string): Promise<IConnectableInput>;
|
||||
|
||||
// Clears any QueryEditor data for the given URI held by this service
|
||||
onQueryInputClosed(uri: string): void;
|
||||
|
||||
@@ -43,7 +43,7 @@ export interface IQueryManagementService {
|
||||
onEditSessionReady(ownerUri: string, success: boolean, message: string): void;
|
||||
|
||||
// Edit Data Functions
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void>;
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable<void>;
|
||||
disposeEdit(ownerUri: string): Thenable<void>;
|
||||
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<sqlops.EditUpdateCellResult>;
|
||||
commitEdit(ownerUri): Thenable<void>;
|
||||
@@ -68,7 +68,7 @@ export interface IQueryRequestHandler {
|
||||
saveResults(requestParams: sqlops.SaveResultsRequestParams): Thenable<sqlops.SaveResultRequestResult>;
|
||||
|
||||
// Edit Data actions
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void>;
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable<void>;
|
||||
disposeEdit(ownerUri: string): Thenable<void>;
|
||||
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<sqlops.EditUpdateCellResult>;
|
||||
commitEdit(ownerUri): Thenable<void>;
|
||||
@@ -244,9 +244,9 @@ export class QueryManagementService implements IQueryManagementService {
|
||||
}
|
||||
|
||||
// Edit Data Functions
|
||||
public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void> {
|
||||
public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable<void> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit);
|
||||
return runner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
||||
import * as Constants from 'sql/parts/query/common/constants';
|
||||
import * as ConnectionConstants from 'sql/parts/connection/common/constants';
|
||||
import { EditDataEditor } from 'sql/parts/editData/editor/editDataEditor';
|
||||
|
||||
const singleQuote = '\'';
|
||||
|
||||
@@ -98,8 +99,8 @@ export class RunQueryKeyboardAction extends Action {
|
||||
|
||||
public run(): TPromise<void> {
|
||||
let editor = this._editorService.getActiveEditor();
|
||||
if (editor && editor instanceof QueryEditor) {
|
||||
let queryEditor: QueryEditor = editor;
|
||||
if (editor && (editor instanceof QueryEditor || editor instanceof EditDataEditor)) {
|
||||
let queryEditor: QueryEditor | EditDataEditor = editor;
|
||||
queryEditor.runQuery();
|
||||
}
|
||||
return TPromise.as(null);
|
||||
@@ -174,8 +175,8 @@ export class CancelQueryKeyboardAction extends Action {
|
||||
|
||||
public run(): TPromise<void> {
|
||||
let editor = this._editorService.getActiveEditor();
|
||||
if (editor && editor instanceof QueryEditor) {
|
||||
let queryEditor: QueryEditor = editor;
|
||||
if (editor && (editor instanceof QueryEditor || editor instanceof EditDataEditor)) {
|
||||
let queryEditor: QueryEditor | EditDataEditor = editor;
|
||||
queryEditor.cancelQuery();
|
||||
}
|
||||
return TPromise.as(null);
|
||||
|
||||
@@ -57,7 +57,7 @@ export interface IQueryModelService {
|
||||
|
||||
|
||||
// Edit Data Functions
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): void;
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): void;
|
||||
disposeEdit(ownerUri: string): Thenable<void>;
|
||||
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<EditUpdateCellResult>;
|
||||
commitEdit(ownerUri): Thenable<void>;
|
||||
|
||||
@@ -352,7 +352,7 @@ export class QueryModelService implements IQueryModelService {
|
||||
}
|
||||
|
||||
// EDIT DATA METHODS /////////////////////////////////////////////////////
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): void {
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): void {
|
||||
// Reuse existing query runner if it exists
|
||||
let queryRunner: QueryRunner;
|
||||
let info: QueryInfo;
|
||||
@@ -368,6 +368,8 @@ export class QueryModelService implements IQueryModelService {
|
||||
|
||||
queryRunner = existingRunner;
|
||||
} else {
|
||||
info = new QueryInfo();
|
||||
|
||||
// We do not have a query runner for this editor, so create a new one
|
||||
// and map it to the results uri
|
||||
queryRunner = this._instantiationService.createInstance(QueryRunner, ownerUri, ownerUri);
|
||||
@@ -376,15 +378,21 @@ export class QueryModelService implements IQueryModelService {
|
||||
});
|
||||
queryRunner.addListener(QREvents.BATCH_START, batch => {
|
||||
let link = undefined;
|
||||
let messageText = LocalizedConstants.runQueryBatchStartMessage;
|
||||
if (batch.selection) {
|
||||
link = {
|
||||
text: strings.format(LocalizedConstants.runQueryBatchStartLine, batch.selection.startLine + 1),
|
||||
uri: ''
|
||||
};
|
||||
if (info.selectionSnippet) {
|
||||
// This indicates it's a query string. Do not include line information since it'll be inaccurate, but show some of the
|
||||
// executed query text
|
||||
messageText = nls.localize('runQueryStringBatchStartMessage', 'Started executing query "{0}"', info.selectionSnippet);
|
||||
} else {
|
||||
link = {
|
||||
text: strings.format(LocalizedConstants.runQueryBatchStartLine, batch.selection.startLine + 1)
|
||||
};
|
||||
}
|
||||
}
|
||||
let message = {
|
||||
message: LocalizedConstants.runQueryBatchStartMessage,
|
||||
batchId: undefined,
|
||||
message: messageText,
|
||||
batchId: batch.id,
|
||||
isError: false,
|
||||
time: new Date().toLocaleTimeString(),
|
||||
link: link
|
||||
@@ -407,13 +415,20 @@ export class QueryModelService implements IQueryModelService {
|
||||
this._fireQueryEvent(e.ownerUri, 'editSessionReady');
|
||||
});
|
||||
|
||||
info = new QueryInfo();
|
||||
info.queryRunner = queryRunner;
|
||||
info.dataService = this._instantiationService.createInstance(DataService, ownerUri);
|
||||
this._queryInfoMap.set(ownerUri, info);
|
||||
}
|
||||
|
||||
queryRunner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit);
|
||||
if (queryString) {
|
||||
if (queryString.length < selectionSnippetMaxLen) {
|
||||
info.selectionSnippet = queryString;
|
||||
} else {
|
||||
info.selectionSnippet = queryString.substring(0, selectionSnippetMaxLen - 3) + '...';
|
||||
}
|
||||
}
|
||||
|
||||
queryRunner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString);
|
||||
}
|
||||
|
||||
public cancelInitializeEdit(input: QueryRunner | string): void {
|
||||
|
||||
@@ -300,13 +300,13 @@ export default class QueryRunner {
|
||||
/*
|
||||
* Handle a session ready event for Edit Data
|
||||
*/
|
||||
public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void> {
|
||||
public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable<void> {
|
||||
// Update internal state to show that we're executing the query
|
||||
this._isExecuting = true;
|
||||
this._totalElapsedMilliseconds = 0;
|
||||
// TODO issue #228 add statusview callbacks here
|
||||
|
||||
return this._queryManagementService.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit).then(result => {
|
||||
return this._queryManagementService.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString).then(result => {
|
||||
// The query has started, so lets fire up the result pane
|
||||
this._eventEmitter.emit(EventType.START);
|
||||
this._queryManagementService.registerRunner(this, ownerUri);
|
||||
|
||||
@@ -29,6 +29,7 @@ import paths = require('vs/base/common/paths');
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput';
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
@@ -120,7 +121,7 @@ export class QueryEditorService implements IQueryEditorService {
|
||||
/**
|
||||
* Creates new edit data session
|
||||
*/
|
||||
public newEditDataEditor(schemaName: string, tableName: string): Promise<IConnectableInput> {
|
||||
public newEditDataEditor(schemaName: string, tableName: string, sqlContent: string): Promise<IConnectableInput> {
|
||||
|
||||
return new Promise<IConnectableInput>((resolve, reject) => {
|
||||
try {
|
||||
@@ -129,8 +130,17 @@ export class QueryEditorService implements IQueryEditorService {
|
||||
let filePath = this.createEditDataFileName(objectName);
|
||||
let docUri: URI = URI.from({ scheme: Schemas.untitled, path: filePath });
|
||||
|
||||
// Create a sql document pane with accoutrements
|
||||
const fileInput = this._untitledEditorService.createOrGet(docUri, 'sql');
|
||||
fileInput.resolve().then(m => {
|
||||
if (sqlContent) {
|
||||
m.textEditorModel.setValue(sqlContent);
|
||||
}
|
||||
});
|
||||
|
||||
// Create an EditDataInput for editing
|
||||
let editDataInput: EditDataInput = this._instantiationService.createInstance(EditDataInput, docUri, schemaName, tableName);
|
||||
const resultsInput: EditDataResultsInput = this._instantiationService.createInstance(EditDataResultsInput, docUri.toString());
|
||||
let editDataInput: EditDataInput = this._instantiationService.createInstance(EditDataInput, docUri, schemaName, tableName, fileInput, sqlContent, resultsInput);
|
||||
|
||||
this._editorService.openEditor(editDataInput, { pinned: true })
|
||||
.then((editor) => {
|
||||
@@ -212,7 +222,7 @@ export class QueryEditorService implements IQueryEditorService {
|
||||
}
|
||||
|
||||
let uri: URI = QueryEditorService._getEditorChangeUri(editor.input, changingToSql);
|
||||
if(uri.scheme === Schemas.untitled && editor.input instanceof QueryInput)
|
||||
if(uri.scheme === Schemas.untitled && (editor.input instanceof QueryInput || editor.input instanceof EditDataInput))
|
||||
{
|
||||
QueryEditorService.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
@@ -299,10 +309,8 @@ export class QueryEditorService implements IQueryEditorService {
|
||||
filePath = editDataFileName(counter);
|
||||
}
|
||||
|
||||
// TODO: check if this document name already exists in any open documents tabs
|
||||
let fileNames: string[] = [];
|
||||
this._editorGroupService.getStacksModel().groups.map(group => group.getEditors().map(editor => fileNames.push(editor.getName())));
|
||||
while (fileNames.find(x => x.toUpperCase() === filePath.toUpperCase())) {
|
||||
let untitledEditors = this._untitledEditorService.getAll();
|
||||
while (untitledEditors.find(x => x.getName().toUpperCase() === filePath.toUpperCase())) {
|
||||
counter++;
|
||||
filePath = editDataFileName(counter);
|
||||
}
|
||||
|
||||
@@ -6,28 +6,36 @@
|
||||
-->
|
||||
|
||||
<panel class="fullsize" [options]="panelOpt">
|
||||
<tab [title]="queryComponentTitle" class="fullsize" [identifier]="resultsTabIdentifier">
|
||||
<div id="queryDiv" class="fullsize">
|
||||
<query-component id="queryComp" #queryComponent class="fullsize" [queryParameters]="queryParameters"></query-component>
|
||||
</div>
|
||||
<tab [visibilityType]="'visibility'" [title]="queryComponentTitle" class="fullsize" [identifier]="resultsTabIdentifier">
|
||||
<ng-template>
|
||||
<div id="queryDiv" class="fullsize">
|
||||
<query-component id="queryComp" class="fullsize" [queryParameters]="queryParameters"></query-component>
|
||||
</div>
|
||||
</ng-template>
|
||||
</tab>
|
||||
|
||||
<tab *ngIf="hasQueryPlan" [title]="queryPlanTitle" [identifier]="queryPlanTabIdentifier">
|
||||
<div id="queryPlanDiv" class="headersVisible fullsize" style=" overflow: auto; margin-left: 2px">
|
||||
<queryplan-component #queryPlanComponent class="fullsize" style="display: block"></queryplan-component>
|
||||
</div>
|
||||
<tab *ngIf="hasQueryPlan" [visibilityType]="'visibility'" [title]="queryPlanTitle" [identifier]="queryPlanTabIdentifier">
|
||||
<ng-template>
|
||||
<div id="queryPlanDiv" class="headersVisible fullsize" style=" overflow: auto; margin-left: 2px">
|
||||
<queryplan-component class="fullsize" style="display: block"></queryplan-component>
|
||||
</div>
|
||||
</ng-template>
|
||||
</tab>
|
||||
|
||||
<tab *ngIf="hasQueryPlan" class="fullsize" [title]="topOperationsTitle">
|
||||
<div id="topOperationsDiv" class="fullsize">
|
||||
<top-operations-component #topOperationsComponent class="fullsize" style="display: block" [queryParameters]="queryParameters"></top-operations-component>
|
||||
</div>
|
||||
<tab *ngIf="hasQueryPlan" [visibilityType]="'visibility'" class="fullsize" [title]="topOperationsTitle">
|
||||
<ng-template>
|
||||
<div id="topOperationsDiv" class="fullsize">
|
||||
<top-operations-component class="fullsize" style="display: block" [queryParameters]="queryParameters"></top-operations-component>
|
||||
</div>
|
||||
</ng-template>
|
||||
</tab>
|
||||
|
||||
<tab *ngIf="showChartView" [title]="chartViewerTitle" [identifier]="chartViewerTabIdentifier">
|
||||
<div id="chartViewerDiv" class="headersVisible fullsize" >
|
||||
<chart-viewer #chartViewerComponent class="fullsize" style="display: block">
|
||||
</chart-viewer>
|
||||
</div>
|
||||
<tab *ngIf="showChartView" [visibilityType]="'visibility'" [title]="chartViewerTitle" [identifier]="chartViewerTabIdentifier">
|
||||
<ng-template>
|
||||
<div id="chartViewerDiv" class="headersVisible fullsize" >
|
||||
<chart-viewer [dataSet]="activeDataSet" class="fullsize" style="display: block">
|
||||
</chart-viewer>
|
||||
</div>
|
||||
</ng-template>
|
||||
</tab>
|
||||
</panel>
|
||||
@@ -32,18 +32,16 @@ declare type PaneType = 'messages' | 'results';
|
||||
selector: QUERY_OUTPUT_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('sql/parts/query/views/queryOutput.component.html'))
|
||||
})
|
||||
export class QueryOutputComponent implements OnInit, OnDestroy {
|
||||
export class QueryOutputComponent implements OnDestroy {
|
||||
|
||||
@ViewChild('queryComponent') queryComponent: QueryComponent;
|
||||
|
||||
@ViewChild('queryPlanComponent') queryPlanComponent: QueryPlanComponent;
|
||||
|
||||
@ViewChild('topOperationsComponent') topOperationsComponent: TopOperationsComponent;
|
||||
|
||||
@ViewChild('chartViewerComponent') chartViewerComponent: ChartViewerComponent;
|
||||
@ViewChild(QueryComponent) queryComponent: QueryComponent;
|
||||
@ViewChild(QueryPlanComponent) queryPlanComponent: QueryPlanComponent;
|
||||
@ViewChild(TopOperationsComponent) topOperationsComponent: TopOperationsComponent;
|
||||
|
||||
@ViewChild(PanelComponent) private _panel: PanelComponent;
|
||||
|
||||
private activeDataSet: any;
|
||||
|
||||
// tslint:disable:no-unused-variable
|
||||
private readonly queryComponentTitle: string = nls.localize('results', 'Results');
|
||||
private readonly queryPlanTitle: string = nls.localize('queryPlan', 'Query Plan');
|
||||
@@ -78,7 +76,7 @@ export class QueryOutputComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* Called by Angular when the object is initialized
|
||||
*/
|
||||
public ngOnInit(): void {
|
||||
public ngAfterViewInit(): void {
|
||||
this._disposables.push(toDisposableSubscription(this.queryComponent.queryPlanAvailable.subscribe((xml) => {
|
||||
this.hasQueryPlan = true;
|
||||
this._cd.detectChanges();
|
||||
@@ -90,7 +88,7 @@ export class QueryOutputComponent implements OnInit, OnDestroy {
|
||||
this._disposables.push(toDisposableSubscription(this.queryComponent.showChartRequested.subscribe((dataSet) => {
|
||||
this.showChartView = true;
|
||||
this._cd.detectChanges();
|
||||
this.chartViewerComponent.dataSet = dataSet;
|
||||
this.activeDataSet = dataSet;
|
||||
this._panel.selectTab(this.chartViewerTabIdentifier);
|
||||
})));
|
||||
|
||||
@@ -104,7 +102,6 @@ export class QueryOutputComponent implements OnInit, OnDestroy {
|
||||
})));
|
||||
|
||||
this._disposables.push(toDisposableSubscription(this.queryComponent.goToNextQueryOutputTabRequested.subscribe(() => {
|
||||
let activeTab = this._panel.getActiveTab;
|
||||
this._panel.selectOnNextTab();
|
||||
})));
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
.empty-task-message {
|
||||
padding: 10px 22px 0 22px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .task-group {
|
||||
|
||||
@@ -11,9 +11,11 @@ import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.com
|
||||
import { BootstrapParams } from 'sql/services/bootstrap/bootstrapParams';
|
||||
import { BOOTSTRAP_SERVICE_ID, IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { ComponentEventType } from '../../parts/modelComponents/interfaces';
|
||||
|
||||
export interface DialogComponentParams extends BootstrapParams {
|
||||
modelViewId: string;
|
||||
validityChangedCallback: (valid: boolean) => void;
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -27,16 +29,23 @@ export interface DialogComponentParams extends BootstrapParams {
|
||||
export class DialogContainer implements AfterContentInit {
|
||||
private _onResize = new Emitter<void>();
|
||||
public readonly onResize: Event<void> = this._onResize.event;
|
||||
private _params: DialogComponentParams;
|
||||
|
||||
public modelViewId: string;
|
||||
@ViewChild(ModelViewContent) private _modelViewContent: ModelViewContent;
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
|
||||
@Inject(BOOTSTRAP_SERVICE_ID) bootstrapService: IBootstrapService) {
|
||||
this.modelViewId = (bootstrapService.getBootstrapParams(el.nativeElement.tagName) as DialogComponentParams).modelViewId;
|
||||
this._params = bootstrapService.getBootstrapParams(el.nativeElement.tagName) as DialogComponentParams;
|
||||
this.modelViewId = this._params.modelViewId;
|
||||
}
|
||||
|
||||
ngAfterContentInit(): void {
|
||||
this._modelViewContent.onEvent(event => {
|
||||
if (event.eventType === ComponentEventType.validityChanged) {
|
||||
this._params.validityChangedCallback(event.args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
|
||||
@@ -21,12 +21,13 @@ import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { localize } from 'vs/nls';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
|
||||
export class DialogModal extends Modal {
|
||||
private _dialogPane: DialogPane;
|
||||
|
||||
// Wizard HTML elements
|
||||
private _body: HTMLElement;
|
||||
private _onDone = new Emitter<void>();
|
||||
private _onCancel = new Emitter<void>();
|
||||
|
||||
// Buttons
|
||||
private _cancelButton: Button;
|
||||
@@ -65,20 +66,23 @@ export class DialogModal extends Modal {
|
||||
});
|
||||
}
|
||||
|
||||
this._cancelButton = this.addDialogButton(this._dialog.cancelButton, () => this.cancel());
|
||||
this.updateButtonElement(this._cancelButton, this._dialog.cancelButton);
|
||||
this._doneButton = this.addDialogButton(this._dialog.okButton, () => this.done());
|
||||
this.updateButtonElement(this._doneButton, this._dialog.okButton);
|
||||
this._doneButton = this.addDialogButton(this._dialog.okButton, () => this.done(), false);
|
||||
this._dialog.okButton.registerClickEvent(this._onDone.event);
|
||||
this._cancelButton = this.addDialogButton(this._dialog.cancelButton, () => this.cancel(), false);
|
||||
this._dialog.cancelButton.registerClickEvent(this._onCancel.event);
|
||||
}
|
||||
|
||||
private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined): Button {
|
||||
private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined, registerClickEvent: boolean = true): Button {
|
||||
let buttonElement = this.addFooterButton(button.label, onSelect);
|
||||
buttonElement.enabled = button.enabled;
|
||||
button.registerClickEvent(buttonElement.onDidClick);
|
||||
if (registerClickEvent) {
|
||||
button.registerClickEvent(buttonElement.onDidClick);
|
||||
}
|
||||
button.onUpdate(() => {
|
||||
this.updateButtonElement(buttonElement, button);
|
||||
});
|
||||
attachButtonStyler(buttonElement, this._themeService);
|
||||
this.updateButtonElement(buttonElement, button);
|
||||
return buttonElement;
|
||||
}
|
||||
|
||||
@@ -89,12 +93,13 @@ export class DialogModal extends Modal {
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
let body: HTMLElement;
|
||||
new Builder(container).div({ class: 'dialogModal-body' }, (bodyBuilder) => {
|
||||
this._body = bodyBuilder.getHTMLElement();
|
||||
body = bodyBuilder.getHTMLElement();
|
||||
});
|
||||
|
||||
this._dialogPane = new DialogPane(this._dialog, this._bootstrapService);
|
||||
this._dialogPane.createBody(this._body);
|
||||
this._dialogPane.createBody(body);
|
||||
}
|
||||
|
||||
public open(): void {
|
||||
@@ -102,11 +107,15 @@ export class DialogModal extends Modal {
|
||||
}
|
||||
|
||||
public done(): void {
|
||||
this.dispose();
|
||||
this.hide();
|
||||
if (this._dialog.okButton.enabled) {
|
||||
this._onDone.fire();
|
||||
this.dispose();
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
this._onCancel.fire();
|
||||
this.dispose();
|
||||
this.hide();
|
||||
}
|
||||
@@ -119,6 +128,20 @@ export class DialogModal extends Modal {
|
||||
super.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridable to change behavior of escape key
|
||||
*/
|
||||
protected onClose(e: StandardKeyboardEvent) {
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridable to change behavior of enter key
|
||||
*/
|
||||
protected onAccept(e: StandardKeyboardEvent) {
|
||||
this.done();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this._dialogPane.dispose();
|
||||
|
||||
@@ -16,11 +16,14 @@ import { DialogComponentParams } from 'sql/platform/dialog/dialogContainer.compo
|
||||
import { Builder } from 'vs/base/browser/builder';
|
||||
import { IThemable } from 'vs/platform/theme/common/styler';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export class DialogPane extends Disposable implements IThemable {
|
||||
private _activeTabIndex: number;
|
||||
private _tabbedPanel: TabbedPanel;
|
||||
private _moduleRef: NgModuleRef<{}>;
|
||||
private _moduleRefs: NgModuleRef<{}>[] = [];
|
||||
|
||||
// Validation
|
||||
private _modelViewValidityMap = new Map<string, boolean>();
|
||||
|
||||
// HTML Elements
|
||||
private _body: HTMLElement;
|
||||
@@ -46,12 +49,20 @@ export class DialogPane extends Disposable implements IThemable {
|
||||
} else {
|
||||
this._tabbedPanel = new TabbedPanel(this._body);
|
||||
this._dialog.content.forEach((tab, tabIndex) => {
|
||||
let tabContainer = document.createElement('div');
|
||||
tabContainer.style.display = 'none';
|
||||
this._body.appendChild(tabContainer);
|
||||
this.initializeModelViewContainer(tabContainer, tab.content);
|
||||
this._tabbedPanel.pushTab({
|
||||
title: tab.title,
|
||||
identifier: 'dialogPane.' + this._dialog.title + '.' + tabIndex,
|
||||
view: {
|
||||
render: (container) => {
|
||||
this.initializeModelViewContainer(container, tab.content);
|
||||
if (tabContainer.parentElement === this._body) {
|
||||
this._body.removeChild(tabContainer);
|
||||
}
|
||||
container.appendChild(tabContainer);
|
||||
tabContainer.style.display = 'block';
|
||||
},
|
||||
layout: (dimension) => { }
|
||||
} as IPanelView
|
||||
@@ -60,7 +71,6 @@ export class DialogPane extends Disposable implements IThemable {
|
||||
}
|
||||
});
|
||||
|
||||
this._activeTabIndex = 0;
|
||||
return this._body;
|
||||
}
|
||||
|
||||
@@ -72,9 +82,12 @@ export class DialogPane extends Disposable implements IThemable {
|
||||
DialogModule,
|
||||
bodyContainer,
|
||||
'dialog-modelview-container',
|
||||
{ modelViewId: modelViewId } as DialogComponentParams,
|
||||
{
|
||||
modelViewId: modelViewId,
|
||||
validityChangedCallback: (valid: boolean) => this._setValidity(modelViewId, valid)
|
||||
} as DialogComponentParams,
|
||||
undefined,
|
||||
(moduleRef) => this._moduleRef = moduleRef);
|
||||
(moduleRef) => this._moduleRefs.push(moduleRef));
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
@@ -93,8 +106,23 @@ export class DialogPane extends Disposable implements IThemable {
|
||||
this._body.style.color = styles.dialogForeground ? styles.dialogForeground.toString() : undefined;
|
||||
}
|
||||
|
||||
private _setValidity(modelViewId: string, valid: boolean) {
|
||||
let oldValidity = this.isValid();
|
||||
this._modelViewValidityMap.set(modelViewId, valid);
|
||||
let newValidity = this.isValid();
|
||||
if (newValidity !== oldValidity) {
|
||||
this._dialog.notifyValidityChanged(newValidity);
|
||||
}
|
||||
}
|
||||
|
||||
private isValid(): boolean {
|
||||
let valid = true;
|
||||
this._modelViewValidityMap.forEach(value => valid = valid && value);
|
||||
return valid;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose();
|
||||
this._moduleRef.destroy();
|
||||
this._moduleRefs.forEach(moduleRef => moduleRef.destroy());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as sqlops from 'sqlops';
|
||||
import { localize } from 'vs/nls';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export class DialogTab implements sqlops.window.modelviewdialog.DialogTab {
|
||||
export class DialogTab {
|
||||
public content: string;
|
||||
|
||||
constructor(public title: string, content?: string) {
|
||||
@@ -19,7 +19,7 @@ export class DialogTab implements sqlops.window.modelviewdialog.DialogTab {
|
||||
}
|
||||
}
|
||||
|
||||
export class Dialog implements sqlops.window.modelviewdialog.Dialog {
|
||||
export class Dialog {
|
||||
private static readonly DONE_BUTTON_LABEL = localize('dialogModalDoneButtonLabel', 'Done');
|
||||
private static readonly CANCEL_BUTTON_LABEL = localize('dialogModalCancelButtonLabel', 'Cancel');
|
||||
|
||||
@@ -28,11 +28,24 @@ export class Dialog implements sqlops.window.modelviewdialog.Dialog {
|
||||
public cancelButton: DialogButton = new DialogButton(Dialog.CANCEL_BUTTON_LABEL, true);
|
||||
public customButtons: DialogButton[];
|
||||
|
||||
private _valid: boolean = true;
|
||||
private _validityChangedEmitter = new Emitter<boolean>();
|
||||
public readonly onValidityChanged = this._validityChangedEmitter.event;
|
||||
|
||||
constructor(public title: string, content?: string | DialogTab[]) {
|
||||
if (content) {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
public get valid(): boolean {
|
||||
return this._valid;
|
||||
}
|
||||
|
||||
public notifyValidityChanged(valid: boolean) {
|
||||
this._valid = valid;
|
||||
this._validityChangedEmitter.fire(valid);
|
||||
}
|
||||
}
|
||||
|
||||
export class DialogButton implements sqlops.window.modelviewdialog.Button {
|
||||
|
||||
@@ -67,6 +67,10 @@ export class FixedListView<T> extends CollapsibleView {
|
||||
this.setFixed(this.fixedSize);
|
||||
}
|
||||
|
||||
public get list(): List<T> {
|
||||
return this._list;
|
||||
}
|
||||
|
||||
public listContentHeight(): number {
|
||||
return this._list.contentHeight;
|
||||
}
|
||||
@@ -86,6 +90,9 @@ export class FixedListView<T> extends CollapsibleView {
|
||||
protected changeState(state: CollapsibleState): void {
|
||||
super.changeState(state);
|
||||
this.setFixed(this.fixedSize);
|
||||
if (this.list) {
|
||||
this.list.getHTMLElement().hidden = (state === CollapsibleState.COLLAPSED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -142,7 +142,7 @@ export class AccountManagementService implements IAccountManagementService {
|
||||
})
|
||||
.then(null, err => {
|
||||
// On error, check to see if the error is because the user cancelled. If so, just ignore
|
||||
if ('userCancelledSignIn' in err) {
|
||||
if (err && 'userCancelledSignIn' in err) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(err);
|
||||
|
||||
@@ -15,11 +15,13 @@ export interface IView {
|
||||
}
|
||||
|
||||
export interface IModelView extends IView {
|
||||
initializeModel(rootComponent: IComponentShape): void;
|
||||
initializeModel(rootComponent: IComponentShape, validationCallback?: (componentId: string) => Thenable<boolean>): void;
|
||||
clearContainer(componentId: string): void;
|
||||
addToContainer(containerId: string, item: IItemConfig): void;
|
||||
setLayout(componentId: string, layout: any): void;
|
||||
setProperties(componentId: string, properties: { [key: string]: any }): void;
|
||||
registerEvent(componentId: string);
|
||||
onEvent: Event<any>;
|
||||
validate(componentId: string): Thenable<boolean>;
|
||||
readonly onDestroy: Event<void>;
|
||||
}
|
||||
|
||||
3
src/sql/sqlops.d.ts
vendored
3
src/sql/sqlops.d.ts
vendored
@@ -651,7 +651,7 @@ declare module 'sqlops' {
|
||||
createRow(ownerUri: string): Thenable<EditCreateRowResult>;
|
||||
deleteRow(ownerUri: string, rowId: number): Thenable<void>;
|
||||
disposeEdit(ownerUri: string): Thenable<void>;
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void>;
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable<void>;
|
||||
revertCell(ownerUri: string, rowId: number, columnId: number): Thenable<EditRevertCellResult>;
|
||||
revertRow(ownerUri: string, rowId: number): Thenable<void>;
|
||||
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<EditUpdateCellResult>;
|
||||
@@ -879,6 +879,7 @@ declare module 'sqlops' {
|
||||
objectName: string;
|
||||
schemaName: string;
|
||||
objectType: string;
|
||||
queryString: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
96
src/sql/sqlops.proposed.d.ts
vendored
96
src/sql/sqlops.proposed.d.ts
vendored
@@ -31,6 +31,7 @@ declare module 'sqlops' {
|
||||
export interface ComponentBuilder<T extends Component> {
|
||||
component(): T;
|
||||
withProperties<U>(properties: U): ComponentBuilder<T>;
|
||||
withValidation(validation: (component: T) => boolean): ComponentBuilder<T>;
|
||||
}
|
||||
export interface ContainerBuilder<T extends Component, TLayout, TItemLayout> extends ComponentBuilder<T> {
|
||||
withLayout(layout: TLayout): ContainerBuilder<T, TLayout, TItemLayout>;
|
||||
@@ -56,6 +57,22 @@ declare module 'sqlops' {
|
||||
* @memberof Component
|
||||
*/
|
||||
updateProperties(properties: { [key: string]: any }): Thenable<boolean>;
|
||||
|
||||
enabled: boolean;
|
||||
/**
|
||||
* Event fired to notify that the component's validity has changed
|
||||
*/
|
||||
readonly onValidityChanged: vscode.Event<boolean>;
|
||||
|
||||
/**
|
||||
* Whether the component is valid or not
|
||||
*/
|
||||
readonly valid: boolean;
|
||||
|
||||
/**
|
||||
* Run the component's validations
|
||||
*/
|
||||
validate(): Thenable<boolean>;
|
||||
}
|
||||
|
||||
export interface FormComponent {
|
||||
@@ -146,6 +163,8 @@ declare module 'sqlops' {
|
||||
|
||||
export interface FormItemLayout {
|
||||
horizontal: boolean;
|
||||
width: number;
|
||||
componentWidth: number;
|
||||
}
|
||||
|
||||
export interface FormLayout {
|
||||
@@ -169,10 +188,24 @@ declare module 'sqlops' {
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* ID of the task to be called when this is clicked on.
|
||||
* These should be registered using the {tasks.registerTask} API.
|
||||
* Name of the clickable action. If not defined then no action will be shown
|
||||
*/
|
||||
taskId: string;
|
||||
actionTitle?: string;
|
||||
/**
|
||||
* Data sent on callback being run.
|
||||
*/
|
||||
callbackData?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines status indicators that can be shown to the user as part of
|
||||
* components such as the Card UI
|
||||
*/
|
||||
export enum StatusIndicator {
|
||||
None = 0,
|
||||
Ok = 1,
|
||||
Warning = 2,
|
||||
Error = 3
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,10 +216,19 @@ declare module 'sqlops' {
|
||||
label: string;
|
||||
value?: string;
|
||||
actions?: ActionDescriptor[];
|
||||
status?: StatusIndicator;
|
||||
}
|
||||
|
||||
export type InputBoxInputType = 'color' | 'date' | 'datetime-local' | 'email' | 'month' | 'number' | 'password' | 'range' | 'search' | 'text' | 'time' | 'url' | 'week';
|
||||
|
||||
export interface InputBoxProperties {
|
||||
value?: string;
|
||||
ariaLabel?: string;
|
||||
placeHolder?: string;
|
||||
height: number;
|
||||
width: number;
|
||||
inputType?: InputBoxInputType;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export interface CheckBoxProperties {
|
||||
@@ -207,10 +249,10 @@ declare module 'sqlops' {
|
||||
label: string;
|
||||
value: string;
|
||||
actions?: ActionDescriptor[];
|
||||
onDidActionClick: vscode.Event<ActionDescriptor>;
|
||||
}
|
||||
|
||||
export interface InputBoxComponent extends Component {
|
||||
value: string;
|
||||
export interface InputBoxComponent extends Component, InputBoxProperties {
|
||||
onTextChanged: vscode.Event<any>;
|
||||
}
|
||||
|
||||
@@ -264,6 +306,21 @@ declare module 'sqlops' {
|
||||
*/
|
||||
readonly modelBuilder: ModelBuilder;
|
||||
|
||||
/**
|
||||
* Whether or not the model view's root component is valid
|
||||
*/
|
||||
readonly valid: boolean;
|
||||
|
||||
/**
|
||||
* Raised when the model view's valid property changes
|
||||
*/
|
||||
readonly onValidityChanged: vscode.Event<boolean>;
|
||||
|
||||
/**
|
||||
* Run the model view root component's validations
|
||||
*/
|
||||
validate(): Thenable<boolean>;
|
||||
|
||||
/**
|
||||
* Initializes the model with a root component definition.
|
||||
* Once this has been done, the components will be laid out in the UI and
|
||||
@@ -272,7 +329,7 @@ declare module 'sqlops' {
|
||||
initializeModel<T extends Component>(root: T): Thenable<void>;
|
||||
}
|
||||
|
||||
export namespace dashboard {
|
||||
export namespace ui {
|
||||
/**
|
||||
* Register a provider for a model-view widget
|
||||
*/
|
||||
@@ -309,8 +366,21 @@ declare module 'sqlops' {
|
||||
*/
|
||||
export function closeDialog(dialog: Dialog): void;
|
||||
|
||||
export interface ModelViewPanel {
|
||||
/**
|
||||
* Register model view content for the dialog.
|
||||
* Doesn't do anything if model view is already registered
|
||||
*/
|
||||
registerContent(handler: (view: ModelView) => void): void;
|
||||
|
||||
/**
|
||||
* Returns the model view content if registered. Returns undefined if model review is not registered
|
||||
*/
|
||||
readonly modelView: ModelView;
|
||||
}
|
||||
|
||||
// Model view dialog classes
|
||||
export interface Dialog {
|
||||
export interface Dialog extends ModelViewPanel {
|
||||
/**
|
||||
* The title of the dialog
|
||||
*/
|
||||
@@ -336,9 +406,19 @@ declare module 'sqlops' {
|
||||
* Any additional buttons that should be displayed
|
||||
*/
|
||||
customButtons: Button[];
|
||||
|
||||
/**
|
||||
* Whether the dialog's content is valid
|
||||
*/
|
||||
readonly valid: boolean;
|
||||
|
||||
/**
|
||||
* Fired whenever the dialog's valid property changes
|
||||
*/
|
||||
readonly onValidityChanged: vscode.Event<boolean>;
|
||||
}
|
||||
|
||||
export interface DialogTab {
|
||||
export interface DialogTab extends ModelViewPanel {
|
||||
/**
|
||||
* The title of the tab
|
||||
*/
|
||||
|
||||
@@ -92,7 +92,8 @@ export interface IItemConfig {
|
||||
export enum ComponentEventType {
|
||||
PropertiesChanged,
|
||||
onDidChange,
|
||||
onDidClick
|
||||
onDidClick,
|
||||
validityChanged
|
||||
}
|
||||
|
||||
export interface IComponentEventArgs {
|
||||
@@ -118,3 +119,26 @@ export interface IModelViewButtonDetails {
|
||||
enabled: boolean;
|
||||
hidden: boolean;
|
||||
}
|
||||
|
||||
/// Card-related APIs that need to be here to avoid early load issues
|
||||
// with enums causing requiring of sqlops API to fail.
|
||||
export enum StatusIndicator {
|
||||
None = 0,
|
||||
Ok = 1,
|
||||
Warning = 2,
|
||||
Error = 3
|
||||
}
|
||||
|
||||
export interface CardProperties {
|
||||
label: string;
|
||||
value?: string;
|
||||
actions?: ActionDescriptor[];
|
||||
status?: StatusIndicator;
|
||||
}
|
||||
|
||||
export interface ActionDescriptor {
|
||||
label: string;
|
||||
actionTitle?: string;
|
||||
callbackData?: any;
|
||||
}
|
||||
|
||||
|
||||
@@ -247,8 +247,8 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
|
||||
return this._resolveProvider<sqlops.QueryProvider>(handle).disposeEdit(ownerUri);
|
||||
}
|
||||
|
||||
$initializeEdit(handle: number, ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void> {
|
||||
return this._resolveProvider<sqlops.QueryProvider>(handle).initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit);
|
||||
$initializeEdit(handle: number, ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Thenable<void> {
|
||||
return this._resolveProvider<sqlops.QueryProvider>(handle).initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString);
|
||||
}
|
||||
|
||||
$revertCell(handle: number, ownerUri: string, rowId: number, columnId: number): Thenable<sqlops.EditRevertCellResult> {
|
||||
|
||||
@@ -14,10 +14,11 @@ import * as sqlops from 'sqlops';
|
||||
|
||||
import { SqlMainContext, ExtHostModelViewShape, MainThreadModelViewShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||
import { IItemConfig, ModelComponentTypes, IComponentShape, IComponentEventArgs, ComponentEventType } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IActionDescriptor } from 'vs/editor/standalone/browser/standaloneCodeEditor';
|
||||
|
||||
class ModelBuilderImpl implements sqlops.ModelBuilder {
|
||||
private nextComponentId: number;
|
||||
private readonly _eventHandlers = new Map<string, IWithEventHandler>();
|
||||
private readonly _componentBuilders = new Map<string, ComponentBuilderImpl<any>>();
|
||||
|
||||
constructor(private readonly _proxy: MainThreadModelViewShape, private readonly _handle: number) {
|
||||
this.nextComponentId = 0;
|
||||
@@ -25,67 +26,92 @@ class ModelBuilderImpl implements sqlops.ModelBuilder {
|
||||
|
||||
navContainer(): sqlops.ContainerBuilder<sqlops.NavContainer, any, any> {
|
||||
let id = this.getNextComponentId();
|
||||
return new ContainerBuilderImpl(this._proxy, this._handle, ModelComponentTypes.NavContainer, id);
|
||||
let container: ContainerBuilderImpl<sqlops.NavContainer, any, any> = new ContainerBuilderImpl(this._proxy, this._handle, ModelComponentTypes.NavContainer, id);
|
||||
this._componentBuilders.set(id, container);
|
||||
return container;
|
||||
}
|
||||
|
||||
flexContainer(): sqlops.FlexBuilder {
|
||||
let id = this.getNextComponentId();
|
||||
return new ContainerBuilderImpl<sqlops.FlexContainer, sqlops.FlexLayout, sqlops.FlexItemLayout>(this._proxy, this._handle, ModelComponentTypes.FlexContainer, id);
|
||||
let container: ContainerBuilderImpl<sqlops.FlexContainer, any, any> = new ContainerBuilderImpl<sqlops.FlexContainer, sqlops.FlexLayout, sqlops.FlexItemLayout>(this._proxy, this._handle, ModelComponentTypes.FlexContainer, id);
|
||||
this._componentBuilders.set(id, container);
|
||||
return container;
|
||||
}
|
||||
|
||||
formContainer(): sqlops.FormBuilder {
|
||||
let id = this.getNextComponentId();
|
||||
return new FormContainerBuilder(this._proxy, this._handle, ModelComponentTypes.Form, id);
|
||||
let container = new FormContainerBuilder(this._proxy, this._handle, ModelComponentTypes.Form, id);
|
||||
this._componentBuilders.set(id, container);
|
||||
return container;
|
||||
}
|
||||
|
||||
card(): sqlops.ComponentBuilder<sqlops.CardComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
return this.withEventHandler(new CardWrapper(this._proxy, this._handle, id), id);
|
||||
let builder: ComponentBuilderImpl<sqlops.CardComponent> = this.getComponentBuilder(new CardWrapper(this._proxy, this._handle, id), id);
|
||||
this._componentBuilders.set(id, builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
inputBox(): sqlops.ComponentBuilder<sqlops.InputBoxComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
return this.withEventHandler(new InputBoxWrapper(this._proxy, this._handle, id), id);
|
||||
let builder: ComponentBuilderImpl<sqlops.InputBoxComponent> = this.getComponentBuilder(new InputBoxWrapper(this._proxy, this._handle, id), id);
|
||||
this._componentBuilders.set(id, builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
checkBox(): sqlops.ComponentBuilder<sqlops.CheckBoxComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
return this.withEventHandler(new CheckBoxWrapper(this._proxy, this._handle, id), id);
|
||||
let builder: ComponentBuilderImpl<sqlops.CheckBoxComponent> = this.getComponentBuilder(new CheckBoxWrapper(this._proxy, this._handle, id), id);
|
||||
this._componentBuilders.set(id, builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
button(): sqlops.ComponentBuilder<sqlops.ButtonComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
return this.withEventHandler(new ButtonWrapper(this._proxy, this._handle, id), id);
|
||||
let builder: ComponentBuilderImpl<sqlops.ButtonComponent> = this.getComponentBuilder(new ButtonWrapper(this._proxy, this._handle, id), id);
|
||||
this._componentBuilders.set(id, builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
dropDown(): sqlops.ComponentBuilder<sqlops.DropDownComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
return this.withEventHandler(new DropDownWrapper(this._proxy, this._handle, id), id);
|
||||
let builder: ComponentBuilderImpl<sqlops.DropDownComponent> = this.getComponentBuilder(new DropDownWrapper(this._proxy, this._handle, id), id);
|
||||
this._componentBuilders.set(id, builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
dashboardWidget(widgetId: string): sqlops.ComponentBuilder<sqlops.WidgetComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
return this.withEventHandler<sqlops.WidgetComponent>(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWidget, id), id);
|
||||
let builder = this.getComponentBuilder<sqlops.WidgetComponent>(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWidget, id), id);
|
||||
this._componentBuilders.set(id, builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
dashboardWebview(webviewId: string): sqlops.ComponentBuilder<sqlops.WebviewComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
return this.withEventHandler(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWebview, id), id);
|
||||
let builder: ComponentBuilderImpl<sqlops.WebviewComponent> = this.getComponentBuilder(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWebview, id), id);
|
||||
this._componentBuilders.set(id, builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
withEventHandler<T extends sqlops.Component>(component: ComponentWrapper, id: string): sqlops.ComponentBuilder<T> {
|
||||
getComponentBuilder<T extends sqlops.Component>(component: ComponentWrapper, id: string): ComponentBuilderImpl<T> {
|
||||
let componentBuilder: ComponentBuilderImpl<T> = new ComponentBuilderImpl<T>(component);
|
||||
this._eventHandlers.set(id, componentBuilder);
|
||||
this._componentBuilders.set(id, componentBuilder);
|
||||
return componentBuilder;
|
||||
}
|
||||
|
||||
handleEvent(componentId: string, eventArgs: IComponentEventArgs): void {
|
||||
let eventHandler = this._eventHandlers.get(componentId);
|
||||
let eventHandler = this._componentBuilders.get(componentId);
|
||||
if (eventHandler) {
|
||||
eventHandler.handleEvent(eventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
public runCustomValidations(componentId: string): boolean {
|
||||
let component = this._componentBuilders.get(componentId).componentWrapper();
|
||||
return component.runCustomValidations();
|
||||
}
|
||||
|
||||
private getNextComponentId(): string {
|
||||
return `component${this._handle}_${this.nextComponentId++}`;
|
||||
}
|
||||
@@ -105,11 +131,20 @@ class ComponentBuilderImpl<T extends sqlops.Component> implements sqlops.Compone
|
||||
return <T><any>this._component;
|
||||
}
|
||||
|
||||
componentWrapper(): ComponentWrapper {
|
||||
return this._component;
|
||||
}
|
||||
|
||||
withProperties<U>(properties: U): sqlops.ComponentBuilder<T> {
|
||||
this._component.properties = properties;
|
||||
return this;
|
||||
}
|
||||
|
||||
withValidation(validation: (component: T) => boolean): sqlops.ComponentBuilder<T> {
|
||||
this._component.customValidations.push(validation);
|
||||
return this;
|
||||
}
|
||||
|
||||
handleEvent(eventArgs: IComponentEventArgs) {
|
||||
this._component.onEvent(eventArgs);
|
||||
}
|
||||
@@ -194,6 +229,10 @@ class ComponentWrapper implements sqlops.Component {
|
||||
public properties: { [key: string]: any } = {};
|
||||
public layout: any;
|
||||
public itemConfigs: InternalItemConfig[];
|
||||
public customValidations: ((component: ThisType<ComponentWrapper>) => boolean)[] = [];
|
||||
private _valid: boolean = true;
|
||||
private _onValidityChangedEmitter = new Emitter<boolean>();
|
||||
public readonly onValidityChanged = this._onValidityChangedEmitter.event;
|
||||
|
||||
private _onErrorEmitter = new Emitter<Error>();
|
||||
public readonly onError: vscode.Event<Error> = this._onErrorEmitter.event;
|
||||
@@ -220,6 +259,14 @@ class ComponentWrapper implements sqlops.Component {
|
||||
return this.itemConfigs.map(itemConfig => itemConfig.component);
|
||||
}
|
||||
|
||||
public get enabled(): boolean {
|
||||
return this.properties['enabled'];
|
||||
}
|
||||
|
||||
public set enabled(value: boolean) {
|
||||
this.setProperty('enabled', value);
|
||||
}
|
||||
|
||||
public toComponentShape(): IComponentShape {
|
||||
return <IComponentShape>{
|
||||
id: this.id,
|
||||
@@ -270,15 +317,18 @@ class ComponentWrapper implements sqlops.Component {
|
||||
public onEvent(eventArgs: IComponentEventArgs) {
|
||||
if (eventArgs && eventArgs.eventType === ComponentEventType.PropertiesChanged) {
|
||||
this.properties = eventArgs.args;
|
||||
}
|
||||
else if (eventArgs && eventArgs.eventType === ComponentEventType.validityChanged) {
|
||||
this._valid = eventArgs.args;
|
||||
} else if (eventArgs) {
|
||||
let emitter = this._emitterMap.get(eventArgs.eventType);
|
||||
if (emitter) {
|
||||
emitter.fire();
|
||||
}
|
||||
let emitter = this._emitterMap.get(eventArgs.eventType);
|
||||
if (emitter) {
|
||||
emitter.fire(eventArgs.args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected setProperty(key: string, value: any): Thenable<boolean> {
|
||||
protected async setProperty(key: string, value: any): Promise<boolean> {
|
||||
if (!this.properties[key] || this.properties[key] !== value) {
|
||||
// Only notify the front end if a value has been updated
|
||||
this.properties[key] = value;
|
||||
@@ -290,6 +340,28 @@ class ComponentWrapper implements sqlops.Component {
|
||||
private handleError(err: Error): void {
|
||||
this._onErrorEmitter.fire(err);
|
||||
}
|
||||
|
||||
public runCustomValidations(): boolean {
|
||||
let isValid = true;
|
||||
try {
|
||||
this.customValidations.forEach(validation => {
|
||||
if (!validation(this)) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
isValid = false;
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
public validate() {
|
||||
return this._proxy.$validate(this._handle, this._id);
|
||||
}
|
||||
|
||||
public get valid(): boolean {
|
||||
return this._valid;
|
||||
}
|
||||
}
|
||||
|
||||
class ContainerWrapper<T, U> extends ComponentWrapper implements sqlops.Container<T, U> {
|
||||
@@ -305,6 +377,7 @@ class CardWrapper extends ComponentWrapper implements sqlops.CardComponent {
|
||||
constructor(proxy: MainThreadModelViewShape, handle: number, id: string) {
|
||||
super(proxy, handle, ModelComponentTypes.Card, id);
|
||||
this.properties = {};
|
||||
this._emitterMap.set(ComponentEventType.onDidClick, new Emitter<any>());
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
@@ -325,6 +398,11 @@ class CardWrapper extends ComponentWrapper implements sqlops.CardComponent {
|
||||
public set actions(a: sqlops.ActionDescriptor[]) {
|
||||
this.setProperty('actions', a);
|
||||
}
|
||||
|
||||
public get onDidActionClick(): vscode.Event<sqlops.ActionDescriptor> {
|
||||
let emitter = this._emitterMap.get(ComponentEventType.onDidClick);
|
||||
return emitter && emitter.event;
|
||||
}
|
||||
}
|
||||
|
||||
class InputBoxWrapper extends ComponentWrapper implements sqlops.InputBoxComponent {
|
||||
@@ -342,6 +420,41 @@ class InputBoxWrapper extends ComponentWrapper implements sqlops.InputBoxCompone
|
||||
this.setProperty('value', v);
|
||||
}
|
||||
|
||||
public get ariaLabel(): string {
|
||||
return this.properties['ariaLabel'];
|
||||
}
|
||||
public set ariaLabel(v: string) {
|
||||
this.setProperty('ariaLabel', v);
|
||||
}
|
||||
|
||||
public get placeHolder(): string {
|
||||
return this.properties['placeHolder'];
|
||||
}
|
||||
public set placeHolder(v: string) {
|
||||
this.setProperty('placeHolder', v);
|
||||
}
|
||||
|
||||
public get height(): number {
|
||||
return this.properties['height'];
|
||||
}
|
||||
public set height(v: number) {
|
||||
this.setProperty('height', v);
|
||||
}
|
||||
|
||||
public get width(): number {
|
||||
return this.properties['width'];
|
||||
}
|
||||
public set width(v: number) {
|
||||
this.setProperty('width', v);
|
||||
}
|
||||
|
||||
public get inputType(): sqlops.InputBoxInputType {
|
||||
return this.properties['inputType'];
|
||||
}
|
||||
public set inputType(v: sqlops.InputBoxInputType) {
|
||||
this.setProperty('inputType', v);
|
||||
}
|
||||
|
||||
public get onTextChanged(): vscode.Event<any> {
|
||||
let emitter = this._emitterMap.get(ComponentEventType.onDidChange);
|
||||
return emitter && emitter.event;
|
||||
@@ -428,8 +541,11 @@ class ButtonWrapper extends ComponentWrapper implements sqlops.ButtonComponent {
|
||||
class ModelViewImpl implements sqlops.ModelView {
|
||||
|
||||
public onClosedEmitter = new Emitter<any>();
|
||||
private _onValidityChangedEmitter = new Emitter<boolean>();
|
||||
public readonly onValidityChanged = this._onValidityChangedEmitter.event;
|
||||
|
||||
private _modelBuilder: ModelBuilderImpl;
|
||||
private _component: sqlops.Component;
|
||||
|
||||
constructor(
|
||||
private readonly _proxy: MainThreadModelViewShape,
|
||||
@@ -456,17 +572,31 @@ class ModelViewImpl implements sqlops.ModelView {
|
||||
return this._modelBuilder;
|
||||
}
|
||||
|
||||
public get valid(): boolean {
|
||||
return this._component.valid;
|
||||
}
|
||||
|
||||
public handleEvent(componentId: string, eventArgs: IComponentEventArgs): void {
|
||||
this._modelBuilder.handleEvent(componentId, eventArgs);
|
||||
}
|
||||
|
||||
public initializeModel<T extends sqlops.Component>(component: T): Thenable<void> {
|
||||
component.onValidityChanged(valid => this._onValidityChangedEmitter.fire(valid));
|
||||
this._component = component;
|
||||
let componentImpl = <any>component as ComponentWrapper;
|
||||
if (!componentImpl) {
|
||||
return Promise.reject(nls.localize('unknownConfig', 'Unkown component configuration, must use ModelBuilder to create a configuration object'));
|
||||
}
|
||||
return this._proxy.$initializeModel(this._handle, componentImpl.toComponentShape());
|
||||
}
|
||||
|
||||
public validate(): Thenable<boolean> {
|
||||
return this._proxy.$validate(this._handle, this._component.id);
|
||||
}
|
||||
|
||||
public runCustomValidations(componentId: string): boolean {
|
||||
return this._modelBuilder.runCustomValidations(componentId);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostModelView implements ExtHostModelViewShape {
|
||||
@@ -504,4 +634,9 @@ export class ExtHostModelView implements ExtHostModelViewShape {
|
||||
view.handleEvent(componentId, eventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
$runCustomValidations(handle: number, componentId: string): Thenable<boolean> {
|
||||
const view = this._modelViews.get(handle);
|
||||
return Promise.resolve(view.runCustomValidations(componentId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,27 +12,90 @@ import * as nls from 'vs/nls';
|
||||
import * as vscode from 'vscode';
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
import { SqlMainContext, ExtHostModelViewDialogShape, MainThreadModelViewDialogShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||
import { SqlMainContext, ExtHostModelViewDialogShape, MainThreadModelViewDialogShape, ExtHostModelViewShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||
import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
class DialogImpl implements sqlops.window.modelviewdialog.Dialog {
|
||||
class ModelViewPanelImpl implements sqlops.window.modelviewdialog.ModelViewPanel {
|
||||
private _modelView: sqlops.ModelView;
|
||||
private _handle: number;
|
||||
private _modelViewId: string;
|
||||
|
||||
constructor(private _viewType: string,
|
||||
protected _extHostModelView: ExtHostModelViewShape) {
|
||||
}
|
||||
|
||||
public registerContent(handler: (view: sqlops.ModelView) => void): void {
|
||||
if (!this._modelViewId) {
|
||||
let viewId = this._viewType + this.handle;
|
||||
this.setModelViewId(viewId);
|
||||
this._extHostModelView.$registerProvider(viewId, modelView => {
|
||||
this._modelView = modelView;
|
||||
handler(modelView);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public set handle(value: number) {
|
||||
this._handle = value;
|
||||
}
|
||||
|
||||
public setModelViewId(value: string) {
|
||||
this._modelViewId = value;
|
||||
}
|
||||
|
||||
public get modelView(): sqlops.ModelView {
|
||||
return this._modelView;
|
||||
}
|
||||
|
||||
public set modelView(value: sqlops.ModelView) {
|
||||
this._modelView = value;
|
||||
}
|
||||
}
|
||||
|
||||
class DialogImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdialog.Dialog {
|
||||
public title: string;
|
||||
public content: string | sqlops.window.modelviewdialog.DialogTab[];
|
||||
public okButton: sqlops.window.modelviewdialog.Button;
|
||||
public cancelButton: sqlops.window.modelviewdialog.Button;
|
||||
public customButtons: sqlops.window.modelviewdialog.Button[];
|
||||
public readonly onValidityChanged: vscode.Event<boolean>;
|
||||
private _valid: boolean = true;
|
||||
|
||||
constructor(private _extHostModelViewDialog: ExtHostModelViewDialog) {
|
||||
|
||||
constructor(private _extHostModelViewDialog: ExtHostModelViewDialog,
|
||||
extHostModelView: ExtHostModelViewShape) {
|
||||
super('modelViewDialog', extHostModelView);
|
||||
this.okButton = this._extHostModelViewDialog.createButton(nls.localize('dialogOkLabel', 'Done'));
|
||||
this.cancelButton = this._extHostModelViewDialog.createButton(nls.localize('dialogCancelLabel', 'Cancel'));
|
||||
this.onValidityChanged = this._extHostModelViewDialog.getValidityChangedEvent(this);
|
||||
this.onValidityChanged(valid => this._valid = valid);
|
||||
}
|
||||
|
||||
public get valid(): boolean {
|
||||
return this._valid;
|
||||
}
|
||||
|
||||
public setModelViewId(value: string) {
|
||||
super.setModelViewId(value);
|
||||
this.content = value;
|
||||
}
|
||||
}
|
||||
|
||||
class TabImpl implements sqlops.window.modelviewdialog.DialogTab {
|
||||
class TabImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdialog.DialogTab {
|
||||
constructor(
|
||||
private _extHostModelViewDialog: ExtHostModelViewDialog,
|
||||
extHostModelView: ExtHostModelViewShape) {
|
||||
super('modelViewDialogTab', extHostModelView);
|
||||
}
|
||||
|
||||
public title: string;
|
||||
public content: string;
|
||||
public handle: number;
|
||||
|
||||
constructor(private _extHostModelViewDialog: ExtHostModelViewDialog) { }
|
||||
public setModelViewId(value: string) {
|
||||
super.setModelViewId(value);
|
||||
this.content = value;
|
||||
}
|
||||
}
|
||||
|
||||
class ButtonImpl implements sqlops.window.modelviewdialog.Button {
|
||||
@@ -89,10 +152,12 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
|
||||
private readonly _tabHandles = new Map<sqlops.window.modelviewdialog.DialogTab, number>();
|
||||
private readonly _buttonHandles = new Map<sqlops.window.modelviewdialog.Button, number>();
|
||||
|
||||
private readonly _validityEmitters = new Map<number, Emitter<boolean>>();
|
||||
private readonly _onClickCallbacks = new Map<number, () => void>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext
|
||||
mainContext: IMainContext,
|
||||
private _extHostModelView: ExtHostModelViewShape
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(SqlMainContext.MainThreadModelViewDialog);
|
||||
}
|
||||
@@ -134,6 +199,13 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
|
||||
this._onClickCallbacks.get(handle)();
|
||||
}
|
||||
|
||||
public $onDialogValidityChanged(handle: number, valid: boolean): void {
|
||||
let emitter = this._validityEmitters.get(handle);
|
||||
if (emitter) {
|
||||
emitter.fire(valid);
|
||||
}
|
||||
}
|
||||
|
||||
public open(dialog: sqlops.window.modelviewdialog.Dialog): void {
|
||||
let handle = this.getDialogHandle(dialog);
|
||||
this.updateDialogContent(dialog);
|
||||
@@ -188,16 +260,16 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
|
||||
}
|
||||
|
||||
public createDialog(title: string): sqlops.window.modelviewdialog.Dialog {
|
||||
let dialog = new DialogImpl(this);
|
||||
let dialog = new DialogImpl(this, this._extHostModelView);
|
||||
dialog.title = title;
|
||||
this.getDialogHandle(dialog);
|
||||
dialog.handle = this.getDialogHandle(dialog);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public createTab(title: string): sqlops.window.modelviewdialog.DialogTab {
|
||||
let tab = new TabImpl(this);
|
||||
let tab = new TabImpl(this, this._extHostModelView);
|
||||
tab.title = title;
|
||||
this.getTabHandle(tab);
|
||||
tab.handle = this.getTabHandle(tab);
|
||||
return tab;
|
||||
}
|
||||
|
||||
@@ -208,4 +280,14 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
|
||||
button.label = label;
|
||||
return button;
|
||||
}
|
||||
|
||||
public getValidityChangedEvent(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||
let handle = this.getDialogHandle(dialog);
|
||||
let emitter = this._validityEmitters.get(handle);
|
||||
if (!emitter) {
|
||||
emitter = new Emitter<boolean>();
|
||||
this._validityEmitters.set(handle, emitter);
|
||||
}
|
||||
return emitter.event;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user