Files
azuredatastudio/src/sql/base/browser/ui/modal/optionsDialog.ts
Alan Ren f36f3ffd21 connect dialog ux improvement (#3105)
* connect dialog ux improvement

* more style updates
2018-11-04 17:10:05 -08:00

273 lines
10 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/optionsDialog';
import { Button } from 'sql/base/browser/ui/button/button';
import { FixedCollapsibleView } from 'sql/platform/views/fixedCollapsibleView';
import * as DialogHelper from './dialogHelper';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
import { IModalOptions, Modal } from './modal';
import * as OptionsDialogHelper from './optionsDialogHelper';
import { attachButtonStyler, attachModalDialogStyler } from 'sql/common/theme/styler';
import * as sqlops from 'sqlops';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { Event, Emitter } from 'vs/base/common/event';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { localize } from 'vs/nls';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import * as styler from 'vs/platform/theme/common/styler';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { SplitView, CollapsibleState } from 'sql/base/browser/ui/splitview/splitview';
import { Builder, $ } from 'vs/base/browser/builder';
import { Widget } from 'vs/base/browser/ui/widget';
import { ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
export class CategoryView extends FixedCollapsibleView {
private _treecontainer: HTMLElement;
private _collapsed: CollapsibleState;
constructor(private viewTitle: string, private _bodyContainer: HTMLElement, collapsed: boolean, initialBodySize: number, headerSize: number) {
super(
initialBodySize,
{
expandedBodySize: initialBodySize,
sizing: headerSize,
initialState: collapsed ? CollapsibleState.COLLAPSED : CollapsibleState.EXPANDED,
ariaHeaderLabel: viewTitle
});
this._collapsed = collapsed ? CollapsibleState.COLLAPSED : CollapsibleState.EXPANDED;
}
public renderHeader(container: HTMLElement): void {
const titleDiv = $('div.title').appendTo(container);
$('span').text(this.viewTitle).appendTo(titleDiv);
}
public renderBody(container: HTMLElement): void {
this._treecontainer = document.createElement('div');
container.appendChild(this._treecontainer);
this._treecontainer.appendChild(this._bodyContainer);
this.changeState(this._collapsed);
}
public layoutBody(size: number): void {
this._treecontainer.style.height = size + 'px';
}
}
export class OptionsDialog extends Modal {
private _body: HTMLElement;
private _optionGroups: HTMLElement;
private _dividerBuilder: Builder;
private _okButton: Button;
private _closeButton: Button;
private _optionTitle: Builder;
private _optionDescription: Builder;
private _optionElements: { [optionName: string]: OptionsDialogHelper.IOptionElement } = {};
private _optionValues: { [optionName: string]: string };
private _optionRowSize = 31;
private _optionCategoryPadding = 30;
private _categoryHeaderSize = 22;
private _onOk = new Emitter<void>();
public onOk: Event<void> = this._onOk.event;
private _onCloseEvent = new Emitter<void>();
public onCloseEvent: Event<void> = this._onCloseEvent.event;
public okLabel: string = localize('optionsDialog.ok', 'OK');
public cancelLabel: string = localize('optionsDialog.cancel', 'Cancel');
constructor(
title: string,
name: string,
options: IModalOptions,
@IPartService partService: IPartService,
@IWorkbenchThemeService private _workbenchThemeService: IWorkbenchThemeService,
@IContextViewService private _contextViewService: IContextViewService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@IClipboardService clipboardService: IClipboardService
) {
super(title, name, partService, telemetryService, clipboardService, _workbenchThemeService, contextKeyService, options);
}
public render() {
super.render();
attachModalDialogStyler(this, this._themeService);
if (this.backButton) {
this.backButton.onDidClick(() => this.cancel());
attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND });
}
this._okButton = this.addFooterButton(this.okLabel, () => this.ok());
this._closeButton = this.addFooterButton(this.cancelLabel, () => this.cancel());
// Theme styler
attachButtonStyler(this._okButton, this._themeService);
attachButtonStyler(this._closeButton, this._themeService);
let self = this;
this._register(self._workbenchThemeService.onDidColorThemeChange(e => self.updateTheme(e)));
self.updateTheme(self._workbenchThemeService.getColorTheme());
}
protected renderBody(container: HTMLElement) {
new Builder(container).div({ class: 'optionsDialog-options' }, (bodyBuilder) => {
this._body = bodyBuilder.getHTMLElement();
});
let builder = new Builder(this._body);
builder.div({}, (dividerContainer) => {
this._dividerBuilder = dividerContainer;
});
builder.div({ class: 'optionsDialog-description' }, (descriptionContainer) => {
descriptionContainer.div({ class: 'modal-title' }, (optionTitle) => {
this._optionTitle = optionTitle;
});
descriptionContainer.div({ class: 'optionsDialog-description-content' }, (optionDescription) => {
this._optionDescription = optionDescription;
});
});
}
// Update theming that is specific to options dialog flyout body
private updateTheme(theme: IColorTheme): void {
let borderColor = theme.getColor(contrastBorder);
let border = borderColor ? borderColor.toString() : null;
if (this._dividerBuilder) {
this._dividerBuilder.style('border-top-width', border ? '1px' : null);
this._dividerBuilder.style('border-top-style', border ? 'solid' : null);
this._dividerBuilder.style('border-top-color', border);
}
}
private onOptionLinkClicked(optionName: string): void {
var option = this._optionElements[optionName].option;
this._optionTitle.text(option.displayName);
this._optionDescription.text(option.description);
}
private fillInOptions(container: Builder, options: sqlops.ServiceOption[]): void {
for (var i = 0; i < options.length; i++) {
var option: sqlops.ServiceOption = options[i];
var rowContainer = DialogHelper.appendRow(container, option.displayName, 'optionsDialog-label', 'optionsDialog-input');
OptionsDialogHelper.createOptionElement(option, rowContainer, this._optionValues, this._optionElements, this._contextViewService, (name) => this.onOptionLinkClicked(name));
}
}
private registerStyling(): void {
// Theme styler
for (var optionName in this._optionElements) {
var widget: Widget = this._optionElements[optionName].optionWidget;
var option = this._optionElements[optionName].option;
switch (option.valueType) {
case ServiceOptionType.category:
case ServiceOptionType.boolean:
this._register(styler.attachSelectBoxStyler(<SelectBox>widget, this._themeService));
break;
case ServiceOptionType.string:
case ServiceOptionType.password:
case ServiceOptionType.number:
this._register(styler.attachInputBoxStyler(<InputBox>widget, this._themeService));
}
}
}
public get optionValues(): { [name: string]: any } {
return this._optionValues;
}
public hideError() {
this.setError('');
}
public showError(err: string) {
this.setError(err);
}
/* Overwrite escape key behavior */
protected onClose() {
this.close();
}
/* Overwrite enter key behavior */
protected onAccept() {
this.ok();
}
public ok(): void {
if (OptionsDialogHelper.validateInputs(this._optionElements)) {
OptionsDialogHelper.updateOptions(this._optionValues, this._optionElements);
this._onOk.fire();
this.close();
}
}
public cancel() {
this.close();
}
public close() {
this._optionGroups.remove();
this.dispose();
this.hide();
this._onCloseEvent.fire();
}
public open(options: sqlops.ServiceOption[], optionValues: { [name: string]: any }) {
this._optionValues = optionValues;
var firstOption: string;
var containerGroup: Builder;
var layoutSize = 0;
var optionsContentBuilder: Builder = $().div({ class: 'optionsDialog-options-groups' }, (container) => {
containerGroup = container;
this._optionGroups = container.getHTMLElement();
});
var splitview = new SplitView(containerGroup.getHTMLElement());
let categoryMap = OptionsDialogHelper.groupOptionsByCategory(options);
for (var category in categoryMap) {
var serviceOptions: sqlops.ServiceOption[] = categoryMap[category];
var bodyContainer = $().element('table', { class: 'optionsDialog-table' }, (tableContainer: Builder) => {
this.fillInOptions(tableContainer, serviceOptions);
});
var viewSize = this._optionCategoryPadding + serviceOptions.length * this._optionRowSize;
layoutSize += (viewSize + this._categoryHeaderSize);
var categoryView = new CategoryView(category, bodyContainer.getHTMLElement(), false, viewSize, this._categoryHeaderSize);
splitview.addView(categoryView);
if (!firstOption) {
firstOption = serviceOptions[0].name;
}
}
splitview.layout(layoutSize);
let body = new Builder(this._body);
body.append(optionsContentBuilder.getHTMLElement(), 0);
this.show();
var firstOptionWidget = this._optionElements[firstOption].optionWidget;
this.registerStyling();
firstOptionWidget.focus();
}
protected layout(height?: number): void {
// Nothing currently laid out in this class
}
public dispose(): void {
super.dispose();
for (var optionName in this._optionElements) {
var widget: Widget = this._optionElements[optionName].optionWidget;
widget.dispose();
delete this._optionElements[optionName];
}
}
}