mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-06 17:23:53 -05:00
Move code around for more linting (#8190)
* testing * moving around all the code * fix strict nulls
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DialogModal } from 'sql/workbench/services/dialog/browser/dialogModal';
|
||||
import { WizardModal } from 'sql/workbench/services/dialog/browser/wizardModal';
|
||||
import { Dialog, Wizard } from 'sql/workbench/services/dialog/common/dialogTypes';
|
||||
import { IModalOptions } from 'sql/workbench/browser/modal/modal';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
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>();
|
||||
private _wizardModals = new Map<Wizard, WizardModal>();
|
||||
|
||||
constructor(@IInstantiationService private _instantiationService: IInstantiationService) { }
|
||||
|
||||
public showDialog(dialog: Dialog, dialogName?: string, options?: IModalOptions): void {
|
||||
let name = dialogName ? dialogName : 'CustomDialog';
|
||||
let dialogModal = this._instantiationService.createInstance(DialogModal, dialog, name, options || defaultOptions);
|
||||
this._dialogModals.set(dialog, dialogModal);
|
||||
dialogModal.render();
|
||||
dialogModal.open();
|
||||
}
|
||||
|
||||
public showWizard(wizard: Wizard, options?: IModalOptions): void {
|
||||
let wizardModal = this._instantiationService.createInstance(WizardModal, wizard, 'WizardPage', options || defaultWizardOptions);
|
||||
this._wizardModals.set(wizard, wizardModal);
|
||||
wizardModal.render();
|
||||
wizardModal.open();
|
||||
}
|
||||
|
||||
public closeDialog(dialog: Dialog): void {
|
||||
let dialogModal = this._dialogModals.get(dialog);
|
||||
if (dialogModal) {
|
||||
dialogModal.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public closeWizard(wizard: Wizard): void {
|
||||
let wizardModal = this._wizardModals.get(wizard);
|
||||
if (wizardModal) {
|
||||
wizardModal.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
79
src/sql/workbench/services/dialog/browser/dialog.module.ts
Normal file
79
src/sql/workbench/services/dialog/browser/dialog.module.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/dialogModal';
|
||||
|
||||
import { forwardRef, NgModule, ComponentFactoryResolver, Inject, ApplicationRef } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { CommonModule, APP_BASE_HREF } from '@angular/common';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { DialogContainer } from 'sql/workbench/services/dialog/browser/dialogContainer.component';
|
||||
import { WizardNavigation } from 'sql/workbench/services/dialog/browser/wizardNavigation.component';
|
||||
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/browser/modelComponentRegistry';
|
||||
import { ModelViewContent } from 'sql/workbench/browser/modelComponents/modelViewContent.component';
|
||||
import { ModelComponentWrapper } from 'sql/workbench/browser/modelComponents/modelComponentWrapper.component';
|
||||
import { ComponentHostDirective } from 'sql/workbench/parts/dashboard/browser/core/componentHost.directive';
|
||||
import { providerIterator } from 'sql/workbench/services/bootstrap/browser/bootstrapService';
|
||||
import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service';
|
||||
import { EditableDropDown } from 'sql/platform/browser/editableDropdown/editableDropdown.component';
|
||||
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
|
||||
import { SelectBox } from 'sql/platform/browser/selectBox/selectBox.component';
|
||||
import { InputBox } from 'sql/platform/browser/inputbox/inputBox.component';
|
||||
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IBootstrapParams, ISelector } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
|
||||
|
||||
export const DialogModule = (params, selector: string, instantiationService: IInstantiationService): any => {
|
||||
|
||||
/* Model-backed components */
|
||||
let extensionComponents = Registry.as<IComponentRegistry>(Extensions.ComponentContribution).getAllCtors();
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
Checkbox,
|
||||
SelectBox,
|
||||
EditableDropDown,
|
||||
InputBox,
|
||||
DialogContainer,
|
||||
WizardNavigation,
|
||||
ModelViewContent,
|
||||
ModelComponentWrapper,
|
||||
ComponentHostDirective,
|
||||
...extensionComponents
|
||||
],
|
||||
entryComponents: [DialogContainer, WizardNavigation, ...extensionComponents],
|
||||
imports: [
|
||||
FormsModule,
|
||||
CommonModule,
|
||||
BrowserModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: APP_BASE_HREF, useValue: '/' },
|
||||
CommonServiceInterface,
|
||||
{ provide: IBootstrapParams, useValue: params },
|
||||
{ provide: ISelector, useValue: selector },
|
||||
...providerIterator(instantiationService)
|
||||
]
|
||||
})
|
||||
class ModuleClass {
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
|
||||
@Inject(ISelector) private selector: string
|
||||
) {
|
||||
}
|
||||
|
||||
ngDoBootstrap(appRef: ApplicationRef) {
|
||||
let componentClass = this.selector.startsWith(WizardNavigation.SELECTOR) ? WizardNavigation : DialogContainer;
|
||||
const factoryWrapper: any = this._resolver.resolveComponentFactory<WizardNavigation | DialogContainer>(componentClass);
|
||||
factoryWrapper.factory.selector = this.selector;
|
||||
appRef.bootstrap(factoryWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
return ModuleClass;
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/dialogModal';
|
||||
import { Component, ViewChild, Inject, forwardRef, ElementRef, AfterViewInit } from '@angular/core';
|
||||
import { ModelViewContent } from 'sql/workbench/browser/modelComponents/modelViewContent.component';
|
||||
import { DialogPane } from 'sql/workbench/services/dialog/browser/dialogPane';
|
||||
import { ComponentEventType } from 'sql/workbench/browser/modelComponents/interfaces';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
|
||||
|
||||
export interface LayoutRequestParams {
|
||||
modelViewId?: string;
|
||||
alwaysRefresh?: boolean;
|
||||
}
|
||||
export interface DialogComponentParams extends IBootstrapParams {
|
||||
modelViewId: string;
|
||||
validityChangedCallback: (valid: boolean) => void;
|
||||
onLayoutRequested: Event<LayoutRequestParams>;
|
||||
dialogPane: DialogPane;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'dialog-modelview-container',
|
||||
providers: [],
|
||||
template: `
|
||||
<div class="dialogContainer" *ngIf="_dialogPane && _dialogPane.displayPageTitle">
|
||||
<div class="dialogModal-wizardHeader" *ngIf="_dialogPane && _dialogPane.displayPageTitle">
|
||||
<div *ngIf="_dialogPane.pageNumber" class="wizardPageNumber">Step {{_dialogPane.pageNumber}}</div>
|
||||
<h1 class="wizardPageTitle" role="alert">{{_dialogPane.title}}</h1>
|
||||
<div *ngIf="_dialogPane.description">{{_dialogPane.description}}</div>
|
||||
</div>
|
||||
<div style="flex: 1 1 auto; position: relative;">
|
||||
<modelview-content [modelViewId]="modelViewId" style="width: 100%; height: 100%; position: absolute;">
|
||||
</modelview-content>
|
||||
</div>
|
||||
</div>
|
||||
<modelview-content [modelViewId]="modelViewId" *ngIf="!_dialogPane || !_dialogPane.displayPageTitle">
|
||||
</modelview-content>
|
||||
`
|
||||
})
|
||||
export class DialogContainer implements AfterViewInit {
|
||||
private _onResize = new Emitter<void>();
|
||||
public readonly onResize: Event<void> = this._onResize.event;
|
||||
private _dialogPane: DialogPane;
|
||||
|
||||
public modelViewId: string;
|
||||
@ViewChild(ModelViewContent) private _modelViewContent: ModelViewContent;
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(IBootstrapParams) private _params: DialogComponentParams) {
|
||||
this.modelViewId = this._params.modelViewId;
|
||||
this._params.onLayoutRequested(layoutParams => {
|
||||
if (layoutParams && (layoutParams.alwaysRefresh || layoutParams.modelViewId === this.modelViewId)) {
|
||||
this.layout();
|
||||
}
|
||||
});
|
||||
this._dialogPane = this._params.dialogPane;
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this._modelViewContent.onEvent(event => {
|
||||
if (event.isRootComponent && event.eventType === ComponentEventType.validityChanged) {
|
||||
this._params.validityChangedCallback(event.args);
|
||||
}
|
||||
});
|
||||
let element = <HTMLElement>this._el.nativeElement;
|
||||
element.style.height = '100%';
|
||||
element.style.width = '100%';
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
this._modelViewContent.layout();
|
||||
}
|
||||
}
|
||||
175
src/sql/workbench/services/dialog/browser/dialogModal.ts
Normal file
175
src/sql/workbench/services/dialog/browser/dialogModal.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/dialogModal';
|
||||
import { Modal, IModalOptions } from 'sql/workbench/browser/modal/modal';
|
||||
import { attachModalDialogStyler } from 'sql/platform/theme/common/styler';
|
||||
import { Dialog, DialogButton } from 'sql/workbench/services/dialog/common/dialogTypes';
|
||||
import { DialogPane } from 'sql/workbench/services/dialog/browser/dialogPane';
|
||||
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { DialogMessage } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { append, $ } from 'vs/base/browser/dom';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
|
||||
export class DialogModal extends Modal {
|
||||
private _dialogPane: DialogPane;
|
||||
private _onDone = new Emitter<void>();
|
||||
private _onCancel = new Emitter<void>();
|
||||
|
||||
// Buttons
|
||||
private _cancelButton: Button;
|
||||
private _doneButton: Button;
|
||||
|
||||
constructor(
|
||||
private _dialog: Dialog,
|
||||
name: string,
|
||||
options: IModalOptions,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@IWorkbenchThemeService themeService: IWorkbenchThemeService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IClipboardService clipboardService: IClipboardService,
|
||||
@ILogService logService: ILogService,
|
||||
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService
|
||||
) {
|
||||
super(_dialog.title, name, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, options);
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
this._dialogPane.layout();
|
||||
}
|
||||
|
||||
public render() {
|
||||
super.render();
|
||||
attachModalDialogStyler(this, this._themeService);
|
||||
|
||||
if (this.backButton) {
|
||||
this.backButton.onDidClick(() => this.cancel());
|
||||
attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND });
|
||||
}
|
||||
|
||||
if (this._dialog.customButtons) {
|
||||
this._dialog.customButtons.forEach(button => {
|
||||
let buttonElement = this.addDialogButton(button);
|
||||
this.updateButtonElement(buttonElement, button);
|
||||
});
|
||||
}
|
||||
|
||||
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, message.description);
|
||||
} else {
|
||||
this.setError('');
|
||||
}
|
||||
};
|
||||
|
||||
messageChangeHandler(this._dialog.message);
|
||||
this._dialog.onMessageChange(message => messageChangeHandler(message));
|
||||
}
|
||||
|
||||
private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined, registerClickEvent: boolean = true, requireDialogValid: boolean = false): Button {
|
||||
let buttonElement = this.addFooterButton(button.label, onSelect, button.position);
|
||||
buttonElement.enabled = button.enabled;
|
||||
if (registerClickEvent) {
|
||||
button.registerClickEvent(buttonElement.onDidClick);
|
||||
}
|
||||
button.onUpdate(() => {
|
||||
this.updateButtonElement(buttonElement, button, requireDialogValid);
|
||||
});
|
||||
attachButtonStyler(buttonElement, this._themeService);
|
||||
this.updateButtonElement(buttonElement, button, requireDialogValid);
|
||||
return buttonElement;
|
||||
}
|
||||
|
||||
private updateButtonElement(buttonElement: Button, dialogButton: DialogButton, requireDialogValid: boolean = false) {
|
||||
buttonElement.label = dialogButton.label;
|
||||
buttonElement.enabled = requireDialogValid ? dialogButton.enabled && this._dialog.valid : dialogButton.enabled;
|
||||
dialogButton.hidden ? buttonElement.element.parentElement.classList.add('dialogModal-hidden') : buttonElement.element.parentElement.classList.remove('dialogModal-hidden');
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
const body = append(container, $('div.dialogModal-body'));
|
||||
|
||||
this._dialogPane = new DialogPane(this._dialog.title, this._dialog.content,
|
||||
valid => this._dialog.notifyValidityChanged(valid), this._instantiationService, this._themeService, false);
|
||||
this._dialogPane.createBody(body);
|
||||
}
|
||||
|
||||
public open(): void {
|
||||
this.show();
|
||||
}
|
||||
|
||||
public async done(): Promise<void> {
|
||||
if (this._doneButton.enabled) {
|
||||
let buttonSpinnerHandler = setTimeout(() => {
|
||||
this._doneButton.enabled = false;
|
||||
this._doneButton.element.innerHTML = ' ';
|
||||
this._doneButton.element.classList.add('validating');
|
||||
}, 100);
|
||||
if (await this._dialog.validateClose()) {
|
||||
this._onDone.fire();
|
||||
this.dispose();
|
||||
this.hide();
|
||||
}
|
||||
clearTimeout(buttonSpinnerHandler);
|
||||
this._doneButton.element.classList.remove('validating');
|
||||
this.updateButtonElement(this._doneButton, this._dialog.okButton, true);
|
||||
}
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
this._onCancel.fire();
|
||||
this.dispose();
|
||||
this.hide();
|
||||
}
|
||||
|
||||
protected hide(): void {
|
||||
super.hide();
|
||||
}
|
||||
|
||||
protected show(): void {
|
||||
super.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridable to change behavior of escape key
|
||||
*/
|
||||
protected onClose(e: StandardKeyboardEvent) {
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridable to change behavior of enter key
|
||||
*/
|
||||
protected onAccept(e: StandardKeyboardEvent) {
|
||||
this.done();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this._dialogPane.dispose();
|
||||
}
|
||||
}
|
||||
177
src/sql/workbench/services/dialog/browser/dialogPane.ts
Normal file
177
src/sql/workbench/services/dialog/browser/dialogPane.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/dialogModal';
|
||||
|
||||
import { NgModuleRef } from '@angular/core';
|
||||
|
||||
import { IModalDialogStyles } from 'sql/workbench/browser/modal/modal';
|
||||
import { DialogTab } from 'sql/workbench/services/dialog/common/dialogTypes';
|
||||
import { TabbedPanel } from 'sql/base/browser/ui/panel/panel';
|
||||
import { bootstrapAngular } from 'sql/workbench/services/bootstrap/browser/bootstrapService';
|
||||
import { DialogModule } from 'sql/workbench/services/dialog/browser/dialog.module';
|
||||
import { DialogComponentParams, LayoutRequestParams } from 'sql/workbench/services/dialog/browser/dialogContainer.component';
|
||||
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IThemable } from 'vs/platform/theme/common/styler';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { attachTabbedPanelStyler } from 'sql/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export class DialogPane extends Disposable implements IThemable {
|
||||
private _tabbedPanel: TabbedPanel;
|
||||
private _moduleRefs: NgModuleRef<{}>[] = [];
|
||||
|
||||
// Validation
|
||||
private _modelViewValidityMap = new Map<string, boolean>();
|
||||
|
||||
private _body: HTMLElement;
|
||||
private _selectedTabIndex: number = 0; //TODO: can be an option
|
||||
private _onLayoutChange = new Emitter<LayoutRequestParams>();
|
||||
private _selectedTabContent: string;
|
||||
public pageNumber?: number;
|
||||
|
||||
constructor(
|
||||
public title: string,
|
||||
private _content: string | DialogTab[],
|
||||
private _validityChangedCallback: (valid: boolean) => void,
|
||||
private _instantiationService: IInstantiationService,
|
||||
private _themeService: IThemeService,
|
||||
public displayPageTitle: boolean,
|
||||
public description?: string,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public createBody(container: HTMLElement): HTMLElement {
|
||||
this._body = DOM.append(container, DOM.$('div.dialogModal-pane'));
|
||||
if (typeof this._content === 'string' || this._content.length < 2) {
|
||||
let modelViewId = typeof this._content === 'string' ? this._content : this._content[0].content;
|
||||
this.initializeModelViewContainer(this._body, modelViewId);
|
||||
} else {
|
||||
this._tabbedPanel = new TabbedPanel(this._body);
|
||||
attachTabbedPanelStyler(this._tabbedPanel, this._themeService);
|
||||
this._content.forEach((tab, tabIndex) => {
|
||||
if (this._selectedTabIndex === tabIndex) {
|
||||
this._selectedTabContent = tab.content;
|
||||
}
|
||||
let tabContainer = document.createElement('div');
|
||||
tabContainer.style.display = 'none';
|
||||
this._body.appendChild(tabContainer);
|
||||
this.initializeModelViewContainer(tabContainer, tab.content, tab);
|
||||
this._tabbedPanel.onTabChange(e => {
|
||||
tabContainer.style.height = (this.getTabDimension().height - this._tabbedPanel.headersize) + 'px';
|
||||
this._onLayoutChange.fire({ modelViewId: tab.content });
|
||||
});
|
||||
this._tabbedPanel.pushTab({
|
||||
title: tab.title,
|
||||
identifier: 'dialogPane.' + this.title + '.' + tabIndex,
|
||||
view: {
|
||||
render: (container) => {
|
||||
if (tabContainer.parentElement === this._body) {
|
||||
this._body.removeChild(tabContainer);
|
||||
}
|
||||
container.appendChild(tabContainer);
|
||||
tabContainer.style.display = 'block';
|
||||
},
|
||||
layout: (dimension) => { this.getTabDimension(); },
|
||||
focus: () => { this.focus(); }
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return this._body;
|
||||
}
|
||||
|
||||
private getTabDimension(): DOM.Dimension {
|
||||
return new DOM.Dimension(DOM.getContentWidth(this._body) - 5, DOM.getContentHeight(this._body) - 5);
|
||||
}
|
||||
|
||||
public layout(alwaysRefresh: boolean = false): void {
|
||||
let layoutParams: LayoutRequestParams = {
|
||||
alwaysRefresh: alwaysRefresh,
|
||||
modelViewId: this._selectedTabContent
|
||||
};
|
||||
if (this._tabbedPanel) {
|
||||
this._tabbedPanel.layout(this.getTabDimension());
|
||||
this._onLayoutChange.fire(layoutParams);
|
||||
} else if (alwaysRefresh) {
|
||||
this._onLayoutChange.fire(layoutParams);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap angular for the dialog's model view controller with the given model view ID
|
||||
*/
|
||||
private initializeModelViewContainer(bodyContainer: HTMLElement, modelViewId: string, tab?: DialogTab) {
|
||||
bootstrapAngular(this._instantiationService,
|
||||
DialogModule,
|
||||
bodyContainer,
|
||||
'dialog-modelview-container',
|
||||
{
|
||||
modelViewId: modelViewId,
|
||||
validityChangedCallback: (valid: boolean) => {
|
||||
this._setValidity(modelViewId, valid);
|
||||
if (tab) {
|
||||
tab.notifyValidityChanged(valid);
|
||||
}
|
||||
},
|
||||
onLayoutRequested: this._onLayoutChange.event,
|
||||
dialogPane: this
|
||||
} as DialogComponentParams,
|
||||
undefined,
|
||||
(moduleRef) => {
|
||||
return this._moduleRefs.push(moduleRef);
|
||||
});
|
||||
}
|
||||
|
||||
public show(focus: boolean = false): void {
|
||||
this._body.classList.remove('dialogModal-hidden');
|
||||
if (focus) {
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
this._body.classList.add('dialogModal-hidden');
|
||||
}
|
||||
|
||||
private focus(): void {
|
||||
let focusedElement = <HTMLElement>this._body.querySelector('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled])');
|
||||
focusedElement ? focusedElement.focus() : this._body.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the theme registry on theme change to style the component
|
||||
*/
|
||||
public style(styles: IModalDialogStyles): void {
|
||||
this._body.style.backgroundColor = styles.dialogBodyBackground ? styles.dialogBodyBackground.toString() : undefined;
|
||||
this._body.style.color = styles.dialogForeground ? styles.dialogForeground.toString() : undefined;
|
||||
}
|
||||
|
||||
private _setValidity(modelViewId: string, valid: boolean) {
|
||||
let oldValidity = this.isValid();
|
||||
this._modelViewValidityMap.set(modelViewId, valid);
|
||||
let newValidity = this.isValid();
|
||||
if (newValidity !== oldValidity) {
|
||||
this._validityChangedCallback(newValidity);
|
||||
}
|
||||
}
|
||||
|
||||
private isValid(): boolean {
|
||||
let valid = true;
|
||||
this._modelViewValidityMap.forEach(value => valid = valid && value);
|
||||
return valid;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose();
|
||||
this._body.remove();
|
||||
this._moduleRefs.forEach(moduleRef => moduleRef.destroy());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.dialogModal-body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-width: 500px;
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
.modal.wide .dialogModal-body {
|
||||
min-width: 800px;
|
||||
}
|
||||
|
||||
.dialog-message-and-page-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dialogModal-page-container {
|
||||
flex: 1 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dialogModal-pane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.dialogModal-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.footer-button.dialogModal-hidden {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.footer-button .validating {
|
||||
background-size: 15px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.vs .footer-button .validating {
|
||||
background-image: url("loading.svg");
|
||||
}
|
||||
|
||||
.vs-dark .footer-button .validating,
|
||||
.hc-black .footer-button .validating {
|
||||
background-image: url("loading_inverse.svg");
|
||||
}
|
||||
|
||||
.dialogModal-wizardHeader {
|
||||
padding: 10px 30px;
|
||||
}
|
||||
|
||||
.dialogModal-wizardHeader h1 {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dialogContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
31
src/sql/workbench/services/dialog/browser/media/loading.svg
Normal file
31
src/sql/workbench/services/dialog/browser/media/loading.svg
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version='1.0' standalone='no' ?>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
|
||||
<style>
|
||||
circle {
|
||||
animation: ball 0.6s linear infinite;
|
||||
}
|
||||
|
||||
circle:nth-child(2) { animation-delay: 0.075s; }
|
||||
circle:nth-child(3) { animation-delay: 0.15s; }
|
||||
circle:nth-child(4) { animation-delay: 0.225s; }
|
||||
circle:nth-child(5) { animation-delay: 0.3s; }
|
||||
circle:nth-child(6) { animation-delay: 0.375s; }
|
||||
circle:nth-child(7) { animation-delay: 0.45s; }
|
||||
circle:nth-child(8) { animation-delay: 0.525s; }
|
||||
|
||||
@keyframes ball {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0.3; }
|
||||
}
|
||||
</style>
|
||||
<g>
|
||||
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version='1.0' standalone='no' ?>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
|
||||
<style>
|
||||
circle {
|
||||
animation: ball 0.6s linear infinite;
|
||||
}
|
||||
|
||||
circle:nth-child(2) { animation-delay: 0.075s; }
|
||||
circle:nth-child(3) { animation-delay: 0.15s; }
|
||||
circle:nth-child(4) { animation-delay: 0.225s; }
|
||||
circle:nth-child(5) { animation-delay: 0.3s; }
|
||||
circle:nth-child(6) { animation-delay: 0.375s; }
|
||||
circle:nth-child(7) { animation-delay: 0.45s; }
|
||||
circle:nth-child(8) { animation-delay: 0.525s; }
|
||||
|
||||
@keyframes ball {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0.3; }
|
||||
}
|
||||
</style>
|
||||
<g style="fill:white;">
|
||||
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
|
||||
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
|
||||
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,93 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.wizardNavigation-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 80px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hc-black .wizardNavigation-container {
|
||||
border-right-color: #2b56f2;
|
||||
border-right-style: solid;
|
||||
border-right-width: 1px;
|
||||
background-color: unset;
|
||||
}
|
||||
|
||||
.wizardNavigation-pageNumber {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
max-height: 100px;
|
||||
}
|
||||
|
||||
.wizardNavigation-pageNumber a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wizardNavigation-dot {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
background-color: rgb(200, 200, 200);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
border-style: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.hc-black .wizardNavigation-dot {
|
||||
flex-grow: 1;
|
||||
background-color: unset;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: white;
|
||||
}
|
||||
|
||||
.wizardNavigation-connector {
|
||||
width: 3px;
|
||||
display: inline-block;
|
||||
flex-grow: 1;
|
||||
background-color: rgb(200, 200, 200);
|
||||
}
|
||||
|
||||
.hc-black .wizardNavigation-connector {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wizardNavigation-connector.active,
|
||||
.wizardNavigation-dot.active {
|
||||
background-color: rgb(9, 109, 201);
|
||||
}
|
||||
|
||||
.hc-black .wizardNavigation-dot.active {
|
||||
border-color: #2b56f2;
|
||||
background-color: unset;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.hc-black .wizardNavigation-dot.active:hover,
|
||||
.hc-black .wizardNavigation-dot.active.currentPage:hover {
|
||||
border-color: #F38518;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.wizardNavigation-dot.active.currentPage {
|
||||
border-style: double;
|
||||
}
|
||||
|
||||
.hc-black .wizardNavigation-dot.active.currentPage {
|
||||
border-style: solid;
|
||||
border-color: #F38518;
|
||||
}
|
||||
|
||||
.wizardNavigation-connector.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
309
src/sql/workbench/services/dialog/browser/wizardModal.ts
Normal file
309
src/sql/workbench/services/dialog/browser/wizardModal.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/dialogModal';
|
||||
import { Modal, IModalOptions } from 'sql/workbench/browser/modal/modal';
|
||||
import { attachModalDialogStyler } from 'sql/platform/theme/common/styler';
|
||||
import { Wizard, DialogButton, WizardPage } from 'sql/workbench/services/dialog/common/dialogTypes';
|
||||
import { DialogPane } from 'sql/workbench/services/dialog/browser/dialogPane';
|
||||
import { bootstrapAngular } from 'sql/workbench/services/bootstrap/browser/bootstrapService';
|
||||
import { DialogMessage } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { DialogModule } from 'sql/workbench/services/dialog/browser/dialog.module';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { append, $ } from 'vs/base/browser/dom';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
|
||||
export class WizardModal extends Modal {
|
||||
private _dialogPanes = new Map<WizardPage, DialogPane>();
|
||||
private _onDone = new Emitter<void>();
|
||||
private _onCancel = new Emitter<void>();
|
||||
|
||||
// Wizard HTML elements
|
||||
private _body: HTMLElement;
|
||||
|
||||
private _messageAndPageContainer: HTMLElement;
|
||||
private _pageContainer: HTMLElement;
|
||||
|
||||
// Buttons
|
||||
private _previousButton: Button;
|
||||
private _nextButton: Button;
|
||||
private _generateScriptButton: Button;
|
||||
private _doneButton: Button;
|
||||
private _cancelButton: Button;
|
||||
|
||||
constructor(
|
||||
private _wizard: Wizard,
|
||||
name: string,
|
||||
options: IModalOptions,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IClipboardService clipboardService: IClipboardService,
|
||||
@ILogService logService: ILogService,
|
||||
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService
|
||||
) {
|
||||
super(_wizard.title, name, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, options);
|
||||
this._useDefaultMessageBoxLocation = false;
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
|
||||
}
|
||||
|
||||
public render() {
|
||||
super.render();
|
||||
attachModalDialogStyler(this, this._themeService);
|
||||
|
||||
if (this.backButton) {
|
||||
this.backButton.onDidClick(() => this.cancel());
|
||||
attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND });
|
||||
}
|
||||
|
||||
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), true, true);
|
||||
this._generateScriptButton = this.addDialogButton(this._wizard.generateScriptButton, () => undefined);
|
||||
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, message.description);
|
||||
} else {
|
||||
this.setError('');
|
||||
}
|
||||
};
|
||||
|
||||
messageChangeHandler(this._wizard.message);
|
||||
this._wizard.onMessageChange(message => messageChangeHandler(message));
|
||||
}
|
||||
|
||||
private addDialogButton(button: DialogButton, onSelect: () => void = () => undefined, registerClickEvent: boolean = true, requirePageValid: boolean = false): Button {
|
||||
let buttonElement = this.addFooterButton(button.label, onSelect, button.position);
|
||||
buttonElement.enabled = button.enabled;
|
||||
if (registerClickEvent) {
|
||||
button.registerClickEvent(buttonElement.onDidClick);
|
||||
}
|
||||
button.onUpdate(() => {
|
||||
this.updateButtonElement(buttonElement, button, requirePageValid);
|
||||
});
|
||||
attachButtonStyler(buttonElement, this._themeService);
|
||||
this.updateButtonElement(buttonElement, button, requirePageValid);
|
||||
return buttonElement;
|
||||
}
|
||||
|
||||
private updateButtonElement(buttonElement: Button, dialogButton: DialogButton, requirePageValid: boolean = false) {
|
||||
buttonElement.label = dialogButton.label;
|
||||
buttonElement.enabled = requirePageValid ? dialogButton.enabled && this._wizard.pages[this._wizard.currentPage].valid : dialogButton.enabled;
|
||||
dialogButton.hidden ? buttonElement.element.parentElement.classList.add('dialogModal-hidden') : buttonElement.element.parentElement.classList.remove('dialogModal-hidden');
|
||||
|
||||
if (dialogButton.focused) {
|
||||
buttonElement.focus();
|
||||
}
|
||||
|
||||
this.setButtonsForPage(this._wizard.currentPage);
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
this._body = append(container, $('div.dialogModal-body'));
|
||||
|
||||
this.initializeNavigation(this._body);
|
||||
|
||||
const mpContainer = append(this._body, $('div.dialog-message-and-page-container'));
|
||||
this._messageAndPageContainer = mpContainer;
|
||||
mpContainer.append(this._messageElement);
|
||||
this._pageContainer = append(mpContainer, $('div.dialogModal-page-container'));
|
||||
|
||||
this._wizard.pages.forEach(page => {
|
||||
this.registerPage(page);
|
||||
});
|
||||
this._wizard.onPageAdded(page => {
|
||||
this.registerPage(page);
|
||||
this.updatePageNumbers();
|
||||
this.showPage(this._wizard.currentPage, false);
|
||||
});
|
||||
this._wizard.onPageRemoved(page => {
|
||||
let dialogPane = this._dialogPanes.get(page);
|
||||
this._dialogPanes.delete(page);
|
||||
this.updatePageNumbers();
|
||||
this.showPage(this._wizard.currentPage, false);
|
||||
dialogPane.dispose();
|
||||
});
|
||||
this.updatePageNumbers();
|
||||
}
|
||||
|
||||
private updatePageNumbers(): void {
|
||||
this._wizard.pages.forEach((page, index) => {
|
||||
let dialogPane = this._dialogPanes.get(page);
|
||||
dialogPane.pageNumber = index + 1;
|
||||
});
|
||||
}
|
||||
|
||||
private registerPage(page: WizardPage): void {
|
||||
let dialogPane = new DialogPane(page.title, page.content, valid => page.notifyValidityChanged(valid), this._instantiationService, this._themeService, this._wizard.displayPageTitles, page.description);
|
||||
dialogPane.createBody(this._pageContainer);
|
||||
this._dialogPanes.set(page, dialogPane);
|
||||
page.onUpdate(() => this.setButtonsForPage(this._wizard.currentPage));
|
||||
}
|
||||
|
||||
private async showPage(index: number, validate: boolean = true, focus: boolean = false): Promise<void> {
|
||||
let pageToShow = this._wizard.pages[index];
|
||||
if (!pageToShow) {
|
||||
this.done(validate);
|
||||
return;
|
||||
}
|
||||
if (validate && !await this.validateNavigation(index)) {
|
||||
return;
|
||||
}
|
||||
this._dialogPanes.forEach((dialogPane, page) => {
|
||||
if (page === pageToShow) {
|
||||
dialogPane.show(focus);
|
||||
} else {
|
||||
dialogPane.hide();
|
||||
}
|
||||
});
|
||||
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;
|
||||
|
||||
pageToShow.onValidityChanged(valid => {
|
||||
if (index === this._wizard.currentPage) {
|
||||
this._nextButton.enabled = this._wizard.nextButton.enabled && pageToShow.valid;
|
||||
this._doneButton.enabled = this._wizard.doneButton.enabled && pageToShow.valid;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private setButtonsForPage(index: number) {
|
||||
if (this._previousButton) {
|
||||
if (this._wizard.pages[index - 1]) {
|
||||
this._previousButton.element.parentElement.classList.remove('dialogModal-hidden');
|
||||
this._previousButton.enabled = this._wizard.pages[index - 1].enabled;
|
||||
} else {
|
||||
this._previousButton.element.parentElement.classList.add('dialogModal-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
if (this._nextButton && this._doneButton) {
|
||||
if (this._wizard.pages[index + 1]) {
|
||||
let isPageValid = this._wizard.pages[index] && this._wizard.pages[index].valid;
|
||||
this._nextButton.element.parentElement.classList.remove('dialogModal-hidden');
|
||||
this._nextButton.enabled = isPageValid && this._wizard.pages[index + 1].enabled;
|
||||
this._doneButton.element.parentElement.classList.add('dialogModal-hidden');
|
||||
} else {
|
||||
this._nextButton.element.parentElement.classList.add('dialogModal-hidden');
|
||||
this._doneButton.element.parentElement.classList.remove('dialogModal-hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap angular for the wizard's left nav bar
|
||||
*/
|
||||
private initializeNavigation(bodyContainer: HTMLElement) {
|
||||
bootstrapAngular(this._instantiationService,
|
||||
DialogModule,
|
||||
bodyContainer,
|
||||
'wizard-navigation',
|
||||
{
|
||||
wizard: this._wizard,
|
||||
navigationHandler: (index: number) => this.showPage(index, index > this._wizard.currentPage, true)
|
||||
},
|
||||
undefined,
|
||||
() => undefined);
|
||||
}
|
||||
|
||||
public open(): void {
|
||||
this.showPage(0, false, true);
|
||||
this.show();
|
||||
}
|
||||
|
||||
public async done(validate: boolean = true): Promise<void> {
|
||||
if (this._doneButton.enabled) {
|
||||
if (validate && !await this.validateNavigation(undefined)) {
|
||||
return;
|
||||
}
|
||||
this._onDone.fire();
|
||||
this.dispose();
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
this._onCancel.fire();
|
||||
this.dispose();
|
||||
this.hide();
|
||||
}
|
||||
|
||||
private async validateNavigation(newPage: number): Promise<boolean> {
|
||||
let button = newPage === undefined ? this._doneButton : this._nextButton;
|
||||
let buttonSpinnerHandler = setTimeout(() => {
|
||||
button.enabled = false;
|
||||
button.element.innerHTML = ' ';
|
||||
button.element.classList.add('validating');
|
||||
}, 100);
|
||||
let navigationValid = await this._wizard.validateNavigation(newPage);
|
||||
clearTimeout(buttonSpinnerHandler);
|
||||
button.element.classList.remove('validating');
|
||||
this.updateButtonElement(button, newPage === undefined ? this._wizard.doneButton : this._wizard.nextButton, true);
|
||||
return navigationValid;
|
||||
}
|
||||
|
||||
protected hide(): void {
|
||||
super.hide();
|
||||
}
|
||||
|
||||
protected show(): void {
|
||||
super.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridable to change behavior of escape key
|
||||
*/
|
||||
protected onClose(e: StandardKeyboardEvent) {
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridable to change behavior of enter key
|
||||
*/
|
||||
protected onAccept(e: StandardKeyboardEvent) {
|
||||
if (this._wizard.currentPage === this._wizard.pages.length - 1) {
|
||||
this.done();
|
||||
} else {
|
||||
if (this._nextButton.enabled) {
|
||||
this.showPage(this._wizard.currentPage + 1, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this._dialogPanes.forEach(dialogPane => dialogPane.dispose());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/wizardNavigation';
|
||||
import { Component, Inject, forwardRef, ElementRef, AfterViewInit, ChangeDetectorRef, ViewChild } from '@angular/core';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Wizard } from '../common/dialogTypes';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
|
||||
|
||||
export class WizardNavigationParams implements IBootstrapParams {
|
||||
wizard: Wizard;
|
||||
navigationHandler: (index: number) => void;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: WizardNavigation.SELECTOR,
|
||||
providers: [],
|
||||
template: `
|
||||
<div #container class="wizardNavigation-container">
|
||||
<ng-container *ngFor="let item of _params.wizard.pages; let i = index">
|
||||
<div class="wizardNavigation-pageNumber">
|
||||
<div class="wizardNavigation-connector" [ngClass]="{'invisible': !hasTopConnector(i), 'active': isActive(i)}"></div>
|
||||
<a [attr.href]="isActive(i) ? '' : null" [title]="item.title">
|
||||
<span class="wizardNavigation-dot" [ngClass]="{'active': isActive(i), 'currentPage': isCurrentPage(i)}" (click)="navigate(i)">{{i+1}}</span>
|
||||
</a>
|
||||
<div class="wizardNavigation-connector" [ngClass]="{'invisible': !hasBottomConnector(i), 'active': isActive(i)}"></div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class WizardNavigation implements AfterViewInit {
|
||||
public static readonly SELECTOR = 'wizard-navigation';
|
||||
|
||||
private _onResize = new Emitter<void>();
|
||||
public readonly onResize: Event<void> = this._onResize.event;
|
||||
|
||||
@ViewChild('container', { read: ElementRef }) private _container: ElementRef;
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
|
||||
@Inject(IBootstrapParams) private _params: WizardNavigationParams,
|
||||
@Inject(IWorkbenchThemeService) private _themeService: IWorkbenchThemeService) {
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this._themeService.onThemeChange(() => this.style());
|
||||
this.style();
|
||||
this._params.wizard.onPageChanged(() => this._changeRef.detectChanges());
|
||||
}
|
||||
|
||||
hasTopConnector(index: number): boolean {
|
||||
return index > 0;
|
||||
}
|
||||
|
||||
hasBottomConnector(index: number): boolean {
|
||||
return index + 1 !== this._params.wizard.pages.length;
|
||||
}
|
||||
|
||||
isActive(index: number): boolean {
|
||||
return index <= this._params.wizard.currentPage;
|
||||
}
|
||||
|
||||
isCurrentPage(index: number): boolean {
|
||||
return index === this._params.wizard.currentPage;
|
||||
}
|
||||
|
||||
navigate(index: number): void {
|
||||
if (this.isActive(index)) {
|
||||
this._params.navigationHandler(index);
|
||||
}
|
||||
}
|
||||
|
||||
private style(): void {
|
||||
let theme = this._themeService.getTheme();
|
||||
let navigationBackgroundColor = theme.getColor(SIDE_BAR_BACKGROUND);
|
||||
if (theme.type === 'light') {
|
||||
navigationBackgroundColor = navigationBackgroundColor.lighten(0.03);
|
||||
} else if (theme.type === 'dark') {
|
||||
navigationBackgroundColor = navigationBackgroundColor.darken(0.03);
|
||||
}
|
||||
(this._container.nativeElement as HTMLElement).style.backgroundColor = navigationBackgroundColor.toString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user