Initial work for custom model view dialogs (#1183)

This commit is contained in:
Matt Irvine
2018-04-19 16:24:02 -07:00
committed by GitHub
parent 61d05f6782
commit 93aa052856
11 changed files with 619 additions and 1 deletions

View File

@@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as sqlops from 'sqlops';
import { OptionsDialog } from 'sql/base/browser/ui/modal/optionsDialog';
import { DialogModal } from 'sql/platform/dialog/dialogModal';
import { Dialog } 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: true, isWide: true };
export class CustomDialogService {
constructor( @IInstantiationService private _instantiationService: IInstantiationService) { }
public showDialog(dialog: Dialog, options?: IModalOptions): void {
let optionsDialog = this._instantiationService.createInstance(DialogModal, dialog, 'CustomDialog', options || defaultOptions);
optionsDialog.render();
optionsDialog.open();
}
}

View File

@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/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/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 { BOOTSTRAP_SERVICE_ID, IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { Registry } from 'vs/platform/registry/common/platform';
/* Model-backed components */
let extensionComponents = Registry.as<IComponentRegistry>(Extensions.ComponentContribution).getAllCtors();
@NgModule({
declarations: [
DialogContainer,
ModelViewContent,
ModelComponentWrapper,
ComponentHostDirective,
...extensionComponents
],
entryComponents: [DialogContainer, ...extensionComponents],
imports: [
FormsModule,
CommonModule,
BrowserModule
],
providers: [{ provide: APP_BASE_HREF, useValue: '/' }, CommonServiceInterface]
})
export class DialogModule {
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
@Inject(BOOTSTRAP_SERVICE_ID) private _bootstrapService: IBootstrapService,
@Inject(forwardRef(() => CommonServiceInterface)) bootstrap: CommonServiceInterface,
) {
}
ngDoBootstrap(appRef: ApplicationRef) {
const factoryWrapper: any = this._resolver.resolveComponentFactory(DialogContainer);
const uniqueSelector: string = this._bootstrapService.getUniqueSelector('dialog-modelview-container');
factoryWrapper.factory.selector = uniqueSelector;
appRef.bootstrap(factoryWrapper);
}
}

View File

@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/dialogModal';
import { Component, AfterContentInit, ViewChild, Input, Inject, forwardRef, ElementRef } from '@angular/core';
import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component';
import { BootstrapParams } from 'sql/services/bootstrap/bootstrapParams';
import { BOOTSTRAP_SERVICE_ID, IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
import Event, { Emitter } from 'vs/base/common/event';
export interface DialogComponentParams extends BootstrapParams {
modelViewId: string;
}
@Component({
selector: 'dialog-modelview-container',
providers: [],
template: `
<modelview-content [modelViewId]="modelViewId">
</modelview-content>
`
})
export class DialogContainer implements AfterContentInit {
private _onResize = new Emitter<void>();
public readonly onResize: Event<void> = this._onResize.event;
public modelViewId: string;
@ViewChild(ModelViewContent) private _modelViewContent: ModelViewContent;
constructor(
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(BOOTSTRAP_SERVICE_ID) bootstrapService: IBootstrapService) {
this.modelViewId = (bootstrapService.getBootstrapParams(el.nativeElement.tagName) as DialogComponentParams).modelViewId;
}
ngAfterContentInit(): void {
}
public layout(): void {
this._modelViewContent.layout();
}
}

View File

@@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/dialogModal';
import { Modal, IModalOptions } from 'sql/base/browser/ui/modal/modal';
import { attachModalDialogStyler } from 'sql/common/theme/styler';
import { Dialog } from 'sql/platform/dialog/dialogTypes';
import { DialogPane } from 'sql/platform/dialog/dialogPane';
import { IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
import { Builder } from 'vs/base/browser/builder';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
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 { localize } from 'vs/nls';
export class DialogModal extends Modal {
private static readonly DONE_BUTTON_LABEL = localize('dialogModalDoneButtonLabel', 'Done');
private static readonly CANCEL_BUTTON_LABEL = localize('dialogModalCancelButtonLabel', 'Cancel');
private _dialogPane: DialogPane;
// Wizard HTML elements
private _body: HTMLElement;
// Buttons
private _cancelButton: Button;
private _doneButton: Button;
constructor(
private _dialog: Dialog,
name: string,
options: IModalOptions,
@IPartService partService: IPartService,
@IWorkbenchThemeService private _themeService: IWorkbenchThemeService,
@ITelemetryService telemetryService: ITelemetryService,
@IContextKeyService contextKeyService: IContextKeyService,
@IBootstrapService private _bootstrapService: IBootstrapService
) {
super(_dialog.title, name, partService, telemetryService, contextKeyService, options);
}
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 });
}
this._cancelButton = this.addFooterButton(DialogModal.CANCEL_BUTTON_LABEL, () => this.cancel());
this._doneButton = this.addFooterButton(DialogModal.DONE_BUTTON_LABEL, () => this.done());
attachButtonStyler(this._cancelButton, this._themeService);
attachButtonStyler(this._doneButton, this._themeService);
}
protected renderBody(container: HTMLElement): void {
new Builder(container).div({ class: 'dialogModal-body' }, (bodyBuilder) => {
this._body = bodyBuilder.getHTMLElement();
});
this._dialogPane = new DialogPane(this._dialog, this._bootstrapService);
this._dialogPane.createBody(this._body);
}
public open(): void {
this.show();
}
public done(): void {
this.dispose();
this.hide();
}
public cancel(): void {
this.dispose();
this.hide();
}
protected hide(): void {
super.hide();
}
protected show(): void {
super.show();
}
public dispose(): void {
super.dispose();
this._dialogPane.dispose();
}
}

