mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-25 17:23:10 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
106
src/sql/base/browser/ui/modal/dialogHelper.ts
Normal file
106
src/sql/base/browser/ui/modal/dialogHelper.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Builder } from 'vs/base/browser/builder';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox';
|
||||
import * as data from 'data';
|
||||
import * as types from 'vs/base/common/types';
|
||||
|
||||
export function appendRow(container: Builder, label: string, labelClass: string, cellContainerClass: string): Builder {
|
||||
let cellContainer: Builder;
|
||||
container.element('tr', {}, (rowContainer) => {
|
||||
rowContainer.element('td', { class: labelClass }, (labelCellContainer) => {
|
||||
labelCellContainer.div({}, (labelContainer) => {
|
||||
labelContainer.innerHtml(label);
|
||||
});
|
||||
});
|
||||
rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => {
|
||||
cellContainer = inputCellContainer;
|
||||
});
|
||||
});
|
||||
|
||||
return cellContainer;
|
||||
}
|
||||
|
||||
export function appendRowLink(container: Builder, label: string, labelClass: string, cellContainerClass: string): Builder {
|
||||
let rowButton: Button;
|
||||
container.element('tr', {}, (rowContainer) => {
|
||||
rowContainer.element('td', { class: labelClass }, (labelCellContainer) => {
|
||||
labelCellContainer.div({}, (labelContainer) => {
|
||||
labelContainer.innerHtml(label);
|
||||
});
|
||||
});
|
||||
rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => {
|
||||
inputCellContainer.element('div', {}, (rowContainer) => {
|
||||
rowButton = new Button(rowContainer);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return new Builder(rowButton.getElement());
|
||||
}
|
||||
|
||||
export function createCheckBox(container: Builder, label: string, checkboxClass: string, isChecked: boolean, onCheck?: (viaKeyboard: boolean) => void): Checkbox {
|
||||
let checkbox = new Checkbox({
|
||||
actionClassName: checkboxClass,
|
||||
title: label,
|
||||
isChecked: isChecked,
|
||||
onChange: (viaKeyboard) => {
|
||||
if (onCheck) {
|
||||
onCheck(viaKeyboard);
|
||||
}
|
||||
}
|
||||
});
|
||||
container.getHTMLElement().appendChild(checkbox.domNode);
|
||||
container.div({}, (labelContainer) => {
|
||||
labelContainer.innerHtml(label);
|
||||
});
|
||||
|
||||
return checkbox;
|
||||
}
|
||||
|
||||
export function appendInputSelectBox(container: Builder, selectBox: SelectBox): SelectBox {
|
||||
selectBox.render(container.getHTMLElement());
|
||||
return selectBox;
|
||||
}
|
||||
|
||||
export function isNullOrWhiteSpace(value: string): boolean {
|
||||
// returns true if the string is null or contains white space/tab chars only
|
||||
return !value || value.trim().length === 0;
|
||||
}
|
||||
|
||||
export function getBooleanValueFromStringOrBoolean(value: any): boolean {
|
||||
if (types.isBoolean(value)) {
|
||||
return value;
|
||||
} else if (types.isString(value)) {
|
||||
return value.toLowerCase() === 'true';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getCategoryDisplayName(categories: data.CategoryValue[], categoryName: string) {
|
||||
var displayName: string;
|
||||
categories.forEach(c => {
|
||||
if (c.name === categoryName) {
|
||||
displayName = c.displayName;
|
||||
}
|
||||
});
|
||||
return displayName;
|
||||
}
|
||||
|
||||
export function getCategoryName(categories: data.CategoryValue[], categoryDisplayName: string) {
|
||||
var categoryName: string;
|
||||
categories.forEach(c => {
|
||||
if (c.displayName === categoryDisplayName) {
|
||||
categoryName = c.name;
|
||||
}
|
||||
});
|
||||
return categoryName;
|
||||
}
|
||||
1
src/sql/base/browser/ui/modal/media/back.svg
Normal file
1
src/sql/base/browser/ui/modal/media/back.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#212121;}</style></defs><title>back_16x16</title><path class="cls-1" d="M16.15,8.5H2.1l6.15,6.15-.7.7L.19,8,7.55.65l.7.7L2.1,7.5h14Z"/></svg>
|
||||
|
After Width: | Height: | Size: 259 B |
1
src/sql/base/browser/ui/modal/media/back_inverse.svg
Normal file
1
src/sql/base/browser/ui/modal/media/back_inverse.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>back_inverse_16x16</title><path class="cls-1" d="M16.15,8.5H2.1l6.15,6.15-.7.7L.19,8,7.55.65l.7.7L2.1,7.5h14Z"/></svg>
|
||||
|
After Width: | Height: | Size: 264 B |
178
src/sql/base/browser/ui/modal/media/modal.css
Normal file
178
src/sql/base/browser/ui/modal/media/modal.css
Normal file
@@ -0,0 +1,178 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-shell .modal {
|
||||
background-color: rgba(204, 204, 204, 0.6);
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
z-index: 500;
|
||||
}
|
||||
|
||||
.monaco-shell .modal:not(.flyout-dialog) .modal-dialog {
|
||||
margin: auto;
|
||||
width: 640px;
|
||||
height: 480px;
|
||||
}
|
||||
|
||||
.modal .modal-header {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.modal .modal-footer {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.modal .icon.in-progress {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.monaco-shell .modal.flyout-dialog .modal-dialog {
|
||||
margin: auto auto auto auto;
|
||||
height: 100%;
|
||||
width: 500px;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.monaco-shell .modal.flyout-dialog.wide .modal-dialog {
|
||||
width: 1200px;
|
||||
max-width: 95%;
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
.monaco-shell .modal.flyout-dialog .modal-content {
|
||||
height: 100%;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.modal .modal-title {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.modal .modal-title-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.monaco-shell .modal.flyout-dialog .modal-body {
|
||||
height: calc(100% - 105px);
|
||||
}
|
||||
|
||||
/* modal body for angular component dialog */
|
||||
.monaco-shell .modal.flyout-dialog .angular-modal-body {
|
||||
height: calc(100% - 90px);
|
||||
}
|
||||
|
||||
/* Style for body and footer in modal dialog. This should be applied to dialog created with angular component. */
|
||||
.monaco-shell .modal.flyout-dialog .modal-body-and-footer {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* modl body content style(excluding dialogErrorMessage section) for angulr component dialog */
|
||||
.angular-modal-body-content {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.modal.flyout-dialog .angular-form {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.modal.flyout-dialog .dialog-label {
|
||||
width: 100%;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
/* Add space in the bottom to separate each input elements */
|
||||
.modal.flyout-dialog .input-divider {
|
||||
width: 100%;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.modal.flyout-dialog .input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
height: 25px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.modal.flyout-dialog .modal-body {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.vs-dark.monaco-shell .modal.flyout-dialog .input {
|
||||
background-color: #3C3C3C;
|
||||
}
|
||||
|
||||
.vs-dark.monaco-shell .modal.flyout-dialog input:disabled {
|
||||
background-color: #E1E1E1;
|
||||
color: #3C3C3C;
|
||||
}
|
||||
|
||||
.modal .select-box {
|
||||
width: 100%;
|
||||
height: 25px;
|
||||
color: #6C6C6C;
|
||||
font-size: 11px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.modal.flyout-dialog .dialogErrorMessage {
|
||||
overflow: hidden;
|
||||
padding-left: 10px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal .modal-footer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal .modal-footer .left-footer {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.modal .modal-footer .right-footer {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.modal .footer-button a.monaco-button.monaco-text-button {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.vs-dark.monaco-shell .modal.flyout-dialog .footer-button a.monaco-button.monaco-text-button {
|
||||
outline-color: #8e8c8c;
|
||||
}
|
||||
|
||||
.modal.flyout-dialog .footer-button a.monaco-button.monaco-text-button:focus {
|
||||
outline-width: 1px;
|
||||
}
|
||||
|
||||
.modal .footer-button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.modal .right-footer .footer-button:last-of-type {
|
||||
margin-right: none;
|
||||
}
|
||||
|
||||
.modal.flyout-dialog .icon.error {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
60
src/sql/base/browser/ui/modal/media/optionsDialog.css
Normal file
60
src/sql/base/browser/ui/modal/media/optionsDialog.css
Normal file
@@ -0,0 +1,60 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.optionsDialog-label {
|
||||
width: 120px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.optionsDialog-input {
|
||||
width: 200px;
|
||||
padding-bottom: 5px;
|
||||
padding-right: 7px;
|
||||
}
|
||||
|
||||
.options-dialog .select-box {
|
||||
width: 321px;
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
.optionsDialog-options-groups {
|
||||
padding: 15px;
|
||||
height: calc(100% - 150px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.backButtonIcon {
|
||||
content: url('back.svg');
|
||||
width: 20px;
|
||||
margin-right: 10px;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vs-dark.monaco-shell .backButtonIcon {
|
||||
content: url('back_inverse.svg');
|
||||
}
|
||||
|
||||
.optionsDialog-description {
|
||||
padding: 15px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.optionsDialog-options {
|
||||
height: calc(100% - 30px);
|
||||
}
|
||||
|
||||
.optionsDialog-description-content {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.optionsDialog-table{
|
||||
width:100%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
td.optionsDialog-input.select-container {
|
||||
padding-right: 9px;
|
||||
}
|
||||
432
src/sql/base/browser/ui/modal/modal.ts
Normal file
432
src/sql/base/browser/ui/modal/modal.ts
Normal file
@@ -0,0 +1,432 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!sql/media/icons/common-icons';
|
||||
import 'vs/css!./media/modal';
|
||||
import { IThemable } from 'vs/platform/theme/common/styler';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { Builder, $, withElementById } from 'vs/base/browser/builder';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
import * as TelemetryUtils from 'sql/common/telemetryUtilities';
|
||||
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||
|
||||
export const MODAL_SHOWING_KEY = 'modalShowing';
|
||||
export const MODAL_SHOWING_CONTEXT = new RawContextKey<Array<string>>(MODAL_SHOWING_KEY, []);
|
||||
|
||||
export interface IModalDialogStyles {
|
||||
dialogForeground?: Color;
|
||||
dialogBorder?: Color;
|
||||
dialogHeaderAndFooterBackground?: Color;
|
||||
dialogBodyBackground?: Color;
|
||||
}
|
||||
|
||||
export interface IModalOptions {
|
||||
isFlyout?: boolean;
|
||||
isWide?: boolean;
|
||||
isAngular?: boolean;
|
||||
hasBackButton?: boolean;
|
||||
hasTitleIcon?: boolean;
|
||||
hasErrors?: boolean;
|
||||
hasSpinner?: boolean;
|
||||
}
|
||||
|
||||
// Needed for angular component dialogs to style modal footer
|
||||
export class ModalFooterStyle {
|
||||
public static backgroundColor;
|
||||
public static borderTopWidth;
|
||||
public static borderTopStyle;
|
||||
public static borderTopColor;
|
||||
}
|
||||
|
||||
const defaultOptions: IModalOptions = {
|
||||
isFlyout: true,
|
||||
isWide: false,
|
||||
isAngular: false,
|
||||
hasBackButton: false,
|
||||
hasTitleIcon: false,
|
||||
hasErrors: false,
|
||||
hasSpinner: false
|
||||
};
|
||||
|
||||
export abstract class Modal extends Disposable implements IThemable {
|
||||
|
||||
private _errorMessage: Builder;
|
||||
private _spinnerElement: HTMLElement;
|
||||
private _errorIconElement: HTMLElement;
|
||||
private _dialogForeground: Color;
|
||||
private _dialogBorder: Color;
|
||||
private _dialogHeaderAndFooterBackground: Color;
|
||||
private _dialogBodyBackground: Color;
|
||||
|
||||
private _modalDialog: Builder;
|
||||
private _modalHeaderSection: Builder;
|
||||
private _modalBodySection: HTMLElement;
|
||||
private _modalFooterSection: Builder;
|
||||
private _closeButtonInHeader: Builder;
|
||||
private _builder: Builder;
|
||||
private _footerBuilder: Builder;
|
||||
private _modalTitle: Builder;
|
||||
private _modalTitleIcon: HTMLElement;
|
||||
private _leftFooter: Builder;
|
||||
private _rightFooter: Builder;
|
||||
private _footerButtons: Button[];
|
||||
|
||||
private _keydownListener: IDisposable;
|
||||
private _resizeListener: IDisposable;
|
||||
|
||||
private _modalOptions: IModalOptions;
|
||||
private _backButton: Button;
|
||||
|
||||
private _modalShowingContext: IContextKey<Array<string>>;
|
||||
private readonly _staticKey: string;
|
||||
|
||||
/**
|
||||
* Get the back button, only available after render and if the hasBackButton option is true
|
||||
*/
|
||||
protected get backButton(): Button {
|
||||
return this._backButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the dialog to have wide layout dynamically.
|
||||
* Temporary solution to render file browser as wide or narrow layout.
|
||||
* This will be removed once backup dialog is changed to wide layout.
|
||||
*/
|
||||
public setWide(isWide: boolean): void {
|
||||
if (this._builder.hasClass('wide') && isWide === false) {
|
||||
this._builder.removeClass('wide');
|
||||
} else if (!this._builder.hasClass('wide') && isWide === true) {
|
||||
this._builder.addClass('wide');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for modal
|
||||
* @param _title Title of the modal, if undefined, the title section is not rendered
|
||||
* @param _name Name of the modal, used for telemetry
|
||||
* @param _partService
|
||||
* @param options Modal options
|
||||
*/
|
||||
constructor(
|
||||
private _title: string,
|
||||
private _name: string,
|
||||
private _partService: IPartService,
|
||||
private _telemetryService: ITelemetryService,
|
||||
private _contextKeyService: IContextKeyService,
|
||||
options?: IModalOptions
|
||||
) {
|
||||
super();
|
||||
this._modalOptions = options || Object.create(null);
|
||||
mixin(this._modalOptions, defaultOptions, false);
|
||||
this._staticKey = generateUuid();
|
||||
this._modalShowingContext = MODAL_SHOWING_CONTEXT.bindTo(_contextKeyService);
|
||||
this._footerButtons = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and render the modal, will call {@link Modal#renderBody}
|
||||
*/
|
||||
public render() {
|
||||
let modalBodyClass = (this._modalOptions.isAngular === false ? 'modal-body' : 'modal-body-and-footer');
|
||||
let parts: Array<HTMLElement> = [];
|
||||
// This modal header section refers to the header of of the dialog
|
||||
// will not be rendered if the title is passed in as undefined
|
||||
if (this._title !== undefined) {
|
||||
this._modalHeaderSection = $().div({ class: 'modal-header' }, (modalHeader) => {
|
||||
if (this._modalOptions.hasBackButton) {
|
||||
modalHeader.div({ class: 'modal-go-back' }, (cellContainer) => {
|
||||
this._backButton = new Button(cellContainer);
|
||||
this._backButton.icon = 'backButtonIcon';
|
||||
});
|
||||
}
|
||||
if (this._modalOptions.hasTitleIcon) {
|
||||
modalHeader.div({ class: 'modal-title-icon' }, (modalIcon) => {
|
||||
this._modalTitleIcon = modalIcon.getHTMLElement();
|
||||
});
|
||||
}
|
||||
modalHeader.div({ class: 'modal-title' }, (modalTitle) => {
|
||||
this._modalTitle = modalTitle;
|
||||
modalTitle.innerHtml(this._title);
|
||||
});
|
||||
});
|
||||
parts.push(this._modalHeaderSection.getHTMLElement());
|
||||
}
|
||||
|
||||
// This modal body section refers to the body of of the dialog
|
||||
let body: Builder;
|
||||
$().div({ class: modalBodyClass }, (builder) => {
|
||||
body = builder;
|
||||
});
|
||||
this._modalBodySection = body.getHTMLElement();
|
||||
parts.push(body.getHTMLElement());
|
||||
|
||||
this.renderBody(body.getHTMLElement());
|
||||
|
||||
if (this._modalOptions.isAngular === false && this._modalOptions.hasErrors) {
|
||||
body.div({ class: 'dialogErrorMessage', id: 'dialogErrorMessage' }, (errorMessageContainer) => {
|
||||
errorMessageContainer.div({ class: 'icon error' }, (iconContainer) => {
|
||||
this._errorIconElement = iconContainer.getHTMLElement();
|
||||
this._errorIconElement.style.visibility = 'hidden';
|
||||
});
|
||||
errorMessageContainer.div({ class: 'errorMessage' }, (messageContainer) => {
|
||||
this._errorMessage = messageContainer;
|
||||
});
|
||||
});
|
||||
}
|
||||
// This modal footer section refers to the footer of of the dialog
|
||||
if (this._modalOptions.isAngular === false) {
|
||||
this._modalFooterSection = $().div({ class: 'modal-footer' }, (modelFooter) => {
|
||||
if (this._modalOptions.hasSpinner) {
|
||||
modelFooter.div({ 'class': 'icon in-progress' }, (spinnerContainer) => {
|
||||
this._spinnerElement = spinnerContainer.getHTMLElement();
|
||||
this._spinnerElement.style.visibility = 'hidden';
|
||||
});
|
||||
}
|
||||
modelFooter.div({ 'class': 'left-footer' }, (leftFooter) => {
|
||||
this._leftFooter = leftFooter;
|
||||
});
|
||||
modelFooter.div({ 'class': 'right-footer' }, (rightFooter) => {
|
||||
this._rightFooter = rightFooter;
|
||||
});
|
||||
this._footerBuilder = modelFooter;
|
||||
});
|
||||
parts.push(this._modalFooterSection.getHTMLElement());
|
||||
}
|
||||
|
||||
let builderClass = 'modal fade';
|
||||
if (this._modalOptions.isFlyout) {
|
||||
builderClass += ' flyout-dialog';
|
||||
}
|
||||
if (this._modalOptions.isWide) {
|
||||
builderClass += ' wide';
|
||||
}
|
||||
|
||||
// The builder builds the dialog. It append header, body and footer sections.
|
||||
this._builder = $().div({ class: builderClass, 'role': 'dialog' }, (dialogContainer) => {
|
||||
this._modalDialog = dialogContainer.div({ class: 'modal-dialog ', role: 'document' }, (modalDialog) => {
|
||||
modalDialog.div({ class: 'modal-content' }, (modelContent) => {
|
||||
parts.forEach((part) => {
|
||||
modelContent.append(part);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called for extended classes to render the body
|
||||
* @param container The parent container to attach the rendered body to
|
||||
*/
|
||||
protected abstract renderBody(container: HTMLElement): void;
|
||||
|
||||
/**
|
||||
* Overridable to change behavior of escape key
|
||||
*/
|
||||
protected onClose(e: StandardKeyboardEvent) {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridable to change behavior of enter key
|
||||
*/
|
||||
protected onAccept(e: StandardKeyboardEvent) {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the modal and attaches key listeners
|
||||
*/
|
||||
protected show() {
|
||||
this._modalShowingContext.get().push(this._staticKey);
|
||||
this._builder.appendTo(withElementById(this._partService.getWorkbenchElementId()).getHTMLElement().parentElement);
|
||||
this._keydownListener = DOM.addDisposableListener(document, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
let context = this._modalShowingContext.get();
|
||||
if (context[context.length - 1] === this._staticKey) {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Enter)) {
|
||||
this.onAccept(event);
|
||||
} else if (event.equals(KeyCode.Escape)) {
|
||||
this.onClose(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
this._resizeListener = DOM.addDisposableListener(window, DOM.EventType.RESIZE, (e: Event) => {
|
||||
this.layout(DOM.getTotalHeight(this._builder.getHTMLElement()));
|
||||
});
|
||||
|
||||
this.layout(DOM.getTotalHeight(this._builder.getHTMLElement()));
|
||||
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.ModalDialogOpened, { name: this._name });
|
||||
}
|
||||
|
||||
/**
|
||||
* Required to be implemented so that scrolling and other functions operate correctly. Should re-layout controls in the modal
|
||||
*/
|
||||
protected abstract layout(height?: number): void;
|
||||
|
||||
/**
|
||||
* Hides the modal and removes key listeners
|
||||
*/
|
||||
protected hide() {
|
||||
this._footerButtons.forEach(button => button.applyStyles());
|
||||
this._modalShowingContext.get().pop();
|
||||
this._builder.offDOM();
|
||||
this._keydownListener.dispose();
|
||||
this._resizeListener.dispose();
|
||||
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.ModalDialogClosed, { name: this._name });
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a button to the footer of the modal
|
||||
* @param label Label to show on the button
|
||||
* @param onSelect The callback to call when the button is selected
|
||||
*/
|
||||
protected addFooterButton(label: string, onSelect: () => void, orientation: 'left' | 'right' = 'right'): Button {
|
||||
let footerButton = $('div.footer-button');
|
||||
let button = new Button(footerButton);
|
||||
button.label = label;
|
||||
button.addListener('click', () => onSelect());
|
||||
if (orientation === 'left') {
|
||||
footerButton.appendTo(this._leftFooter);
|
||||
} else {
|
||||
footerButton.appendTo(this._rightFooter);
|
||||
}
|
||||
this._footerButtons.push(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an error in the error message element
|
||||
* @param err Text to show in the error message
|
||||
*/
|
||||
protected setError(err: string) {
|
||||
if (this._modalOptions.hasErrors) {
|
||||
if (err === '') {
|
||||
this._errorIconElement.style.visibility = 'hidden';
|
||||
} else {
|
||||
this._errorIconElement.style.visibility = 'visible';
|
||||
}
|
||||
this._errorMessage.innerHtml(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the spinner element that shows something is happening, hidden by default
|
||||
*/
|
||||
protected showSpinner(): void {
|
||||
if (this._modalOptions.hasSpinner) {
|
||||
this._spinnerElement.style.visibility = 'visible';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the spinner element to show that something was happening, hidden by default
|
||||
*/
|
||||
protected hideSpinner(): void {
|
||||
if (this._modalOptions.hasSpinner) {
|
||||
this._spinnerElement.style.visibility = 'hidden';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set spinner element to show or hide
|
||||
*/
|
||||
public set spinner(show: boolean) {
|
||||
if (show) {
|
||||
this.showSpinner();
|
||||
} else {
|
||||
this.hideSpinner();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return background color of header and footer
|
||||
*/
|
||||
protected get headerAndFooterBackground(): string {
|
||||
return this._dialogHeaderAndFooterBackground ? this._dialogHeaderAndFooterBackground.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the title of the modal
|
||||
* @param title
|
||||
*/
|
||||
protected set title(title: string) {
|
||||
if (this._title !== undefined) {
|
||||
this._modalTitle.innerHtml(title);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the icon title class name
|
||||
* @param iconClassName
|
||||
*/
|
||||
protected set titleIconClassName(iconClassName: string) {
|
||||
if (this._modalTitleIcon) {
|
||||
this._modalTitleIcon.className = 'modal-title-icon ' + iconClassName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the theme registry on theme change to style the component
|
||||
*/
|
||||
public style(styles: IModalDialogStyles): void {
|
||||
this._dialogForeground = styles.dialogForeground;
|
||||
this._dialogBorder = styles.dialogBorder;
|
||||
this._dialogHeaderAndFooterBackground = styles.dialogHeaderAndFooterBackground;
|
||||
this._dialogBodyBackground = styles.dialogBodyBackground;
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
private applyStyles(): void {
|
||||
const foreground = this._dialogForeground ? this._dialogForeground.toString() : null;
|
||||
const border = this._dialogBorder ? this._dialogBorder.toString() : null;
|
||||
const headerAndFooterBackground = this._dialogHeaderAndFooterBackground ? this._dialogHeaderAndFooterBackground.toString() : null;
|
||||
const bodyBackground = this._dialogBodyBackground ? this._dialogBodyBackground.toString() : null;
|
||||
ModalFooterStyle.backgroundColor = headerAndFooterBackground;
|
||||
ModalFooterStyle.borderTopWidth = border ? '1px' : null;
|
||||
ModalFooterStyle.borderTopStyle = border ? 'solid' : null;
|
||||
ModalFooterStyle.borderTopColor = border;
|
||||
|
||||
if (this._closeButtonInHeader) {
|
||||
this._closeButtonInHeader.style('color', foreground);
|
||||
}
|
||||
if (this._modalDialog) {
|
||||
this._modalDialog.style('color', foreground);
|
||||
this._modalDialog.style('border-width', border ? '1px' : null);
|
||||
this._modalDialog.style('border-style', border ? 'solid' : null);
|
||||
this._modalDialog.style('border-color', border);
|
||||
}
|
||||
if (this._modalHeaderSection) {
|
||||
this._modalHeaderSection.style('background-color', headerAndFooterBackground);
|
||||
this._modalHeaderSection.style('border-bottom-width', border ? '1px' : null);
|
||||
this._modalHeaderSection.style('border-bottom-style', border ? 'solid' : null);
|
||||
this._modalHeaderSection.style('border-bottom-color', border);
|
||||
}
|
||||
|
||||
if (this._modalBodySection) {
|
||||
this._modalBodySection.style.backgroundColor = bodyBackground;
|
||||
}
|
||||
|
||||
if (this._modalFooterSection) {
|
||||
this._modalFooterSection.style('background-color', ModalFooterStyle.backgroundColor);
|
||||
this._modalFooterSection.style('border-top-width', ModalFooterStyle.borderTopWidth);
|
||||
this._modalFooterSection.style('border-top-style', ModalFooterStyle.borderTopStyle);
|
||||
this._modalFooterSection.style('border-top-color', ModalFooterStyle.borderTopColor);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose();
|
||||
this._keydownListener.dispose();
|
||||
}
|
||||
}
|
||||
268
src/sql/base/browser/ui/modal/optionsDialog.ts
Normal file
268
src/sql/base/browser/ui/modal/optionsDialog.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { 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 { attachModalDialogStyler } from 'sql/common/theme/styler';
|
||||
|
||||
import * as data from 'data';
|
||||
|
||||
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 'vs/base/browser/ui/splitview/splitview';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
|
||||
class CategoryView extends FixedCollapsibleView {
|
||||
private _treecontainer: HTMLElement;
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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('ok', 'OK');
|
||||
public cancelLabel: string = localize('cancel', 'Cancel');
|
||||
|
||||
constructor(
|
||||
title: string,
|
||||
name: string,
|
||||
options: IModalOptions,
|
||||
@IPartService partService: IPartService,
|
||||
@IWorkbenchThemeService private _themeService: IWorkbenchThemeService,
|
||||
@IContextViewService private _contextViewService: IContextViewService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
super(title, name, partService, telemetryService, contextKeyService, options);
|
||||
}
|
||||
|
||||
public render() {
|
||||
super.render();
|
||||
attachModalDialogStyler(this, this._themeService);
|
||||
if (this.backButton) {
|
||||
this.backButton.addListener('click', () => this.cancel());
|
||||
styler.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
|
||||
styler.attachButtonStyler(this._okButton, this._themeService);
|
||||
styler.attachButtonStyler(this._closeButton, this._themeService);
|
||||
let self = this;
|
||||
this._register(self._themeService.onDidColorThemeChange(e => self.updateTheme(e)));
|
||||
self.updateTheme(self._themeService.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({ class: 'Connection-divider' }, (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.innerHtml(option.displayName);
|
||||
this._optionDescription.innerHtml(option.description);
|
||||
}
|
||||
|
||||
private fillInOptions(container: Builder, options: data.ServiceOption[]): void {
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
var option: data.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 OptionsDialogHelper.ServiceOptionType.category:
|
||||
case OptionsDialogHelper.ServiceOptionType.boolean:
|
||||
this._register(styler.attachSelectBoxStyler(<SelectBox>widget, this._themeService));
|
||||
break;
|
||||
case OptionsDialogHelper.ServiceOptionType.string:
|
||||
case OptionsDialogHelper.ServiceOptionType.password:
|
||||
case OptionsDialogHelper.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: data.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: data.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];
|
||||
}
|
||||
}
|
||||
}
|
||||
189
src/sql/base/browser/ui/modal/optionsDialogHelper.ts
Normal file
189
src/sql/base/browser/ui/modal/optionsDialogHelper.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as DialogHelper from './dialogHelper';
|
||||
import { Builder } from 'vs/base/browser/builder';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import data = require('data');
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export interface IOptionElement {
|
||||
optionWidget: any;
|
||||
option: data.ServiceOption;
|
||||
optionValue: any;
|
||||
}
|
||||
|
||||
export enum ServiceOptionType {
|
||||
string = 0,
|
||||
multistring = 1,
|
||||
password = 2,
|
||||
number = 3,
|
||||
category = 4,
|
||||
boolean = 5
|
||||
}
|
||||
|
||||
export function createOptionElement(option: data.ServiceOption, rowContainer: Builder, options: { [name: string]: any },
|
||||
optionsMap: { [optionName: string]: IOptionElement }, contextViewService: IContextViewService, onFocus: (name) => void): void {
|
||||
let possibleInputs: string[] = [];
|
||||
let optionValue = this.getOptionValueAndCategoryValues(option, options, possibleInputs);
|
||||
let optionWidget: any;
|
||||
let inputElement: HTMLElement;
|
||||
let missingErrorMessage = localize('missingRequireField', ' is required.');
|
||||
let invalidInputMessage = localize('invalidInput', 'Invalid input. Numeric value expected.');
|
||||
switch (option.valueType) {
|
||||
case ServiceOptionType.number:
|
||||
optionWidget = new InputBox(rowContainer.getHTMLElement(), contextViewService, {
|
||||
validationOptions: {
|
||||
validation: (value: string) => {
|
||||
if (!value && option.isRequired) {
|
||||
return { type: MessageType.ERROR, content: option.displayName + missingErrorMessage };
|
||||
} else if (!types.isNumber(Number(value))) {
|
||||
return { type: MessageType.ERROR, content: invalidInputMessage };
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
optionWidget.value = optionValue;
|
||||
inputElement = this.findElement(rowContainer, 'input');
|
||||
break;
|
||||
case ServiceOptionType.category:
|
||||
case ServiceOptionType.boolean:
|
||||
optionWidget = new SelectBox(possibleInputs, optionValue.toString());
|
||||
DialogHelper.appendInputSelectBox(rowContainer, optionWidget);
|
||||
inputElement = this.findElement(rowContainer, 'select-box');
|
||||
break;
|
||||
case ServiceOptionType.string:
|
||||
case ServiceOptionType.password:
|
||||
optionWidget = new InputBox(rowContainer.getHTMLElement(), contextViewService, {
|
||||
validationOptions: {
|
||||
validation: (value: string) => (!value && option.isRequired) ? ({ type: MessageType.ERROR, content: option.displayName + missingErrorMessage }) : null
|
||||
}
|
||||
});
|
||||
optionWidget.value = optionValue;
|
||||
if (option.valueType === ServiceOptionType.password) {
|
||||
optionWidget.inputElement.type = 'password';
|
||||
}
|
||||
inputElement = this.findElement(rowContainer, 'input');
|
||||
}
|
||||
optionsMap[option.name] = { optionWidget: optionWidget, option: option, optionValue: optionValue };
|
||||
inputElement.onfocus = () => onFocus(option.name);
|
||||
}
|
||||
|
||||
export function getOptionValueAndCategoryValues(option: data.ServiceOption, options: { [optionName: string]: any }, possibleInputs: string[]): any {
|
||||
var optionValue = option.defaultValue;
|
||||
if (options[option.name]) {
|
||||
// if the value type is boolean, the option value can be either boolean or string
|
||||
if (option.valueType === ServiceOptionType.boolean) {
|
||||
if (options[option.name] === true || options[option.name] === this.trueInputValue) {
|
||||
optionValue = this.trueInputValue;
|
||||
} else {
|
||||
optionValue = this.falseInputValue;
|
||||
}
|
||||
} else {
|
||||
optionValue = options[option.name];
|
||||
}
|
||||
}
|
||||
|
||||
if (option.valueType === ServiceOptionType.boolean || option.valueType === ServiceOptionType.category) {
|
||||
// If the option is not required, the empty string should be add at the top of possible choices
|
||||
if (!option.isRequired) {
|
||||
possibleInputs.push('');
|
||||
}
|
||||
|
||||
if (option.valueType === ServiceOptionType.boolean) {
|
||||
possibleInputs.push(this.trueInputValue, this.falseInputValue);
|
||||
} else {
|
||||
option.categoryValues.map(c => possibleInputs.push(c.name));
|
||||
}
|
||||
|
||||
// If the option value is not set and default value is null, the option value should be set to the first possible input.
|
||||
if (optionValue === null || optionValue === undefined) {
|
||||
optionValue = possibleInputs[0];
|
||||
}
|
||||
}
|
||||
return optionValue;
|
||||
}
|
||||
|
||||
export function validateInputs(optionsMap: { [optionName: string]: IOptionElement }): boolean {
|
||||
let isValid = true;
|
||||
let isFocused = false;
|
||||
for (var optionName in optionsMap) {
|
||||
var optionElement: IOptionElement = optionsMap[optionName];
|
||||
var widget = optionElement.optionWidget;
|
||||
var isInputBox = (optionElement.option.valueType === ServiceOptionType.string ||
|
||||
optionElement.option.valueType === ServiceOptionType.password ||
|
||||
optionElement.option.valueType === ServiceOptionType.number);
|
||||
|
||||
if (isInputBox) {
|
||||
if (!widget.validate()) {
|
||||
isValid = false;
|
||||
if (!isFocused) {
|
||||
isFocused = true;
|
||||
widget.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
export function updateOptions(options: { [optionName: string]: any }, optionsMap: { [optionName: string]: IOptionElement }): void {
|
||||
for (var optionName in optionsMap) {
|
||||
var optionElement: IOptionElement = optionsMap[optionName];
|
||||
if (optionElement.optionWidget.value !== options[optionName]) {
|
||||
if (!optionElement.optionWidget.value && options[optionName]) {
|
||||
delete options[optionName];
|
||||
}
|
||||
if (optionElement.optionWidget.value) {
|
||||
if (optionElement.option.valueType === ServiceOptionType.boolean) {
|
||||
options[optionName] = (optionElement.optionWidget.value === this.trueInputValue) ? true : false;
|
||||
} else {
|
||||
options[optionName] = optionElement.optionWidget.value;
|
||||
}
|
||||
}
|
||||
optionElement.optionValue = options[optionName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export let trueInputValue: string = 'True';
|
||||
export let falseInputValue: string = 'False';
|
||||
|
||||
export function findElement(container: Builder, className: string): HTMLElement {
|
||||
var elementBuilder: Builder = container;
|
||||
while (elementBuilder.getHTMLElement()) {
|
||||
var htmlElement = elementBuilder.getHTMLElement();
|
||||
if (htmlElement.className === className) {
|
||||
break;
|
||||
}
|
||||
elementBuilder = elementBuilder.child(0);
|
||||
}
|
||||
return elementBuilder.getHTMLElement();
|
||||
}
|
||||
|
||||
export function groupOptionsByCategory(options: data.ServiceOption[]): { [category: string]: data.ServiceOption[] } {
|
||||
var connectionOptionsMap: { [category: string]: data.ServiceOption[] } = {};
|
||||
options.forEach(option => {
|
||||
var groupName = option.groupName;
|
||||
if (groupName === null || groupName === undefined) {
|
||||
groupName = 'General';
|
||||
}
|
||||
|
||||
if (!!connectionOptionsMap[groupName]) {
|
||||
connectionOptionsMap[groupName].push(option);
|
||||
} else {
|
||||
connectionOptionsMap[groupName] = [option];
|
||||
}
|
||||
});
|
||||
return connectionOptionsMap;
|
||||
}
|
||||
Reference in New Issue
Block a user