From b03c0a3e2d69e3ee1448bf59b5ba6051c38500ad Mon Sep 17 00:00:00 2001 From: Alan Ren Date: Thu, 13 Sep 2018 15:07:08 -0700 Subject: [PATCH] accessibility setting based select database dropdown (#2579) --- .../parts/query/editor/media/queryActions.css | 5 + src/sql/parts/query/editor/queryEditor.ts | 3 +- src/sql/parts/query/execution/queryActions.ts | 123 +++++++++++++----- .../parts/query/editor/queryActions.test.ts | 17 ++- .../parts/query/editor/queryEditor.test.ts | 4 +- 5 files changed, 115 insertions(+), 37 deletions(-) create mode 100644 src/sql/parts/query/editor/media/queryActions.css diff --git a/src/sql/parts/query/editor/media/queryActions.css b/src/sql/parts/query/editor/media/queryActions.css new file mode 100644 index 0000000000..7dda6c6612 --- /dev/null +++ b/src/sql/parts/query/editor/media/queryActions.css @@ -0,0 +1,5 @@ +.monaco-select-box { + cursor: pointer; + min-width: 150px; + padding: 2px; +} \ No newline at end of file diff --git a/src/sql/parts/query/editor/queryEditor.ts b/src/sql/parts/query/editor/queryEditor.ts index e6a51bcb45..4b28f01b8a 100644 --- a/src/sql/parts/query/editor/queryEditor.ts +++ b/src/sql/parts/query/editor/queryEditor.ts @@ -43,7 +43,6 @@ import { import { IQueryModelService } from 'sql/parts/query/execution/queryModel'; import { IEditorDescriptorService } from 'sql/parts/query/editor/editorDescriptorService'; import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement'; -import { attachEditableDropdownStyler } from 'sql/common/theme/styler'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -500,7 +499,7 @@ export class QueryEditor extends BaseEditor { public get listDatabasesActionItem(): ListDatabasesActionItem { if (!this._listDatabasesActionItem) { this._listDatabasesActionItem = this._instantiationService.createInstance(ListDatabasesActionItem, this, this._listDatabasesAction); - this._register(attachEditableDropdownStyler(this._listDatabasesActionItem, this.themeService)); + this._register(this._listDatabasesActionItem.attachStyler(this.themeService)); } return this._listDatabasesActionItem; } diff --git a/src/sql/parts/query/execution/queryActions.ts b/src/sql/parts/query/execution/queryActions.ts index b8379b3444..5a40d7fe23 100644 --- a/src/sql/parts/query/execution/queryActions.ts +++ b/src/sql/parts/query/execution/queryActions.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!sql/parts/query/editor/media/queryActions'; import * as nls from 'vs/nls'; import { Builder, $ } from 'vs/base/browser/builder'; import { Dropdown } from 'sql/base/browser/ui/editableDropdown/dropdown'; @@ -12,6 +13,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { attachEditableDropdownStyler, attachSelectBoxStyler } from 'sql/common/theme/styler'; import { ISelectionData } from 'sqlops'; import { @@ -25,6 +27,8 @@ import { QueryEditor } from 'sql/parts/query/editor/queryEditor'; import { IQueryModelService } from 'sql/parts/query/execution/queryModel'; import { INotificationService } from 'vs/platform/notification/common/notification'; import Severity from 'vs/base/common/severity'; +import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; /** * Action class that query-based Actions will extend. This base class automatically handles activating and @@ -431,6 +435,9 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem private _isConnected: boolean; private $databaseListDropdown: Builder; private _dropdown: Dropdown; + private _databaseSelectBox: SelectBox; + private _isInAccessibilityMode: boolean; + private readonly _selectDatabaseString: string = nls.localize("selectDatabase", "Select Database"); // CONSTRUCTOR ///////////////////////////////////////////////////////// constructor( @@ -439,23 +446,33 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @INotificationService private _notificationService: INotificationService, @IContextViewService contextViewProvider: IContextViewService, - @IThemeService themeService: IThemeService + @IThemeService themeService: IThemeService, + @IConfigurationService private readonly _configurationService: IConfigurationService ) { super(); this._toDispose = []; this.$databaseListDropdown = $('.databaseListDropdown'); - let selectString = nls.localize("selectDatabase", "Select Database"); - this._dropdown = new Dropdown(this.$databaseListDropdown.getHTMLElement(), contextViewProvider, themeService, { - strictSelection: true, - placeholder: selectString, - ariaLabel: selectString, - actionLabel: nls.localize('listDatabases.toggleDatabaseNameDropdown', 'Select Database Toggle Dropdown') - }); - this._dropdown.onValueChange(s => this.databaseSelected(s)); + this._isInAccessibilityMode = this._configurationService.getValue('editor.accessibilitySupport') === 'on'; + + if (this._isInAccessibilityMode) { + this._databaseSelectBox = new SelectBox([this._selectDatabaseString], this._selectDatabaseString, contextViewProvider, undefined, { ariaLabel: this._selectDatabaseString }); + this._databaseSelectBox.render(this.$databaseListDropdown.getHTMLElement()); + this._databaseSelectBox.onDidSelect(e => { this.databaseSelected(e.selected); }); + this._databaseSelectBox.disable(); + + } else { + this._dropdown = new Dropdown(this.$databaseListDropdown.getHTMLElement(), contextViewProvider, themeService, { + strictSelection: true, + placeholder: this._selectDatabaseString, + ariaLabel: this._selectDatabaseString, + actionLabel: nls.localize('listDatabases.toggleDatabaseNameDropdown', 'Select Database Toggle Dropdown') + }); + this._dropdown.onValueChange(s => this.databaseSelected(s)); + this._toDispose.push(this._dropdown.onFocus(() => { self.onDropdownFocus(); })); + } // Register event handlers let self = this; - this._toDispose.push(this._dropdown.onFocus(() => { self.onDropdownFocus(); })); this._toDispose.push(this._connectionManagementService.onConnectionChanged(params => { self.onConnectionChanged(params); })); } @@ -465,7 +482,12 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem } public style(styles) { - this._dropdown.style(styles); + if (this._isInAccessibilityMode) { + this._databaseSelectBox.style(styles); + } + else { + this._dropdown.style(styles); + } } public setActionContext(context: any): void { @@ -477,11 +499,27 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem } public focus(): void { - this._dropdown.focus(); + if (this._isInAccessibilityMode) { + this._databaseSelectBox.focus(); + } else { + this._dropdown.focus(); + } } public blur(): void { - this._dropdown.blur(); + if (this._isInAccessibilityMode) { + this._databaseSelectBox.blur(); + } else { + this._dropdown.blur(); + } + } + + public attachStyler(themeService: IThemeService): IDisposable { + if (this._isInAccessibilityMode) { + return attachSelectBoxStyler(this, themeService); + } else { + return attachEditableDropdownStyler(this, themeService); + } } public dispose(): void { @@ -496,9 +534,15 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem public onDisconnect(): void { this._isConnected = false; - this._dropdown.enabled = false; this._currentDatabaseName = undefined; - this._dropdown.value = ''; + + if (this._isInAccessibilityMode) { + this._databaseSelectBox.disable(); + this._databaseSelectBox.setOptions([this._selectDatabaseString]); + } else { + this._dropdown.enabled = false; + this._dropdown.value = ''; + } } // PRIVATE HELPERS ///////////////////////////////////////////////////// @@ -515,22 +559,22 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem this._connectionManagementService.changeDatabase(this._editor.uri, dbName) .then( - result => { - if (!result) { + result => { + if (!result) { + this.resetDatabaseName(); + this._notificationService.notify({ + severity: Severity.Error, + message: nls.localize('changeDatabase.failed', "Failed to change database") + }); + } + }, + error => { this.resetDatabaseName(); this._notificationService.notify({ severity: Severity.Error, - message: nls.localize('changeDatabase.failed', "Failed to change database") + message: nls.localize('changeDatabase.failedWithError', "Failed to change database {0}", error) }); - } - }, - error => { - this.resetDatabaseName(); - this._notificationService.notify({ - severity: Severity.Error, - message: nls.localize('changeDatabase.failedWithError', "Failed to change database {0}", error) }); - }); } private getCurrentDatabaseName() { @@ -545,7 +589,11 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem } private resetDatabaseName() { - this._dropdown.value = this.getCurrentDatabaseName(); + if (this._isInAccessibilityMode) { + this._databaseSelectBox.selectWithOptionName(this.getCurrentDatabaseName()); + } else { + this._dropdown.value = this.getCurrentDatabaseName(); + } } private onConnectionChanged(connParams: IConnectionParams): void { @@ -579,9 +627,26 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem private updateConnection(databaseName: string) { this._isConnected = true; - this._dropdown.enabled = true; this._currentDatabaseName = databaseName; - this._dropdown.value = databaseName; + + if (this._isInAccessibilityMode) { + this._databaseSelectBox.enable(); + let self = this; + let uri = self._editor.connectedUri; + if (!uri) { + return; + } + self._connectionManagementService.listDatabases(uri) + .then(result => { + if (result && result.databaseNames) { + this._databaseSelectBox.setOptions(result.databaseNames); + } + this._databaseSelectBox.selectWithOptionName(databaseName); + }); + } else { + this._dropdown.enabled = true; + this._dropdown.value = databaseName; + } } // TESTING PROPERTIES ////////////////////////////////////////////////// diff --git a/src/sqltest/parts/query/editor/queryActions.test.ts b/src/sqltest/parts/query/editor/queryActions.test.ts index 605c6cd166..596187ec6c 100644 --- a/src/sqltest/parts/query/editor/queryActions.test.ts +++ b/src/sqltest/parts/query/editor/queryActions.test.ts @@ -28,6 +28,7 @@ import { ConnectionManagementService } from 'sql/parts/connection/common/connect import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { TestThemeService } from 'sqltest/stubs/themeTestService'; +import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; import * as TypeMoq from 'typemoq'; import * as assert from 'assert'; @@ -40,6 +41,7 @@ suite('SQL QueryAction Tests', () => { let editor: TypeMoq.Mock; let calledRunQueryOnInput: boolean = undefined; let testQueryInput: TypeMoq.Mock; + let configurationService: TypeMoq.Mock; setup(() => { // Setup a reusable mock QueryInput @@ -56,6 +58,13 @@ suite('SQL QueryAction Tests', () => { editor.setup(x => x.getSelection()).returns(() => undefined); editor.setup(x => x.getSelection(false)).returns(() => undefined); editor.setup(x => x.isSelectionEmpty()).returns(() => false); + configurationService = TypeMoq.Mock.ofInstance({ + getValue: () => undefined, + onDidChangeConfiguration: () => undefined + } as any); + configurationService.setup(x => x.getValue(TypeMoq.It.isAny())).returns(() => { + return {}; + }); }); test('setClass sets child CSS class correctly', (done) => { @@ -463,7 +472,7 @@ suite('SQL QueryAction Tests', () => { }); // If I query without having initialized anything, state should be clear - listItem = new ListDatabasesActionItem(editor.object, undefined, connectionManagementService.object, undefined, undefined, undefined); + listItem = new ListDatabasesActionItem(editor.object, undefined, connectionManagementService.object, undefined, undefined, undefined, configurationService.object); assert.equal(listItem.isEnabled(), false, 'do not expect dropdown enabled unless connected'); assert.equal(listItem.currentDatabaseName, undefined, 'do not expect dropdown to have entries unless connected'); @@ -498,7 +507,7 @@ suite('SQL QueryAction Tests', () => { cms.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => { databaseName: databaseName }); // ... Create a database dropdown that has been connected - let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null); + let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null, configurationService.object); listItem.onConnected(); // If: I raise a connection changed event @@ -522,7 +531,7 @@ suite('SQL QueryAction Tests', () => { cms.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => { databaseName: databaseName }); // ... Create a database dropdown that has been connected - let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null); + let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null, configurationService.object); listItem.onConnected(); // If: I raise a connection changed event for the 'wrong' URI @@ -549,7 +558,7 @@ suite('SQL QueryAction Tests', () => { cms.setup(x => x.onConnectionChanged).returns(() => dbChangedEmitter.event); // ... Create a database dropdown - let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null); + let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, null, null, null, configurationService.object); // If: I raise a connection changed event let eventParams = { diff --git a/src/sqltest/parts/query/editor/queryEditor.test.ts b/src/sqltest/parts/query/editor/queryEditor.test.ts index f75c5caac1..ebe7fddc9b 100644 --- a/src/sqltest/parts/query/editor/queryEditor.test.ts +++ b/src/sqltest/parts/query/editor/queryEditor.test.ts @@ -93,7 +93,7 @@ suite('SQL QueryEditor Tests', () => { instantiationService.setup(x => x.createInstance(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((classDef, editor, action) => { if (classDef.ID) { if (classDef.ID === 'listDatabaseQueryActionItem') { - return new ListDatabasesActionItem(editor, action, connectionManagementService.object, undefined, undefined, undefined); + return new ListDatabasesActionItem(editor, action, connectionManagementService.object, undefined, undefined, undefined, configurationService.object); } } // Default @@ -344,7 +344,7 @@ suite('SQL QueryEditor Tests', () => { queryActionInstantiationService.setup(x => x.createInstance(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) .returns((definition, editor, action, selectBox) => { if (definition.ID === 'listDatabaseQueryActionItem') { - let item = new ListDatabasesActionItem(editor, action, queryConnectionService.object, undefined, undefined, undefined); + let item = new ListDatabasesActionItem(editor, action, queryConnectionService.object, undefined, undefined, undefined,configurationService.object); return item; } // Default