/*--------------------------------------------------------------------------------------------- * 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!./media/connectionDialog'; import nls = require('vs/nls'); import { Button } from 'sql/base/browser/ui/button/button'; import { attachModalDialogStyler, attachButtonStyler } from 'sql/common/theme/styler'; import { TPromise } from 'vs/base/common/winjs.base'; import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { Modal } from 'sql/base/browser/ui/modal/modal'; import { IConnectionManagementService, INewConnectionParams } from 'sql/parts/connection/common/connectionManagement'; import * as DialogHelper from 'sql/base/browser/ui/modal/dialogHelper'; import { TreeCreationUtils } from 'sql/parts/registeredServer/viewlet/treeCreationUtils'; import { TreeUpdateUtils } from 'sql/parts/registeredServer/viewlet/treeUpdateUtils'; import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile'; 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 { IPartService } from 'vs/workbench/services/part/common/partService'; import Event, { Emitter } from 'vs/base/common/event'; import { Builder, $ } from 'vs/base/browser/builder'; import { ICancelableEvent } from 'vs/base/parts/tree/browser/treeDefaults'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import * as TelemetryKeys from 'sql/common/telemetryKeys'; import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { ITree } from 'vs/base/parts/tree/browser/tree'; import { RecentConnectionTreeController, RecentConnectionActionsProvider } from 'sql/parts/connection/connectionDialog/recentConnectionTreeController'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMessageService, IConfirmation } from 'vs/platform/message/common/message'; export interface OnShowUIResponse { selectedProviderType: string; container: HTMLElement; } export class ConnectionDialogWidget extends Modal { private _bodyBuilder: Builder; private _recentConnectionBuilder: Builder; private _noRecentConnectionBuilder: Builder; private _dividerBuilder: Builder; private _connectButton: Button; private _closeButton: Button; private _providerTypeSelectBox: SelectBox; private _newConnectionParams: INewConnectionParams; private _recentConnectionTree: ITree; private $connectionUIContainer: Builder; private _onInitDialog = new Emitter(); public onInitDialog: Event = this._onInitDialog.event; private _onCancel = new Emitter(); public onCancel: Event = this._onCancel.event; private _onConnect = new Emitter(); public onConnect: Event = this._onConnect.event; private _onShowUiComponent = new Emitter(); public onShowUiComponent: Event = this._onShowUiComponent.event; private _onFillinConnectionInputs = new Emitter(); public onFillinConnectionInputs: Event = this._onFillinConnectionInputs.event; private _onResetConnection = new Emitter(); public onResetConnection: Event = this._onResetConnection.event; constructor( private providerTypeOptions: string[], private selectedProviderType: string, @IInstantiationService private _instantiationService: IInstantiationService, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IWorkbenchThemeService private _themeService: IWorkbenchThemeService, @IPartService _partService: IPartService, @ITelemetryService telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @IContextMenuService private _contextMenuService: IContextMenuService, @IMessageService private _messageService: IMessageService ) { super(localize('connection', 'Connection'), TelemetryKeys.Connection, _partService, telemetryService, contextKeyService, { hasSpinner: true, hasErrors: true }); } protected renderBody(container: HTMLElement): void { this._bodyBuilder = new Builder(container); this._providerTypeSelectBox = new SelectBox(this.providerTypeOptions, this.selectedProviderType); this._bodyBuilder.div({ class: 'connection-recent', id: 'recentConnection' }, (builder) => { this._recentConnectionBuilder = new Builder(builder.getHTMLElement()); this._noRecentConnectionBuilder = new Builder(builder.getHTMLElement()); this.createRecentConnections(); this._recentConnectionBuilder.hide(); }); this._bodyBuilder.div({ class: 'Connection-divider' }, (dividerContainer) => { this._dividerBuilder = dividerContainer; }); this._bodyBuilder.div({ class: 'connection-type' }, (modelTableContent) => { let connectTypeLabel = localize('connectType', 'Connection type'); modelTableContent.element('table', { class: 'connection-table-content' }, (tableContainer) => { DialogHelper.appendInputSelectBox( DialogHelper.appendRow(tableContainer, connectTypeLabel, 'connection-label', 'connection-input'), this._providerTypeSelectBox); }); }); this.$connectionUIContainer = $('.connection-provider-info#connectionProviderInfo'); this.$connectionUIContainer.appendTo(this._bodyBuilder); let self = this; this._register(self._themeService.onDidColorThemeChange(e => self.updateTheme(e))); self.updateTheme(self._themeService.getColorTheme()); } /** * Render the connection flyout */ public render() { super.render(); attachModalDialogStyler(this, this._themeService); let connectLabel = localize('connect', 'Connect'); let cancelLabel = localize('cancel', 'Cancel'); this._connectButton = this.addFooterButton(connectLabel, () => this.connect()); this._connectButton.enabled = false; this._closeButton = this.addFooterButton(cancelLabel, () => this.cancel()); this.registerListeners(); this.onProviderTypeSelected(this._providerTypeSelectBox.value); } // Update theming that is specific to connection 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 registerListeners(): void { // Theme styler this._register(styler.attachSelectBoxStyler(this._providerTypeSelectBox, this._themeService)); this._register(attachButtonStyler(this._connectButton, this._themeService)); this._register(attachButtonStyler(this._closeButton, this._themeService)); this._register(this._providerTypeSelectBox.onDidSelect(selectedProviderType => { this.onProviderTypeSelected(selectedProviderType.selected); })); } private onProviderTypeSelected(selectedProviderType: string) { // Show connection form based on server type this.$connectionUIContainer.empty(); this._onShowUiComponent.fire({ selectedProviderType: selectedProviderType, container: this.$connectionUIContainer.getHTMLElement() }); this.initDialog(); } private connect(element?: IConnectionProfile): void { if (this._connectButton.enabled) { this._connectButton.enabled = false; this._providerTypeSelectBox.disable(); this.showSpinner(); this._onConnect.fire(element); } } /* Overwrite espace key behavior */ protected onClose(e: StandardKeyboardEvent) { this.cancel(); } /* Overwrite enter key behavior */ protected onAccept(e: StandardKeyboardEvent) { if (!e.target.classList.contains('monaco-tree')) { this.connect(); } } private cancel() { this._onCancel.fire(); this.close(); } public close() { this.resetConnection(); this.hide(); } private clearRecentConnectionList(): TPromise { let confirm: IConfirmation = { message: nls.localize('clearRecentConnectionMessage', 'Are you sure you want to delete all the connections from the list?'), primaryButton: localize('yes', 'Yes'), secondaryButton: localize('no', 'No'), type: 'question' }; return this._messageService.confirm(confirm).then(confirmation => { if (!confirmation.confirmed) { return TPromise.as(false); } else { this._connectionManagementService.clearRecentConnectionsList(); this.open(false); return TPromise.as(true); } }); } private createRecentConnectionList(): void { this._recentConnectionBuilder.div({ class: 'connection-recent-content' }, (recentConnectionContainer) => { let recentHistoryLabel = localize('recentHistory', 'Recent history'); recentConnectionContainer.div({ class: 'recent-titles-container'}, (container) => { container.div({ class: 'connection-history-label' }, (recentTitle) => { recentTitle.innerHtml(recentHistoryLabel); }); container.div({ class: 'search-action clear-search-results'}, (clearSearchIcon) => { clearSearchIcon.on('click', () => this.clearRecentConnectionList()); }); }); recentConnectionContainer.div({ class: 'server-explorer-viewlet' }, (divContainer: Builder) => { divContainer.div({ class: 'explorer-servers' }, (treeContainer: Builder) => { let leftClick = (element: any, eventish: ICancelableEvent, origin: string) => { // element will be a server group if the tree is clicked rather than a item if (element instanceof ConnectionProfile) { this.onRecentConnectionClick({ payload: { origin: origin, originalEvent: eventish } }, element); } }; let actionProvider = this._instantiationService.createInstance(RecentConnectionActionsProvider, this._instantiationService, this._connectionManagementService, this._messageService); let controller = new RecentConnectionTreeController(leftClick, actionProvider, this._connectionManagementService, this._contextMenuService); actionProvider.onRecentConnectionRemoved(() => { this.open(this._connectionManagementService.getRecentConnections().length > 0); }); controller.onRecentConnectionRemoved(() => { this.open(this._connectionManagementService.getRecentConnections().length > 0); }) this._recentConnectionTree = TreeCreationUtils.createConnectionTree(treeContainer.getHTMLElement(), this._instantiationService, controller); // Theme styler this._register(styler.attachListStyler(this._recentConnectionTree, this._themeService)); divContainer.append(this._recentConnectionTree.getHTMLElement()); }); }); }); } private createRecentConnections() { this.createRecentConnectionList(); this._noRecentConnectionBuilder.div({ class: 'connection-recent-content' }, (noRecentConnectionContainer) => { let recentHistoryLabel = localize('recentHistory', 'Recent history'); noRecentConnectionContainer.div({ class: 'connection-history-label' }, (recentTitle) => { recentTitle.innerHtml(recentHistoryLabel); }); let noRecentHistoryLabel = localize('noRecentConnections', 'No Recent Connections'); noRecentConnectionContainer.div({ class: 'no-recent-connections' }, (noRecentTitle) => { noRecentTitle.innerHtml(noRecentHistoryLabel); }); }); } private onRecentConnectionClick(event: any, element: IConnectionProfile) { let isMouseOrigin = event.payload && (event.payload.origin === 'mouse'); let isDoubleClick = isMouseOrigin && event.payload.originalEvent && event.payload.originalEvent.detail === 2; if (isDoubleClick) { this.connect(element); } else { if (element) { this._onFillinConnectionInputs.fire(element); } } } /** * Open the flyout dialog * @param recentConnections Are there recent connections that should be shown */ public open(recentConnections: boolean) { this.show(); if (recentConnections) { this._noRecentConnectionBuilder.hide(); this._recentConnectionBuilder.show(); } else { this._recentConnectionBuilder.hide(); this._noRecentConnectionBuilder.show(); } TreeUpdateUtils.structuralTreeUpdate(this._recentConnectionTree, 'recent', this._connectionManagementService); // call layout with view height this.layout(); this.initDialog(); } protected layout(height?: number): void { // Height is the overall height. Since we're laying out a specific component, always get its actual height this._recentConnectionTree.layout(DOM.getTotalHeight(this._recentConnectionTree.getHTMLElement())); } /** * Set the state of the connect button * @param enabled The state to set the the button */ public set connectButtonState(enabled: boolean) { this._connectButton.enabled = enabled; } /** * Get the connect button state */ public get connectButtonState(): boolean { return this._connectButton.enabled; } private initDialog(): void { super.setError(''); this.hideSpinner(); this._onInitDialog.fire(); } public resetConnection(): void { this.hideSpinner(); this._connectButton.enabled = true; this._providerTypeSelectBox.enable(); this._onResetConnection.fire(); } public get newConnectionParams(): INewConnectionParams { return this._newConnectionParams; } public set newConnectionParams(params: INewConnectionParams) { this._newConnectionParams = params; } public updateProvider(displayName: string) { this._providerTypeSelectBox.selectWithOptionName(displayName); this.onProviderTypeSelected(displayName); } }