Compare commits

...

11 Commits

Author SHA1 Message Date
Matt Irvine
ca755365ce Fix dialog/wizard undefined connectionInfo bug (#1725) 2018-06-25 18:26:25 -07:00
Matt Irvine
ea979de19f Disable wizard/dialog next/done buttons when page not valid (#1708) 2018-06-25 12:25:48 -07:00
Anthony Dresser
473ddfcdf1 Modifying angular bootstrap to add injection at the module level (#1691)
* work on fixing injection

* change bootstrapping method

* add a catch for testing

* remove unneeded code
2018-06-22 16:09:13 -07:00
Leila Lali
a627285a4c Feature/selectable card component (#1703)
* added selectable card

* creating new card type
2018-06-22 14:25:21 -07:00
Alan Ren
322847469d fix dropdown component issue (#1709)
* fix dropdown component issue

* handle enable property default
2018-06-21 18:00:07 -07:00
Matt Irvine
6c5fac997f Add dialog close validation (#1704) 2018-06-21 16:55:47 -07:00
Leila Lali
1871fd383e Feature/form improvements (#1707)
* adding more options for form

* added form requied and info for item
2018-06-21 15:26:52 -07:00
Matt Irvine
f5b147ca4b Add info/warning/error messages for wizards and dialogs (#1696) 2018-06-21 11:55:23 -07:00
Anthony Dresser
9d2b206156 Accessibility enhancements (#1678)
* adds aria-label to inputs for connection

* formatting

* add ariaLabels to all checkboxes/inputboxes/dropdowns

* fix labels on database dropdown action

* fix compile errors

* remove unnecessary code
2018-06-20 16:56:19 -07:00
Karl Burtram
a372c76e07 Update SQL Ops to 0.31.1 for July iteration 2018-06-20 12:52:37 -07:00
Karl Burtram
b1ce07d3ae Add SQL CARBON EDIT tag in product class (#1690) 2018-06-20 12:42:32 -07:00
48 changed files with 898 additions and 325 deletions

View File

@@ -38,5 +38,6 @@
}
}
],
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"git.ignoreLimitWarning": true
}

View File

@@ -49,9 +49,9 @@ core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.1.7":
version "0.1.7"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/d50285b03d0d5073c086362c5c96afb279320607"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.1.9":
version "0.1.9"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/a1a79895cb79658b75d78aa5cfd745855019148d"
dependencies:
vscode-languageclient "3.5.0"

View File

@@ -1,6 +1,6 @@
{
"name": "sqlops",
"version": "0.30.6",
"version": "0.31.1",
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
"author": {
"name": "Microsoft Corporation"

View File

@@ -11,7 +11,7 @@ export class ToggleDropdownAction extends Action {
private static readonly ID = 'dropdownAction.toggle';
private static readonly ICON = 'dropdown-arrow';
constructor(label, private _fn: () => any) {
constructor(private _fn: () => any, label: string) {
super(ToggleDropdownAction.ID, label, ToggleDropdownAction.ICON);
}

View File

@@ -53,6 +53,10 @@ export interface IDropdownOptions extends IDropdownStyles {
* Value to use as aria-label for the input box
*/
ariaLabel?: string;
/**
* Label for the dropdown action
*/
actionLabel: string;
}
export interface IDropdownStyles {
@@ -66,7 +70,8 @@ const defaults: IDropdownOptions = {
strictSelection: true,
maxHeight: 300,
errorMessage: errorMessage,
contextBorder: Color.fromHex('#696969')
contextBorder: Color.fromHex('#696969'),
actionLabel: nls.localize('dropdownAction.toggle', "Toggle dropdown")
};
interface ListResource {
@@ -115,11 +120,11 @@ export class Dropdown extends Disposable {
this.$input = $('.dropdown-input').style('width', '100%').appendTo(this.$el);
this.$treeContainer = $('.dropdown-tree');
this._toggleAction = new ToggleDropdownAction(nls.localize('dropdown.toggle', '{0} Toggle Dropdown', this._options.ariaLabel), () => {
this._toggleAction = new ToggleDropdownAction(() => {
this._showList();
this._tree.domFocus();
this._tree.focusFirst();
});
}, opt.actionLabel);
this._input = new InputBox(this.$input.getHTMLElement(), contextViewService, {
validationOptions: {

View File

@@ -172,4 +172,22 @@
margin-right: 10px;
width: 20px;
height: 20px;
}
.modal .modal-footer .dialogErrorMessage {
align-items: center;
max-height: 30px;
margin-right: 5px;
}
.modal .dialogErrorMessage .icon {
float: left;
margin-right: 10px;
width: auto;
height: 20px;
}
.modal .modal-footer .dialogErrorMessage .errorMessage {
max-height: 100%;
overflow-y: scroll;
}

View File

@@ -21,9 +21,13 @@ import { Button } from 'sql/base/browser/ui/button/button';
import * as TelemetryUtils from 'sql/common/telemetryUtilities';
import * as TelemetryKeys from 'sql/common/telemetryKeys';
import { localize } from 'vs/nls';
import { MessageLevel } from 'sql/workbench/api/common/sqlExtHostTypes';
export const MODAL_SHOWING_KEY = 'modalShowing';
export const MODAL_SHOWING_CONTEXT = new RawContextKey<Array<string>>(MODAL_SHOWING_KEY, []);
const INFO_ALT_TEXT = localize('infoAltText', 'Info');
const WARNING_ALT_TEXT = localize('warningAltText', 'Warning');
const ERROR_ALT_TEXT = localize('errorAltText', 'Error');
export interface IModalDialogStyles {
dialogForeground?: Color;
@@ -145,7 +149,7 @@ export abstract class Modal extends Disposable implements IThemable {
/**
* Build and render the modal, will call {@link Modal#renderBody}
*/
public render() {
public render(errorMessagesInFooter: boolean = false) {
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
@@ -182,17 +186,6 @@ export abstract class Modal extends Disposable implements IThemable {
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) => {
@@ -221,6 +214,19 @@ export abstract class Modal extends Disposable implements IThemable {
builderClass += ' wide';
}
if (this._modalOptions.isAngular === false && this._modalOptions.hasErrors) {
let builder = errorMessagesInFooter ? this._leftFooter : body;
builder.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;
});
});
}
// 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) => {
@@ -355,14 +361,33 @@ export abstract class Modal extends Disposable implements IThemable {
* Show an error in the error message element
* @param err Text to show in the error message
*/
protected setError(err: string) {
protected setError(err: string, level: MessageLevel = MessageLevel.Error) {
if (this._modalOptions.hasErrors) {
if (err === '') {
this._errorIconElement.style.visibility = 'hidden';
} else {
const levelClasses = ['info', 'warning', 'error'];
let selectedLevel = levelClasses[2];
let altText = ERROR_ALT_TEXT;
if (level === MessageLevel.Information) {
selectedLevel = levelClasses[0];
altText = INFO_ALT_TEXT;
} else if (level === MessageLevel.Warning) {
selectedLevel = levelClasses[1];
altText = WARNING_ALT_TEXT;
}
levelClasses.forEach(level => {
if (selectedLevel === level) {
this._errorIconElement.classList.add(level);
} else {
this._errorIconElement.classList.remove(level);
}
});
this._errorIconElement.title = altText;
this._errorIconElement.style.visibility = 'visible';
}
this._errorMessage.innerHtml(err);
this._errorMessage.text(err);
}
}

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-blue{fill:#1BA1E2;} .icon-white{fill:#FFFFFF;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M0 8c0-4.418 3.582-8 8-8s8 3.582 8 8-3.582 8-8 8-8-3.582-8-8z" id="outline"/><path class="icon-vs-blue" d="M8 1c-3.865 0-7 3.135-7 7s3.135 7 7 7 7-3.135 7-7-3.135-7-7-7zm1 12h-2v-7h2v7zm0-8h-2v-2h2v2z" id="iconBg"/><path class="icon-white" d="M7 6h2v7h-2v-7zm0-1h2v-2h-2v2z" id="iconFg"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-blue{fill:#1BA1E2;} .icon-white{fill:#FFFFFF;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M0 8c0-4.418 3.582-8 8-8s8 3.582 8 8-3.582 8-8 8-8-3.582-8-8z" id="outline"/><path class="icon-vs-blue" d="M8 1c-3.865 0-7 3.135-7 7s3.135 7 7 7 7-3.135 7-7-3.135-7-7-7zm1 12h-2v-7h2v7zm0-8h-2v-2h2v2z" id="iconBg"/><path class="icon-white" d="M7 6h2v7h-2v-7zm0-1h2v-2h-2v2z" id="iconFg"/></svg>

Before

Width:  |  Height:  |  Size: 627 B

After

Width:  |  Height:  |  Size: 624 B

View File

@@ -6,12 +6,14 @@
import { NgModule, Inject, forwardRef, ApplicationRef, ComponentFactoryResolver, Type } from '@angular/core';
import { APP_BASE_HREF, CommonModule } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { CreateLoginComponent, CREATELOGIN_SELECTOR } from 'sql/parts/admin/security/createLogin.component';
import { IBootstrapParams, providerIterator } from 'sql/services/bootstrap/bootstrapService';
import { CreateLoginComponent } from 'sql/parts/admin/security/createLogin.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
// Connection Dashboard main angular module
export const CreateLoginModule = (params: IBootstrapParams, selector: string): Type<any> => {
export const CreateLoginModule = (params: IBootstrapParams, selector: string, instantiationService: IInstantiationService): Type<any> => {
@NgModule({
declarations: [
@@ -24,7 +26,8 @@ export const CreateLoginModule = (params: IBootstrapParams, selector: string): T
],
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
{ provide: IBootstrapParams, useValue: params }
{ provide: IBootstrapParams, useValue: params },
...providerIterator(instantiationService)
]
})
class ModuleClass {

View File

@@ -172,7 +172,8 @@ export class ConnectionWidget {
strictSelection: false,
placeholder: this._defaultDatabaseName,
maxHeight: 125,
ariaLabel: databaseOption.displayName
ariaLabel: databaseOption.displayName,
actionLabel: localize('toggleDatabaseNameDropdown', 'Select Database Toggle Dropdown')
});
let serverGroupLabel = localize('serverGroup', 'Server group');

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Inject, NgModule, forwardRef, ApplicationRef, ComponentFactoryResolver, NgModuleRef, NgModuleFactory } from '@angular/core';
import { Inject, NgModule, forwardRef, ApplicationRef, ComponentFactoryResolver } from '@angular/core';
import { CommonModule, APP_BASE_HREF } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule, Routes, UrlSerializer, Router, NavigationEnd } from '@angular/router';
@@ -14,7 +14,7 @@ import { ChartsModule } from 'ng2-charts/ng2-charts';
import CustomUrlSerializer from 'sql/common/urlSerializer';
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
import { Extensions as ComponentExtensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { IBootstrapParams, ISelector, providerIterator } from 'sql/services/bootstrap/bootstrapService';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -32,7 +32,7 @@ import { CommonServiceInterface } from 'sql/services/common/commonServiceInterfa
import { ComponentHostDirective } from 'sql/parts/dashboard/common/componentHost.directive';
/* Base Components */
import { DashboardComponent, DASHBOARD_SELECTOR } from 'sql/parts/dashboard/dashboard.component';
import { DashboardComponent } from 'sql/parts/dashboard/dashboard.component';
import { DashboardWidgetWrapper } from 'sql/parts/dashboard/contents/dashboardWidgetWrapper.component';
import { DashboardWidgetContainer } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.component';
import { DashboardGridContainer } from 'sql/parts/dashboard/containers/dashboardGridContainer.component';
@@ -80,7 +80,8 @@ import { ExplorerWidget } from 'sql/parts/dashboard/widgets/explorer/explorerWid
import { TasksWidget } from 'sql/parts/dashboard/widgets/tasks/tasksWidget.component';
import { InsightsWidget } from 'sql/parts/dashboard/widgets/insights/insightsWidget.component';
import { WebviewWidget } from 'sql/parts/dashboard/widgets/webview/webviewWidget.component';
import { JobStepsViewComponent } from '../jobManagement/views/jobStepsView.component';
import { JobStepsViewComponent } from 'sql/parts/jobManagement/views/jobStepsView.component';
import { IInstantiationService, _util } from 'vs/platform/instantiation/common/instantiation';
let widgetComponents = [
PropertiesWidgetComponent,
@@ -109,7 +110,7 @@ const appRoutes: Routes = [
];
// Connection Dashboard main angular module
export const DashboardModule = (params, selector: string): any => {
export const DashboardModule = (params, selector: string, instantiationService: IInstantiationService): any => {
@NgModule({
declarations: [
...baseComponents,
@@ -140,31 +141,31 @@ export const DashboardModule = (params, selector: string): any => {
{ provide: IBreadcrumbService, useClass: BreadcrumbService },
{ provide: CommonServiceInterface, useClass: DashboardServiceInterface },
{ provide: UrlSerializer, useClass: CustomUrlSerializer },
{ provide: IBootstrapParams, useValue: params }
{ provide: IBootstrapParams, useValue: params },
{ provide: ISelector, useValue: selector },
...providerIterator(instantiationService)
]
})
class ModuleClass {
private _bootstrap: DashboardServiceInterface;
private navigations = 0;
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
@Inject(forwardRef(() => CommonServiceInterface)) bootstrap: CommonServiceInterface,
@Inject(forwardRef(() => Router)) private _router: Router,
@Inject(ITelemetryService) private telemetryService: ITelemetryService
@Inject(ITelemetryService) private telemetryService: ITelemetryService,
@Inject(ISelector) private selector: string
) {
this._bootstrap = bootstrap as DashboardServiceInterface;
}
ngDoBootstrap(appRef: ApplicationRef) {
const factory = this._resolver.resolveComponentFactory(DashboardComponent);
this._bootstrap.selector = selector;
(<any>factory).factory.selector = selector;
(<any>factory).factory.selector = this.selector;
appRef.bootstrap(factory);
this._router.events.subscribe(e => {
if (e instanceof NavigationEnd) {
this._bootstrap.handlePageNavigation();
this.navigations++;
TelemetryUtils.addTelemetry(this.telemetryService, TelemetryKeys.DashboardNavigated, {
numberOfNavigations: this._bootstrap.getNumberOfPageNavigations(),
numberOfNavigations: this.navigations,
routeUrl: e.url
});
}

View File

@@ -4,50 +4,29 @@
*--------------------------------------------------------------------------------------------*/
/* Node Modules */
import { Injectable, Inject, forwardRef, OnDestroy } from '@angular/core';
import { Injectable, Inject, forwardRef } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
/* SQL imports */
import { IDashboardComponentParams } from 'sql/services/bootstrap/bootstrapParams';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { IMetadataService } from 'sql/services/metadata/metadataService';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { ConnectionManagementInfo } from 'sql/parts/connection/common/connectionManagementInfo';
import { IAdminService } from 'sql/parts/admin/common/adminService';
import { IQueryManagementService } from 'sql/parts/query/common/queryManagement';
import { toDisposableSubscription } from 'sql/parts/common/rxjsUtils';
import { IInsightsDialogService } from 'sql/parts/insights/common/interfaces';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { AngularEventType, IAngularEvent, IAngularEventingService } from 'sql/services/angularEventing/angularEventingService';
import { IDashboardTab } from 'sql/platform/dashboard/common/dashboardRegistry';
import { TabSettingConfig } from 'sql/parts/dashboard/common/dashboardWidget';
import { IDashboardViewService } from 'sql/services/dashboard/common/dashboardViewService';
import { AngularDisposable } from 'sql/base/common/lifecycle';
import { ConnectionContextkey } from 'sql/parts/connection/common/connectionContextKey';
import { SingleConnectionMetadataService, SingleConnectionManagementService, SingleAdminService, SingleQueryManagementService, CommonServiceInterface }
from 'sql/services/common/commonServiceInterface.service';
import { ProviderMetadata, DatabaseInfo, SimpleExecuteResult } from 'sqlops';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
/* VS imports */
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { ConfigurationEditingService, IConfigurationValue } from 'vs/workbench/services/configuration/node/configurationEditingService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Event, Emitter } from 'vs/base/common/event';
import Severity from 'vs/base/common/severity';
import * as nls from 'vs/nls';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { deepClone } from 'vs/base/common/objects';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { INotificationService } from 'vs/platform/notification/common/notification';
const DASHBOARD_SETTINGS = 'dashboard';
@@ -85,45 +64,22 @@ export class DashboardServiceInterface extends CommonServiceInterface {
private _numberOfPageNavigations = 0;
constructor(
@Inject(forwardRef(() => Router)) private _router: Router,
@Inject(INotificationService) private _notificationService: INotificationService,
@Inject(IMetadataService) metadataService: IMetadataService,
@Inject(IConnectionManagementService) connectionManagementService: IConnectionManagementService,
@Inject(IAdminService) adminService: IAdminService,
@Inject(IQueryManagementService) queryManagementService: IQueryManagementService,
@Inject(IBootstrapParams) params: IDashboardComponentParams,
@Inject(forwardRef(() => Router)) private _router: Router,
@Inject(INotificationService) private _notificationService: INotificationService,
@Inject(IAngularEventingService) private angularEventingService: IAngularEventingService,
@Inject(IConfigurationService) private _configService: IConfigurationService,
@Inject(IBootstrapParams) _params: IDashboardComponentParams
@Inject(IConfigurationService) private _configService: IConfigurationService
) {
super(_params, metadataService, connectionManagementService, adminService, queryManagementService);
}
private get params(): IDashboardComponentParams {
return this._params;
}
/**
* Set the selector for this dashboard instance, should only be set once
*/
public set selector(selector: string) {
this._uniqueSelector = selector;
this._getbootstrapParams();
}
protected _getbootstrapParams(): void {
this.scopedContextKeyService = this.params.scopedContextService;
this._connectionContextKey = this.params.connectionContextKey;
this.dashboardContextKey = this._dashboardContextKey.bindTo(this.scopedContextKeyService);
this.uri = this.params.ownerUri;
}
/**
* Set the uri for this dashboard instance, should only be set once
* Inits all the services that depend on knowing a uri
*/
protected set uri(uri: string) {
super.setUri(uri);
this._register(toDisposableSubscription(this.angularEventingService.onAngularEvent(this._uri, (event) => this.handleDashboardEvent(event))));
super(params, metadataService, connectionManagementService, adminService, queryManagementService);
// during testing there may not be params
if (this._params) {
this.dashboardContextKey = this._dashboardContextKey.bindTo(this.scopedContextKeyService);
this._register(toDisposableSubscription(this.angularEventingService.onAngularEvent(this._uri, (event) => this.handleDashboardEvent(event))));
}
}
/**

View File

@@ -4,20 +4,23 @@
*--------------------------------------------------------------------------------------------*/
import {
ApplicationRef, ComponentFactoryResolver, ModuleWithProviders, NgModule,
ApplicationRef, ComponentFactoryResolver, NgModule,
Inject, forwardRef, Type
} from '@angular/core';
import { APP_BASE_HREF, CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { BackupComponent, BACKUP_SELECTOR } from 'sql/parts/disasterRecovery/backup/backup.component';
import { IBootstrapParams, ISelector, providerIterator } from 'sql/services/bootstrap/bootstrapService';
import { BackupComponent } from 'sql/parts/disasterRecovery/backup/backup.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
// work around
const BrowserAnimationsModule = (<any>require.__$__nodeRequire('@angular/platform-browser/animations')).BrowserAnimationsModule;
// Backup wizard main angular module
export const BackupModule = (params: IBootstrapParams, selector: string): Type<any> => {
export const BackupModule = (params: IBootstrapParams, selector: string, instantiationService: IInstantiationService): Type<any> => {
@NgModule({
declarations: [
BackupComponent
@@ -31,19 +34,22 @@ export const BackupModule = (params: IBootstrapParams, selector: string): Type<a
],
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
{ provide: IBootstrapParams, useValue: params }
{ provide: IBootstrapParams, useValue: params },
{ provide: ISelector, useValue: selector },
...providerIterator(instantiationService)
]
})
class ModuleClass {
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
@Inject(ISelector) private selector: string
) {
}
ngDoBootstrap(appRef: ApplicationRef) {
const factory = this._resolver.resolveComponentFactory(BackupComponent);
(<any>factory).factory.selector = selector;
(<any>factory).factory.selector = this.selector;
appRef.bootstrap(factory);
}
}

View File

@@ -235,7 +235,8 @@ export class RestoreDialog extends Modal {
this._databaseDropdown = new Dropdown(inputCellContainer.getHTMLElement(), this._contextViewService, this._themeService,
{
strictSelection: false,
ariaLabel: LocalizedStrings.TARGETDATABASE
ariaLabel: LocalizedStrings.TARGETDATABASE,
actionLabel: localize('toggleDatabaseNameDropdown', 'Select Database Toggle Dropdown')
}
);
this._databaseDropdown.onValueChange(s => {

View File

@@ -7,12 +7,14 @@
import { ApplicationRef, ComponentFactoryResolver, NgModule, Inject, forwardRef, Type } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { EditDataComponent, EDITDATA_SELECTOR } from 'sql/parts/grid/views/editData/editData.component';
import { SlickGrid } from 'angular2-slickgrid';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
export const EditDataModule = (params: IBootstrapParams, selector: string): Type<any> => {
import { EditDataComponent } from 'sql/parts/grid/views/editData/editData.component';
import { IBootstrapParams, ISelector, providerIterator } from 'sql/services/bootstrap/bootstrapService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export const EditDataModule = (params: IBootstrapParams, selector: string, instantiationService: IInstantiationService): Type<any> => {
@NgModule({
@@ -30,19 +32,22 @@ export const EditDataModule = (params: IBootstrapParams, selector: string): Type
EditDataComponent
],
providers: [
{ provide: IBootstrapParams, useValue: params }
{ provide: IBootstrapParams, useValue: params },
{ provide: ISelector, useValue: selector },
...providerIterator(instantiationService)
]
})
class ModuleClass {
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
@Inject(ISelector) private selector: string
) {
}
ngDoBootstrap(appRef: ApplicationRef) {
const factory = this._resolver.resolveComponentFactory(EditDataComponent);
(<any>factory).factory.selector = selector;
(<any>factory).factory.selector = this.selector;
appRef.bootstrap(factory);
}
}

View File

@@ -10,21 +10,16 @@ import {
import * as sqlops from 'sqlops';
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
import { ComponentWithIconBase } from 'sql/parts/modelComponents/componentWithIconBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
import { attachButtonStyler } from 'sql/common/theme/styler';
import { Button } from 'sql/base/browser/ui/button/button';
import { SIDE_BAR_BACKGROUND, SIDE_BAR_TITLE_FOREGROUND } from 'vs/workbench/common/theme';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import URI from 'vs/base/common/uri';
import { IdGenerator } from 'vs/base/common/idGenerator';
import { createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom';
import { focusBorder, foreground } from 'vs/platform/theme/common/colorRegistry';
import { Color } from 'vs/base/common/color';
type IUserFriendlyIcon = string | URI | { light: string | URI; dark: string | URI };
@Component({
selector: 'modelview-button',
@@ -32,12 +27,10 @@ type IUserFriendlyIcon = string | URI | { light: string | URI; dark: string | UR
<div #input style="width: 100%"></div>
`
})
export default class ButtonComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
export default class ButtonComponent extends ComponentWithIconBase implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _button: Button;
private _iconClass: string;
private _iconPath: IUserFriendlyIcon;
@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
constructor(
@@ -71,9 +64,6 @@ export default class ButtonComponent extends ComponentBase implements IComponent
}
ngOnDestroy(): void {
if (this._iconClass) {
removeCSSRulesContainingSelector(this._iconClass);
}
this.baseDestroy();
}
@@ -101,14 +91,11 @@ export default class ButtonComponent extends ComponentBase implements IComponent
this.updateIcon();
}
private updateIcon() {
if (this.iconPath && this.iconPath !== this._iconPath) {
this._iconPath = this.iconPath;
protected updateIcon() {
if (this.iconPath) {
if (!this._iconClass) {
const ids = new IdGenerator('button-component-icon-' + Math.round(Math.random() * 1000));
this._iconClass = ids.nextId();
super.updateIcon();
this._button.icon = this._iconClass + ' icon';
// Styling for icon button
this._register(attachButtonStyler(this._button, this.themeService, {
buttonBackground: Color.transparent.toString(),
@@ -117,36 +104,6 @@ export default class ButtonComponent extends ComponentBase implements IComponent
buttonForeground: foreground
}));
}
removeCSSRulesContainingSelector(this._iconClass);
const icon = this.getLightIconPath(this.iconPath);
const iconDark = this.getDarkIconPath(this.iconPath) || icon;
createCSSRule(`.icon.${this._iconClass}`, `background-image: url("${icon}")`);
createCSSRule(`.vs-dark .icon.${this._iconClass}, .hc-black .icon.${this._iconClass}`, `background-image: url("${iconDark}")`);
}
}
private getLightIconPath(iconPath: IUserFriendlyIcon): string {
if (iconPath && iconPath['light']) {
return this.getIconPath(iconPath['light']);
} else {
return this.getIconPath(<string | URI>iconPath);
}
}
private getDarkIconPath(iconPath: IUserFriendlyIcon): string {
if (iconPath && iconPath['dark']) {
return this.getIconPath(iconPath['dark']);
}
return null;
}
private getIconPath(iconPath: string | URI): string {
if (typeof iconPath === 'string') {
return URI.file(iconPath).toString();
} else {
let uri = URI.revive(iconPath);
return uri.toString();
}
}
@@ -160,13 +117,7 @@ export default class ButtonComponent extends ComponentBase implements IComponent
this.setPropertyFromUI<sqlops.ButtonProperties, string>(this.setValueProperties, newValue);
}
public get iconPath(): string | URI | { light: string | URI; dark: string | URI } {
return this.getPropertyOrDefault<sqlops.ButtonProperties, IUserFriendlyIcon>((props) => props.iconPath, undefined);
}
public set iconPath(newValue: string | URI | { light: string | URI; dark: string | URI }) {
this.setPropertyFromUI<sqlops.ButtonProperties, IUserFriendlyIcon>((properties, iconPath) => { properties.iconPath = iconPath; }, newValue);
}
private setValueProperties(properties: sqlops.ButtonProperties, label: string): void {
properties.label = label;

View File

@@ -1,19 +1,32 @@
<div *ngIf="label" class="model-card">
<div *ngIf="label" [class]="getClass()" (click)="onCardClick()" (mouseover)="onCardHoverChanged($event)" (mouseout)="onCardHoverChanged($event)">
<span *ngIf="hasStatus" class="card-status">
<div class="status-content" [style.backgroundColor]="statusColor"></div>
</span>
<div class="card-content">
<h4 class="card-label">{{label}}</h4>
<p class="card-value">{{value}}</p>
<span *ngIf="actions">
<table class="model-table">
<tr *ngFor="let action of actions">
<td class="table-row">{{action.label}}</td>
<td *ngIf="action.actionTitle" class="table-row">
<a class="pointer prominent" (click)="onDidActionClick(action)">{{action.actionTitle}}</a>
</td>
</tr>
</table>
</span>
</div>
<ng-container *ngIf="isVerticalButton">
<div class="card-vertical-button">
<div *ngIf="iconPath" class="iconContainer"><div [class]="iconClass" [style.width]="iconWidth" [style.height]="iconHeight"></div>
<hr/>
<h4 class="card-label">{{label}}</h4>
</div>
</div>
</ng-container>
<ng-container *ngIf="isDetailsCard">
<div class="card-content">
<h4 class="card-label">{{label}}</h4>
<p class="card-value">{{value}}</p>
<span *ngIf="actions">
<table class="model-table">
<tr *ngFor="let action of actions">
<td class="table-row">{{action.label}}</td>
<td *ngIf="action.actionTitle" class="table-row">
<a class="pointer prominent" (click)="onDidActionClick(action)">{{action.actionTitle}}</a>
</td>
</tr>
</table>
</span>
</div>
</ng-container>
</div>

View File

@@ -15,14 +15,14 @@ import * as colors from 'vs/platform/theme/common/colorRegistry';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
import { ComponentWithIconBase } from 'sql/parts/modelComponents/componentWithIconBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
import { StatusIndicator, CardProperties, ActionDescriptor } from 'sql/workbench/api/common/sqlExtHostTypes';
@Component({
templateUrl: decodeURI(require.toUrl('sql/parts/modelComponents/card.component.html'))
})
export default class CardComponent extends ComponentBase implements IComponent, OnDestroy {
export default class CardComponent extends ComponentWithIconBase implements IComponent, OnDestroy {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
@@ -30,7 +30,7 @@ export default class CardComponent extends ComponentBase implements IComponent,
constructor(@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
) {
super(changeRef);
}
@@ -46,6 +46,39 @@ export default class CardComponent extends ComponentBase implements IComponent,
this.baseDestroy();
}
private _defaultBorderColor = 'rgb(214, 214, 214)';
private _hasFocus: boolean;
public onCardClick() {
if (this.selectable) {
this.selected = !this.selected;
this._changeRef.detectChanges();
this._onEventEmitter.fire({
eventType: ComponentEventType.onDidClick,
args: this.selected
});
}
}
public getBorderColor() {
if (this.selectable && this.selected || this._hasFocus) {
return 'Blue';
} else {
return this._defaultBorderColor;
}
}
public getClass(): string {
return (this.selectable && this.selected || this._hasFocus) ? 'model-card selected' :
'model-card unselected';
}
public onCardHoverChanged(event: any) {
if (this.selectable) {
this._hasFocus = event.type === 'mouseover';
this._changeRef.detectChanges();
}
}
/// IComponent implementation
public layout(): void {
@@ -57,6 +90,19 @@ export default class CardComponent extends ComponentBase implements IComponent,
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
this.updateIcon();
}
public get iconClass(): string {
return this._iconClass + ' icon' + ' cardIcon';
}
private get selectable(): boolean {
return this.cardType === 'VerticalButton';
}
// CSS-bound properties
public get label(): string {
@@ -67,6 +113,27 @@ export default class CardComponent extends ComponentBase implements IComponent,
return this.getPropertyOrDefault<CardProperties, string>((props) => props.value, '');
}
public get cardType(): string {
return this.getPropertyOrDefault<CardProperties, string>((props) => props.cardType, 'Details');
}
public get selected(): boolean {
return this.getPropertyOrDefault<sqlops.CardProperties, boolean>((props) => props.selected, false);
}
public set selected(newValue: boolean) {
this.setPropertyFromUI<sqlops.CardProperties, boolean>((props, value) => props.selected = value, newValue);
}
public get isDetailsCard(): boolean {
return !this.cardType || this.cardType === 'Details';
}
public get isVerticalButton(): boolean {
return this.cardType === 'VerticalButton';
}
public get actions(): ActionDescriptor[] {
return this.getPropertyOrDefault<CardProperties, ActionDescriptor[]>((props) => props.actions, []);
}

View File

@@ -7,12 +7,26 @@
margin: 15px;
border-width: 1px;
border-style: solid;
border-color: rgb(214, 214, 214);
text-align: left;
vertical-align: top;
box-shadow: rgba(120, 120, 120, 0.75) 0px 0px 6px;
}
.model-card.selected {
border-color: darkblue
}
.vs-dark .monaco-workbench .model-card.selected,
.hc-black .monaco-workbench .model-card.selected {
border-color: darkblue
}
.model-card.unselected {
border-color: rgb(214, 214, 214);
}
.model-card .card-content {
position: relative;
display: inline-block;
@@ -23,6 +37,16 @@
min-width: 30px;
}
.model-card .card-vertical-button {
position: relative;
display: inline-block;
height: auto;
width: auto;
padding: 5px 5px 5px 5px;
min-height: 130px;
min-width: 130px;
}
.model-card .card-label {
font-size: 12px;
font-weight: bold;
@@ -33,6 +57,19 @@
line-height: 18px;
}
.model-card .iconContainer {
width: 100%;
height: 50px;
text-align: center;
padding: 10px 0px 10px 0px;
}
.model-card .cardIcon {
display: inline-block;
width: 40px;
height: 40px;
}
.model-card .card-status {
position: absolute;
top: 7px;

View File

@@ -18,6 +18,12 @@ import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboar
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { ModelComponentWrapper } from 'sql/parts/modelComponents/modelComponentWrapper.component';
import URI from 'vs/base/common/uri';
import { IdGenerator } from 'vs/base/common/idGenerator';
import { createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom';
export type IUserFriendlyIcon = string | URI | { light: string | URI; dark: string | URI };
export class ItemDescriptor<T> {
constructor(public descriptor: IComponentDescriptor, public config: T) { }
@@ -98,10 +104,6 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
if (enabled === undefined) {
enabled = true;
properties['enabled'] = enabled;
this.fireEvent({
eventType: ComponentEventType.PropertiesChanged,
args: this.getProperties()
});
}
return <boolean>enabled;
}

View File

@@ -0,0 +1,107 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, OnInit, QueryList
} from '@angular/core';
import { IComponent, IComponentDescriptor, IModelStore, IComponentEventArgs, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
import * as sqlops from 'sqlops';
import URI from 'vs/base/common/uri';
import { IdGenerator } from 'vs/base/common/idGenerator';
import { createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom';
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
export type IUserFriendlyIcon = string | URI | { light: string | URI; dark: string | URI };
export class ItemDescriptor<T> {
constructor(public descriptor: IComponentDescriptor, public config: T) { }
}
export abstract class ComponentWithIconBase extends ComponentBase {
protected _iconClass: string;
protected _iconPath: IUserFriendlyIcon;
constructor(
changeRef: ChangeDetectorRef) {
super(changeRef);
}
/// IComponent implementation
public get iconClass(): string {
return this._iconClass + ' icon';
}
protected updateIcon() {
if (this.iconPath && this.iconPath !== this._iconPath) {
this._iconPath = this.iconPath;
if (!this._iconClass) {
const ids = new IdGenerator('model-view-component-icon-' + Math.round(Math.random() * 1000));
this._iconClass = ids.nextId();
}
removeCSSRulesContainingSelector(this._iconClass);
const icon = this.getLightIconPath(this.iconPath);
const iconDark = this.getDarkIconPath(this.iconPath) || icon;
createCSSRule(`.icon.${this._iconClass}`, `background-image: url("${icon}")`);
createCSSRule(`.vs-dark .icon.${this._iconClass}, .hc-black .icon.${this._iconClass}`, `background-image: url("${iconDark}")`);
}
}
private getLightIconPath(iconPath: IUserFriendlyIcon): string {
if (iconPath && iconPath['light']) {
return this.getIconPath(iconPath['light']);
} else {
return this.getIconPath(<string | URI>iconPath);
}
}
private getDarkIconPath(iconPath: IUserFriendlyIcon): string {
if (iconPath && iconPath['dark']) {
return this.getIconPath(iconPath['dark']);
}
return null;
}
private getIconPath(iconPath: string | URI): string {
if (typeof iconPath === 'string') {
return URI.file(iconPath).toString();
} else {
let uri = URI.revive(iconPath);
return uri.toString();
}
}
public getIconWidth(): string {
return this.convertSize(this.iconWidth, '40px');
}
public getIconHeight(): string {
return this.convertSize(this.iconHeight, '40px');
}
public get iconPath(): string | URI | { light: string | URI; dark: string | URI } {
return this.getPropertyOrDefault<sqlops.ComponentWithIcon, IUserFriendlyIcon>((props) => props.iconPath, undefined);
}
public get iconHeight(): number | string {
return this.getPropertyOrDefault<sqlops.ComponentWithIcon, number | string>((props) => props.iconHeight, '40px');
}
public get iconWidth(): number | string {
return this.getPropertyOrDefault<sqlops.ComponentWithIcon, number | string>((props) => props.iconWidth, '40px');
}
ngOnDestroy(): void {
if (this._iconClass) {
removeCSSRulesContainingSelector(this._iconClass);
}
super.ngOnDestroy();
}
}

View File

@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import * as sqlops from 'sqlops';
@@ -14,14 +14,11 @@ import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
import { Dropdown, IDropdownOptions } from 'sql/base/browser/ui/editableDropdown/dropdown';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { attachEditableDropdownStyler } from 'sql/common/theme/styler';
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { Event, Emitter } from 'vs/base/common/event';
import { attachListStyler } from 'vs/platform/theme/common/styler';
@Component({
selector: 'modelview-dropdown',
@@ -60,7 +57,8 @@ export default class DropDownComponent extends ComponentBase implements ICompone
strictSelection: false,
placeholder: '',
maxHeight: 125,
ariaLabel: ''
ariaLabel: '',
actionLabel: ''
};
this._editableDropdown = new Dropdown(this._editableDropDownContainer.nativeElement, this.contextViewService, this.themeService,
dropdownOptions);
@@ -147,15 +145,11 @@ export default class DropDownComponent extends ComponentBase implements ICompone
private getSelectedValue(): string {
if (this.values && this.values.length > 0 && this.valuesHaveDisplayName()) {
let selectedValue = <sqlops.CategoryValue>this.value || <sqlops.CategoryValue>this.values[0];
if (!this.value) {
this.value = selectedValue;
}
let valueCategory = (<sqlops.CategoryValue[]>this.values).find(v => v.name === selectedValue.name);
return valueCategory && valueCategory.displayName;
} else {
if (!this.value && this.values && this.values.length > 0) {
this.value = <string>this.values[0];
return <string>this.values[0];
}
return <string>this.value;
}
@@ -200,11 +194,11 @@ export default class DropDownComponent extends ComponentBase implements ICompone
this.setPropertyFromUI<sqlops.DropDownProperties, string[] | sqlops.CategoryValue[]>(this.setValuesProperties, newValue);
}
private setValueProperties(properties: sqlops.DropDownProperties, value: string): void {
private setValueProperties(properties: sqlops.DropDownProperties, value: string | sqlops.CategoryValue): void {
properties.value = value;
}
private setValuesProperties(properties: sqlops.DropDownProperties, values: string[]): void {
private setValuesProperties(properties: sqlops.DropDownProperties, values: string[] | sqlops.CategoryValue[]): void {
properties.values = values;
}
}

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./formLayout';
import 'vs/css!sql/media/icons/common-icons';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
@@ -25,6 +26,9 @@ export interface TitledFormItemLayout {
horizontal: boolean;
componentWidth?: number | string;
componentHeight?: number | string;
titleFontSize?: number | string;
required?: boolean;
info?: string;
}
export interface FormLayout {
@@ -37,12 +41,15 @@ class FormItem {
@Component({
template: `
<div #container *ngIf="items" class="form-table" [style.width]="getFormWidth()" [style.height]="getFormHeight()">
<div #container *ngIf="items" class="form-table" [style.padding]="getFormPadding()" [style.width]="getFormWidth()" [style.height]="getFormHeight()">
<ng-container *ngFor="let item of items">
<div class="form-row" *ngIf="isFormComponent(item)" [style.height]="getRowHeight(item)">
<ng-container *ngIf="isHorizontal(item)">
<div class="form-cell">{{getItemTitle(item)}}</div>
<div class="form-cell" [style.font-size]="getItemTitleFontSize(item)">
{{getItemTitle(item)}}<span class="form-required" *ngIf="isItemRequired(item)">*</span>
<span class="icon info form-info" *ngIf="itemHasInfo(item)" [title]="getItemInfo(item)"></span>
</div>
<div class="form-cell">
<div class="form-component-container">
<div [style.width]="getComponentWidth(item)" [ngClass]="{'form-input-flex': !getComponentWidth(item)}">
@@ -59,7 +66,10 @@ class FormItem {
</div>
</ng-container>
<div class="form-vertical-container" *ngIf="isVertical(item)" [style.height]="getRowHeight(item)">
<div class="form-item-row">{{getItemTitle(item)}}</div>
<div class="form-item-row" [style.font-size]="getItemTitleFontSize(item)">
{{getItemTitle(item)}}<span class="form-required" *ngIf="isItemRequired(item)">*</span>
<span class="icon info form-info" *ngIf="itemHasInfo(item)" [title]="getItemInfo(item)"></span>
</div>
<div class="form-item-row" [style.width]="getComponentWidth(item)" [style.height]="getRowHeight(item)">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore" [style.width]="getComponentWidth(item)" [style.height]="getRowHeight(item)">
</model-component-wrapper>
@@ -121,6 +131,10 @@ export default class FormContainer extends ContainerBase<FormItemLayout> impleme
return this.convertSize(this._formLayout && this._formLayout.width, '');
}
private getFormPadding(): string {
return this._formLayout && this._formLayout.padding ? this._formLayout.padding : '10px 30px 0px 30px';
}
private getFormHeight(): string {
return this.convertSize(this._formLayout && this._formLayout.height, '');
}
@@ -135,11 +149,32 @@ export default class FormContainer extends ContainerBase<FormItemLayout> impleme
return (itemConfig && itemConfig.componentHeight) ? this.convertSize(itemConfig.componentHeight, '') : '';
}
private isItemRequired(item: FormItem): boolean {
let itemConfig = item.config;
return itemConfig && itemConfig.required;
}
private getItemInfo(item: FormItem): string {
let itemConfig = item.config;
return itemConfig && itemConfig.info;
}
private itemHasInfo(item: FormItem): boolean {
let itemConfig = item.config;
return itemConfig && itemConfig.info !== undefined;
}
private getItemTitle(item: FormItem): string {
let itemConfig = item.config;
return itemConfig ? itemConfig.title : '';
}
private getItemTitleFontSize(item: FormItem): string {
let itemConfig = item.config;
return itemConfig && itemConfig.titleFontSize ? this.convertSize(itemConfig.titleFontSize, '11px') : '11px';
}
private getActionComponents(item: FormItem): FormItem[] {
let items = this.items;
let itemConfig = item.config;

View File

@@ -37,6 +37,16 @@
flex: 1;
}
.form-required {
color: red;
padding-left: 5px;
}
.form-info {
width: 15px;
height: 15px;
}
.form-component-actions {
padding-left: 5px;
}

View File

@@ -71,7 +71,7 @@ export class ModelViewContent extends ViewBase implements OnInit, IModelView {
@memoize
public get connection(): sqlops.connection.Connection {
if (!this._commonService.connectionManagementService) {
if (!this._commonService.connectionManagementService || !this._commonService.connectionManagementService.connectionInfo) {
return undefined;
}
@@ -86,7 +86,7 @@ export class ModelViewContent extends ViewBase implements OnInit, IModelView {
@memoize
public get serverInfo(): sqlops.ServerInfo {
if (!this._commonService.connectionManagementService) {
if (!this._commonService.connectionManagementService || !this._commonService.connectionManagementService.connectionInfo) {
return undefined;
}

View File

@@ -448,7 +448,8 @@ export class ListDatabasesActionItem extends EventEmitter implements IActionItem
this._dropdown = new Dropdown(this.$databaseListDropdown.getHTMLElement(), contextViewProvider, themeService, {
strictSelection: true,
placeholder: selectString,
ariaLabel: selectString
ariaLabel: selectString,
actionLabel: nls.localize('toggleDatabaseNameDropdown', 'Select Database Toggle Dropdown')
});
this._dropdown.onValueChange(s => this.databaseSelected(s));

View File

@@ -13,13 +13,14 @@ import { ChartsModule } from 'ng2-charts/ng2-charts';
const BrowserAnimationsModule = (<any>require.__$__nodeRequire('@angular/platform-browser/animations')).BrowserAnimationsModule;
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { IBootstrapParams, ISelector, providerIterator } from 'sql/services/bootstrap/bootstrapService';
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { QueryOutputComponent, QUERY_OUTPUT_SELECTOR } from 'sql/parts/query/views/queryOutput.component';
import { QueryOutputComponent } from 'sql/parts/query/views/queryOutput.component';
import { QueryPlanComponent, } from 'sql/parts/queryPlan/queryPlan.component';
import { QueryComponent } from 'sql/parts/grid/views/query/query.component';
import { TopOperationsComponent } from 'sql/parts/queryPlan/topOperations.component';
@@ -41,7 +42,7 @@ let baseComponents = [QueryComponent, ComponentHostDirective, QueryOutputCompone
/* Insights */
let insightComponents = Registry.as<IInsightRegistry>(Extensions.InsightContribution).getAllCtors();
export const QueryOutputModule = (params: IBootstrapParams, selector: string): Type<any> => {
export const QueryOutputModule = (params: IBootstrapParams, selector: string, instantiationService: IInstantiationService): Type<any> => {
@NgModule({
imports: [
@@ -67,19 +68,22 @@ export const QueryOutputModule = (params: IBootstrapParams, selector: string): T
...insightComponents
],
providers: [
{ provide: IBootstrapParams, useValue: params }
{ provide: IBootstrapParams, useValue: params },
{ provide: ISelector, useValue: selector },
...providerIterator(instantiationService)
]
})
class ModuleClass {
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
@Inject(ISelector) private selector: string
) {
}
ngDoBootstrap(appRef: ApplicationRef) {
const factory = this._resolver.resolveComponentFactory(QueryOutputComponent);
(<any>factory).factory.selector = selector;
(<any>factory).factory.selector = this.selector;
appRef.bootstrap(factory);
}
}

View File

@@ -6,11 +6,14 @@
import { NgModule, Inject, forwardRef, ApplicationRef, ComponentFactoryResolver, Type } from '@angular/core';
import { APP_BASE_HREF, CommonModule } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { QueryPlanComponent, QUERYPLAN_SELECTOR } from 'sql/parts/queryPlan/queryPlan.component';
import { IBootstrapParams, ISelector, providerIterator } from 'sql/services/bootstrap/bootstrapService';
import { QueryPlanComponent } from 'sql/parts/queryPlan/queryPlan.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
// Connection Dashboard main angular module
export const QueryPlanModule = (params: IBootstrapParams, selector: string): Type<any> => {
export const QueryPlanModule = (params: IBootstrapParams, selector: string, instantiationService: IInstantiationService): Type<any> => {
@NgModule({
declarations: [
@@ -23,19 +26,22 @@ export const QueryPlanModule = (params: IBootstrapParams, selector: string): Typ
],
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
{ provide: IBootstrapParams, useValue: params }
{ provide: IBootstrapParams, useValue: params },
{ provide: ISelector, useValue: selector },
...providerIterator(instantiationService)
]
})
class ModuleClass {
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
@Inject(ISelector) private selector: string
) {
}
ngDoBootstrap(appRef: ApplicationRef) {
const factory = this._resolver.resolveComponentFactory(QueryPlanComponent);
(<any>factory).factory.selector = selector;
(<any>factory).factory.selector = this.selector;
appRef.bootstrap(factory);
}
}

View File

@@ -12,9 +12,11 @@ import { APP_BASE_HREF, CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { TaskDialogComponent, TASKDIALOG_SELECTOR } from 'sql/parts/tasks/dialog/taskDialog.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TaskDialogComponent } from 'sql/parts/tasks/dialog/taskDialog.component';
import { CreateDatabaseComponent } from 'sql/parts/admin/database/create/createDatabase.component';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { IBootstrapParams, ISelector, providerIterator } from 'sql/services/bootstrap/bootstrapService';
// Setup routes for various child components
const appRoutes: Routes = [
@@ -27,7 +29,7 @@ const appRoutes: Routes = [
{ path: '**', component: CreateDatabaseComponent }
];
export const TaskDialogModule = (params: IBootstrapParams, selector: string): Type<any> => {
export const TaskDialogModule = (params: IBootstrapParams, selector: string, instantiationService: IInstantiationService): Type<any> => {
@NgModule({
declarations: [
TaskDialogComponent,
@@ -42,19 +44,22 @@ export const TaskDialogModule = (params: IBootstrapParams, selector: string): Ty
],
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
{ provide: IBootstrapParams, useValue: params }
{ provide: IBootstrapParams, useValue: params },
{ provide: ISelector, useValue: selector },
...providerIterator(instantiationService)
]
})
class ModuleClass {
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
@Inject(ISelector) private selector: string
) {
}
ngDoBootstrap(appRef: ApplicationRef) {
const factory = this._resolver.resolveComponentFactory(TaskDialogComponent);
(<any>factory).factory.selector = selector;
(<any>factory).factory.selector = this.selector;
appRef.bootstrap(factory);
}
}

View File

@@ -13,8 +13,8 @@ import { Dialog, Wizard, DialogTab } from 'sql/platform/dialog/dialogTypes';
import { IModalOptions } from 'sql/base/browser/ui/modal/modal';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
const defaultOptions: IModalOptions = { hasBackButton: false, isWide: false };
const defaultWizardOptions: IModalOptions = { hasBackButton: false, isWide: true };
const defaultOptions: IModalOptions = { hasBackButton: false, isWide: false, hasErrors: true };
const defaultWizardOptions: IModalOptions = { hasBackButton: false, isWide: true, hasErrors: true };
export class CustomDialogService {
private _dialogModals = new Map<Dialog, DialogModal>();

View File

@@ -11,22 +11,25 @@ import { forwardRef, NgModule, ComponentFactoryResolver, Inject, ApplicationRef
import { FormsModule } from '@angular/forms';
import { CommonModule, APP_BASE_HREF } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { DialogContainer } from 'sql/platform/dialog/dialogContainer.component';
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component';
import { ModelComponentWrapper } from 'sql/parts/modelComponents/modelComponentWrapper.component';
import { ComponentHostDirective } from 'sql/parts/dashboard/common/componentHost.directive';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { IBootstrapParams, ISelector, providerIterator } from 'sql/services/bootstrap/bootstrapService';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { Registry } from 'vs/platform/registry/common/platform';
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
/* Model-backed components */
let extensionComponents = Registry.as<IComponentRegistry>(Extensions.ComponentContribution).getAllCtors();
export const DialogModule = (params, selector: string): any => {
export const DialogModule = (params, selector: string, instantiationService: IInstantiationService): any => {
@NgModule({
declarations: [
Checkbox,
@@ -47,20 +50,22 @@ export const DialogModule = (params, selector: string): any => {
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
CommonServiceInterface,
{ provide: IBootstrapParams, useValue: params }
{ provide: IBootstrapParams, useValue: params },
{ provide: ISelector, useValue: selector },
...providerIterator(instantiationService)
]
})
class ModuleClass {
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
@Inject(forwardRef(() => CommonServiceInterface)) bootstrap: CommonServiceInterface,
@Inject(ISelector) private selector: string
) {
}
ngDoBootstrap(appRef: ApplicationRef) {
const factoryWrapper: any = this._resolver.resolveComponentFactory(DialogContainer);
factoryWrapper.factory.selector = selector;
factoryWrapper.factory.selector = this.selector;
appRef.bootstrap(factoryWrapper);
}
}

View File

@@ -24,6 +24,7 @@ import { localize } from 'vs/nls';
import { Emitter } from 'vs/base/common/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { DialogMessage, MessageLevel } from '../../workbench/api/common/sqlExtHostTypes';
export class DialogModal extends Modal {
private _dialogPane: DialogPane;
@@ -52,7 +53,7 @@ export class DialogModal extends Modal {
}
public render() {
super.render();
super.render(true);
attachModalDialogStyler(this, this._themeService);
if (this.backButton) {
@@ -67,29 +68,43 @@ export class DialogModal extends Modal {
});
}
this._doneButton = this.addDialogButton(this._dialog.okButton, () => this.done(), false);
this._doneButton = this.addDialogButton(this._dialog.okButton, () => this.done(), false, true);
this._dialog.okButton.registerClickEvent(this._onDone.event);
this._dialog.onValidityChanged(valid => {
this._doneButton.enabled = valid && this._dialog.okButton.enabled;
});
this._cancelButton = this.addDialogButton(this._dialog.cancelButton, () => this.cancel(), false);
this._dialog.cancelButton.registerClickEvent(this._onCancel.event);
let messageChangeHandler = (message: DialogMessage) => {
if (message && message.text) {
this.setError(message.text, message.level);
} else {
this.setError('');
}
};
messageChangeHandler(this._dialog.message);
this._dialog.onMessageChange(message => messageChangeHandler(message));
}
private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined, registerClickEvent: boolean = true): Button {
private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined, registerClickEvent: boolean = true, requireDialogValid: boolean = false): Button {
let buttonElement = this.addFooterButton(button.label, onSelect);
buttonElement.enabled = button.enabled;
if (registerClickEvent) {
button.registerClickEvent(buttonElement.onDidClick);
}
button.onUpdate(() => {
this.updateButtonElement(buttonElement, button);
this.updateButtonElement(buttonElement, button, requireDialogValid);
});
attachButtonStyler(buttonElement, this._themeService);
this.updateButtonElement(buttonElement, button);
this.updateButtonElement(buttonElement, button, requireDialogValid);
return buttonElement;
}
private updateButtonElement(buttonElement: Button, dialogButton: DialogButton) {
private updateButtonElement(buttonElement: Button, dialogButton: DialogButton, requireDialogValid: boolean = false) {
buttonElement.label = dialogButton.label;
buttonElement.enabled = dialogButton.enabled;
buttonElement.enabled = requireDialogValid ? dialogButton.enabled && this._dialog.valid : dialogButton.enabled;
dialogButton.hidden ? buttonElement.element.classList.add('dialogModal-hidden') : buttonElement.element.classList.remove('dialogModal-hidden');
}
@@ -108,11 +123,13 @@ export class DialogModal extends Modal {
this.show();
}
public done(): void {
public async done(): Promise<void> {
if (this._dialog.okButton.enabled) {
this._onDone.fire();
this.dispose();
this.hide();
if (await this._dialog.validateClose()) {
this._onDone.fire();
this.dispose();
this.hide();
}
}
}

View File

@@ -15,6 +15,7 @@ import { TabbedPanel, IPanelTab, IPanelView } from 'sql/base/browser/ui/panel/pa
import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService';
import { DialogModule } from 'sql/platform/dialog/dialog.module';
import { DialogComponentParams } from 'sql/platform/dialog/dialogContainer.component';
import { DialogMessage } from 'sql/workbench/api/common/sqlExtHostTypes';
import * as DOM from 'vs/base/browser/dom';
import { Builder } from 'vs/base/browser/builder';

View File

@@ -8,6 +8,7 @@
import * as sqlops from 'sqlops';
import { localize } from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { DialogMessage } from 'sql/workbench/api/common/sqlExtHostTypes';
export class ModelViewPane {
private _valid: boolean = true;
@@ -45,6 +46,10 @@ export class Dialog extends ModelViewPane {
public okButton: DialogButton = new DialogButton(Dialog.DONE_BUTTON_LABEL, true);
public cancelButton: DialogButton = new DialogButton(Dialog.CANCEL_BUTTON_LABEL, true);
public customButtons: DialogButton[];
private _onMessageChange = new Emitter<DialogMessage>();
public readonly onMessageChange = this._onMessageChange.event;
private _message: DialogMessage;
private _closeValidator: () => boolean | Thenable<boolean>;
constructor(public title: string, content?: string | DialogTab[]) {
super();
@@ -52,6 +57,29 @@ export class Dialog extends ModelViewPane {
this.content = content;
}
}
public get message(): DialogMessage {
return this._message;
}
public set message(value: DialogMessage) {
if (this._message && !value || !this._message && value || this._message && value && (this._message.level !== value.level || this._message.text !== value.text)) {
this._message = value;
this._onMessageChange.fire(this._message);
}
}
public registerCloseValidator(validator: () => boolean | Thenable<boolean>): void {
this._closeValidator = validator;
}
public validateClose(): Thenable<boolean> {
if (this._closeValidator) {
return Promise.resolve(this._closeValidator());
} else {
return Promise.resolve(true);
}
}
}
export class DialogButton implements sqlops.window.modelviewdialog.Button {
@@ -140,6 +168,9 @@ export class Wizard {
private _pageRemovedEmitter = new Emitter<WizardPage>();
public readonly onPageRemoved = this._pageRemovedEmitter.event;
private _navigationValidator: (pageChangeInfo: sqlops.window.modelviewdialog.WizardPageChangeInfo) => boolean | Thenable<boolean>;
private _onMessageChange = new Emitter<DialogMessage>();
public readonly onMessageChange = this._onMessageChange.event;
private _message: DialogMessage;
constructor(public title: string) { }
@@ -207,4 +238,15 @@ export class Wizard {
return Promise.resolve(true);
}
}
public get message(): DialogMessage {
return this._message;
}
public set message(value: DialogMessage) {
if (this._message && !value || !this._message && value || this._message && value && (this._message.level !== value.level || this._message.text !== value.text)) {
this._message = value;
this._onMessageChange.fire(this._message);
}
}
}

View File

@@ -5,7 +5,7 @@
.dialogModal-body {
display: flex;
flex-direction: row;
flex-direction: column;
height: 100%;
width: 100%;
min-width: 500px;
@@ -29,4 +29,4 @@
.footer-button.dialogModal-hidden {
margin: 0;
}
}

View File

@@ -24,6 +24,7 @@ import { localize } from 'vs/nls';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Emitter } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { DialogMessage, MessageLevel } from '../../workbench/api/common/sqlExtHostTypes';
export class WizardModal extends Modal {
private _dialogPanes = new Map<WizardPage, DialogPane>();
@@ -58,7 +59,7 @@ export class WizardModal extends Modal {
}
public render() {
super.render();
super.render(true);
attachModalDialogStyler(this, this._themeService);
if (this.backButton) {
@@ -66,32 +67,59 @@ export class WizardModal extends Modal {
attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND });
}
this._previousButton = this.addDialogButton(this._wizard.backButton, () => this.showPage(this.getCurrentPage() - 1));
this._nextButton = this.addDialogButton(this._wizard.nextButton, () => this.showPage(this.getCurrentPage() + 1));
if (this._wizard.customButtons) {
this._wizard.customButtons.forEach(button => {
let buttonElement = this.addDialogButton(button);
this.updateButtonElement(buttonElement, button);
});
}
this._previousButton = this.addDialogButton(this._wizard.backButton, () => this.showPage(this._wizard.currentPage - 1));
this._nextButton = this.addDialogButton(this._wizard.nextButton, () => this.showPage(this._wizard.currentPage + 1), true, true);
this._generateScriptButton = this.addDialogButton(this._wizard.generateScriptButton, () => undefined);
this._doneButton = this.addDialogButton(this._wizard.doneButton, () => this.done(), false);
this._doneButton = this.addDialogButton(this._wizard.doneButton, () => this.done(), false, true);
this._wizard.doneButton.registerClickEvent(this._onDone.event);
this._cancelButton = this.addDialogButton(this._wizard.cancelButton, () => this.cancel(), false);
this._wizard.cancelButton.registerClickEvent(this._onCancel.event);
let messageChangeHandler = (message: DialogMessage) => {
if (message && message.text) {
this.setError(message.text, message.level);
} else {
this.setError('');
}
};
messageChangeHandler(this._wizard.message);
this._wizard.onMessageChange(message => messageChangeHandler(message));
this._wizard.pages.forEach((page, index) => {
page.onValidityChanged(valid => {
if (index === this._wizard.currentPage) {
this._nextButton.enabled = this._wizard.nextButton.enabled && page.valid;
this._doneButton.enabled = this._wizard.doneButton.enabled && page.valid;
}
});
});
}
private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined, registerClickEvent: boolean = true): Button {
private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined, registerClickEvent: boolean = true, requirePageValid: boolean = false): Button {
let buttonElement = this.addFooterButton(button.label, onSelect);
buttonElement.enabled = button.enabled;
if (registerClickEvent) {
button.registerClickEvent(buttonElement.onDidClick);
}
button.onUpdate(() => {
this.updateButtonElement(buttonElement, button);
this.updateButtonElement(buttonElement, button, requirePageValid);
});
attachButtonStyler(buttonElement, this._themeService);
this.updateButtonElement(buttonElement, button);
this.updateButtonElement(buttonElement, button, requirePageValid);
return buttonElement;
}
private updateButtonElement(buttonElement: Button, dialogButton: DialogButton) {
private updateButtonElement(buttonElement: Button, dialogButton: DialogButton, requirePageValid: boolean = false) {
buttonElement.label = dialogButton.label;
buttonElement.enabled = dialogButton.enabled;
buttonElement.enabled = requirePageValid ? dialogButton.enabled && this._wizard.pages[this._wizard.currentPage].valid : dialogButton.enabled;
dialogButton.hidden ? buttonElement.element.classList.add('dialogModal-hidden') : buttonElement.element.classList.remove('dialogModal-hidden');
}
@@ -100,18 +128,17 @@ export class WizardModal extends Modal {
this._body = bodyBuilder.getHTMLElement();
});
let builder = new Builder(this._body);
this._wizard.pages.forEach(page => {
this.registerPage(page);
});
this._wizard.onPageAdded(page => {
this.registerPage(page);
this.showPage(this.getCurrentPage(), false);
this.showPage(this._wizard.currentPage, false);
});
this._wizard.onPageRemoved(page => {
let dialogPane = this._dialogPanes.get(page);
this._dialogPanes.delete(page);
this.showPage(this.getCurrentPage(), false);
this.showPage(this._wizard.currentPage, false);
dialogPane.dispose();
});
}
@@ -141,6 +168,9 @@ export class WizardModal extends Modal {
});
this.setButtonsForPage(index);
this._wizard.setCurrentPage(index);
let currentPageValid = this._wizard.pages[this._wizard.currentPage].valid;
this._nextButton.enabled = this._wizard.nextButton.enabled && currentPageValid;
this._doneButton.enabled = this._wizard.doneButton.enabled && currentPageValid;
}
private setButtonsForPage(index: number) {
@@ -161,10 +191,6 @@ export class WizardModal extends Modal {
}
}
private getCurrentPage(): number {
return this._wizard.currentPage;
}
public open(): void {
this.showPage(0, false);
this.show();

View File

@@ -3,20 +3,31 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { NgModuleRef, enableProdMode, InjectionToken, ReflectiveInjector, Type, PlatformRef, Provider } from '@angular/core';
import { NgModuleRef, enableProdMode, InjectionToken, Type, PlatformRef, Provider, Injector, Optional, Inject, ComponentFactoryResolver } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { IEditorInput } from 'vs/platform/editor/common/editor';
import { IInstantiationService, _util } from 'vs/platform/instantiation/common/instantiation';
const selectorCounter = new Map<string, number>();
const serviceMap = new Map<string, IInstantiationService>();
export const IBootstrapParams = new InjectionToken('bootstrap_params');
export function providerIterator(service: IInstantiationService): Provider[] {
return Array.from(_util.serviceIds.values()).map(v => {
return {
provide: v, useFactory: () => {
return (<any>service)._getOrCreateServiceInstance(v);
}
};
});
}
export const ISelector = new InjectionToken<string>('selector');
export const IBootstrapParams = new InjectionToken<IBootstrapParams>('bootstrap_params');
export interface IBootstrapParams {
}
export type IModuleFactory<T> = (params: IBootstrapParams, selector: string) => Type<T>;
export type IModuleFactory<T> = (params: IBootstrapParams, selector: string, service: IInstantiationService) => Type<T>;
function createUniqueSelector(selector: string): string {
let num: number;
@@ -37,31 +48,13 @@ export function bootstrapAngular<T>(service: IInstantiationService, moduleType:
let selector = document.createElement(uniqueSelectorString);
container.appendChild(selector);
serviceMap.set(uniqueSelectorString, service);
if (!platform) {
// Perform the bootsrap
const providers: Provider = [];
_util.serviceIds.forEach(id => {
providers.push({
provide: id, useFactory: () => {
return (<any>serviceMap.get(uniqueSelectorString))._getOrCreateServiceInstance(id);
}
});
});
platform = platformBrowserDynamic(providers);
platform = platformBrowserDynamic();
}
platform.bootstrapModule(moduleType(params, uniqueSelectorString)).then(moduleRef => {
platform.bootstrapModule(moduleType(params, uniqueSelectorString, service)).then(moduleRef => {
if (input) {
input.onDispose(() => {
serviceMap.delete(uniqueSelectorString);
moduleRef.onDestroy(() => {
serviceMap.delete(uniqueSelectorString);
});
moduleRef.destroy();
});
}

View File

@@ -95,7 +95,6 @@ export class SingleQueryManagementService {
*/
@Injectable()
export class CommonServiceInterface extends AngularDisposable {
protected _uniqueSelector: string;
protected _uri: string;
/* Special Services */
@@ -115,6 +114,12 @@ export class CommonServiceInterface extends AngularDisposable {
@Inject(IQueryManagementService) protected _queryManagementService: IQueryManagementService
) {
super();
// during testing there may not be params
if (this._params) {
this.scopedContextKeyService = this._params.scopedContextService;
this._connectionContextKey = this._params.connectionContextKey;
this.uri = this._params.ownerUri;
}
}
public get metadataService(): SingleConnectionMetadataService {
@@ -133,20 +138,6 @@ export class CommonServiceInterface extends AngularDisposable {
return this._singleQueryManagementService;
}
/**
* Set the selector for this instance, should only be set once
*/
public set selector(selector: string) {
this._uniqueSelector = selector;
this._getbootstrapParams();
}
protected _getbootstrapParams(): void {
this.scopedContextKeyService = this._params.scopedContextService;
this._connectionContextKey = this._params.connectionContextKey;
this.uri = this._params.ownerUri;
}
protected setUri(uri: string) {
this._uri = uri;
this._singleMetadataService = new SingleConnectionMetadataService(this._metadataService, this._uri);

View File

@@ -239,11 +239,15 @@ declare module 'sqlops' {
horizontal?: boolean;
componentWidth?: number | string;
componentHeight?: number | string;
titleFontSize?: number | string;
required?: boolean;
info?: string;
}
export interface FormLayout {
width?: number | string;
height?: number | string;
padding?: string;
}
export interface GroupLayout {
@@ -296,15 +300,22 @@ declare module 'sqlops' {
Error = 3
}
export enum CardType {
VerticalButton = 'VerticalButton',
Details = 'Details'
}
/**
* Properties representing the card component, can be used
* when using ModelBuilder to create the component
*/
export interface CardProperties {
export interface CardProperties extends ComponentWithIcon {
label: string;
value?: string;
actions?: ActionDescriptor[];
status?: StatusIndicator;
selected?: boolean;
cardType: CardType;
}
export type InputBoxInputType = 'color' | 'date' | 'datetime-local' | 'email' | 'month' | 'number' | 'password' | 'range' | 'search' | 'text' | 'time' | 'url' | 'week';
@@ -314,6 +325,12 @@ declare module 'sqlops' {
width?: number | string;
}
export interface ComponentWithIcon {
iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri };
iconHeight?: number | string;
iconWidth?: number | string;
}
export interface InputBoxProperties extends ComponentProperties {
value?: string;
ariaLabel?: string;
@@ -389,20 +406,17 @@ declare module 'sqlops' {
html?: string;
}
export interface ButtonProperties extends ComponentProperties {
export interface ButtonProperties extends ComponentProperties, ComponentWithIcon {
label?: string;
iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri };
}
export interface LoadingComponentProperties {
loading?: boolean;
}
export interface CardComponent extends Component {
label: string;
value: string;
actions?: ActionDescriptor[];
export interface CardComponent extends Component, CardProperties {
onDidActionClick: vscode.Event<ActionDescriptor>;
onCardSelectedChanged: vscode.Event<any>;
}
export interface TextComponent extends Component {
@@ -582,6 +596,24 @@ declare module 'sqlops' {
*/
export function createWizard(title: string): Wizard;
/**
* Used to control whether a message in a dialog/wizard is displayed as an error,
* warning, or informational message. Default is error.
*/
export enum MessageLevel {
Error = 0,
Warning = 1,
Information = 2
}
/**
* A message shown in a dialog. If the level is not set it defaults to error.
*/
export type DialogMessage = {
readonly text: string,
readonly level?: MessageLevel
};
export interface ModelViewPanel {
/**
* Register model view content for the dialog.
@@ -632,6 +664,21 @@ declare module 'sqlops' {
* Any additional buttons that should be displayed
*/
customButtons: Button[];
/**
* Set the informational message shown in the dialog. Hidden when the message is
* undefined or the text is empty or undefined. The default level is error.
*/
message: DialogMessage;
/**
* Register a callback that will be called when the user tries to click done. Only
* one callback can be registered at once, so each registration call will clear
* the previous registration.
* @param validator The callback that gets executed when the user tries to click
* done. Return true to allow the dialog to close or false to block it from closing
*/
registerCloseValidator(validator: () => boolean | Thenable<boolean>): void;
}
export interface DialogTab extends ModelViewPanel {
@@ -798,6 +845,12 @@ declare module 'sqlops' {
* cancel it.
*/
registerNavigationValidator(validator: (pageChangeInfo: WizardPageChangeInfo) => boolean | Thenable<boolean>): void;
/**
* Set the informational message shown in the wizard. Hidden when the message is
* undefined or the text is empty or undefined. The default level is error.
*/
message: DialogMessage
}
}
}

View File

@@ -149,6 +149,7 @@ export interface IModelViewDialogDetails {
okButton: number;
cancelButton: number;
customButtons: number[];
message: DialogMessage;
}
export interface IModelViewTabDetails {
@@ -179,6 +180,18 @@ export interface IModelViewWizardDetails {
nextButton: number;
backButton: number;
customButtons: number[];
message: DialogMessage;
}
export enum MessageLevel {
Error = 0,
Warning = 1,
Information = 2
}
export interface DialogMessage {
text: string;
level?: MessageLevel;
}
/// Card-related APIs that need to be here to avoid early load issues
@@ -195,6 +208,8 @@ export interface CardProperties {
value?: string;
actions?: ActionDescriptor[];
status?: StatusIndicator;
selected?: boolean;
cardType: CardType;
}
export interface ActionDescriptor {
@@ -223,4 +238,9 @@ export enum DeclarativeDataType {
string = 'string',
category = 'category',
boolean = 'boolean'
}
export enum CardType {
VerticalButton = 'VerticalButton',
Details = 'Details'
}

View File

@@ -15,7 +15,7 @@ import * as vscode from 'vscode';
import * as sqlops from 'sqlops';
import { SqlMainContext, ExtHostModelViewShape, MainThreadModelViewShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { IItemConfig, ModelComponentTypes, IComponentShape, IComponentEventArgs, ComponentEventType } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IItemConfig, ModelComponentTypes, IComponentShape, IComponentEventArgs, ComponentEventType, CardType } from 'sql/workbench/api/common/sqlExtHostTypes';
class ModelBuilderImpl implements sqlops.ModelBuilder {
private nextComponentId: number;
@@ -255,6 +255,9 @@ class FormContainerBuilder extends ContainerBuilderImpl<sqlops.FormContainer, sq
private convertToItemConfig(formComponent: sqlops.FormComponent, itemLayout?: sqlops.FormItemLayout): InternalItemConfig {
let componentWrapper = formComponent.component as ComponentWrapper;
if (itemLayout && itemLayout.required && componentWrapper) {
componentWrapper.required = true;
}
let actions: string[] = undefined;
if (formComponent.actions) {
actions = formComponent.actions.map(action => {
@@ -377,7 +380,8 @@ class ComponentWrapper implements sqlops.Component {
}
public get enabled(): boolean {
return this.properties['enabled'];
let isEnabled = this.properties['enabled'];
return (isEnabled === undefined) ? true : isEnabled;
}
public set enabled(value: boolean) {
@@ -400,6 +404,13 @@ class ComponentWrapper implements sqlops.Component {
this.setProperty('width', v);
}
public get required(): boolean {
return this.properties['required'];
}
public set required(v: boolean) {
this.setProperty('required', v);
}
public toComponentShape(): IComponentShape {
return <IComponentShape>{
id: this.id,
@@ -512,6 +523,7 @@ class CardWrapper extends ComponentWrapper implements sqlops.CardComponent {
super(proxy, handle, ModelComponentTypes.Card, id);
this.properties = {};
this._emitterMap.set(ComponentEventType.onDidClick, new Emitter<any>());
this._emitterMap.set(ComponentEventType.onDidClick, new Emitter<any>());
}
public get label(): string {
@@ -526,17 +538,53 @@ class CardWrapper extends ComponentWrapper implements sqlops.CardComponent {
public set value(v: string) {
this.setProperty('value', v);
}
public get selected(): boolean {
return this.properties['selected'];
}
public set selected(v: boolean) {
this.setProperty('selected', v);
}
public get cardType(): sqlops.CardType {
return this.properties['cardType'];
}
public set cardType(v: sqlops.CardType) {
this.setProperty('cardType', v);
}
public get actions(): sqlops.ActionDescriptor[] {
return this.properties['actions'];
}
public set actions(a: sqlops.ActionDescriptor[]) {
this.setProperty('actions', a);
}
public get iconPath(): string | URI | { light: string | URI; dark: string | URI } {
return this.properties['iconPath'];
}
public set iconPath(v: string | URI | { light: string | URI; dark: string | URI }) {
this.setProperty('iconPath', v);
}
public get iconHeight(): number | string {
return this.properties['iconHeight'];
}
public set iconHeight(v: number | string) {
this.setProperty('iconHeight', v);
}
public get iconWidth(): number | string {
return this.properties['iconWidth'];
}
public set iconWidth(v: number | string) {
this.setProperty('iconWidth', v);
}
public get onDidActionClick(): vscode.Event<sqlops.ActionDescriptor> {
let emitter = this._emitterMap.get(ComponentEventType.onDidClick);
return emitter && emitter.event;
}
public get onCardSelectedChanged(): vscode.Event<any> {
let emitter = this._emitterMap.get(ComponentEventType.onDidClick);
return emitter && emitter.event;
}
}
class InputBoxWrapper extends ComponentWrapper implements sqlops.InputBoxComponent {
@@ -772,7 +820,11 @@ class DropDownWrapper extends ComponentWrapper implements sqlops.DropDownCompone
}
public get value(): string | sqlops.CategoryValue {
return this.properties['value'];
let val = this.properties['value'];
if (!val && this.values && this.values.length > 0) {
val = this.values[0];
}
return val;
}
public set value(v: string | sqlops.CategoryValue) {
this.setProperty('value', v);

View File

@@ -93,6 +93,8 @@ class DialogImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdi
public okButton: sqlops.window.modelviewdialog.Button;
public cancelButton: sqlops.window.modelviewdialog.Button;
public customButtons: sqlops.window.modelviewdialog.Button[];
private _message: sqlops.window.modelviewdialog.DialogMessage;
private _closeValidator: () => boolean | Thenable<boolean>;
constructor(extHostModelViewDialog: ExtHostModelViewDialog,
extHostModelView: ExtHostModelViewShape) {
@@ -105,6 +107,27 @@ class DialogImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdi
super.setModelViewId(value);
this.content = value;
}
public get message(): sqlops.window.modelviewdialog.DialogMessage {
return this._message;
}
public set message(value: sqlops.window.modelviewdialog.DialogMessage) {
this._message = value;
this._extHostModelViewDialog.updateDialogContent(this);
}
public registerCloseValidator(validator: () => boolean | Thenable<boolean>): void {
this._closeValidator = validator;
}
public validateClose(): Thenable<boolean> {
if (this._closeValidator) {
return Promise.resolve(this._closeValidator());
} else {
return Promise.resolve(true);
}
}
}
class TabImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdialog.DialogTab {
@@ -218,6 +241,7 @@ class WizardImpl implements sqlops.window.modelviewdialog.Wizard {
private _pageChangedEmitter = new Emitter<sqlops.window.modelviewdialog.WizardPageChangeInfo>();
public readonly onPageChanged = this._pageChangedEmitter.event;
private _navigationValidator: (info: sqlops.window.modelviewdialog.WizardPageChangeInfo) => boolean | Thenable<boolean>;
private _message: sqlops.window.modelviewdialog.DialogMessage;
constructor(public title: string, private _extHostModelViewDialog: ExtHostModelViewDialog) {
this.doneButton = this._extHostModelViewDialog.createButton(DONE_LABEL);
@@ -234,6 +258,15 @@ class WizardImpl implements sqlops.window.modelviewdialog.Wizard {
return this._currentPage;
}
public get message(): sqlops.window.modelviewdialog.DialogMessage {
return this._message;
}
public set message(value: sqlops.window.modelviewdialog.DialogMessage) {
this._message = value;
this._extHostModelViewDialog.updateWizard(this);
}
public addPage(page: sqlops.window.modelviewdialog.WizardPage, index?: number): Thenable<void> {
return this._extHostModelViewDialog.updateWizardPage(page).then(() => {
this._extHostModelViewDialog.addPage(this, page, index);
@@ -354,6 +387,11 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
return wizard.validateNavigation(info);
}
public $validateDialogClose(handle: number): Thenable<boolean> {
let dialog = this._objectsByHandle.get(handle) as DialogImpl;
return dialog.validateClose();
}
public openDialog(dialog: sqlops.window.modelviewdialog.Dialog): void {
let handle = this.getHandle(dialog);
this.updateDialogContent(dialog);
@@ -387,7 +425,8 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
okButton: this.getHandle(dialog.okButton),
cancelButton: this.getHandle(dialog.cancelButton),
content: dialog.content && typeof dialog.content !== 'string' ? dialog.content.map(tab => this.getHandle(tab)) : dialog.content as string,
customButtons: dialog.customButtons ? dialog.customButtons.map(button => this.getHandle(button)) : undefined
customButtons: dialog.customButtons ? dialog.customButtons.map(button => this.getHandle(button)) : undefined,
message: dialog.message
});
}
@@ -495,7 +534,8 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
generateScriptButton: this.getHandle(wizard.generateScriptButton),
doneButton: this.getHandle(wizard.doneButton),
nextButton: this.getHandle(wizard.nextButton),
customButtons: wizard.customButtons ? wizard.customButtons.map(button => this.getHandle(button)) : undefined
customButtons: wizard.customButtons ? wizard.customButtons.map(button => this.getHandle(button)) : undefined,
message: wizard.message
});
}

View File

@@ -80,6 +80,7 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
dialog.okButton = okButton;
dialog.cancelButton = cancelButton;
dialog.onValidityChanged(valid => this._proxy.$onPanelValidityChanged(handle, valid));
dialog.registerCloseValidator(() => this.validateDialogClose(handle));
this._dialogs.set(handle, dialog);
}
@@ -94,6 +95,8 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
dialog.customButtons = details.customButtons.map(buttonHandle => this.getButton(buttonHandle));
}
dialog.message = details.message;
return Promise.resolve();
}
@@ -169,6 +172,7 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
if (details.customButtons !== undefined) {
wizard.customButtons = details.customButtons.map(buttonHandle => this.getButton(buttonHandle));
}
wizard.message = details.message;
return Promise.resolve();
}
@@ -259,4 +263,8 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
private validateNavigation(handle: number, info: sqlops.window.modelviewdialog.WizardPageChangeInfo): Thenable<boolean> {
return this._proxy.$validateNavigation(handle, info);
}
private validateDialogClose(handle: number): Thenable<boolean> {
return this._proxy.$validateDialogClose(handle);
}
}

View File

@@ -315,7 +315,8 @@ export function createApiFactory(
},
createWizard(title: string): sqlops.window.modelviewdialog.Wizard {
return extHostModelViewDialog.createWizard(title);
}
},
MessageLevel: sqlExtHostTypes.MessageLevel
};
const window: typeof sqlops.window = {
@@ -390,7 +391,8 @@ export function createApiFactory(
workspace,
queryeditor: queryEditor,
ui: ui,
StatusIndicator: sqlExtHostTypes.StatusIndicator
StatusIndicator: sqlExtHostTypes.StatusIndicator,
CardType: sqlExtHostTypes.CardType
};
}
};

View File

@@ -563,6 +563,7 @@ export interface ExtHostModelViewDialogShape {
$onWizardPageChanged(handle: number, info: sqlops.window.modelviewdialog.WizardPageChangeInfo): void;
$updateWizardPageInfo(handle: number, pageHandles: number[], currentPageIndex: number): void;
$validateNavigation(handle: number, info: sqlops.window.modelviewdialog.WizardPageChangeInfo): Thenable<boolean>;
$validateDialogClose(handle: number): Thenable<boolean>;
}
export interface MainThreadModelViewDialogShape extends IDisposable {

View File

@@ -9,6 +9,7 @@ import { Mock, It, Times } from 'typemoq';
import { ExtHostModelViewDialog } from 'sql/workbench/api/node/extHostModelViewDialog';
import { MainThreadModelViewDialogShape, ExtHostModelViewShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
import { MessageLevel } from 'sql/workbench/api/common/sqlExtHostTypes';
'use strict';
@@ -290,4 +291,39 @@ suite('ExtHostModelViewDialog Tests', () => {
assert.equal(validationInfo.lastPage, lastPage);
assert.equal(validationInfo.newPage, newPage);
});
test('Changing the wizard message sends the new message to the main thread', () => {
// Set up the main thread mock to record the call
mockProxy.setup(x => x.$setWizardDetails(It.isAny(), It.isAny()));
let wizard = extHostModelViewDialog.createWizard('wizard_1');
// If I update the wizard's message
let newMessage = {
level: MessageLevel.Error,
text: 'test message'
};
wizard.message = newMessage;
// Then the main thread gets notified of the new details
mockProxy.verify(x => x.$setWizardDetails(It.isAny(), It.is(x => x.message === newMessage)), Times.once());
});
test('Main thread can execute dialog close validation', () => {
// Set up the main thread mock to record the dialog handle
let dialogHandle: number;
mockProxy.setup(x => x.$setDialogDetails(It.isAny(), It.isAny())).callback((handle, details) => dialogHandle = handle);
// Create the dialog and add a validation that records that it has been called
let dialog = extHostModelViewDialog.createDialog('dialog_1');
extHostModelViewDialog.updateDialogContent(dialog);
let callCount = 0;
dialog.registerCloseValidator(() => {
callCount++;
return true;
});
// If I call the validation from the main thread then it should run
extHostModelViewDialog.$validateDialogClose(dialogHandle);
assert.equal(callCount, 1);
});
});

View File

@@ -7,7 +7,7 @@ import * as assert from 'assert';
import { Mock, It, Times } from 'typemoq';
import { MainThreadModelViewDialog } from 'sql/workbench/api/node/mainThreadModelViewDialog';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { IModelViewButtonDetails, IModelViewTabDetails, IModelViewDialogDetails, IModelViewWizardPageDetails, IModelViewWizardDetails } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IModelViewButtonDetails, IModelViewTabDetails, IModelViewDialogDetails, IModelViewWizardPageDetails, IModelViewWizardDetails, DialogMessage, MessageLevel } from 'sql/workbench/api/common/sqlExtHostTypes';
import { CustomDialogService } from 'sql/platform/dialog/customDialogService';
import { Dialog, DialogTab, Wizard } from 'sql/platform/dialog/dialogTypes';
import { ExtHostModelViewDialogShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
@@ -60,7 +60,8 @@ suite('MainThreadModelViewDialog Tests', () => {
$onPanelValidityChanged: (handle, valid) => undefined,
$onWizardPageChanged: (handle, info) => undefined,
$updateWizardPageInfo: (wizardHandle, pageHandles, currentPageIndex) => undefined,
$validateNavigation: (handle, info) => undefined
$validateNavigation: (handle, info) => undefined,
$validateDialogClose: handle => undefined
});
let extHostContext = <IExtHostContext>{
getProxy: proxyType => mockExtHostModelViewDialog.object
@@ -112,7 +113,8 @@ suite('MainThreadModelViewDialog Tests', () => {
content: [tab1Handle, tab2Handle],
okButton: okButtonHandle,
cancelButton: cancelButtonHandle,
customButtons: [button1Handle, button2Handle]
customButtons: [button1Handle, button2Handle],
message: undefined
};
// Set up the wizard details
@@ -152,7 +154,8 @@ suite('MainThreadModelViewDialog Tests', () => {
currentPage: undefined,
title: 'wizard_title',
customButtons: [],
pages: [page1Handle, page2Handle]
pages: [page1Handle, page2Handle],
message: undefined
};
// Register the buttons, tabs, and dialog
@@ -322,10 +325,38 @@ suite('MainThreadModelViewDialog Tests', () => {
mockExtHostModelViewDialog.setup(x => x.$validateNavigation(It.isAny(), It.isAny()));
// If I call validateNavigation on the wizard that gets created
let wizard: Wizard = (mainThreadModelViewDialog as any).getWizard(wizardHandle);
wizard.validateNavigation(1);
mainThreadModelViewDialog.$openWizard(wizardHandle);
openedWizard.validateNavigation(1);
// Then the call gets forwarded to the extension host
mockExtHostModelViewDialog.verify(x => x.$validateNavigation(It.is(handle => handle === wizardHandle), It.is(info => info.newPage === 1)), Times.once());
});
test('Adding a message to a wizard fires events on the created wizard', () => {
mainThreadModelViewDialog.$openWizard(wizardHandle);
let newMessage: DialogMessage;
openedWizard.onMessageChange(message => newMessage = message);
// If I change the wizard's message
wizardDetails.message = {
level: MessageLevel.Error,
text: 'test message'
};
mainThreadModelViewDialog.$setWizardDetails(wizardHandle, wizardDetails);
// Then the message gets changed on the wizard
assert.equal(newMessage, wizardDetails.message, 'New message was not included in the fired event');
assert.equal(openedWizard.message, wizardDetails.message, 'New message was not set on the wizard');
});
test('Creating a dialog adds a close validation that calls the extension host', () => {
mockExtHostModelViewDialog.setup(x => x.$validateDialogClose(It.isAny()));
// If I call validateClose on the dialog that gets created
mainThreadModelViewDialog.$openDialog(dialogHandle);
openedDialog.validateClose();
// Then the call gets forwarded to the extension host
mockExtHostModelViewDialog.verify(x => x.$validateDialogClose(It.is(handle => handle === dialogHandle)), Times.once());
});
});

View File

@@ -52,6 +52,7 @@ export interface IProductConfiguration {
};
documentationUrl: string;
releaseNotesUrl: string;
// {SQL CARBON EDIT}
gettingStartedUrl: string;
keyboardShortcutsUrlMac: string;
keyboardShortcutsUrlLinux: string;