From 238acb14688e0fa48cea8448230f639ba4e2231a Mon Sep 17 00:00:00 2001 From: Kim Santiago <31145923+kisantia@users.noreply.github.com> Date: Wed, 8 Apr 2020 18:15:23 -0700 Subject: [PATCH 01/19] Dashboard style updates to match mockups (#9857) * widget formatting * tab panel style * breadcrumb padding * dark theme colors * Addressing comments * move colors to theme.ts * update properties widget colors * update color names --- .../ui/breadcrumb/breadcrumb.component.ts | 2 +- .../ui/breadcrumb/media/breadcrumb.css | 8 +++ src/sql/base/browser/ui/panel/media/panel.css | 11 ++-- .../contents/dashboardWidgetWrapper.css | 3 ++ .../browser/core/dashboardPanelStyles.ts | 54 ++++++++++++++++--- .../insights/insightsWidget.component.ts | 3 +- .../widgets/insights/insightsWidget.css | 9 ++++ .../propertiesWidget.component.html | 4 +- src/vs/workbench/common/theme.ts | 34 +++++++++++- 9 files changed, 111 insertions(+), 17 deletions(-) create mode 100644 src/sql/workbench/contrib/dashboard/browser/widgets/insights/insightsWidget.css diff --git a/src/sql/base/browser/ui/breadcrumb/breadcrumb.component.ts b/src/sql/base/browser/ui/breadcrumb/breadcrumb.component.ts index 43d002d202..ea2d8cb667 100644 --- a/src/sql/base/browser/ui/breadcrumb/breadcrumb.component.ts +++ b/src/sql/base/browser/ui/breadcrumb/breadcrumb.component.ts @@ -16,7 +16,7 @@ import { subscriptionToDisposable } from 'sql/base/browser/lifecycle'; @Component({ selector: 'breadcrumb', template: ` - + diff --git a/src/sql/base/browser/ui/breadcrumb/media/breadcrumb.css b/src/sql/base/browser/ui/breadcrumb/media/breadcrumb.css index 26ebc9deb8..d2608a912d 100644 --- a/src/sql/base/browser/ui/breadcrumb/media/breadcrumb.css +++ b/src/sql/base/browser/ui/breadcrumb/media/breadcrumb.css @@ -30,3 +30,11 @@ breadcrumb .chevron-right.codicon { breadcrumb .router-link { cursor: pointer; } + +breadcrumb .breadcrumb-container { + display: flex; + flex-flow: row; + align-items: center; + margin: 10px; + margin-left: 19px +} diff --git a/src/sql/base/browser/ui/panel/media/panel.css b/src/sql/base/browser/ui/panel/media/panel.css index fe2215f198..ee425c35b9 100644 --- a/src/sql/base/browser/ui/panel/media/panel.css +++ b/src/sql/base/browser/ui/panel/media/panel.css @@ -58,7 +58,7 @@ panel { } .tabbedPanel.vertical .tabList .tab .tabLabel { - font-size: 12px; + font-size: 13px; padding-bottom: 0px; font-weight: normal; } @@ -80,6 +80,7 @@ panel { display: block; min-width: 150px; line-height: 35px; + padding-left: 22px; } .tabbedPanel .tabList .tab .tabIcon.codicon { @@ -152,13 +153,13 @@ panel { } .tabbedPanel .tab-group-header { - font-weight: bold; - margin: 15px 5px 3px 5px; - line-height: 35px; + font-weight: 600; + font-size: 14px; + margin: 15px 24px 3px 24px; + line-height: 20px; height: 35px; border-style: solid; border-width: 0 0 1px 0; - border-color: rgb(214, 214, 214); } .tabbedPanel .vertical-tab-action-container { diff --git a/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.css b/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.css index 4898f4e36e..d5081ce968 100644 --- a/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.css +++ b/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.css @@ -19,6 +19,9 @@ dashboard-widget-wrapper .widgetHeader { display: flex; flex: 0 0; padding: 3px 0 3px 0; + font-weight: 600; + font-size: 14px; + line-height: 20px; } dashboard-widget-wrapper .icon { diff --git a/src/sql/workbench/contrib/dashboard/browser/core/dashboardPanelStyles.ts b/src/sql/workbench/contrib/dashboard/browser/core/dashboardPanelStyles.ts index a244e574e6..7d660e6ed3 100644 --- a/src/sql/workbench/contrib/dashboard/browser/core/dashboardPanelStyles.ts +++ b/src/sql/workbench/contrib/dashboard/browser/core/dashboardPanelStyles.ts @@ -6,22 +6,20 @@ import 'vs/css!./dashboardPanel'; import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { - TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_BACKGROUND, - TAB_INACTIVE_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BACKGROUND, TAB_BORDER, EDITOR_GROUP_BORDER, DASHBOARD_TAB_ACTIVE_BACKGROUND, DASHBOARD_BORDER + TAB_ACTIVE_BACKGROUND, TAB_INACTIVE_BACKGROUND, + TAB_INACTIVE_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BACKGROUND, TAB_BORDER, EDITOR_GROUP_BORDER, VERTICAL_TAB_ACTIVE_BACKGROUND, DASHBOARD_BORDER, DASHBOARD_WIDGET_SUBTEXT, TAB_LABEL, TAB_GROUP_HEADER, DASHBOARD_WIDGET_TITLE, DASHBOARD_PROPERTIES_NAME } from 'vs/workbench/common/theme'; import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Title Active const tabActiveBackground = theme.getColor(TAB_ACTIVE_BACKGROUND); - const tabActiveForeground = theme.getColor(TAB_ACTIVE_FOREGROUND); - let tabActiveBackgroundVertical = theme.getColor(DASHBOARD_TAB_ACTIVE_BACKGROUND); + const tabActiveBackgroundVertical = theme.getColor(VERTICAL_TAB_ACTIVE_BACKGROUND); - if (tabActiveBackground || tabActiveForeground) { + if (tabActiveBackground) { collector.addRule(` panel.dashboard-panel > .tabbedPanel > .title .tabList .tab:hover .tabLabel, panel.dashboard-panel > .tabbedPanel > .title .tabList .tab .tabLabel.active { - color: ${tabActiveForeground}; border-bottom: 0px solid; } @@ -68,6 +66,26 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = `); } + // tab label + const tabLabelColor = theme.getColor(TAB_LABEL); + if (tabLabelColor) { + collector.addRule(`.tabbedPanel.vertical > .title .tabList .tabLabel { + color: ${tabLabelColor} + }`); + + collector.addRule(`properties-widget .propertiesValue { + color: ${tabLabelColor} + }`); + } + + // tab group header + const tabGroupHeader = theme.getColor(TAB_GROUP_HEADER); + if (tabGroupHeader) { + collector.addRule(`.tabbedPanel .tab-group-header { + border-color: ${tabGroupHeader}; + }`); + } + // Panel title background const panelTitleBackground = theme.getColor(EDITOR_GROUP_HEADER_TABS_BACKGROUND); if (panelTitleBackground) { @@ -125,4 +143,28 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = border-right-color: ${sideBorder}; }`); } + + // widget title + const widgetTitle = theme.getColor(DASHBOARD_WIDGET_TITLE); + if (widgetTitle) { + collector.addRule(`dashboard-widget-wrapper .header { + color: ${widgetTitle}; + }`); + } + + // widget subtext + const subText = theme.getColor(DASHBOARD_WIDGET_SUBTEXT); + if (subText) { + collector.addRule(`.subText { + color: ${subText}; + }`); + } + + // properties name + const propertiesName = theme.getColor(DASHBOARD_PROPERTIES_NAME); + if (propertiesName) { + collector.addRule(`properties-widget .propertiesName { + color: ${propertiesName} + }`); + } }); diff --git a/src/sql/workbench/contrib/dashboard/browser/widgets/insights/insightsWidget.component.ts b/src/sql/workbench/contrib/dashboard/browser/widgets/insights/insightsWidget.component.ts index 3c19445d4b..922f460b88 100644 --- a/src/sql/workbench/contrib/dashboard/browser/widgets/insights/insightsWidget.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/widgets/insights/insightsWidget.component.ts @@ -2,6 +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 'vs/css!./insightsWidget'; import { Component, Inject, forwardRef, AfterContentInit, @@ -46,7 +47,7 @@ interface IStorageResult { selector: 'insights-widget', template: `
{{error}}
-
{{lastUpdated}}
+
{{lastUpdated}}
{{autoRefreshStatus}}
diff --git a/src/sql/workbench/contrib/dashboard/browser/widgets/insights/insightsWidget.css b/src/sql/workbench/contrib/dashboard/browser/widgets/insights/insightsWidget.css new file mode 100644 index 0000000000..cd541e4c80 --- /dev/null +++ b/src/sql/workbench/contrib/dashboard/browser/widgets/insights/insightsWidget.css @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.insights-widget-last-updated { + font-size: 10px; + margin-left: 5px; +} diff --git a/src/sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component.html b/src/sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component.html index 803376e0ae..c61ba9e702 100644 --- a/src/sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component.html +++ b/src/sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component.html @@ -11,8 +11,8 @@ -
{{item.displayName}}
-
{{item.value}}
+
{{item.displayName}}
+
{{item.value}}
diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 4047733265..5e32e7dae9 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -595,14 +595,44 @@ export const WINDOW_INACTIVE_BORDER = registerColor('window.inactiveBorder', { // {{SQL CARBON EDIT}} // < --- Dashboard --- > -export const DASHBOARD_TAB_ACTIVE_BACKGROUND = registerColor('dashboard.tabActiveBackground', { +export const VERTICAL_TAB_ACTIVE_BACKGROUND = registerColor('tab.verticalTabActiveBackground', { dark: '#444444', light: '#e1f0fe', hc: TAB_ACTIVE_BACKGROUND -}, nls.localize('dashboardTabActiveBackground', "Active tab background color for dashboard navigation")); +}, nls.localize('verticalTabActiveBackground', "Active tab background color for vertical tabs")); export const DASHBOARD_BORDER = registerColor('dashboard.border', { dark: '#8A8886', light: '#DDDDDD', hc: contrastBorder }, nls.localize('dashboardBorder', "Color for borders in dashboard")); + +export const TAB_LABEL = registerColor('tab.tabLabel', { + light: '#000000', + dark: 'FFFFFF', + hc: 'FFFFFF' +}, nls.localize('tabLabel', "Color of tab label")); + +export const TAB_GROUP_HEADER = registerColor('tab.tabGroupHeader', { + light: '#dddddd', + dark: '#dddddd', + hc: '#FFFFFF' +}, nls.localize('tabGroupHeader', "Color of tab group header")); + +export const DASHBOARD_WIDGET_TITLE = registerColor('dashboardWidget.title', { + light: '#323130', + dark: '#FFFFFF', + hc: '#FFFFFF' +}, nls.localize('dashboardWidget', 'Color of dashboard widget title')); + +export const DASHBOARD_WIDGET_SUBTEXT = registerColor('dashboardWidget.subText', { + light: '#484644', + dark: '#8A8886', + hc: '#FFFFFF' +}, nls.localize('dashboardWidgetSubtext', "Color for dashboard widget subtext")); + +export const DASHBOARD_PROPERTIES_NAME = registerColor('dashboardWidget.propertiesName', { + light: '#161616', + dark: '#8A8886', + hc: '#FFFFFF' +}, nls.localize('dashboardWidgetPropertiesName', "Color for dashboard properties widget names")); From 0de774eb75954c27189e9abac82973c9a6071206 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Wed, 8 Apr 2020 18:34:13 -0700 Subject: [PATCH 02/19] Move service installs to their extensions (#9778) * move service installs to their extensions * more clean up * fix clearline * remove some stuff --- .../darwin/sql-product-build-darwin.yml | 5 - .../linux/sql-product-build-linux.yml | 6 - .../win32/sql-product-build-win32.yml | 6 - build/gulpfile.sql.js | 39 ----- build/package.json | 1 - build/yarn.lock | 147 +--------------- extensions/admin-tool-ext-win/.vscodeignore | 1 - .../admin-tool-ext-win/InstallSsmsMin.bat | 4 - .../admin-tool-ext-win/build/postinstall.js | 43 +++++ extensions/admin-tool-ext-win/package.json | 4 + extensions/admin-tool-ext-win/yarn.lock | 164 ++++++++++++++++++ extensions/mssql/build/postinstall.js | 44 +++++ extensions/mssql/package.json | 3 +- 13 files changed, 259 insertions(+), 208 deletions(-) delete mode 100644 extensions/admin-tool-ext-win/InstallSsmsMin.bat create mode 100644 extensions/admin-tool-ext-win/build/postinstall.js create mode 100644 extensions/mssql/build/postinstall.js diff --git a/build/azure-pipelines/darwin/sql-product-build-darwin.yml b/build/azure-pipelines/darwin/sql-product-build-darwin.yml index b1d78cff33..19818050dc 100644 --- a/build/azure-pipelines/darwin/sql-product-build-darwin.yml +++ b/build/azure-pipelines/darwin/sql-product-build-darwin.yml @@ -92,11 +92,6 @@ steps: node build/azure-pipelines/mixin displayName: Mix in quality - - script: | - set -e - yarn gulp install-sqltoolsservice - displayName: Install sqltoolsservice - - script: | set -e yarn gulp package-rebuild-extensions diff --git a/build/azure-pipelines/linux/sql-product-build-linux.yml b/build/azure-pipelines/linux/sql-product-build-linux.yml index 8f4f96d39c..00a8425a64 100644 --- a/build/azure-pipelines/linux/sql-product-build-linux.yml +++ b/build/azure-pipelines/linux/sql-product-build-linux.yml @@ -85,12 +85,6 @@ steps: node build/azure-pipelines/mixin displayName: Mix in quality - - script: | - set -e - yarn gulp install-sqltoolsservice - yarn gulp install-ssmsmin - displayName: Install extension binaries - - script: | set -e yarn gulp vscode-linux-x64-min-ci diff --git a/build/azure-pipelines/win32/sql-product-build-win32.yml b/build/azure-pipelines/win32/sql-product-build-win32.yml index 7e9138e376..9f703d4cb0 100644 --- a/build/azure-pipelines/win32/sql-product-build-win32.yml +++ b/build/azure-pipelines/win32/sql-product-build-win32.yml @@ -90,12 +90,6 @@ steps: exec { node build/azure-pipelines/mixin } displayName: Mix in quality - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn gulp "install-sqltoolsservice" } - displayName: Install sqltoolsservice - - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" diff --git a/build/gulpfile.sql.js b/build/gulpfile.sql.js index 8d30fa3f6e..b73d0949d3 100644 --- a/build/gulpfile.sql.js +++ b/build/gulpfile.sql.js @@ -5,20 +5,15 @@ 'use strict'; const gulp = require('gulp'); -const util = require('./lib/util'); const tsfmt = require('typescript-formatter'); const es = require('event-stream'); const filter = require('gulp-filter'); -const serviceDownloader = require('service-downloader').ServiceDownloadProvider; -const platform = require('service-downloader/out/platform').PlatformInformation; const path = require('path'); const ext = require('./lib/extensions'); const task = require('./lib/task'); const glob = require('glob'); const vsce = require('vsce'); const mkdirp = require('mkdirp'); -const fs = require('fs').promises; -const assert = require('assert'); gulp.task('fmt', () => formatStagedFiles()); const formatFiles = (some) => { @@ -94,40 +89,6 @@ const formatStagedFiles = () => { }); }; -async function installService(configPath, runtimId) { - const absoluteConfigPath = require.resolve(configPath); - const config = require(absoluteConfigPath); - const runtime = runtimId || (await platform.getCurrent()).runtimeId; - // fix path since it won't be correct - config.installDirectory = path.join(path.dirname(absoluteConfigPath), config.installDirectory); - console.log('install diectory', config.installDirectory); - let installer = new serviceDownloader(config); - installer.eventEmitter.onAny((event, ...values) => { - console.log(`ServiceDownloader Event : ${event}${values && values.length > 0 ? ` - ${values.join(' ')}` : ''}`); - }); - let serviceInstallFolder = installer.getInstallDirectory(runtime); - console.log('Cleaning up the install folder: ' + serviceInstallFolder); - try { - await util.rimraf(serviceInstallFolder)(); - } catch (e) { - console.error('failed to delete the install folder error: ' + e); - throw e; - } - await installer.installService(runtime); - let stat; - for (const file of config.executableFiles) { - try { - stat = await fs.stat(path.join(serviceInstallFolder, file)); - } catch (e) { } - } - - assert(stat); -} - -gulp.task('install-sqltoolsservice', () => installService('../extensions/mssql/config.json')); - -gulp.task('install-ssmsmin', () => installService('../extensions/admin-tool-ext-win/config.json', 'Windows_64')); // admin-tool-ext is a windows only extension, and we only ship a 64 bit version, so locking the binaries as such - const root = path.dirname(__dirname); gulp.task('package-external-extensions', task.series( diff --git a/build/package.json b/build/package.json index 49c78e63e4..917d7fe5a8 100644 --- a/build/package.json +++ b/build/package.json @@ -47,7 +47,6 @@ "rollup": "^1.20.3", "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-resolve": "^5.2.0", - "service-downloader": "0.2.1", "terser": "4.3.8", "typescript": "^3.9.0-dev.20200327", "vsce": "1.48.0", diff --git a/build/yarn.lock b/build/yarn.lock index ba4022b2f4..8ae54996d8 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -420,20 +420,6 @@ acorn@^7.0.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c" integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ== -agent-base@4: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" - integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== - dependencies: - es6-promisify "^5.0.0" - -agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - ajv@^4.9.1: version "4.11.8" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" @@ -596,13 +582,6 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.2.tgz#8b8a7ca2a658f927e9f307d6d1a42f4199f0f735" integrity sha512-6xrbvN0MOBKSJDdonmSSz2OwFSgxRaVtBDes26mj9KIGtDo+g9xosFRSC+i1gQh2oAN/tQ62AI/pGZGQjVOiRg== -async-retry@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.2.3.tgz#a6521f338358d322b1a0012b79030c6f411d1ce0" - integrity sha512-tfDb02Th6CE6pJUF2gjW5ZVjsgwlucVXOEQMvEX9JgSJMs9gAX+Nz3xRuJBKuUYjTSYORqvDBORdAQ3LU59g7Q== - dependencies: - retry "0.12.0" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -834,11 +813,6 @@ chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== -chownr@^1.1.3: - version "1.1.4" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -1036,20 +1010,6 @@ debug@2.X, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - -debug@^3.1.0: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" @@ -1215,18 +1175,6 @@ entities@^1.1.1, entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== -es6-promise@^4.0.3: - version "4.2.6" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f" - integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - escape-string-regexp@^1.0.2: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -1262,11 +1210,6 @@ estree-walker@^0.6.1: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== -eventemitter2@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452" - integrity sha1-YZegldX7a1folC9v1+qtY6CclFI= - execa@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" @@ -1457,13 +1400,6 @@ fs-minipass@^1.2.5: dependencies: minipass "^2.2.1" -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1805,14 +1741,6 @@ htmlparser2@^3.9.1: inherits "^2.0.1" readable-stream "^3.0.6" -http-proxy-agent@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== - dependencies: - agent-base "4" - debug "3.1.0" - http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -1831,14 +1759,6 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -https-proxy-agent@^2.2.3: - version "2.2.4" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" - integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== - dependencies: - agent-base "^4.3.0" - debug "^3.1.0" - iconv-lite@0.4.23: version "0.4.23" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" @@ -2468,13 +2388,6 @@ minipass@^2.2.1, minipass@^2.3.4: safe-buffer "^5.1.2" yallist "^3.0.0" -minipass@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5" - integrity sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w== - dependencies: - yallist "^4.0.0" - minizlib@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" @@ -2482,14 +2395,6 @@ minizlib@^1.1.1: dependencies: minipass "^2.2.1" -minizlib@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3" - integrity sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - mixin-deep@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" @@ -2505,11 +2410,6 @@ mkdirp@^0.5.0, mkdirp@^0.5.1: dependencies: minimist "0.0.8" -mkdirp@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.3.tgz#4cf2e30ad45959dddea53ad97d518b6c8205e1ea" - integrity sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g== - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -2738,7 +2638,7 @@ os-name@^3.1.0: macos-release "^2.2.0" windows-release "^3.1.0" -os-tmpdir@^1.0.0, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: +os-tmpdir@^1.0.0, os-tmpdir@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= @@ -3047,11 +2947,6 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -retry@0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - reusify@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -3169,20 +3064,6 @@ semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -service-downloader@0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/service-downloader/-/service-downloader-0.2.1.tgz#8bd756bc4bc0cbfdf04fe71d4337f19ce6196203" - integrity sha512-5IEy2nyMJj/f41pI65b8RMeJyCecGNrMmNCpUW8hckZ9cBMyX+VCp8GjYoM6Mz/X0XSaGVz7V5gtCWjfeJI7gA== - dependencies: - async-retry "^1.2.3" - eventemitter2 "^5.0.1" - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.3" - mkdirp "^0.5.1" - tar "^6.0.1" - tmp "^0.0.33" - yauzl "^2.10.0" - set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -3436,18 +3317,6 @@ tar@^4: safe-buffer "^5.1.2" yallist "^3.0.2" -tar@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.1.tgz#7b3bd6c313cb6e0153770108f8d70ac298607efa" - integrity sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q== - dependencies: - chownr "^1.1.3" - fs-minipass "^2.0.0" - minipass "^3.0.0" - minizlib "^2.1.0" - mkdirp "^1.0.3" - yallist "^4.0.0" - terser@*: version "4.2.1" resolved "https://registry.yarnpkg.com/terser/-/terser-4.2.1.tgz#1052cfe17576c66e7bc70fcc7119f22b155bdac1" @@ -3486,13 +3355,6 @@ tmp@0.0.29: dependencies: os-tmpdir "~1.0.1" -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" @@ -3853,12 +3715,7 @@ yallist@^3.0.0, yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yauzl@^2.10.0, yauzl@^2.3.1: +yauzl@^2.3.1: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= diff --git a/extensions/admin-tool-ext-win/.vscodeignore b/extensions/admin-tool-ext-win/.vscodeignore index ebcf4d2a29..e5a5f0a51c 100644 --- a/extensions/admin-tool-ext-win/.vscodeignore +++ b/extensions/admin-tool-ext-win/.vscodeignore @@ -4,6 +4,5 @@ src .gitignore coverConfig.json tsconfig.json -InstallSsmsMin.bat cgmanifest.json .vscode diff --git a/extensions/admin-tool-ext-win/InstallSsmsMin.bat b/extensions/admin-tool-ext-win/InstallSsmsMin.bat deleted file mode 100644 index 49e6dbf88a..0000000000 --- a/extensions/admin-tool-ext-win/InstallSsmsMin.bat +++ /dev/null @@ -1,4 +0,0 @@ -@echo off - -REM Run this command to install SsmsMin for local development testing -gulp install-ssmsmin \ No newline at end of file diff --git a/extensions/admin-tool-ext-win/build/postinstall.js b/extensions/admin-tool-ext-win/build/postinstall.js new file mode 100644 index 0000000000..c6e7313868 --- /dev/null +++ b/extensions/admin-tool-ext-win/build/postinstall.js @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +(async () => { + const serviceDownloader = require('service-downloader').ServiceDownloadProvider; + const path = require('path'); + const fs = require('fs').promises; + const rimraf = require('rimraf'); + const assert = require('assert'); + const readline = require('readline'); + + async function installService(configPath) { + const absoluteConfigPath = require.resolve('../config.json'); + const config = require(absoluteConfigPath); + const runtime = 'Windows_64'; + // fix path since it won't be correct + config.installDirectory = path.join(path.dirname(absoluteConfigPath), config.installDirectory); + let installer = new serviceDownloader(config); + installer.eventEmitter.onAny((event, ...values) => { + readline.cursorTo(process.stdout, 0); + readline.clearLine(process.stdout, 0); + process.stdout.write(`${event}${values && values.length > 0 ? ` - ${values.join(' ')}` : ''}`); + }); + let serviceInstallFolder = installer.getInstallDirectory(runtime); + await new Promise((rs, rj) => rimraf(serviceInstallFolder, (e) => e ? rj(e) : rs())); + await installer.installService(runtime); + let stat; + for (const file of config.executableFiles) { + try { + stat = await fs.stat(path.join(serviceInstallFolder, file)); + } catch (e) { } + } + + assert(stat); + } + + await installService(); +})().catch(e => { + console.error(e); + process.exit(1); +}); diff --git a/extensions/admin-tool-ext-win/package.json b/extensions/admin-tool-ext-win/package.json index 948c3f5563..2b03d2d98b 100644 --- a/extensions/admin-tool-ext-win/package.json +++ b/extensions/admin-tool-ext-win/package.json @@ -12,6 +12,9 @@ "vscode": "^1.30.1", "azdata": ">=1.8.0" }, + "scripts": { + "postinstall": "node ./build/postinstall.js" + }, "activationEvents": [ "onCommand:adminToolExtWin.launchSsmsMinPropertiesDialog", "onCommand:adminToolExtWin.launchSsmsMinGswDialog" @@ -72,6 +75,7 @@ }, "dependencies": { "ads-extension-telemetry": "github:Charles-Gagnon/ads-extension-telemetry#0.1.0", + "service-downloader": "0.2.1", "vscode-nls": "^3.2.1" }, "devDependencies": { diff --git a/extensions/admin-tool-ext-win/yarn.lock b/extensions/admin-tool-ext-win/yarn.lock index d0b0454b63..0e801d5f96 100644 --- a/extensions/admin-tool-ext-win/yarn.lock +++ b/extensions/admin-tool-ext-win/yarn.lock @@ -28,6 +28,13 @@ abbrev@1.0.x: dependencies: vscode-extension-telemetry "0.1.1" +agent-base@4, agent-base@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" + integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== + dependencies: + es6-promisify "^5.0.0" + amdefine@>=0.0.4, amdefine@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" @@ -96,6 +103,13 @@ array-slice@^0.2.3: resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" integrity sha1-3Tz7gO15c6dRF82sabC5nshhhvU= +async-retry@^1.2.3: + version "1.3.1" + resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.1.tgz#139f31f8ddce50c0870b0ba558a6079684aaed55" + integrity sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA== + dependencies: + retry "0.12.0" + async@1.x: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -119,6 +133,11 @@ browser-stdout@1.3.1: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -134,6 +153,11 @@ charenc@~0.0.1: resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= +chownr@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + commander@2.15.1: version "2.15.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" @@ -209,6 +233,18 @@ diff@3.5.0: resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== +es6-promise@^4.0.3: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + escape-string-regexp@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -241,6 +277,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +eventemitter2@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452" + integrity sha1-YZegldX7a1folC9v1+qtY6CclFI= + extend-shallow@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-1.1.4.tgz#19d6bf94dfc09d76ba711f39b872d21ff4dd9071" @@ -253,6 +294,20 @@ fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + dependencies: + pend "~1.2.0" + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -324,6 +379,22 @@ he@1.1.1: resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= +http-proxy-agent@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" + integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== + dependencies: + agent-base "4" + debug "3.1.0" + +https-proxy-agent@^2.2.3: + version "2.2.4" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" + integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== + dependencies: + agent-base "^4.3.0" + debug "^3.1.0" + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -419,11 +490,31 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= +minipass@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5" + integrity sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w== + dependencies: + yallist "^4.0.0" + +minizlib@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3" + integrity sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -431,6 +522,18 @@ mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@~0.5.1: dependencies: minimist "0.0.8" +mkdirp@^0.5.1: + version "0.5.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" + integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== + dependencies: + minimist "^1.2.5" + +mkdirp@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.3.tgz#4cf2e30ad45959dddea53ad97d518b6c8205e1ea" + integrity sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g== + mocha-junit-reporter@^1.17.0: version "1.23.1" resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-1.23.1.tgz#ba11519c0b967f404e4123dd69bc4ba022ab0f12" @@ -516,11 +619,21 @@ optionator@^0.8.1: type-check "~0.3.2" word-wrap "~1.2.3" +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + plugin-error@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace" @@ -571,11 +684,30 @@ resolve@1.1.x: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= +retry@0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + semver@^5.3.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== +service-downloader@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/service-downloader/-/service-downloader-0.2.1.tgz#8bd756bc4bc0cbfdf04fe71d4337f19ce6196203" + integrity sha512-5IEy2nyMJj/f41pI65b8RMeJyCecGNrMmNCpUW8hckZ9cBMyX+VCp8GjYoM6Mz/X0XSaGVz7V5gtCWjfeJI7gA== + dependencies: + async-retry "^1.2.3" + eventemitter2 "^5.0.1" + http-proxy-agent "^2.1.0" + https-proxy-agent "^2.2.3" + mkdirp "^0.5.1" + tar "^6.0.1" + tmp "^0.0.33" + yauzl "^2.10.0" + should-equal@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3" @@ -671,6 +803,18 @@ supports-color@^3.1.0: dependencies: has-flag "^1.0.0" +tar@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.1.tgz#7b3bd6c313cb6e0153770108f8d70ac298607efa" + integrity sha512-bKhKrrz2FJJj5s7wynxy/fyxpE0CmCjmOQ1KV4KkgXFWOgoIT/NbTMnB1n+LFNrNk0SSBVGGxcK5AGsyC+pW5Q== + dependencies: + chownr "^1.1.3" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.0" + mkdirp "^1.0.3" + yallist "^4.0.0" + through2@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.1.tgz#384e75314d49f32de12eebb8136b8eb6b5d59da9" @@ -679,6 +823,13 @@ through2@2.0.1: readable-stream "~2.0.0" xtend "~4.0.0" +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" @@ -759,6 +910,19 @@ xtend@~4.0.0: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + zone.js@0.7.6: version "0.7.6" resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009" diff --git a/extensions/mssql/build/postinstall.js b/extensions/mssql/build/postinstall.js new file mode 100644 index 0000000000..b88d2ec5a2 --- /dev/null +++ b/extensions/mssql/build/postinstall.js @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +(async () => { + const serviceDownloader = require('service-downloader').ServiceDownloadProvider; + const platform = require('service-downloader/out/platform').PlatformInformation; + const path = require('path'); + const fs = require('fs').promises; + const rimraf = require('rimraf'); + const assert = require('assert'); + const readline = require('readline'); + + async function installService() { + const absoluteConfigPath = require.resolve('../config.json'); + const config = require(absoluteConfigPath); + const runtime = (await platform.getCurrent()).runtimeId; + // fix path since it won't be correct + config.installDirectory = path.join(path.dirname(absoluteConfigPath), config.installDirectory); + let installer = new serviceDownloader(config); + installer.eventEmitter.onAny((event, ...values) => { + readline.cursorTo(process.stdout, 0); + readline.clearLine(process.stdout, 0); + process.stdout.write(`${event}${values && values.length > 0 ? ` - ${values.join(' ')}` : ''}`); + }); + let serviceInstallFolder = installer.getInstallDirectory(runtime); + await new Promise((rs, rj) => rimraf(serviceInstallFolder, (e) => e ? rj(e) : rs())); + await installer.installService(runtime); + let stat; + for (const file of config.executableFiles) { + try { + stat = await fs.stat(path.join(serviceInstallFolder, file)); + } catch (e) { } + } + + assert(stat); + } + + await installService(); +})().catch(e => { + console.error(e); + process.exit(1); +}); diff --git a/extensions/mssql/package.json b/extensions/mssql/package.json index 1dbef5f18b..4dc89afdad 100644 --- a/extensions/mssql/package.json +++ b/extensions/mssql/package.json @@ -16,7 +16,8 @@ ], "scripts": { "compile": "gulp compile-extension:mssql-client", - "update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-mssql syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json" + "update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-mssql syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json", + "postinstall": "node ./build/postinstall.js" }, "contributes": { "commands": [ From c2e732438112f2680176240dd5d65bdd4afe750a Mon Sep 17 00:00:00 2001 From: Maddy <12754347+MaddyDev@users.noreply.github.com> Date: Wed, 8 Apr 2020 20:59:51 -0700 Subject: [PATCH 03/19] vbump (#9908) * vbump * vbump in right place --- product.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/product.json b/product.json index 7052477516..82b6f2ec33 100644 --- a/product.json +++ b/product.json @@ -68,7 +68,7 @@ "builtInExtensions": [ { "name": "Microsoft.sqlservernotebook", - "version": "0.3.5", + "version": "0.3.6", "repo": "https://github.com/Microsoft/azuredatastudio" } ] From 59f440dfb4f70beed5ddf46902257361d623e25f Mon Sep 17 00:00:00 2001 From: Karl Burtram Date: Thu, 9 Apr 2020 11:32:19 -0700 Subject: [PATCH 04/19] Revert "remove the docs folder (#9868)" (#9919) This reverts commit 2247682863a62098fbeca17bdd30ee96e964c9a6. --- docs/UX-Design-Guidelines.md | 1 + docs/overview_screen.jpg | Bin 0 -> 61069 bytes 2 files changed, 1 insertion(+) create mode 100644 docs/UX-Design-Guidelines.md create mode 100644 docs/overview_screen.jpg diff --git a/docs/UX-Design-Guidelines.md b/docs/UX-Design-Guidelines.md new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/docs/UX-Design-Guidelines.md @@ -0,0 +1 @@ + diff --git a/docs/overview_screen.jpg b/docs/overview_screen.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4da5a1f81b63eb25b5650022fa9f2003701c8253 GIT binary patch literal 61069 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 literal 0 HcmV?d00001 From 82f21faf79f479dcc8de7f3f9642f2bca60038fb Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Thu, 9 Apr 2020 11:51:49 -0700 Subject: [PATCH 05/19] fix dashboard style (#9917) --- src/sql/base/browser/ui/panel/media/panel.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sql/base/browser/ui/panel/media/panel.css b/src/sql/base/browser/ui/panel/media/panel.css index ee425c35b9..5434d4c3cb 100644 --- a/src/sql/base/browser/ui/panel/media/panel.css +++ b/src/sql/base/browser/ui/panel/media/panel.css @@ -80,7 +80,8 @@ panel { display: block; min-width: 150px; line-height: 35px; - padding-left: 22px; + padding-left: 24px; + padding-right: 24px; } .tabbedPanel .tabList .tab .tabIcon.codicon { From 23f1a08aa0c55da1e3c5b47599ca392e556b04d8 Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Thu, 9 Apr 2020 12:02:00 -0700 Subject: [PATCH 06/19] add ability to dynamically update tabs (#9911) * add dashboard and tabbedPanel samples * add updateTabs to tabbedPanel component * add updateTabs to tabbedPanel component --- .../src/controllers/modelViewDashboard.ts | 52 ++++++++++++++----- src/sql/azdata.proposed.d.ts | 7 +++ .../workbench/api/common/extHostModelView.ts | 51 +++++++++++------- .../api/common/extHostModelViewDialog.ts | 49 +++++++++++------ .../browser/modelComponents/componentBase.ts | 1 + .../modelComponents/tabbedPanel.component.ts | 7 ++- 6 files changed, 117 insertions(+), 50 deletions(-) diff --git a/samples/sqlservices/src/controllers/modelViewDashboard.ts b/samples/sqlservices/src/controllers/modelViewDashboard.ts index 0b17bc1fe9..d57b93ab93 100644 --- a/samples/sqlservices/src/controllers/modelViewDashboard.ts +++ b/samples/sqlservices/src/controllers/modelViewDashboard.ts @@ -11,37 +11,33 @@ export async function openModelViewDashboard(context: vscode.ExtensionContext): dashboard.registerTabs(async (view: azdata.ModelView) => { // Tab with toolbar const button = view.modelBuilder.button().withProperties({ - label: 'Run', + label: 'Add databases tab', iconPath: { light: context.asAbsolutePath('images/compare.svg'), dark: context.asAbsolutePath('images/compare-inverse.svg') } }).component(); - button.onDidClick(() => { - vscode.window.showInformationMessage('Run button clicked'); - }); - const toolbar = view.modelBuilder.toolbarContainer().withItems([button]).withLayout({ orientation: azdata.Orientation.Horizontal }).component(); - const textComponent1 = view.modelBuilder.text().withProperties({ value: 'text 1' }).component(); + const input1 = view.modelBuilder.inputBox().withProperties({ value: 'input 1' }).component(); const homeTab: azdata.DashboardTab = { id: 'home', toolbar: toolbar, - content: textComponent1, + content: input1, title: 'Home', icon: context.asAbsolutePath('images/home.svg') // icon can be the path of a svg file }; // Tab with nested tabbed Panel - const textComponent2 = view.modelBuilder.text().withProperties({ value: 'text 2' }).component(); - const textComponent3 = view.modelBuilder.text().withProperties({ value: 'text 3' }).component(); - + const addTabButton = view.modelBuilder.button().withProperties({ label: 'Add a tab', width: '150px' }).component(); + const removeTabButton = view.modelBuilder.button().withProperties({ label: 'Remove a tab', width: '150px' }).component(); + const container = view.modelBuilder.flexContainer().withItems([addTabButton, removeTabButton]).withLayout({ flexFlow: 'column' }).component(); const nestedTab1 = { title: 'Tab1', - content: textComponent2, + content: container, id: 'tab1', icon: { light: context.asAbsolutePath('images/user.svg'), @@ -49,9 +45,10 @@ export async function openModelViewDashboard(context: vscode.ExtensionContext): } }; + const input2 = view.modelBuilder.inputBox().withProperties({ value: 'input 2' }).component(); const nestedTab2 = { title: 'Tab2', - content: textComponent3, + content: input2, icon: { light: context.asAbsolutePath('images/group.svg'), dark: context.asAbsolutePath('images/group_inverse.svg') @@ -59,6 +56,13 @@ export async function openModelViewDashboard(context: vscode.ExtensionContext): id: 'tab2' }; + const input3 = view.modelBuilder.inputBox().withProperties({ value: 'input 4' }).component(); + const nestedTab3 = { + title: 'Tab3', + content: input3, + id: 'tab3' + }; + const tabbedPanel = view.modelBuilder.tabbedPanel().withTabs([ nestedTab1, nestedTab2 ]).withLayout({ @@ -66,6 +70,15 @@ export async function openModelViewDashboard(context: vscode.ExtensionContext): showIcon: true }).component(); + + addTabButton.onDidClick(() => { + tabbedPanel.updateTabs([nestedTab1, nestedTab2, nestedTab3]); + }); + + removeTabButton.onDidClick(() => { + tabbedPanel.updateTabs([nestedTab1, nestedTab3]); + }); + const settingsTab: azdata.DashboardTab = { id: 'settings', content: tabbedPanel, @@ -81,10 +94,23 @@ export async function openModelViewDashboard(context: vscode.ExtensionContext): ] }; + // Databases tab + const databasesText = view.modelBuilder.inputBox().withProperties({ value: 'This is databases tab', width: '200px' }).component(); + const databasesTab: azdata.DashboardTab = { + id: 'databases', + content: databasesText, + title: 'Databases', + icon: context.asAbsolutePath('images/default.svg') + }; + button.onDidClick(() => { + dashboard.updateTabs([homeTab, databasesTab, securityTabGroup]); + }); + return [ homeTab, securityTabGroup ]; }); - dashboard.open(); + await dashboard.open(); } + diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index b8bc105470..dd41fa4bdf 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -210,6 +210,12 @@ declare module 'azdata' { * The event argument is the id of the selected tab. */ onTabChanged: vscode.Event; + + /** + * update the tabs. + * @param tabs new tabs + */ + updateTabs(tabs: (Tab | TabGroup)[]): void; } /** @@ -310,6 +316,7 @@ declare module 'azdata' { export interface ModelViewDashboard { registerTabs(handler: (view: ModelView) => Thenable<(DashboardTab | DashboardTabGroup)[]>): void; open(): Thenable; + updateTabs(tabs: (DashboardTab | DashboardTabGroup)[]): void; } export function createModelViewDashboard(title: string, options?: ModelViewDashboardOptions): ModelViewDashboard; diff --git a/src/sql/workbench/api/common/extHostModelView.ts b/src/sql/workbench/api/common/extHostModelView.ts index 1787aef328..ebde4907a6 100644 --- a/src/sql/workbench/api/common/extHostModelView.ts +++ b/src/sql/workbench/api/common/extHostModelView.ts @@ -495,29 +495,33 @@ class ToolbarContainerBuilder extends GenericContainerBuilder implements azdata.TabbedPanelComponentBuilder { withTabs(items: (azdata.Tab | azdata.TabGroup)[]): azdata.ContainerBuilder { - const itemConfigs = []; - items.forEach(item => { - if (item && 'tabs' in item) { - item.tabs.forEach(tab => { - itemConfigs.push(this.toItemConfig(tab.content, tab.title, tab.id, item.title, tab.icon)); - }); - } else { - const tab = item; - itemConfigs.push(this.toItemConfig(tab.content, tab.title, tab.id, undefined, tab.icon)); - } - }); - this._component.itemConfigs = itemConfigs; + this._component.itemConfigs = createFromTabs(items); return this; } +} - toItemConfig(content: azdata.Component, title: string, id?: string, group?: string, icon?: string | URI | { light: string | URI; dark: string | URI }): InternalItemConfig { - return new InternalItemConfig(content as ComponentWrapper, { - title: title, - group: group, - id: id, - icon: icon - }); - } +function createFromTabs(items: (azdata.Tab | azdata.TabGroup)[]): InternalItemConfig[] { + const itemConfigs = []; + items.forEach(item => { + if (item && 'tabs' in item) { + item.tabs.forEach(tab => { + itemConfigs.push(toTabItemConfig(tab.content, tab.title, tab.id, item.title, tab.icon)); + }); + } else { + const tab = item; + itemConfigs.push(toTabItemConfig(tab.content, tab.title, tab.id, undefined, tab.icon)); + } + }); + return itemConfigs; +} + +function toTabItemConfig(content: azdata.Component, title: string, id?: string, group?: string, icon?: string | URI | { light: string | URI; dark: string | URI }): InternalItemConfig { + return new InternalItemConfig(content as ComponentWrapper, { + title: title, + group: group, + id: id, + icon: icon + }); } class LoadingComponentBuilder extends ComponentBuilderImpl implements azdata.LoadingComponentBuilder { @@ -1728,6 +1732,13 @@ class TabbedPanelComponentWrapper extends ComponentWrapper implements azdata.Tab this.properties = {}; this._emitterMap.set(ComponentEventType.onDidChange, new Emitter()); } + updateTabs(tabs: (azdata.Tab | azdata.TabGroup)[]): void { + this.clearItems(); + const itemConfigs = createFromTabs(tabs); + itemConfigs.forEach(itemConfig => { + this.addItem(itemConfig.component, itemConfig.config); + }); + } public get onTabChanged(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidChange); diff --git a/src/sql/workbench/api/common/extHostModelViewDialog.ts b/src/sql/workbench/api/common/extHostModelViewDialog.ts index 90db838a59..221bfefcfe 100644 --- a/src/sql/workbench/api/common/extHostModelViewDialog.ts +++ b/src/sql/workbench/api/common/extHostModelViewDialog.ts @@ -458,34 +458,34 @@ class WizardImpl implements azdata.window.Wizard { } class ModelViewDashboardImpl implements azdata.window.ModelViewDashboard { + private _tabbedPanel: azdata.TabbedPanelComponent; + private _view: azdata.ModelView; + constructor( private _editor: ModelViewEditorImpl, private _options?: azdata.ModelViewDashboardOptions ) { } + + updateTabs(tabs: (azdata.DashboardTab | azdata.DashboardTabGroup)[]): void { + if (this._tabbedPanel === undefined || this._view === undefined) { + throw new Error(nls.localize('dashboardNotInitialized', "Tabs are not initialized")); + } + + this._tabbedPanel.updateTabs(this.createTabs(tabs, this._view)); + } + registerTabs(handler: (view: azdata.ModelView) => Thenable<(azdata.DashboardTab | azdata.DashboardTabGroup)[]>): void { this._editor.registerContent(async (view) => { + this._view = view; const dashboardTabs = await handler(view); - const tabs: (azdata.TabGroup | azdata.Tab)[] = []; - dashboardTabs.forEach((item: azdata.DashboardTab | azdata.DashboardTabGroup) => { - if ('tabs' in item) { - tabs.push({ - title: item.title, - tabs: item.tabs.map(tab => { - return this.createTab(tab, view); - }) - }); - } else { - tabs.push(this.createTab(item, view)); - } - }); - - const tabbedPanel = view.modelBuilder.tabbedPanel().withTabs(tabs).withLayout({ + const tabs = this.createTabs(dashboardTabs, view); + this._tabbedPanel = view.modelBuilder.tabbedPanel().withTabs(tabs).withLayout({ orientation: 'vertical', showIcon: this._options?.showIcon ?? true, alwaysShowTabs: this._options?.alwaysShowTabs ?? false }).component(); - return view.initializeModel(tabbedPanel); + return view.initializeModel(this._tabbedPanel); }); } @@ -508,6 +508,23 @@ class ModelViewDashboardImpl implements azdata.window.ModelViewDashboard { return tab; } } + + createTabs(dashboardTabs: (azdata.DashboardTab | azdata.DashboardTabGroup)[], view: azdata.ModelView): (azdata.TabGroup | azdata.Tab)[] { + const tabs: (azdata.TabGroup | azdata.Tab)[] = []; + dashboardTabs.forEach((item: azdata.DashboardTab | azdata.DashboardTabGroup) => { + if ('tabs' in item) { + tabs.push({ + title: item.title, + tabs: item.tabs.map(tab => { + return this.createTab(tab, view); + }) + }); + } else { + tabs.push(this.createTab(item, view)); + } + }); + return tabs; + } } export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape { diff --git a/src/sql/workbench/browser/modelComponents/componentBase.ts b/src/sql/workbench/browser/modelComponents/componentBase.ts index ec5ea4aeba..c46297ef47 100644 --- a/src/sql/workbench/browser/modelComponents/componentBase.ts +++ b/src/sql/workbench/browser/modelComponents/componentBase.ts @@ -352,6 +352,7 @@ export abstract class ContainerBase extends ComponentBase { public clearContainer(): void { this.items = []; + this.onItemsUpdated(); this._changeRef.detectChanges(); } diff --git a/src/sql/workbench/browser/modelComponents/tabbedPanel.component.ts b/src/sql/workbench/browser/modelComponents/tabbedPanel.component.ts index feff21e625..268e94014e 100644 --- a/src/sql/workbench/browser/modelComponents/tabbedPanel.component.ts +++ b/src/sql/workbench/browser/modelComponents/tabbedPanel.component.ts @@ -99,7 +99,12 @@ export default class TabbedPanelComponent extends ContainerBase imple } onItemsUpdated(): void { - const firstTabIndex = this.tabs.findIndex(tab => tab.type === 'tab'); + if (this.items.length === 0) { + this._itemIndexToProcess = 0; + this._tabs = []; + } + + const firstTabIndex = this._tabs.findIndex(tab => tab.type === 'tab'); if (firstTabIndex >= 0) { this._panel.selectTab(firstTabIndex); } From 510c12677ef9b01a2bd09fa21ae30a0beaef6998 Mon Sep 17 00:00:00 2001 From: Amir Omidi Date: Thu, 9 Apr 2020 13:01:24 -0700 Subject: [PATCH 07/19] Remove unusable tenants (#9921) --- .../src/account-provider/auths/azureAuth.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/extensions/azurecore/src/account-provider/auths/azureAuth.ts b/extensions/azurecore/src/account-provider/auths/azureAuth.ts index 9039c01254..673b5aeb95 100644 --- a/extensions/azurecore/src/account-provider/auths/azureAuth.ts +++ b/extensions/azurecore/src/account-provider/auths/azureAuth.ts @@ -219,12 +219,14 @@ export abstract class AzureAuth implements vscode.Disposable { console.log('Base token was empty, account is stale.'); return undefined; } + try { await this.refreshAccessToken(account.key, baseToken.refreshToken, tenant, resource); } catch (ex) { - console.log(ex); - account.isStale = true; - return undefined; + console.log(`Could not refresh access token for ${JSON.stringify(tenant)} - silently removing the tenant from the user's account.`); + azureAccount.properties.tenants = azureAccount.properties.tenants.filter(t => t.id !== tenant.id); + console.log(ex, ex?.data, ex?.response); + continue; } cachedTokens = await this.getCachedToken(account.key, resource.id, tenant.id); @@ -243,9 +245,12 @@ export abstract class AzureAuth implements vscode.Disposable { if (azureAccount.properties.subscriptions) { azureAccount.properties.subscriptions.forEach(subscription => { - response[subscription.id] = { - ...response[subscription.tenantId] - }; + // Make sure that tenant has information populated. + if (response[subscription.tenantId]) { + response[subscription.id] = { + ...response[subscription.tenantId] + }; + } }); } From 06abda74a6e9ec60a85ad5408cffa8056a3ebe20 Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Thu, 9 Apr 2020 13:42:57 -0700 Subject: [PATCH 08/19] use the new icons from UX team (#9920) --- extensions/mssql/package.json | 5 +--- .../mssql/resources/dark/database_inverse.svg | 1 - extensions/mssql/resources/database.svg | 27 +++++++++++++++++++ extensions/mssql/resources/light/database.svg | 1 - .../dashboard/browser/core/dashboardPage.css | 10 ------- .../dashboard/browser/core/media/default.svg | 11 +++++--- .../browser/core/media/default_inverse.svg | 10 ------- .../dashboard/browser/core/media/home.svg | 19 ++++++++++++- .../browser/core/media/home_inverse.svg | 1 - 9 files changed, 54 insertions(+), 31 deletions(-) delete mode 100644 extensions/mssql/resources/dark/database_inverse.svg create mode 100644 extensions/mssql/resources/database.svg delete mode 100644 extensions/mssql/resources/light/database.svg delete mode 100644 src/sql/workbench/contrib/dashboard/browser/core/media/default_inverse.svg delete mode 100644 src/sql/workbench/contrib/dashboard/browser/core/media/home_inverse.svg diff --git a/extensions/mssql/package.json b/extensions/mssql/package.json index 4dc89afdad..873d738bf8 100644 --- a/extensions/mssql/package.json +++ b/extensions/mssql/package.json @@ -494,10 +494,7 @@ "title": "%mssql.tabs.databases%", "when": "dashboardContext == 'server'", "group": "home", - "icon": { - "light": "resources/light/database.svg", - "dark": "resources/dark/database_inverse.svg" - }, + "icon": "resources/database.svg", "container": { "widgets-container": [ { diff --git a/extensions/mssql/resources/dark/database_inverse.svg b/extensions/mssql/resources/dark/database_inverse.svg deleted file mode 100644 index 5fb31021fb..0000000000 --- a/extensions/mssql/resources/dark/database_inverse.svg +++ /dev/null @@ -1 +0,0 @@ -bv \ No newline at end of file diff --git a/extensions/mssql/resources/database.svg b/extensions/mssql/resources/database.svg new file mode 100644 index 0000000000..9200009c54 --- /dev/null +++ b/extensions/mssql/resources/database.svg @@ -0,0 +1,27 @@ + + + + +Icon-databases-130 + + + + + + + + + + + + + + diff --git a/extensions/mssql/resources/light/database.svg b/extensions/mssql/resources/light/database.svg deleted file mode 100644 index bc00d48e2b..0000000000 --- a/extensions/mssql/resources/light/database.svg +++ /dev/null @@ -1 +0,0 @@ -bv \ No newline at end of file diff --git a/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.css b/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.css index 71fb5a64e8..5887ae6dd5 100644 --- a/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.css +++ b/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.css @@ -28,20 +28,10 @@ dashboard-page .home-tab-icon { background-image: url("media/home.svg"); } -.vs-dark dashboard-page .home-tab-icon, -.hc-black dashboard-page .home-tab-icon { - background-image: url("media/home_inverse.svg"); -} - dashboard-page .default-tab-icon { background-image: url("media/default.svg"); } -.vs-dark dashboard-page .default-tab-icon, -.hc-black dashboard-page .default-tab-icon { - background-image: url("media/default_inverse.svg"); -} - dashboard-page .actions-container .action-item .action-label{ padding-left: 20px; padding-right: 5px; diff --git a/src/sql/workbench/contrib/dashboard/browser/core/media/default.svg b/src/sql/workbench/contrib/dashboard/browser/core/media/default.svg index d01374de7b..3a5fe76c0e 100644 --- a/src/sql/workbench/contrib/dashboard/browser/core/media/default.svg +++ b/src/sql/workbench/contrib/dashboard/browser/core/media/default.svg @@ -1,3 +1,8 @@ - - - \ No newline at end of file + + Icon-other-345 + + + + + + diff --git a/src/sql/workbench/contrib/dashboard/browser/core/media/default_inverse.svg b/src/sql/workbench/contrib/dashboard/browser/core/media/default_inverse.svg deleted file mode 100644 index 330119a880..0000000000 --- a/src/sql/workbench/contrib/dashboard/browser/core/media/default_inverse.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/src/sql/workbench/contrib/dashboard/browser/core/media/home.svg b/src/sql/workbench/contrib/dashboard/browser/core/media/home.svg index 29c5e2b0ec..ffdfb8a232 100644 --- a/src/sql/workbench/contrib/dashboard/browser/core/media/home.svg +++ b/src/sql/workbench/contrib/dashboard/browser/core/media/home.svg @@ -1 +1,18 @@ -bv \ No newline at end of file + + + + + + + + + + + + Icon-general-17 + + + + + + diff --git a/src/sql/workbench/contrib/dashboard/browser/core/media/home_inverse.svg b/src/sql/workbench/contrib/dashboard/browser/core/media/home_inverse.svg deleted file mode 100644 index abed877ac3..0000000000 --- a/src/sql/workbench/contrib/dashboard/browser/core/media/home_inverse.svg +++ /dev/null @@ -1 +0,0 @@ -bv \ No newline at end of file From 433049d1b2fd311ef519321427e21383dff29769 Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Thu, 9 Apr 2020 14:57:46 -0700 Subject: [PATCH 09/19] pre-defined dashboard tab groups (#9916) * pre-defined dashboard tab groups * add back the tab group contribution * comments --- extensions/agent/package.json | 3 +- extensions/mssql/package.json | 1 + .../browser/core/dashboardTab.contribution.ts | 28 +++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/extensions/agent/package.json b/extensions/agent/package.json index 2554be8171..1df9f53801 100644 --- a/extensions/agent/package.json +++ b/extensions/agent/package.json @@ -32,6 +32,7 @@ "description": "Manage and troubleshoot SQL Agent jobs", "provider": "MSSQL", "title": "SQL Agent", + "group": "administration", "when": "connectionProvider == 'MSSQL' && !mssql:iscloud && mssql:engineedition != 11 && dashboardContext == 'server'", "container": { "controlhost-container": { @@ -91,7 +92,7 @@ "vscodetestcover": "github:corivera/vscodetestcover#1.0.5" }, "resolutions": { - "esprima": "^4.0.0" + "esprima": "^4.0.0" }, "__metadata": { "id": "10", diff --git a/extensions/mssql/package.json b/extensions/mssql/package.json index 873d738bf8..8c5a707288 100644 --- a/extensions/mssql/package.json +++ b/extensions/mssql/package.json @@ -444,6 +444,7 @@ "description": "%tab.bigDataClusterDescription%", "provider": "MSSQL", "title": "%title.bigDataCluster%", + "group": "monitoring", "when": "connectionProvider == 'MSSQL' && mssql:iscluster && dashboardContext == 'server'", "container": { "grid-container": [ diff --git a/src/sql/workbench/contrib/dashboard/browser/core/dashboardTab.contribution.ts b/src/sql/workbench/contrib/dashboard/browser/core/dashboardTab.contribution.ts index 6f2c73ce8d..5c813e4317 100644 --- a/src/sql/workbench/contrib/dashboard/browser/core/dashboardTab.contribution.ts +++ b/src/sql/workbench/contrib/dashboard/browser/core/dashboardTab.contribution.ts @@ -17,6 +17,7 @@ import { GRID_CONTAINER, validateGridContainerContribution } from 'sql/workbench import { values } from 'vs/base/common/collections'; import { IUserFriendlyIcon } from 'sql/workbench/contrib/dashboard/browser/core/dashboardWidget'; import { isValidIcon, createCSSRuleForIcon } from 'sql/workbench/contrib/dashboard/browser/dashboardIconUtil'; +import { IDashboardTabGroup } from 'sql/workbench/services/dashboard/browser/common/interfaces'; export interface IDashboardTabContrib { id: string; @@ -237,3 +238,30 @@ ExtensionsRegistry.registerExtensionPoint registerTabGroup(tabGroup)); From 8ff53281f9607bd46be06b87b20bfcfdabe4f8af Mon Sep 17 00:00:00 2001 From: Kim Santiago <31145923+kisantia@users.noreply.github.com> Date: Thu, 9 Apr 2020 16:31:52 -0700 Subject: [PATCH 10/19] Dashboard toolbar overflow (#9796) * initial changes for actionbar collapsing * fix more not always all showing after resizing * collapse toolbar if window size is already small when dashboard is opened * make wrapping default behavior and collapse opt in * fix so keyboard navigation works in overflow * more keyboard fixing so that the actions in overflow get triggered * change overflow background with theme * change margin * udpate more button * use icon for ... * addressing comments * overflow css changes to match portal * arrow navigation working * handle tab and shift tab in overflow * keep arrow navigation within overflow * move reused code to helper methods * set roles for overflow * use actionsList instead of document.getElementById all the time * move collapsible action bar to its own class * renamve to overflowActionBar * fix focus rectangle around more element * hide overflow after an action is executed * hide overflow when clicking an action * hide overflow when focus leaves and loop focus within overflow when using arrow keys * fix double down arrow to move focus in overflow * update comment * fix clicking more not hiding overflow * add box-shadow for themes * fix hygiene error * fix hygiene error * widen focused outline for overflow actions --- src/sql/base/browser/ui/taskbar/actionbar.ts | 31 +- .../base/browser/ui/taskbar/media/taskbar.css | 53 +++ .../browser/ui/taskbar/overflowActionbar.ts | 328 ++++++++++++++++++ .../ui/taskbar/overflowActionbarStyles.ts | 43 +++ src/sql/base/browser/ui/taskbar/taskbar.ts | 33 +- .../browser/core/dashboardPage.component.ts | 2 +- .../browser/core/dashboardPanelStyles.ts | 2 +- src/vs/workbench/common/theme.ts | 8 +- 8 files changed, 474 insertions(+), 26 deletions(-) create mode 100644 src/sql/base/browser/ui/taskbar/overflowActionbar.ts create mode 100644 src/sql/base/browser/ui/taskbar/overflowActionbarStyles.ts diff --git a/src/sql/base/browser/ui/taskbar/actionbar.ts b/src/sql/base/browser/ui/taskbar/actionbar.ts index 822f72a65f..68975ce583 100644 --- a/src/sql/base/browser/ui/taskbar/actionbar.ts +++ b/src/sql/base/browser/ui/taskbar/actionbar.ts @@ -27,18 +27,18 @@ const defaultOptions: IActionBarOptions = { */ export class ActionBar extends ActionRunner implements IActionRunner { - private _options: IActionBarOptions; - private _actionRunner: IActionRunner; - private _context: any; + protected _options: IActionBarOptions; + protected _actionRunner: IActionRunner; + protected _context: any; // Items - private _items: IActionViewItem[]; - private _focusedItem?: number; - private _focusTracker: DOM.IFocusTracker; + protected _items: IActionViewItem[]; + protected _focusedItem?: number; + protected _focusTracker: DOM.IFocusTracker; // Elements - private _domNode: HTMLElement; - private _actionsList: HTMLElement; + protected _domNode: HTMLElement; + protected _actionsList: HTMLElement; constructor(container: HTMLElement, options: IActionBarOptions = defaultOptions) { super(); @@ -128,6 +128,7 @@ export class ActionBar extends ActionRunner implements IActionRunner { this._actionsList = document.createElement('ul'); this._actionsList.className = 'actions-container'; this._actionsList.setAttribute('role', 'toolbar'); + this._actionsList.id = 'actions-container'; if (this._options.ariaLabel) { this._actionsList.setAttribute('aria-label', this._options.ariaLabel); } @@ -145,7 +146,7 @@ export class ActionBar extends ActionRunner implements IActionRunner { } } - private updateFocusedItem(): void { + protected updateFocusedItem(): void { let actionIndex = 0; for (let i = 0; i < this._actionsList.children.length; i++) { let elem = this._actionsList.children[i]; @@ -155,7 +156,7 @@ export class ActionBar extends ActionRunner implements IActionRunner { break; } - if (elem.classList.contains('action-item')) { + if (elem.classList.contains('action-item') && i !== this._actionsList.children.length - 1) { actionIndex++; } } @@ -268,7 +269,7 @@ export class ActionBar extends ActionRunner implements IActionRunner { this.updateFocus(); } - private focusNext(): void { + protected focusNext(): void { if (typeof this._focusedItem === 'undefined') { this._focusedItem = this._items.length - 1; } @@ -288,7 +289,7 @@ export class ActionBar extends ActionRunner implements IActionRunner { this.updateFocus(); } - private focusPrevious(): void { + protected focusPrevious(): void { if (typeof this._focusedItem === 'undefined') { this._focusedItem = 0; } @@ -313,7 +314,7 @@ export class ActionBar extends ActionRunner implements IActionRunner { this.updateFocus(); } - private updateFocus(): void { + protected updateFocus(): void { if (typeof this._focusedItem === 'undefined') { this._domNode.focus(); return; @@ -329,7 +330,7 @@ export class ActionBar extends ActionRunner implements IActionRunner { actionItem.focus(); } } else { - if (types.isFunction(actionItem.blur)) { + if (actionItem && types.isFunction(actionItem.blur)) { actionItem.blur(); } } @@ -349,7 +350,7 @@ export class ActionBar extends ActionRunner implements IActionRunner { } } - private cancel(): void { + protected cancel(): void { if (document.activeElement instanceof HTMLElement) { (document.activeElement).blur(); // remove focus from focussed action } diff --git a/src/sql/base/browser/ui/taskbar/media/taskbar.css b/src/sql/base/browser/ui/taskbar/media/taskbar.css index 701d37cc0b..f0f18e4f35 100644 --- a/src/sql/base/browser/ui/taskbar/media/taskbar.css +++ b/src/sql/base/browser/ui/taskbar/media/taskbar.css @@ -47,6 +47,10 @@ margin-right: 5px; } +.carbon-taskbar .action-item.more { + padding-right: 15px; +} + .carbon-taskbar .action-label { background-repeat: no-repeat; background-position: 0% 50%; @@ -86,3 +90,52 @@ .carbon-taskbar .codicon { background-size: 11px; } + +.carbon-taskbar .overflow { + position:absolute; + right:0; + display:none; + z-index: 3; + padding-left: 0; + margin-top: 6px; + margin-right: 5px; +} + +.vs-dark .carbon-taskbar .overflow { + box-sizing: border-box; +} + +.hc-black .carbon-taskbar .overflow { + box-shadow: none; +} + +.carbon-taskbar .overflow li { + padding: 5px; + margin-right: 0; +} + +.carbon-taskbar .overflow li.focused a.action-label { + outline: none; +} + +.carbon-taskbar .overflow a { + padding-left: 20px; +} + +.carbon-taskbar .actions-container .action-item .action-label.moreActionsElement { + height: 18px; + margin-top: 3px; + background-position: center; + padding-right: 20px; +} + +.carbon-taskbar .overflow .action-item .action-label{ + background-size: 16px; + background-position: left; +} + +.overflow .taskbarSeparator { + height: 1px; + width: 100%; + margin-left: 0; +} diff --git a/src/sql/base/browser/ui/taskbar/overflowActionbar.ts b/src/sql/base/browser/ui/taskbar/overflowActionbar.ts new file mode 100644 index 0000000000..fa8339dbeb --- /dev/null +++ b/src/sql/base/browser/ui/taskbar/overflowActionbar.ts @@ -0,0 +1,328 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import 'sql/base/browser/ui/taskbar/overflowActionbarStyles'; + +import { IAction } from 'vs/base/common/actions'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { + IActionBarOptions, ActionsOrientation, IActionViewItem, + IActionOptions +} from 'vs/base/browser/ui/actionbar/actionbar'; +import * as DOM from 'vs/base/browser/dom'; +import * as types from 'vs/base/common/types'; +import * as nls from 'vs/nls'; +import { debounce } from 'vs/base/common/decorators'; +import { ActionBar } from 'sql/base/browser/ui/taskbar/actionbar'; + +const defaultOptions: IActionBarOptions = { + orientation: ActionsOrientation.HORIZONTAL, + context: null +}; + +/** + * Extends Actionbar so that it overflows when the window is resized to be smaller than the actionbar instead of wrapping + */ +export class OverflowActionBar extends ActionBar { + // Elements + private _overflow: HTMLElement; + private _moreItemElement: HTMLElement; + private _moreActionsElement: HTMLElement; + + constructor(container: HTMLElement, options: IActionBarOptions = defaultOptions) { + super(container, options); + + this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, e => { + if (this._actionsList) { + this.resizeToolbar(); + } + })); + + this._overflow = document.createElement('ul'); + this._overflow.id = 'overflow'; + this._overflow.className = 'overflow'; + this._overflow.setAttribute('role', 'menu'); + this._domNode.appendChild(this._overflow); + + this._register(DOM.addDisposableListener(this._overflow, DOM.EventType.FOCUS_OUT, e => { + if (this._overflow && !DOM.isAncestor(e.relatedTarget as HTMLElement, this._overflow) && e.relatedTarget !== this._moreActionsElement) { + this.hideOverflowDisplay(); + } + })); + this._actionsList.style.flexWrap = 'nowrap'; + + container.appendChild(this._domNode); + } + + @debounce(300) + private resizeToolbar() { + let width = this._actionsList.offsetWidth; + let fullWidth = this._actionsList.scrollWidth; + + // collapse actions that are beyond the width of the toolbar + if (width < fullWidth) { + // create '•••' more element if it doesn't exist yet + if (!this._moreItemElement) { + this.createMoreItemElement(); + } + + this._moreItemElement.style.display = 'block'; + while (width < fullWidth) { + let index = this._actionsList.childNodes.length - 2; // remove the last toolbar action before the more actions '...' + if (index > -1) { + this.collapseItem(); + fullWidth = this._actionsList.scrollWidth; + } else { + break; + } + } + } else if (this._overflow?.hasChildNodes()) { // uncollapse actions if there is space for it + while (width === fullWidth && this._overflow.hasChildNodes()) { + // move placeholder in this._items + let placeHolderItem = this._items.splice(this._actionsList.childNodes.length - 1, 1); + this._items.splice(this._actionsList.childNodes.length, 0, placeHolderItem[0]); + + let item = this._overflow.removeChild(this._overflow.firstChild); + // change role back to button when it's in the toolbar + if ((item).className !== 'taskbarSeparator') { + (item.firstChild).setAttribute('role', 'button'); + } + this._actionsList.insertBefore(item, this._actionsList.lastChild); + + // if the action was too wide, collapse it again + if (this._actionsList.scrollWidth > this._actionsList.offsetWidth) { + // move placeholder in this._items + this.collapseItem(); + break; + } else if (!this._overflow.hasChildNodes()) { + this._moreItemElement.style.display = 'none'; + } + } + } + } + + private collapseItem(): void { + // move placeholder in this._items + let placeHolderItem = this._items.splice(this._actionsList.childNodes.length - 1, 1); + this._items.splice(this._actionsList.childNodes.length - 2, 0, placeHolderItem[0]); + + let index = this._actionsList.childNodes.length - 2; // remove the last toolbar action before the more actions '...' + let item = this._actionsList.removeChild(this._actionsList.childNodes[index]); + this._overflow.insertBefore(item, this._overflow.firstChild); + this._register(DOM.addDisposableListener(item, DOM.EventType.CLICK, (e => { this.hideOverflowDisplay(); }))); + + // change role to menuItem when it's in the overflow + if ((this._overflow.firstChild).className !== 'taskbarSeparator') { + (this._overflow.firstChild.firstChild).setAttribute('role', 'menuItem'); + } + } + + private createMoreItemElement(): void { + this._moreItemElement = document.createElement('li'); + this._moreItemElement.className = 'action-item more'; + this._moreItemElement.setAttribute('role', 'presentation'); + this._moreActionsElement = document.createElement('a'); + this._moreActionsElement.className = 'moreActionsElement action-label codicon toggle-more'; + this._moreActionsElement.setAttribute('role', 'button'); + this._moreActionsElement.title = nls.localize('toggleMore', "Toggle More"); + this._moreActionsElement.tabIndex = 0; + this._moreActionsElement.setAttribute('aria-haspopup', 'true'); + this._register(DOM.addDisposableListener(this._moreActionsElement, DOM.EventType.CLICK, (e => { + this.moreElementOnClick(e); + }))); + this._register(DOM.addDisposableListener(this._moreActionsElement, DOM.EventType.KEY_UP, (ev => { + let event = new StandardKeyboardEvent(ev); + if (event.keyCode === KeyCode.Enter || event.keyCode === KeyCode.Space) { + this.moreElementOnClick(event); + } + }))); + + this._register(DOM.addDisposableListener(this._overflow, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { + let event = new StandardKeyboardEvent(e); + + // Close overflow if Escape is pressed + if (event.equals(KeyCode.Escape)) { + this.hideOverflowDisplay(); + this._moreActionsElement.focus(); + } else if (event.equals(KeyCode.UpArrow)) { + // up arrow on first element in overflow should move focus to the bottom of the overflow + if (this._focusedItem === this._actionsList.childElementCount) { + this._focusedItem = this._actionsList.childElementCount + this._overflow.childElementCount - 2; + this.updateFocus(); + } else { + this.focusPrevious(); + } + } else if (event.equals(KeyCode.DownArrow)) { + // down arrow on last element should move focus to the first element of the overflow + if (this._focusedItem === this._actionsList.childNodes.length + this._overflow.childNodes.length - 2) { + this._focusedItem = this._actionsList.childElementCount; + this.updateFocus(); + } else { + this.focusNext(); + } + } else if (event.equals(KeyMod.Shift | KeyCode.Tab)) { + this.hideOverflowDisplay(); + this._focusedItem = this._actionsList.childElementCount - 1; + this.updateFocus(); + } else if (event.equals(KeyCode.Tab)) { + this.hideOverflowDisplay(); + } + DOM.EventHelper.stop(event, true); + })); + + this._moreItemElement.appendChild(this._moreActionsElement); + this._actionsList.appendChild(this._moreItemElement); + this._items.push(undefined); // add place holder for more item element + } + + private moreElementOnClick(event: MouseEvent | StandardKeyboardEvent): void { + this._overflow.style.display = this._overflow.style.display === 'block' ? 'none' : 'block'; + if (this._overflow.style.display === 'block') { + this._focusedItem = this._actionsList.childElementCount; + this.updateFocus(); + } + DOM.EventHelper.stop(event, true); + } + + private hideOverflowDisplay(): void { + this._overflow.style.display = 'none'; + this._focusedItem = this._actionsList.childElementCount - 1; + } + + protected updateFocusedItem(): void { + let actionIndex = 0; + for (let i = 0; i < this._actionsList.children.length; i++) { + let elem = this._actionsList.children[i]; + + if (DOM.isAncestor(document.activeElement, elem)) { + this._focusedItem = actionIndex; + break; + } + + if (elem.classList.contains('action-item') && i !== this._actionsList.children.length - 1) { + actionIndex++; + } + } + + // move focus to overflow items if there are any + if (this._overflow) { + for (let i = 0; i < this._overflow.children.length; i++) { + let elem = this._overflow.children[i]; + + if (DOM.isAncestor(document.activeElement, elem)) { + this._focusedItem = actionIndex; + break; + } + + if (elem.classList.contains('action-item')) { + actionIndex++; + } + } + } + } + + /** + * Push an HTML Element onto the action bar UI in the position specified by options. + * Pushes to the last position if no options are provided. + */ + public pushElement(element: HTMLElement, options: IActionOptions = {}): void { + super.pushElement(element, options); + this.resizeToolbar(); + } + + /** + * Push an action onto the action bar UI in the position specified by options. + * Pushes to the last position if no options are provided. + */ + public pushAction(arg: IAction | IAction[], options: IActionOptions = {}): void { + super.pushAction(arg, options); + this.resizeToolbar(); + } + + protected focusNext(): void { + if (typeof this._focusedItem === 'undefined') { + this._focusedItem = this._items.length - 1; + } + + let startIndex = this._focusedItem; + let item: IActionViewItem; + + do { + this._focusedItem = (this._focusedItem + 1) % this._items.length; + item = this._items[this._focusedItem]; + } while (this._focusedItem !== startIndex && item && !item.isEnabled()); + + if (this._focusedItem === startIndex && item && !item.isEnabled()) { + this._focusedItem = undefined; + } + + this.updateFocus(); + } + + protected focusPrevious(): void { + if (typeof this._focusedItem === 'undefined') { + this._focusedItem = 0; + } + + let startIndex = this._focusedItem; + let item: IActionViewItem; + + do { + this._focusedItem = this._focusedItem - 1; + + if (this._focusedItem < 0) { + this._focusedItem = this._items.length - 1; + } + + item = this._items[this._focusedItem]; + } while (this._focusedItem !== startIndex && item && !item.isEnabled()); + + if (this._focusedItem === startIndex && item && !item.isEnabled()) { + this._focusedItem = undefined; + } + + this.updateFocus(); + } + + protected updateFocus(): void { + if (typeof this._focusedItem === 'undefined') { + this._domNode.focus(); + return; + } + + for (let i = 0; i < this._items.length; i++) { + let item = this._items[i]; + + let actionItem = item; + + if (i === this._focusedItem) { + // placeholder for location of moreActionsElement + if (!actionItem) { + this._moreActionsElement.focus(); + } + else if (types.isFunction(actionItem.focus)) { + actionItem.focus(); + } + } else { + if (actionItem && types.isFunction(actionItem.blur)) { + actionItem.blur(); + } + } + } + } + + protected cancel(): void { + super.cancel(); + + if (this._overflow) { + this.hideOverflowDisplay(); + } + } + + public run(action: IAction, context?: any): Promise { + this.hideOverflowDisplay(); + return this._actionRunner.run(action, context); + } +} diff --git a/src/sql/base/browser/ui/taskbar/overflowActionbarStyles.ts b/src/sql/base/browser/ui/taskbar/overflowActionbarStyles.ts new file mode 100644 index 0000000000..ad1a62075a --- /dev/null +++ b/src/sql/base/browser/ui/taskbar/overflowActionbarStyles.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// eslint-disable-next-line code-import-patterns +import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +// eslint-disable-next-line code-import-patterns +import { EDITOR_PANE_BACKGROUND, DASHBOARD_BORDER, TOOLBAR_OVERFLOW_SHADOW } from 'vs/workbench/common/theme'; +// eslint-disable-next-line code-import-patterns +import { focusBorder } from 'vs/platform/theme/common/colorRegistry'; + +registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { + const overflowBackground = theme.getColor(EDITOR_PANE_BACKGROUND); + if (overflowBackground) { + collector.addRule(`.carbon-taskbar .overflow { + background-color: ${overflowBackground}; + }`); + } + + const overflowShadow = theme.getColor(TOOLBAR_OVERFLOW_SHADOW); + if (overflowShadow) { + collector.addRule(`.carbon-taskbar .overflow { + box-shadow: 0px 4px 4px ${overflowShadow}; + }`); + } + + const border = theme.getColor(DASHBOARD_BORDER); + if (border) { + collector.addRule(`.carbon-taskbar .overflow { + border: 1px solid ${border}; + }`); + } + + const activeOutline = theme.getColor(focusBorder); + if (activeOutline) { + collector.addRule(`.carbon-taskbar .overflow li.focused { + outline: 1px solid; + outline-offset: -3px; + outline-color: ${activeOutline} + }`); + } +}); diff --git a/src/sql/base/browser/ui/taskbar/taskbar.ts b/src/sql/base/browser/ui/taskbar/taskbar.ts index ebceddbb7d..312cded555 100644 --- a/src/sql/base/browser/ui/taskbar/taskbar.ts +++ b/src/sql/base/browser/ui/taskbar/taskbar.ts @@ -11,6 +11,7 @@ import { ActionBar } from './actionbar'; import { IActionRunner, IAction } from 'vs/base/common/actions'; import { ActionsOrientation, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IToolBarOptions } from 'vs/base/browser/ui/toolbar/toolbar'; +import { OverflowActionBar } from 'sql/base/browser/ui/taskbar/overflowActionbar'; /** * A wrapper for the different types of content a QueryTaskbar can display @@ -33,20 +34,36 @@ export class Taskbar { private options: IToolBarOptions; private actionBar: ActionBar; - constructor(container: HTMLElement, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) { + constructor(container: HTMLElement, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }, collapseOverflow: boolean = false) { this.options = options; let element = document.createElement('div'); element.className = 'monaco-toolbar carbon-taskbar'; container.appendChild(element); - this.actionBar = new ActionBar(element, { - orientation: options.orientation, - ariaLabel: options.ariaLabel, - actionViewItemProvider: (action: IAction): IActionViewItem | undefined => { - return options.actionViewItemProvider ? options.actionViewItemProvider(action) : undefined; - } - }); + if (collapseOverflow) { + this.actionBar = new OverflowActionBar( + element, + { + orientation: options.orientation, + ariaLabel: options.ariaLabel, + actionViewItemProvider: (action: IAction): IActionViewItem | undefined => { + return options.actionViewItemProvider ? options.actionViewItemProvider(action) : undefined; + } + } + ); + } else { + this.actionBar = new ActionBar( + element, + { + orientation: options.orientation, + ariaLabel: options.ariaLabel, + actionViewItemProvider: (action: IAction): IActionViewItem | undefined => { + return options.actionViewItemProvider ? options.actionViewItemProvider(action) : undefined; + } + } + ); + } } /** diff --git a/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.component.ts b/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.component.ts index 2694812395..3e75c4c0fa 100644 --- a/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.component.ts @@ -202,7 +202,7 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig private createToolbar(parentElement: HTMLElement, tabId: string): void { // clear out toolbar DOM.clearNode(parentElement); - this.toolbar = this._register(new Taskbar(parentElement, { actionViewItemProvider: action => this.createActionItemProvider(action as Action) })); + this.toolbar = this._register(new Taskbar(parentElement, { actionViewItemProvider: action => this.createActionItemProvider(action as Action) }, true)); let content = []; content = this.getToolbarContent(tabId); if (tabId === this.homeTabId) { diff --git a/src/sql/workbench/contrib/dashboard/browser/core/dashboardPanelStyles.ts b/src/sql/workbench/contrib/dashboard/browser/core/dashboardPanelStyles.ts index 7d660e6ed3..bb0446b7e8 100644 --- a/src/sql/workbench/contrib/dashboard/browser/core/dashboardPanelStyles.ts +++ b/src/sql/workbench/contrib/dashboard/browser/core/dashboardPanelStyles.ts @@ -136,7 +136,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = } const sideBorder = theme.getColor(DASHBOARD_BORDER); - if (divider) { + if (sideBorder) { collector.addRule(`panel.dashboard-panel > .tabbedPanel.vertical > .title > .tabContainer { border-right-width: 1px; border-right-style: solid; diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 5e32e7dae9..e2833c75de 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { registerColor, editorBackground, contrastBorder, transparent, editorWidgetBackground, textLinkForeground, lighten, darken, focusBorder, activeContrastBorder, editorWidgetForeground, editorErrorForeground, editorWarningForeground, editorInfoForeground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme } from 'vs/platform/theme/common/themeService'; -import { Color } from 'vs/base/common/color'; +import { Color, RGBA } from 'vs/base/common/color'; // < --- Workbench (not customizable) --- > @@ -636,3 +636,9 @@ export const DASHBOARD_PROPERTIES_NAME = registerColor('dashboardWidget.properti dark: '#8A8886', hc: '#FFFFFF' }, nls.localize('dashboardWidgetPropertiesName', "Color for dashboard properties widget names")); + +export const TOOLBAR_OVERFLOW_SHADOW = registerColor('toolbar.overflowShadow', { + light: new Color(new RGBA(0, 0, 0, .132)), + dark: new Color(new RGBA(0, 0, 0, 0.25)), + hc: null +}, nls.localize('toolbarOverflowShadow', "Toolbar overflow shadow color")); From e5cf13726ea4c8801bff4e57766781ce437edeb9 Mon Sep 17 00:00:00 2001 From: Kim Santiago <31145923+kisantia@users.noreply.github.com> Date: Thu, 9 Apr 2020 16:53:40 -0700 Subject: [PATCH 11/19] bump sqltoolsservice to 2.0.0-release.56 (#9922) --- extensions/mssql/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/mssql/config.json b/extensions/mssql/config.json index b7eaec37c6..afdb9b7109 100644 --- a/extensions/mssql/config.json +++ b/extensions/mssql/config.json @@ -1,6 +1,6 @@ { "downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}", - "version": "2.0.0-release.53", + "version": "2.0.0-release.56", "downloadFileNames": { "Windows_86": "win-x86-netcoreapp2.2.zip", "Windows_64": "win-x64-netcoreapp2.2.zip", From 8fd20c206954808731e0982d774367c6147dfdb9 Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Thu, 9 Apr 2020 17:51:31 -0700 Subject: [PATCH 12/19] Ignore jpgs for whitespace check (#9925) --- build/gulpfile.hygiene.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index d86f486ad3..e3021c33d8 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -97,7 +97,7 @@ const indentationFilter = [ '!extensions/markdown-language-features/media/*.js', // {{SQL CARBON EDIT}} '!build/actions/**/dist/*', - '!**/*.{xlf,docx,sql,vsix,bacpac,ipynb}', + '!**/*.{xlf,docx,sql,vsix,bacpac,ipynb,jpg}', '!extensions/mssql/sqltoolsservice/**', '!extensions/import/flatfileimportservice/**', '!extensions/admin-tool-ext-win/ssmsmin/**', From 4856e0a978682b0034742459b53afac1c5fe9ef2 Mon Sep 17 00:00:00 2001 From: Alex Ma Date: Fri, 10 Apr 2020 10:56:47 -0700 Subject: [PATCH 13/19] Sash bar for Edit Data SQL pane (#9818) * WIP sash fix * stuff to fix * working mostly * added tabbedPanel fix * added null checks * removed additional space in editdata.css * wip change for border-top * Fix for query editor and editdata colors in modes * removed junk additions for panel.css * fixed div and classes for editDataGridPanel, * small optimizations * Small tweaks * simplified color checks * test commit * Test changes * no need for important * updates made * removed small space --- .../editData/browser/editDataEditor.ts | 28 ++++++++++++++++++- .../editData/browser/editDataGridPanel.ts | 5 ++-- .../editData/browser/editDataResultsEditor.ts | 1 - .../editData/browser/media/editData.css | 6 ++-- .../query/browser/queryResultsEditor.ts | 1 - 5 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/sql/workbench/contrib/editData/browser/editDataEditor.ts b/src/sql/workbench/contrib/editData/browser/editDataEditor.ts index edd05ce414..51f5c7b1b3 100644 --- a/src/sql/workbench/contrib/editData/browser/editDataEditor.ts +++ b/src/sql/workbench/contrib/editData/browser/editDataEditor.ts @@ -13,6 +13,7 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { PANEL_BORDER } from 'vs/workbench/common/theme'; import { EditDataInput } from 'sql/workbench/browser/editData/editDataInput'; @@ -279,6 +280,7 @@ export class EditDataEditor extends BaseEditor { } else { this._resultsEditorContainer = DOM.append(parentElement, input.results.container); } + this.updateStyles(); } /** @@ -297,6 +299,14 @@ export class EditDataEditor extends BaseEditor { this._sash.show(); } + + updateStyles() { + if (this._resultsEditorContainer) { + this._resultsEditorContainer.style.borderTopColor = this.getColor(PANEL_BORDER); + } + super.updateStyles(); + } + /** * Appends the HTML for the SQL editor. Creates new HTML every time. */ @@ -619,12 +629,26 @@ export class EditDataEditor extends BaseEditor { } else { this._sash.hide(); } + this.updateSashVisibility(); } this._updateTaskbar(newInput); return this._setNewInput(newInput, options); } + private updateSashVisibility(): void { + // change the visibility of the sash. + if (this._resultsEditorContainer) { + if (this.queryPaneEnabled()) { + this._resultsEditorContainer.style.borderTopStyle = 'solid'; + this._resultsEditorContainer.style.borderTopWidth = '1px'; + } else { + this._resultsEditorContainer.style.borderTopStyle = ''; + this._resultsEditorContainer.style.borderTopWidth = ''; + } + } + } + private _updateQueryEditorVisible(currentEditorIsVisible: boolean): void { if (this._queryEditorVisible) { let visible = currentEditorIsVisible; @@ -670,9 +694,11 @@ export class EditDataEditor extends BaseEditor { public toggleQueryPane(): void { this.editDataInput.queryPaneEnabled = !this.queryPaneEnabled(); + this.updateSashVisibility(); if (this.queryPaneEnabled()) { this._showQueryEditor(); - } else { + } + else { this._hideQueryEditor(); } this._doLayout(false); diff --git a/src/sql/workbench/contrib/editData/browser/editDataGridPanel.ts b/src/sql/workbench/contrib/editData/browser/editDataGridPanel.ts index c3aa0c77a0..e3d430ac38 100644 --- a/src/sql/workbench/contrib/editData/browser/editDataGridPanel.ts +++ b/src/sql/workbench/contrib/editData/browser/editDataGridPanel.ts @@ -98,8 +98,9 @@ export class EditDataGridPanel extends GridParentComponent { @ILogService protected logService: ILogService ) { super(contextMenuService, keybindingService, contextKeyService, configurationService, clipboardService, queryEditorService, logService); - this.nativeElement = document.createElement('editdatagridpanel'); - this.nativeElement.className = 'slickgridContainer'; + this.nativeElement = document.createElement('div'); + this.nativeElement.className = 'editDataGridPanel'; + this.nativeElement.classList.add('slickgridContainer'); this.dataService = dataService; this.actionProvider = this.instantiationService.createInstance(EditDataGridActionProvider, this.dataService, this.onGridSelectAll(), this.onDeleteRow(), this.onRevertRow()); onRestoreViewState(() => this.restoreViewState()); diff --git a/src/sql/workbench/contrib/editData/browser/editDataResultsEditor.ts b/src/sql/workbench/contrib/editData/browser/editDataResultsEditor.ts index 2c723231a1..5f66a78823 100644 --- a/src/sql/workbench/contrib/editData/browser/editDataResultsEditor.ts +++ b/src/sql/workbench/contrib/editData/browser/editDataResultsEditor.ts @@ -24,7 +24,6 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; 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; diff --git a/src/sql/workbench/contrib/editData/browser/media/editData.css b/src/sql/workbench/contrib/editData/browser/media/editData.css index f43c425a9c..a15943c7a8 100644 --- a/src/sql/workbench/contrib/editData/browser/media/editData.css +++ b/src/sql/workbench/contrib/editData/browser/media/editData.css @@ -3,10 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.editdatagridpanel * { - box-sizing: border-box; +.editDataGridPanel.slickgridContainer { + height: 447px; + width: 632px; } + #workbench\.editor\.editDataEditor .monaco-toolbar .monaco-select-box { margin-top: 4px; margin-bottom: 4px; diff --git a/src/sql/workbench/contrib/query/browser/queryResultsEditor.ts b/src/sql/workbench/contrib/query/browser/queryResultsEditor.ts index 3f58ed9ae8..448eadb32b 100644 --- a/src/sql/workbench/contrib/query/browser/queryResultsEditor.ts +++ b/src/sql/workbench/contrib/query/browser/queryResultsEditor.ts @@ -74,7 +74,6 @@ export function getBareResultsGridInfoStyles(info: BareResultsGridInfo): string export class QueryResultsEditor extends BaseEditor { public static ID: string = 'workbench.editor.queryResultsEditor'; - public static AngularSelectorString: string = 'slickgrid-container.slickgridContainer'; protected _rawOptions: BareResultsGridInfo; private resultsView: QueryResultsView; From 3eab267da61fa626f2d0f93a4b862f7ddfea819e Mon Sep 17 00:00:00 2001 From: Kim Santiago <31145923+kisantia@users.noreply.github.com> Date: Fri, 10 Apr 2020 13:39:27 -0700 Subject: [PATCH 14/19] fix bottom of widgets being cutoff (#9930) --- .../browser/contents/dashboardWidgetWrapper.component.ts | 1 + .../dashboard/browser/contents/dashboardWidgetWrapper.css | 1 + 2 files changed, 2 insertions(+) diff --git a/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.component.ts b/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.component.ts index e6d77c56a4..14f4d0dac8 100644 --- a/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.component.ts @@ -129,6 +129,7 @@ export class DashboardWidgetWrapper extends AngularDisposable implements OnInit this._collapseAction = this.instantiationService.createInstance(CollapseWidgetAction, this._bootstrap.getUnderlyingUri(), this.guid, this.collapsed); if (this.bottomCollapse) { this._bottomActionbar.push(this._collapseAction, { icon: true, label: false }); + this._bottomActionbarRef.nativeElement.style.display = 'block'; } else { this._actionbar.push(this._collapseAction, { icon: true, label: false }); } diff --git a/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.css b/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.css index d5081ce968..e9afec67ff 100644 --- a/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.css +++ b/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.css @@ -48,4 +48,5 @@ dashboard-widget-wrapper .bottomActionbar { flex: 0 0 auto; align-self: center; margin-top: -28px; + display: none; } From ba41e926c4c2b9b2e9dd80bd405fd0ecac3d734b Mon Sep 17 00:00:00 2001 From: Amir Omidi Date: Fri, 10 Apr 2020 15:12:57 -0700 Subject: [PATCH 15/19] Handle first time use when user doesn't have a cloud shell (#9890) * Handle first time use when user doesn't have a cloud shell * Catch errors * Update code * Update string per PM feedback * Update the internal URI --- .../azureResource/services/terminalService.ts | 94 ++++++++++++++----- 1 file changed, 71 insertions(+), 23 deletions(-) diff --git a/extensions/azurecore/src/azureResource/services/terminalService.ts b/extensions/azurecore/src/azureResource/services/terminalService.ts index b7f6a7e91c..9553b7acb2 100644 --- a/extensions/azurecore/src/azureResource/services/terminalService.ts +++ b/extensions/azurecore/src/azureResource/services/terminalService.ts @@ -5,13 +5,42 @@ import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; -import axios, { AxiosRequestConfig } from 'axios'; +import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; import * as WS from 'ws'; import { IAzureTerminalService } from '../interfaces'; import { AzureAccount, AzureAccountSecurityToken, Tenant } from '../../account-provider/interfaces'; const localize = nls.loadMessageBundle(); + + +const handleNeverUsed = async (): Promise => { + const neverUsedString = localize('azure.coudTerminal.neverUsed', "If you have not launched Azure Cloud Shell from this account before, please visit https://shell.azure.com/ to get started. Once you are set up, you can use AzureCloud Shell directly in Azure Data Studio."); + enum TerminalOption { + OPEN_SITE, + OK + } + interface TerminalMessageItem extends vscode.MessageItem { + action: TerminalOption; + } + + const openAzureShellButton: TerminalMessageItem = { + action: TerminalOption.OPEN_SITE, + title: localize('azure.cloudTerminal.openAzureShell', "Open Azure Shell") + }; + + const okButton: TerminalMessageItem = { + action: TerminalOption.OK, + title: localize('azure.cloudTerminal.ok', "OK") + }; + + const option = await vscode.window.showInformationMessage(neverUsedString, openAzureShellButton, okButton); + + if (option.action === TerminalOption.OPEN_SITE) { + vscode.env.openExternal(vscode.Uri.parse('https://aka.ms/AA83f8f')); + } +}; + export class AzureTerminalService implements IAzureTerminalService { private readonly apiVersion = '?api-version=2018-10-01'; @@ -31,9 +60,16 @@ export class AzureTerminalService implements IAzureTerminalService { }; const metadata = account.properties.providerSettings; - const userSettingsUri = this.getConsoleUserSettingsUri(metadata.settings.armResource.endpoint); - const userSettingsResult = await axios.get(userSettingsUri, settings); + + let userSettingsResult: AxiosResponse; + try { + userSettingsResult = await axios.get(userSettingsUri, settings); + } catch (ex) { + console.log(ex, ex.response); + await handleNeverUsed(); + return; + } const preferredShell = userSettingsResult.data?.properties?.preferredShellType ?? 'bash'; const preferredLocation = userSettingsResult.data?.properties?.preferredLocation; @@ -43,7 +79,14 @@ export class AzureTerminalService implements IAzureTerminalService { settings.headers['x-ms-console-preferred-location'] = preferredLocation; } - const provisionResult = await axios.put(consoleRequestUri, {}, settings); + let provisionResult: AxiosResponse; + try { + provisionResult = await axios.put(consoleRequestUri, {}, settings); + } catch (ex) { + console.log(ex, ex.response); + await handleNeverUsed(); + return; + } if (provisionResult.data?.properties?.provisioningState !== 'Succeeded') { throw new Error(provisionResult.data); @@ -114,7 +157,7 @@ class AzureTerminal implements vscode.Pseudoterminal { } async open(initialDimensions: vscode.TerminalDimensions): Promise { - return this.resetTerminalSize(initialDimensions); + this.setDimensions(initialDimensions); } close(): void { @@ -131,24 +174,22 @@ class AzureTerminal implements vscode.Pseudoterminal { } async setDimensions(dimensions: vscode.TerminalDimensions): Promise { - return this.resetTerminalSize(dimensions); + if (!dimensions) { + return; + } + this.terminalDimensions = dimensions; + return this.resetTerminalSize(); } - private async resetTerminalSize(dimensions: vscode.TerminalDimensions): Promise { + private async resetTerminalSize(): Promise { try { - - if (!this.terminalDimensions) { // first time - this.writeEmitter.fire(localize('azure.connectingShellTerminal', "Connecting terminal...\n")); - } - - if (dimensions) { - this.terminalDimensions = dimensions; - } - // Close the shell before this and restablish a new connection this.close(); const terminalUri = await this.establishTerminal(this.terminalDimensions); + if (!terminalUri) { + return; + } this.socket = new WS(terminalUri); this.socket.on('message', (data: WS.Data) => { @@ -172,13 +213,20 @@ class AzureTerminal implements vscode.Pseudoterminal { private async establishTerminal(dimensions: vscode.TerminalDimensions): Promise { - const terminalResult = await axios.post(`${this.consoleUri}/terminals?rows=${dimensions.rows}&cols=${dimensions.columns}&shell=${this.shell}`, undefined, { - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.token}` - } - }); + let terminalResult: AxiosResponse; + try { + terminalResult = await axios.post(`${this.consoleUri}/terminals?rows=${dimensions.rows}&cols=${dimensions.columns}&shell=${this.shell}`, undefined, { + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.token}` + } + }); + } catch (ex) { + console.log(ex, ex.response); + await handleNeverUsed(); + return undefined; + } const terminalUri = terminalResult.data?.socketUri; From b1a9c8418b12b1dd1ba69651c7097365a177ac60 Mon Sep 17 00:00:00 2001 From: Kim Santiago <31145923+kisantia@users.noreply.github.com> Date: Fri, 10 Apr 2020 15:47:53 -0700 Subject: [PATCH 16/19] center properties title when collapsed (#9932) --- .../dashboard/browser/contents/dashboardWidgetWrapper.css | 2 +- .../dashboard/browser/pages/databaseDashboardPage.component.ts | 2 +- .../dashboard/browser/pages/serverDashboardPage.component.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.css b/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.css index e9afec67ff..c87d4571dc 100644 --- a/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.css +++ b/src/sql/workbench/contrib/dashboard/browser/contents/dashboardWidgetWrapper.css @@ -47,6 +47,6 @@ dashboard-widget-wrapper .actionbar { dashboard-widget-wrapper .bottomActionbar { flex: 0 0 auto; align-self: center; - margin-top: -28px; + margin-top: -27px; display: none; } diff --git a/src/sql/workbench/contrib/dashboard/browser/pages/databaseDashboardPage.component.ts b/src/sql/workbench/contrib/dashboard/browser/pages/databaseDashboardPage.component.ts index a48be5bdc3..28431ba5f5 100644 --- a/src/sql/workbench/contrib/dashboard/browser/pages/databaseDashboardPage.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/pages/databaseDashboardPage.component.ts @@ -35,7 +35,7 @@ export class DatabaseDashboardPage extends DashboardPage implements OnInit { background_color: colors.editorBackground, border: 'none', fontSize: '14px', - padding: '5px 0 0 0', + padding: '2px 0 0 0', provider: undefined, edition: undefined }; diff --git a/src/sql/workbench/contrib/dashboard/browser/pages/serverDashboardPage.component.ts b/src/sql/workbench/contrib/dashboard/browser/pages/serverDashboardPage.component.ts index 3f05917111..046f970076 100644 --- a/src/sql/workbench/contrib/dashboard/browser/pages/serverDashboardPage.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/pages/serverDashboardPage.component.ts @@ -37,7 +37,7 @@ export class ServerDashboardPage extends DashboardPage implements OnInit { background_color: colors.editorBackground, border: 'none', fontSize: '14px', - padding: '5px 0 0 0', + padding: '2px 0 0 0', provider: undefined, edition: undefined }; From e450369d5ec2a92092d20ad4e40ed48ce28e41a3 Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Fri, 10 Apr 2020 23:05:21 -0700 Subject: [PATCH 17/19] unify the panel styles (#9934) --- src/sql/base/browser/ui/panel/media/panel.css | 9 +- .../base/browser/ui/panel/panel.component.ts | 78 ++++++++++- src/sql/base/browser/ui/panel/panel.ts | 3 + .../browser/ui/panel/tabHeader.component.ts | 2 +- .../modelComponents/media/tabbedPanel.css | 46 ------- .../modelComponents/tabbedPanel.component.ts | 7 +- src/sql/workbench/common/styler.ts | 11 +- .../browser/core/dashboardPage.component.ts | 2 + .../dashboard/browser/core/dashboardPanel.css | 16 --- .../browser/core/dashboardPanelStyles.ts | 130 +----------------- 10 files changed, 104 insertions(+), 200 deletions(-) diff --git a/src/sql/base/browser/ui/panel/media/panel.css b/src/sql/base/browser/ui/panel/media/panel.css index 5434d4c3cb..209513521a 100644 --- a/src/sql/base/browser/ui/panel/media/panel.css +++ b/src/sql/base/browser/ui/panel/media/panel.css @@ -76,6 +76,10 @@ panel { min-width: 65px; } +.tabbedPanel.horizontal > .title .tabList .tab-header { + margin: 5px; +} + .tabbedPanel.vertical > .title .tabList .tab-header { display: block; min-width: 150px; @@ -156,9 +160,8 @@ panel { .tabbedPanel .tab-group-header { font-weight: 600; font-size: 14px; - margin: 15px 24px 3px 24px; - line-height: 20px; - height: 35px; + margin: 10px 24px 5px 24px; + line-height: 35px; border-style: solid; border-width: 0 0 1px 0; } diff --git a/src/sql/base/browser/ui/panel/panel.component.ts b/src/sql/base/browser/ui/panel/panel.component.ts index 2510014791..cd35561468 100644 --- a/src/sql/base/browser/ui/panel/panel.component.ts +++ b/src/sql/base/browser/ui/panel/panel.component.ts @@ -23,6 +23,9 @@ import * as nls from 'vs/nls'; import { TabHeaderComponent } from 'sql/base/browser/ui/panel/tabHeader.component'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; +import { IThemable } from 'vs/base/common/styler'; +import { ITabbedPanelStyles } from 'sql/base/browser/ui/panel/panel'; +import { createStyleSheet } from 'vs/base/browser/dom'; export interface IPanelOptions { /** @@ -49,7 +52,7 @@ let idPool = 0; @Component({ selector: 'panel', template: ` -
+
@@ -81,7 +84,7 @@ let idPool = 0;
` }) -export class PanelComponent extends Disposable { +export class PanelComponent extends Disposable implements IThemable { @Input() public options?: IPanelOptions; @Input() public actions?: Array; @ContentChildren(TabComponent) private readonly _tabs!: QueryList; @@ -95,12 +98,15 @@ export class PanelComponent extends Disposable { private _actionbar?: ActionBar; private _mru: TabComponent[] = []; private _tabExpanded: boolean = true; + private _styleElement: HTMLStyleElement; protected AutoScrollbarVisibility = ScrollbarVisibility.Auto; // used by angular template protected HiddenScrollbarVisibility = ScrollbarVisibility.Hidden; // used by angular template protected NavigationBarLayout = NavigationBarLayout; // used by angular template @ViewChild('panelActionbar', { read: ElementRef }) private _actionbarRef!: ElementRef; + @ViewChild('rootContainer', { read: ElementRef }) private _rootContainer!: ElementRef; + constructor( @Inject(forwardRef(() => NgZone)) private _zone: NgZone, @Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef) { @@ -122,6 +128,8 @@ export class PanelComponent extends Disposable { ngOnInit(): void { this.options = mixin(this.options || {}, defaultOptions, false); + const rootContainerElement = this._rootContainer.nativeElement as HTMLElement; + this._styleElement = createStyleSheet(rootContainerElement); } ngAfterContentInit(): void { @@ -321,4 +329,70 @@ export class PanelComponent extends Disposable { return header.nativeElement === document.activeElement; }); } + + style(styles: ITabbedPanelStyles) { + if (this._styleElement) { + const content: string[] = []; + if (styles.titleInactiveForeground) { + content.push(`.tabbedPanel.horizontal > .title .tabList .tab-header { + color: ${styles.titleInactiveForeground} + }`); + } + if (styles.titleActiveBorder && styles.titleActiveForeground) { + content.push(`.tabbedPanel.horizontal > .title .tabList .tab-header:focus, + .tabbedPanel.horizontal > .title .tabList .tab-header.active { + border-color: ${styles.titleActiveBorder}; + border-style: solid; + color: ${styles.titleActiveForeground} + }`); + + content.push(`.tabbedPanel.horizontal > .title .tabList .tab-header:focus, + .tabbedPanel.horizontal > .title .tabList .tab-header.active {; + border-width: 0 0 ${styles.activeTabContrastBorder ? '0' : '2'}px 0; + }`); + + content.push(`.tabbedPanel.horizontal > .title .tabList .tab-header:hover { + color: ${styles.titleActiveForeground} + }`); + } + + if (styles.activeBackgroundForVerticalLayout) { + content.push(`.tabbedPanel.vertical > .title .tabList .tab-header.active { + background-color:${styles.activeBackgroundForVerticalLayout} + }`); + } + + if (styles.border) { + content.push(`.tabbedPanel.vertical > .title > .tabContainer { + border-right-width: 1px; + border-right-style: solid; + border-right-color: ${styles.border}; + } + + .tabbedPanel .tab-group-header { + border-color: ${styles.border}; + }`); + } + + if (styles.activeTabContrastBorder) { + content.push(` + .tabbedPanel > .title .tabList .tab-header.active { + outline: 1px solid; + outline-offset: -3px; + outline-color: ${styles.activeTabContrastBorder}; + } + `); + } else { + content.push(`.tabbedPanel.horizontal > .title .tabList .tab-header:focus { + outline-width: 0px; + }`); + } + + const newStyles = content.join('\n'); + if (newStyles !== this._styleElement.innerHTML) { + this._styleElement.innerHTML = newStyles; + } + } + } + } diff --git a/src/sql/base/browser/ui/panel/panel.ts b/src/sql/base/browser/ui/panel/panel.ts index 85921f09d3..b9ba9a6ab4 100644 --- a/src/sql/base/browser/ui/panel/panel.ts +++ b/src/sql/base/browser/ui/panel/panel.ts @@ -23,6 +23,9 @@ export interface ITabbedPanelStyles { titleInactiveForeground?: Color; focusBorder?: Color; outline?: Color; + activeBackgroundForVerticalLayout?: Color; + border?: Color; + activeTabContrastBorder?: Color; } export interface IPanelOptions { diff --git a/src/sql/base/browser/ui/panel/tabHeader.component.ts b/src/sql/base/browser/ui/panel/tabHeader.component.ts index 5da411aac2..12aa962493 100644 --- a/src/sql/base/browser/ui/panel/tabHeader.component.ts +++ b/src/sql/base/browser/ui/panel/tabHeader.component.ts @@ -19,7 +19,7 @@ import { CloseTabAction } from 'sql/base/browser/ui/panel/tabActions'; @Component({ selector: 'tab-header', template: ` -