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

@@ -64,6 +64,10 @@ export class ModelViewContent extends ViewBase implements OnInit, IModelView {
@memoize
public get connection(): sqlops.connection.Connection {
if (!this._commonService.connectionManagementService) {
return undefined;
}
let currentConnection = this._commonService.connectionManagementService.connectionInfo.connectionProfile;
let connection: sqlops.connection.Connection = {
providerName: currentConnection.providerName,
@@ -75,6 +79,10 @@ export class ModelViewContent extends ViewBase implements OnInit, IModelView {
@memoize
public get serverInfo(): sqlops.ServerInfo {
if (!this._commonService.connectionManagementService) {
return undefined;
}
return this._commonService.connectionManagementService.connectionInfo.serverInfo;
}
}

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;
}

View File

@@ -203,4 +203,116 @@ declare module 'sqlops' {
*/
export function registerModelViewProvider(widgetId: string, handler: (view: ModelView) => void): void;
}
export namespace window {
export namespace modelviewdialog {
/**
* Create a dialog with the given title
* @param title The title of the dialog, displayed at the top
*/
export function createDialog(title: string): Dialog;
/**
* Create a dialog tab which can be included as part of the content of a dialog
* @param title The title of the page, displayed on the tab to select the page
*/
export function createTab(title: string): DialogTab;
/**
* Create a button which can be included in a dialog
* @param label The label of the button
*/
export function createButton(label: string): Button;
// Model view dialog classes
export interface Dialog {
/**
* The title of the dialog
*/
title: string,
/**
* The content of the dialog. If multiple tabs are given they will be displayed with tabs
* If a string is given, it should be the ID of the dialog's model view content
* TODO mairvine 4/18/18: use a model view content type
*/
content: string | DialogTab[],
/**
* The caption of the OK button
*/
okTitle: string;
/**
* The caption of the Cancel button
*/
cancelTitle: string;
/**
* Any additional buttons that should be displayed
*/
customButtons: Button[];
/**
* Opens the dialog
*/
open(): void;
/**
* Closes the dialog
*/
close(): void;
/**
* Updates the dialog on screen to reflect changes to the buttons or content
*/
updateContent(): void;
/**
* Raised when dialog's ok button is pressed
*/
readonly onOk: vscode.Event<void>;
/**
* Raised when dialog is canceled
*/
readonly onCancel: vscode.Event<void>;
}
export interface DialogTab {
/**
* The title of the tab
*/
title: string,
/**
* A string giving the ID of the tab's model view content
* TODO mairvine 4/18/18: use a model view content type
*/
content: string;
/**
* Updates the dialog on screen to reflect changes to the content
*/
updateContent(): void;
}
export interface Button {
/**
* The label displayed on the button
*/
label: string,
/**
* Whether the button is enabled
*/
enabled: boolean,
/**
* Raised when the button is clicked
*/
readonly onClick: vscode.Event<void>;
}
}
}
}

View File

@@ -280,10 +280,19 @@ export function createApiFactory(
}
};
const modelViewDialog: typeof sqlops.window.modelviewdialog = {
// TODO mairvine 4/18/18: Implement the extension layer for custom dialogs
createDialog(title: string): sqlops.window.modelviewdialog.Dialog { return undefined; },
createTab(title: string): sqlops.window.modelviewdialog.DialogTab { return undefined; },
createButton(label: string): sqlops.window.modelviewdialog.Button { return undefined; }
};
const window: typeof sqlops.window = {
createDialog(name: string) {
return extHostModalDialogs.createDialog(name);
}
},
modelviewdialog: modelViewDialog
};
const tasks: typeof sqlops.tasks = {