View File

@@ -0,0 +1,95 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/dialogModal';
import { NgModuleRef } from '@angular/core';
import { IModalDialogStyles } from 'sql/base/browser/ui/modal/modal';
import { Dialog } from 'sql/platform/dialog/dialogTypes';
import { TabbedPanel, IPanelTab, IPanelView } from 'sql/base/browser/ui/panel/panel';
import { IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
import { DialogModule } from 'sql/platform/dialog/dialog.module';
import { DialogComponentParams } from 'sql/platform/dialog/dialogContainer.component';
import { Builder } from 'vs/base/browser/builder';
import { IThemable } from 'vs/platform/theme/common/styler';
import { Disposable } from 'vs/base/common/lifecycle';
export class DialogPane extends Disposable implements IThemable {
private _activeTabIndex: number;
private _tabbedPanel: TabbedPanel;
private _moduleRef: NgModuleRef<{}>;
// HTML Elements
private _body: HTMLElement;
private _tabBar: HTMLElement;
private _tabs: HTMLElement[];
private _tabContent: HTMLElement[];
constructor(
private _dialog: Dialog,
private _bootstrapService: IBootstrapService
) {
super();
this._tabs = [];
this._tabContent = [];
}
public createBody(container: HTMLElement): HTMLElement {
new Builder(container).div({ class: 'dialogModal-pane' }, (bodyBuilder) => {
this._body = bodyBuilder.getHTMLElement();
if (typeof this._dialog.content === 'string' || this._dialog.content.length < 2) {
let modelViewId = typeof this._dialog.content === 'string' ? this._dialog.content : this._dialog.content[0].content;
this.initializeModelViewContainer(this._body, modelViewId);
} else {
this._tabbedPanel = new TabbedPanel(this._body);
this._dialog.content.forEach((tab, tabIndex) => {
this._tabbedPanel.pushTab({
title: tab.title,
identifier: 'dialogPane.' + this._dialog.title + '.' + tabIndex,
view: {
render: (container) => {
this.initializeModelViewContainer(container, tab.content);
},
layout: (dimension) => { }
} as IPanelView
} as IPanelTab);
});
}
});
this._activeTabIndex = 0;
return this._body;
}
/**
* Bootstrap angular for the dialog's model view controller with the given model view ID
*/
private initializeModelViewContainer(bodyContainer: HTMLElement, modelViewId: string) {
this._bootstrapService.bootstrap(
DialogModule,
bodyContainer,
'dialog-modelview-container',
{ modelViewId: modelViewId } as DialogComponentParams,
undefined,
(moduleRef) => this._moduleRef = moduleRef);
}
public show(): void {
this._body.classList.remove('dialogModal-hidden');
}
public hide(): void {
this._body.classList.add('dialogModal-hidden');
}
/**
* 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;
}
}

View File

@@ -0,0 +1,55 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as sqlops from 'sqlops';
import Event, { Emitter } from 'vs/base/common/event';
export class DialogTab implements sqlops.window.modelviewdialog.DialogTab {
public content: string;
constructor(public title: string, content?: string) {
if (content) {
this.content = content;
}
}
public updateContent(): void { }
}
export class Dialog implements sqlops.window.modelviewdialog.Dialog {
public content: string | DialogTab[];
public okTitle: string;
public cancelTitle: string;
public customButtons: DialogButton[];
private _onOk: Emitter<void> = new Emitter<void>();
public readonly onOk: Event<void> = this._onOk.event;
private _onCancel: Emitter<void> = new Emitter<void>();
public readonly onCancel: Event<void> = this._onCancel.event;
constructor(public title: string, content?: string | DialogTab[]) {
if (content) {
this.content = content;
}
}
public open(): void { }
public close(): void { }
public updateContent(): void { }
}
export class DialogButton implements sqlops.window.modelviewdialog.Button {
public label: string;
public enabled: boolean;
private _onClick: Emitter<void> = new Emitter<void>();
public readonly onClick: Event<void> = this._onClick.event;
constructor(label: string, enabled: boolean) {
this.label = label;
this.enabled = enabled;
}
}

View File

@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* 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;
}
.dialogModal-pane {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
.dialogModal-pane.dialogModal-hidden {
display: none;
}