mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-25 09:35:37 -05:00
Feat/model backed ui (#1145)
This is an initial PR for a new model-driven UI where extensions can provide definitions of the components & how they're laid out using Containers. #1140, #1141, #1142, #1143 and #1144 are all tracking additional work needed to improve the initial implementation and fix some issues with the implementation. Features: - Supports defining a FlexContainer that maps to a flexbox-based layout. - Supports creating a card component, which is a key-value pair based control that will lay out simple information to a user. Eventually this will have an optional set of actions associated with it. - Has a sample project which shows how to use the API and was used for verification
This commit is contained in:
@@ -26,7 +26,7 @@ export class RowDetailView {
|
||||
this._options = mixin(options, this._defaults, false);
|
||||
}
|
||||
|
||||
public init(grid: any) {
|
||||
public init(grid: any): void {
|
||||
this._grid = grid;
|
||||
this._dataView = this._grid.getData();
|
||||
|
||||
@@ -61,17 +61,17 @@ export class RowDetailView {
|
||||
this._options = $.extend(true, {}, this._options, options);
|
||||
}
|
||||
|
||||
public handleClick(e: any, args: any) {
|
||||
public handleClick(e: any, args: any): void {
|
||||
// clicking on a row select checkbox
|
||||
if (this._options.useRowClick || this._grid.getColumns()[args.cell].id === this._options.columnId && $(e.target).hasClass("detailView-toggle")) {
|
||||
if (this._options.useRowClick || this._grid.getColumns()[args.cell].id === this._options.columnId && $(e.target).hasClass('detailView-toggle')) {
|
||||
// if editing, try to commit
|
||||
if (this._grid.getEditorLock().isActive() && !this._grid.getEditorLock().commitCurrentEdit()) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
return;
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
var item = this._dataView.getItem(args.row);
|
||||
let item = this._dataView.getItem(args.row);
|
||||
|
||||
// trigger an event before toggling
|
||||
this.onBeforeRowDetailToggle.notify({
|
||||
@@ -100,36 +100,35 @@ export class RowDetailView {
|
||||
// If we scroll save detail views that go out of cache range
|
||||
public handleScroll(e, args) {
|
||||
|
||||
var range = this._grid.getRenderedRange();
|
||||
let range = this._grid.getRenderedRange();
|
||||
|
||||
var start = (range.top > 0 ? range.top : 0);
|
||||
var end = (range.bottom > this._dataView.getLength() ? range.bottom : this._dataView.getLength());
|
||||
let start: number = (range.top > 0 ? range.top : 0);
|
||||
let end: number = (range.bottom > this._dataView.getLength() ? range.bottom : this._dataView.getLength());
|
||||
if (end <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the item at the top of the view
|
||||
var topMostItem = this._dataView.getItemByIdx(start);
|
||||
let topMostItem = this._dataView.getItemByIdx(start);
|
||||
|
||||
// Check it is a parent item
|
||||
if (topMostItem._parent === undefined)
|
||||
{
|
||||
if (topMostItem._parent === undefined) {
|
||||
// This is a standard row as we have no parent.
|
||||
var nextItem = this._dataView.getItemByIdx(start + 1);
|
||||
if(nextItem !== undefined && nextItem._parent !== undefined)
|
||||
{
|
||||
let nextItem = this._dataView.getItemByIdx(start + 1);
|
||||
if (nextItem !== undefined && nextItem._parent !== undefined) {
|
||||
// This is likely the expanded Detail Row View
|
||||
// Check for safety
|
||||
if(nextItem._parent === topMostItem)
|
||||
{
|
||||
if (nextItem._parent === topMostItem) {
|
||||
this.saveDetailView(topMostItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the bottom most item that is likely to go off screen
|
||||
var bottomMostItem = this._dataView.getItemByIdx(end - 1);
|
||||
let bottomMostItem = this._dataView.getItemByIdx(end - 1);
|
||||
|
||||
// If we are a detailView and we are about to go out of cache view
|
||||
if(bottomMostItem._parent !== undefined)
|
||||
{
|
||||
if (bottomMostItem._parent !== undefined) {
|
||||
this.saveDetailView(bottomMostItem._parent);
|
||||
}
|
||||
}
|
||||
@@ -143,17 +142,17 @@ export class RowDetailView {
|
||||
|
||||
// Collapse all of the open items
|
||||
public collapseAll() {
|
||||
for (var i = this._expandedRows.length - 1; i >= 0; i--) {
|
||||
for (let i = this._expandedRows.length - 1; i >= 0; i--) {
|
||||
this.collapseItem(this._expandedRows[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Saves the current state of the detail view
|
||||
public saveDetailView(item) {
|
||||
var view = $('#innerDetailView_' + item.id);
|
||||
let view = $('#innerDetailView_' + item.id);
|
||||
if (view) {
|
||||
var html = $('#innerDetailView_' + item.id).html();
|
||||
if(html !== undefined) {
|
||||
let html = $('#innerDetailView_' + item.id).html();
|
||||
if (html !== undefined) {
|
||||
item._detailContent = html;
|
||||
}
|
||||
}
|
||||
@@ -230,7 +229,7 @@ export class RowDetailView {
|
||||
|
||||
args.itemDetail._detailViewLoaded = true;
|
||||
|
||||
var idxParent = this._dataView.getIdxById(args.itemDetail.id);
|
||||
let idxParent = this._dataView.getIdxById(args.itemDetail.id);
|
||||
this._dataView.updateItem(args.itemDetail.id, args.itemDetail);
|
||||
|
||||
// trigger an event once the post template is finished loading
|
||||
@@ -254,7 +253,7 @@ export class RowDetailView {
|
||||
//////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////
|
||||
public getPaddingItem(parent, offset) {
|
||||
var item: any = {};
|
||||
let item: any = {};
|
||||
|
||||
for (let prop in this._grid.getData()) {
|
||||
item[prop] = null;
|
||||
@@ -284,7 +283,7 @@ export class RowDetailView {
|
||||
item._height = (item._sizePadding * this._grid.getOptions().rowHeight);
|
||||
|
||||
let idxParent = this._dataView.getIdxById(item.id);
|
||||
for (var idx = 1; idx <= item._sizePadding; idx++) {
|
||||
for (let idx = 1; idx <= item._sizePadding; idx++) {
|
||||
this._dataView.insertItem(idxParent + idx, this.getPaddingItem(item, idx));
|
||||
}
|
||||
}
|
||||
@@ -307,12 +306,12 @@ export class RowDetailView {
|
||||
public detailSelectionFormatter(row, cell, value, columnDef, dataContext) {
|
||||
|
||||
if (dataContext._collapsed === undefined) {
|
||||
dataContext._collapsed = true,
|
||||
dataContext._sizePadding = 0, //the required number of pading rows
|
||||
dataContext._height = 0, //the actual height in pixels of the detail field
|
||||
dataContext._isPadding = false,
|
||||
dataContext._parent = undefined,
|
||||
dataContext._offset = 0
|
||||
dataContext._collapsed = true;
|
||||
dataContext._sizePadding = 0; //the required number of pading rows
|
||||
dataContext._height = 0; //the actual height in pixels of the detail field
|
||||
dataContext._isPadding = false;
|
||||
dataContext._parent = undefined;
|
||||
dataContext._offset = 0;
|
||||
}
|
||||
|
||||
if (dataContext._isPadding === true) {
|
||||
@@ -320,9 +319,9 @@ export class RowDetailView {
|
||||
} else if (dataContext._collapsed) {
|
||||
return '<div class=\'detailView-toggle expand\'></div>';
|
||||
} else {
|
||||
var html = [];
|
||||
var rowHeight = this._grid.getOptions().rowHeight;
|
||||
var bottomMargin = 5;
|
||||
let html = [];
|
||||
let rowHeight = this._grid.getOptions().rowHeight;
|
||||
let bottomMargin = 5;
|
||||
|
||||
//V313HAX:
|
||||
//putting in an extra closing div after the closing toggle div and ommiting a
|
||||
@@ -339,7 +338,7 @@ export class RowDetailView {
|
||||
html.push("style='height:", dataContext._height, "px;"); //set total height of padding
|
||||
html.push("top:", rowHeight, "px'>"); //shift detail below 1st row
|
||||
html.push("<div id='detailViewContainer_", dataContext.id, "' class='detail-container' style='max-height:" + (dataContext._height - rowHeight + bottomMargin) + "px'>"); //sub ctr for custom styling
|
||||
html.push("<div id='innerDetailView_" , dataContext.id , "'>" , dataContext._detailContent, "</div></div>");
|
||||
html.push("<div id='innerDetailView_", dataContext.id, "'>", dataContext._detailContent, "</div></div>");
|
||||
//&omit a final closing detail container </div> that would come next
|
||||
|
||||
return html.join('');
|
||||
@@ -351,33 +350,32 @@ export class RowDetailView {
|
||||
if (!item) return;
|
||||
|
||||
// Grad each of the dom items
|
||||
var mainContainer = document.getElementById('detailViewContainer_' + item.id);
|
||||
var cellItem = document.getElementById('cellDetailView_' + item.id);
|
||||
var inner = document.getElementById('innerDetailView_' + item.id);
|
||||
let mainContainer = document.getElementById('detailViewContainer_' + item.id);
|
||||
let cellItem = document.getElementById('cellDetailView_' + item.id);
|
||||
let inner = document.getElementById('innerDetailView_' + item.id);
|
||||
|
||||
if (!mainContainer || !cellItem || !inner) return;
|
||||
|
||||
for (var idx = 1; idx <= item._sizePadding; idx++) {
|
||||
for (let idx = 1; idx <= item._sizePadding; idx++) {
|
||||
this._dataView.deleteItem(item.id + "." + idx);
|
||||
}
|
||||
|
||||
var rowHeight = this._grid.getOptions().rowHeight; // height of a row
|
||||
var lineHeight = 13; //we know cuz we wrote the custom css innit ;)
|
||||
let rowHeight = this._grid.getOptions().rowHeight; // height of a row
|
||||
let lineHeight = 13; //we know cuz we wrote the custom css innit ;)
|
||||
|
||||
// Get the inner Item height as this will be the actual size
|
||||
var itemHeight = inner.clientHeight;
|
||||
let itemHeight = inner.clientHeight;
|
||||
|
||||
// Now work out how many rows
|
||||
var rowCount = Math.ceil(itemHeight / rowHeight) + 1;
|
||||
let rowCount = Math.ceil(itemHeight / rowHeight) + 1;
|
||||
|
||||
item._sizePadding = Math.ceil(((rowCount * 2) * lineHeight) / rowHeight);
|
||||
item._height = (item._sizePadding * rowHeight);
|
||||
|
||||
// If the padding is now more than the original minRowBuff we need to increase it
|
||||
if (this._grid.getOptions().minRowBuffer < item._sizePadding)
|
||||
{
|
||||
if (this._grid.getOptions().minRowBuffer < item._sizePadding) {
|
||||
// Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3
|
||||
this._grid.getOptions().minRowBuffer =item._sizePadding + 3;
|
||||
this._grid.getOptions().minRowBuffer = item._sizePadding + 3;
|
||||
}
|
||||
|
||||
mainContainer.setAttribute("style", "max-height: " + item._height + "px");
|
||||
@@ -386,7 +384,7 @@ export class RowDetailView {
|
||||
}
|
||||
|
||||
let idxParent = this._dataView.getIdxById(item.id);
|
||||
for (var idx = 1; idx <= item._sizePadding; idx++) {
|
||||
for (let idx = 1; idx <= item._sizePadding; idx++) {
|
||||
this._dataView.insertItem(idxParent + idx, this.getPaddingItem(item, idx));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboar
|
||||
import { WIDGETS_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution';
|
||||
import { GRID_CONTAINER } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution';
|
||||
import { WEBVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWebviewContainer.contribution';
|
||||
import { MODELVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardModelViewContainer.contribution';
|
||||
import { CONTROLHOST_CONTAINER } from 'sql/parts/dashboard/containers/dashboardControlHostContainer.contribution';
|
||||
import { NAV_SECTION } from 'sql/parts/dashboard/containers/dashboardNavSection.contribution';
|
||||
import { IDashboardContainerRegistry, Extensions as DashboardContainerExtensions, IDashboardContainer, registerContainerType } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
|
||||
@@ -25,6 +26,7 @@ const containerTypes = [
|
||||
WIDGETS_CONTAINER,
|
||||
GRID_CONTAINER,
|
||||
WEBVIEW_CONTAINER,
|
||||
MODELVIEW_CONTAINER,
|
||||
CONTROLHOST_CONTAINER,
|
||||
NAV_SECTION
|
||||
];
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
</dashboard-webview-container>
|
||||
<dashboard-widget-container *ngIf="getContentType(tab) === 'widgets-container'" [tab]="tab">
|
||||
</dashboard-widget-container>
|
||||
<dashboard-modelview-container *ngIf="getContentType(tab) === 'modelview-container'" [tab]="tab">
|
||||
</dashboard-modelview-container>
|
||||
<dashboard-controlhost-container *ngIf="getContentType(tab) === 'controlhost-container'" [tab]="tab">
|
||||
</dashboard-controlhost-container>
|
||||
<dashboard-nav-section *ngIf="getContentType(tab) === 'nav-section'" [tab]="tab">
|
||||
|
||||
@@ -39,7 +39,7 @@ export class DashboardErrorContainer extends DashboardTab implements AfterViewIn
|
||||
|
||||
ngAfterViewInit() {
|
||||
let errorMessage = this._errorMessageContainer.nativeElement as HTMLElement;
|
||||
errorMessage.innerHTML = nls.localize('dashboardNavSection_loadTabError', 'The {0} has an invalid content. Please contact extension owner.', this.tab.title);
|
||||
errorMessage.innerHTML = nls.localize('dashboardNavSection_loadTabError', 'The "{0}" section has invalid content. Please contact extension owner.', this.tab.title);
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
|
||||
@@ -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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import 'vs/css!./dashboardWebviewContainer';
|
||||
|
||||
import { Component, forwardRef, Input, AfterContentInit, ViewChild } from '@angular/core';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { TabConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component';
|
||||
|
||||
@Component({
|
||||
selector: 'dashboard-modelview-container',
|
||||
providers: [{ provide: DashboardTab, useExisting: forwardRef(() => DashboardModelViewContainer) }],
|
||||
template: `
|
||||
<modelview-content [modelViewId]="tab.id">
|
||||
</modelview-content>
|
||||
`
|
||||
})
|
||||
export class DashboardModelViewContainer extends DashboardTab implements AfterContentInit {
|
||||
@Input() private tab: TabConfig;
|
||||
|
||||
private _onResize = new Emitter<void>();
|
||||
public readonly onResize: Event<void> = this._onResize.event;
|
||||
|
||||
@ViewChild(ModelViewContent) private _modelViewContent: ModelViewContent;
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
ngAfterContentInit(): void {
|
||||
this._register(this._modelViewContent.onResize(() => {
|
||||
this._onResize.fire();
|
||||
}));
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
this._modelViewContent.layout();
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this.tab.id;
|
||||
}
|
||||
|
||||
public get editable(): boolean {
|
||||
return this.tab.editable;
|
||||
}
|
||||
|
||||
public refresh(): void {
|
||||
// no op
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
import { registerContainerType, registerNavSectionContainerType } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
|
||||
|
||||
export const MODELVIEW_CONTAINER = 'modelview-container';
|
||||
|
||||
let modelviewSchema: IJSONSchema = {
|
||||
type: 'null',
|
||||
description: nls.localize('dashboard.container.modelview', "The model-backed view that will be displayed in this tab."),
|
||||
default: null
|
||||
};
|
||||
|
||||
registerContainerType(MODELVIEW_CONTAINER, modelviewSchema);
|
||||
registerNavSectionContainerType(MODELVIEW_CONTAINER, modelviewSchema);
|
||||
@@ -0,0 +1,10 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
dashboard-modelview-container {
|
||||
height: 100%;
|
||||
width : 100%;
|
||||
display: block;
|
||||
}
|
||||
@@ -11,6 +11,8 @@
|
||||
</dashboard-webview-container>
|
||||
<dashboard-widget-container *ngIf="getContentType(tab) === 'widgets-container'" [tab]="tab">
|
||||
</dashboard-widget-container>
|
||||
<dashboard-modelview-container *ngIf="getContentType(tab) === 'modelview-container'" [tab]="tab">
|
||||
</dashboard-modelview-container>
|
||||
<dashboard-grid-container *ngIf="getContentType(tab) === 'grid-container'" [tab]="tab">
|
||||
</dashboard-grid-container>
|
||||
<dashboard-error-container *ngIf="getContentType(tab) === 'error-container'" [tab]="tab">
|
||||
|
||||
@@ -16,7 +16,7 @@ import { memoize } from 'vs/base/common/decorators';
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { TabConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { IDashboardWebview } from 'sql/services/dashboardWebview/common/dashboardWebviewService';
|
||||
import { IDashboardWebview } from 'sql/services/dashboard/common/dashboardViewService';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
@@ -46,7 +46,7 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._dashboardService.dashboardWebviewService.registerWebview(this);
|
||||
this._dashboardService.dashboardViewService.registerWebview(this);
|
||||
this._createWebview();
|
||||
this._register(addDisposableListener(window, EventType.RESIZE, e => {
|
||||
this.layout();
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ChartsModule } from 'ng2-charts/ng2-charts';
|
||||
import CustomUrlSerializer from 'sql/common/urlSerializer';
|
||||
import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
|
||||
import { Extensions as ComponentExtensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
|
||||
@@ -35,9 +36,12 @@ import { DashboardWidgetWrapper } from 'sql/parts/dashboard/contents/dashboardWi
|
||||
import { DashboardWidgetContainer } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.component';
|
||||
import { DashboardGridContainer } from 'sql/parts/dashboard/containers/dashboardGridContainer.component';
|
||||
import { DashboardWebviewContainer } from 'sql/parts/dashboard/containers/dashboardWebviewContainer.component';
|
||||
import { DashboardModelViewContainer } from 'sql/parts/dashboard/containers/dashboardModelViewContainer.component';
|
||||
import { DashboardErrorContainer } from 'sql/parts/dashboard/containers/dashboardErrorContainer.component';
|
||||
import { DashboardNavSection } from 'sql/parts/dashboard/containers/dashboardNavSection.component';
|
||||
import { WidgetContent } from 'sql/parts/dashboard/contents/widgetContent.component';
|
||||
import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component';
|
||||
import { ModelComponentWrapper } from 'sql/parts/modelComponents/modelComponentWrapper.component';
|
||||
import { WebviewContent } from 'sql/parts/dashboard/contents/webviewContent.component';
|
||||
import { BreadcrumbComponent } from 'sql/base/browser/ui/breadcrumb/breadcrumb.component';
|
||||
import { IBreadcrumbService } from 'sql/base/browser/ui/breadcrumb/interfaces';
|
||||
@@ -49,9 +53,9 @@ import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.comp
|
||||
import { JobHistoryComponent } from 'sql/parts/jobManagement/views/jobHistory.component';
|
||||
|
||||
let baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer,
|
||||
DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, WebviewContent, WidgetContent,
|
||||
DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, ModelViewContent, WebviewContent, WidgetContent,
|
||||
ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer,
|
||||
JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent];
|
||||
JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent, DashboardModelViewContainer, ModelComponentWrapper];
|
||||
|
||||
/* Panel */
|
||||
import { PanelModule } from 'sql/base/browser/ui/panel/panel.module';
|
||||
@@ -81,6 +85,9 @@ let widgetComponents = [
|
||||
/* Insights */
|
||||
let insightComponents = Registry.as<IInsightRegistry>(Extensions.InsightContribution).getAllCtors();
|
||||
|
||||
/* Model-backed components */
|
||||
let extensionComponents = Registry.as<IComponentRegistry>(ComponentExtensions.ComponentContribution).getAllCtors();
|
||||
|
||||
// Setup routes for various child components
|
||||
const appRoutes: Routes = [
|
||||
{ path: 'database-dashboard', component: DatabaseDashboardPage },
|
||||
@@ -99,13 +106,15 @@ const appRoutes: Routes = [
|
||||
...baseComponents,
|
||||
...pageComponents,
|
||||
...widgetComponents,
|
||||
...insightComponents
|
||||
...insightComponents,
|
||||
...extensionComponents
|
||||
],
|
||||
// also for widgets
|
||||
entryComponents: [
|
||||
DashboardComponent,
|
||||
...widgetComponents,
|
||||
...insightComponents
|
||||
...insightComponents,
|
||||
...extensionComponents
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
||||
@@ -23,7 +23,7 @@ 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 { IDashboardWebviewService } from 'sql/services/dashboardWebview/common/dashboardWebviewService';
|
||||
import { IDashboardViewService } from 'sql/services/dashboard/common/dashboardViewService';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
import { ConnectionContextkey } from 'sql/parts/connection/common/connectionContextKey';
|
||||
|
||||
@@ -138,7 +138,7 @@ export class DashboardServiceInterface extends AngularDisposable {
|
||||
private _capabilitiesService = this._bootstrapService.capabilitiesService;
|
||||
private _configurationEditingService = this._bootstrapService.configurationEditorService;
|
||||
private _commandService = this._bootstrapService.commandService;
|
||||
private _dashboardWebviewService = this._bootstrapService.dashboardWebviewService;
|
||||
private _dashboardViewService = this._bootstrapService.dashboardViewService;
|
||||
private _partService = this._bootstrapService.partService;
|
||||
private _angularEventingService = this._bootstrapService.angularEventingService;
|
||||
private _environmentService = this._bootstrapService.environmentService;
|
||||
@@ -211,8 +211,8 @@ export class DashboardServiceInterface extends AngularDisposable {
|
||||
return this._instantiationService;
|
||||
}
|
||||
|
||||
public get dashboardWebviewService(): IDashboardWebviewService {
|
||||
return this._dashboardWebviewService;
|
||||
public get dashboardViewService(): IDashboardViewService {
|
||||
return this._dashboardViewService;
|
||||
}
|
||||
|
||||
public get partService(): IPartService {
|
||||
|
||||
@@ -204,6 +204,8 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
|
||||
}
|
||||
componentInstance.data = { columns: result.columnInfo.map(item => item.columnName), rows: result.rows.map(row => row.map(item => item.displayValue)) };
|
||||
|
||||
componentInstance.data = { columns: result.columnInfo.map(item => item.columnName), rows: result.rows.map(row => row.map(item => item.displayValue)) };
|
||||
|
||||
if (componentInstance.init) {
|
||||
componentInstance.init();
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { memoize } from 'vs/base/common/decorators';
|
||||
|
||||
import { DashboardWidget, IDashboardWidget, WidgetConfig, WIDGET_CONFIG } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { IDashboardWebview } from 'sql/services/dashboardWebview/common/dashboardWebviewService';
|
||||
import { IDashboardWebview } from 'sql/services/dashboard/common/dashboardViewService';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
@@ -47,7 +47,7 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget,
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._dashboardService.dashboardWebviewService.registerWebview(this);
|
||||
this._dashboardService.dashboardViewService.registerWebview(this);
|
||||
this._createWebview();
|
||||
}
|
||||
|
||||
|
||||
68
src/sql/parts/modelComponents/card.component.ts
Normal file
68
src/sql/parts/modelComponents/card.component.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./flexContainer';
|
||||
|
||||
import { Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
|
||||
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList,
|
||||
} from '@angular/core';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
|
||||
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/parts/modelComponents/interfaces';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div *ngIf="label" class="cardComponent" style="position: absolute; height: 100%; width: 100%; margin: 10px 0px 10px 0px;">
|
||||
<span style="margin-left: 10px; display: inline-block;">
|
||||
<div style="font-size: 11px; font-weight: lighter">{{label}}</div>
|
||||
<div>{{value}}</div>
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export default class CardComponent extends ComponentBase implements IComponent, OnDestroy {
|
||||
@Input() descriptor: IComponentDescriptor;
|
||||
@Input() modelStore: IModelStore;
|
||||
|
||||
constructor(@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef) {
|
||||
super(changeRef);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.baseInit();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.baseDestroy();
|
||||
}
|
||||
|
||||
/// IComponent implementation
|
||||
|
||||
public layout(): void {
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
public setLayout (layout: any): void {
|
||||
// TODO allow configuring the look and feel
|
||||
this.layout();
|
||||
}
|
||||
|
||||
// CSS-bound properties
|
||||
|
||||
public get label(): string {
|
||||
return this.getPropertyOrDefault<sqlops.CardProperties, string>((props) => props.label, '');
|
||||
}
|
||||
|
||||
public get value(): string {
|
||||
return this.getPropertyOrDefault<sqlops.CardProperties, string>((props) => props.value, '');
|
||||
}
|
||||
|
||||
public get actions(): sqlops.ActionDescriptor[] {
|
||||
return this.getPropertyOrDefault<sqlops.CardProperties, sqlops.ActionDescriptor[]>((props) => props.actions, []);
|
||||
}
|
||||
|
||||
}
|
||||
95
src/sql/parts/modelComponents/componentBase.ts
Normal file
95
src/sql/parts/modelComponents/componentBase.ts
Normal 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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import 'vs/css!./flexContainer';
|
||||
|
||||
import { Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
|
||||
ViewChild, ElementRef, Injector, OnDestroy, OnInit
|
||||
} from '@angular/core';
|
||||
|
||||
import * as types from 'vs/base/common/types';
|
||||
|
||||
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/parts/modelComponents/interfaces';
|
||||
import { FlexLayout, FlexItemLayout } from 'sqlops';
|
||||
import { ComponentHostDirective } from 'sql/parts/dashboard/common/componentHost.directive';
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
|
||||
export class ItemDescriptor<T> {
|
||||
constructor(public descriptor: IComponentDescriptor, public config: T) {}
|
||||
}
|
||||
|
||||
export abstract class ComponentBase implements IComponent, OnDestroy, OnInit {
|
||||
protected properties: { [key: string]: any; } = {};
|
||||
constructor(
|
||||
protected _changeRef: ChangeDetectorRef) {
|
||||
}
|
||||
|
||||
/// IComponent implementation
|
||||
|
||||
abstract descriptor: IComponentDescriptor;
|
||||
abstract modelStore: IModelStore;
|
||||
|
||||
public layout(): void {
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
protected baseInit(): void {
|
||||
if (this.modelStore) {
|
||||
this.modelStore.registerComponent(this);
|
||||
}
|
||||
}
|
||||
|
||||
abstract ngOnInit(): void;
|
||||
|
||||
protected baseDestroy(): void {
|
||||
if (this.modelStore) {
|
||||
this.modelStore.unregisterComponent(this);
|
||||
}
|
||||
}
|
||||
|
||||
abstract ngOnDestroy(): void;
|
||||
|
||||
abstract setLayout (layout: any): void;
|
||||
|
||||
public setProperties(properties: { [key: string]: any; }): void {
|
||||
if (!properties) {
|
||||
this.properties = {};
|
||||
}
|
||||
this.properties = properties;
|
||||
this.layout();
|
||||
}
|
||||
|
||||
protected getProperties<TPropertyBag>(): TPropertyBag {
|
||||
return this.properties as TPropertyBag;
|
||||
}
|
||||
|
||||
protected getPropertyOrDefault<TPropertyBag, TValue>(propertyGetter: (TPropertyBag) => TValue, defaultVal: TValue) {
|
||||
let property = propertyGetter(this.getProperties<TPropertyBag>());
|
||||
return types.isUndefinedOrNull(property) ? defaultVal : property;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ContainerBase<T> extends ComponentBase {
|
||||
protected items: ItemDescriptor<T>[];
|
||||
|
||||
constructor(
|
||||
_changeRef: ChangeDetectorRef
|
||||
) {
|
||||
super(_changeRef);
|
||||
this.items = [];
|
||||
}
|
||||
|
||||
/// IComponent container-related implementation
|
||||
public addToContainer(componentDescriptor: IComponentDescriptor, config: any): void {
|
||||
this.items.push(new ItemDescriptor(componentDescriptor, config));
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
public clearContainer(): void {
|
||||
this.items = [];
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
abstract setLayout (layout: any): void;
|
||||
}
|
||||
15
src/sql/parts/modelComponents/components.contribution.ts
Normal file
15
src/sql/parts/modelComponents/components.contribution.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import FlexContainer from './flexContainer.component';
|
||||
import CardComponent from './card.component';
|
||||
import { registerComponentType } from 'sql/platform/dashboard/common/modelComponentRegistry';
|
||||
import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
export const FLEX_CONTAINER = 'flex-container';
|
||||
registerComponentType(FLEX_CONTAINER, ModelComponentTypes.FlexContainer, FlexContainer);
|
||||
|
||||
export const CARD_COMPONENT = 'card-component';
|
||||
registerComponentType(CARD_COMPONENT, ModelComponentTypes.Card, CardComponent);
|
||||
86
src/sql/parts/modelComponents/flexContainer.component.ts
Normal file
86
src/sql/parts/modelComponents/flexContainer.component.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./flexContainer';
|
||||
|
||||
import { Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
|
||||
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList,
|
||||
} from '@angular/core';
|
||||
|
||||
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/parts/modelComponents/interfaces';
|
||||
import { FlexLayout, FlexItemLayout } from 'sqlops';
|
||||
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { ContainerBase } from 'sql/parts/modelComponents/componentBase';
|
||||
import { ModelComponentWrapper } from 'sql/parts/modelComponents/modelComponentWrapper.component';
|
||||
|
||||
class FlexItem {
|
||||
constructor(public descriptor: IComponentDescriptor, public config: FlexItemLayout) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div *ngIf="items" class="flexContainer" [style.flexFlow]="flexFlow" [style.justifyContent]="justifyContent">
|
||||
<div *ngFor="let item of items" [style.flex]="getItemFlex(item)" [style.order]="getItemOrder(item)" >
|
||||
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
|
||||
</model-component-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export default class FlexContainer extends ContainerBase<FlexItemLayout> implements IComponent, OnDestroy {
|
||||
@Input() descriptor: IComponentDescriptor;
|
||||
@Input() modelStore: IModelStore;
|
||||
private _flexFlow: string;
|
||||
private _justifyContent: string;
|
||||
|
||||
@ViewChildren(ModelComponentWrapper) private _componentWrappers: QueryList<ModelComponentWrapper>;
|
||||
|
||||
constructor(@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef) {
|
||||
super(changeRef);
|
||||
this._flexFlow = ''; // default
|
||||
this._justifyContent = ''; // default
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.baseInit();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.baseDestroy();
|
||||
}
|
||||
|
||||
|
||||
/// IComponent implementation
|
||||
|
||||
public layout(): void {
|
||||
if (this._componentWrappers) {
|
||||
this._componentWrappers.forEach(wrapper => {
|
||||
wrapper.layout();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public setLayout (layout: FlexLayout): void {
|
||||
this._flexFlow = layout.flexFlow ? layout.flexFlow : '';
|
||||
this._justifyContent= layout.justifyContent ? layout.justifyContent : '';
|
||||
this.layout();
|
||||
}
|
||||
|
||||
// CSS-bound properties
|
||||
public get flexFlow(): string {
|
||||
return this._flexFlow;
|
||||
}
|
||||
|
||||
public get justifyContent(): string {
|
||||
return this._justifyContent;
|
||||
}
|
||||
|
||||
private getItemFlex(item: FlexItem): string {
|
||||
return item.config ? item.config.flex : '';
|
||||
}
|
||||
private getItemOrder(item: FlexItem): number {
|
||||
return item.config ? item.config.order : 0;
|
||||
}
|
||||
}
|
||||
4
src/sql/parts/modelComponents/flexContainer.css
Normal file
4
src/sql/parts/modelComponents/flexContainer.css
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
.flexContainer {
|
||||
display: flex
|
||||
}
|
||||
73
src/sql/parts/modelComponents/interfaces.ts
Normal file
73
src/sql/parts/modelComponents/interfaces.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { InjectionToken } from '@angular/core';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
/**
|
||||
* An instance of a model-backed component. This will be a UI element
|
||||
*
|
||||
* @export
|
||||
* @interface IComponent
|
||||
*/
|
||||
export interface IComponent {
|
||||
descriptor: IComponentDescriptor;
|
||||
modelStore: IModelStore;
|
||||
layout();
|
||||
clearContainer?: () => void;
|
||||
addToContainer?: (componentDescriptor: IComponentDescriptor, config: any) => void;
|
||||
setLayout?: (layout: any) => void;
|
||||
setProperties?: (properties: { [key: string]: any; }) => void;
|
||||
}
|
||||
|
||||
export const COMPONENT_CONFIG = new InjectionToken<IComponentConfig>('component_config');
|
||||
|
||||
export interface IComponentConfig {
|
||||
descriptor: IComponentDescriptor;
|
||||
modelStore: IModelStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a component and can be used to map from the model-backed version of the
|
||||
* world to the frontend UI;
|
||||
*
|
||||
* @export
|
||||
* @interface IComponentDescriptor
|
||||
*/
|
||||
export interface IComponentDescriptor {
|
||||
/**
|
||||
* The type of this component. Used to map to the correct angular selector
|
||||
* when loading the component
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* A unique ID for this component
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface IModelStore {
|
||||
/**
|
||||
* Creates and saves the reference of a component descriptor.
|
||||
* This can be used during creation of a component later
|
||||
*/
|
||||
createComponentDescriptor(type: string, createComponentDescriptor): IComponentDescriptor;
|
||||
/**
|
||||
* gets the descriptor for a previously created component ID
|
||||
*/
|
||||
getComponentDescriptor(componentId: string): IComponentDescriptor;
|
||||
registerComponent(component: IComponent): void;
|
||||
unregisterComponent(component: IComponent): void;
|
||||
getComponent(componentId: string): IComponent;
|
||||
/**
|
||||
* Runs on a component immediately if the component exists, or runs on
|
||||
* registration of the component otherwise
|
||||
*
|
||||
* @param {string} componentId unique identifier of the component
|
||||
* @param {(component: IComponent) => void} action some action to perform
|
||||
* @memberof IModelStore
|
||||
*/
|
||||
eventuallyRunOnComponent<T>(componentId: string, action: (component: IComponent) => T): Promise<T>;
|
||||
}
|
||||
150
src/sql/parts/modelComponents/modelComponentWrapper.component.ts
Normal file
150
src/sql/parts/modelComponents/modelComponentWrapper.component.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!sql/media/icons/common-icons';
|
||||
|
||||
import {
|
||||
Component, Input, Inject, forwardRef, ComponentFactoryResolver, AfterContentInit, ViewChild,
|
||||
ElementRef, OnInit, ChangeDetectorRef, OnDestroy, ReflectiveInjector, Injector, Type, ComponentRef
|
||||
} from '@angular/core';
|
||||
|
||||
import { ComponentHostDirective } from 'sql/parts/dashboard/common/componentHost.directive';
|
||||
import { error } from 'sql/base/common/log';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { IComponent, IComponentConfig, IComponentDescriptor, IModelStore, COMPONENT_CONFIG } from './interfaces';
|
||||
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
|
||||
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import * as colors from 'vs/platform/theme/common/colorRegistry';
|
||||
import * as themeColors from 'vs/workbench/common/theme';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
const componentRegistry = <IComponentRegistry> Registry.as(Extensions.ComponentContribution);
|
||||
|
||||
@Component({
|
||||
selector: 'model-component-wrapper',
|
||||
template: `
|
||||
<ng-template component-host>
|
||||
</ng-template>
|
||||
`
|
||||
})
|
||||
export class ModelComponentWrapper extends AngularDisposable implements OnInit {
|
||||
@Input() descriptor: IComponentDescriptor;
|
||||
@Input() modelStore: IModelStore;
|
||||
|
||||
@memoize
|
||||
public get guid(): string {
|
||||
return generateUuid();
|
||||
}
|
||||
|
||||
private _componentInstance: IComponent;
|
||||
|
||||
@ViewChild(ComponentHostDirective) componentHost: ComponentHostDirective;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ComponentFactoryResolver)) private _componentFactoryResolver: ComponentFactoryResolver,
|
||||
@Inject(forwardRef(() => ElementRef)) private _ref: ElementRef,
|
||||
@Inject(forwardRef(() => DashboardServiceInterface)) private _bootstrap: DashboardServiceInterface,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeref: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => Injector)) private _injector: Injector
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
let self = this;
|
||||
this._register(self._bootstrap.themeService.onDidColorThemeChange((event: IColorTheme) => {
|
||||
self.updateTheme(event);
|
||||
}));
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.updateTheme(this._bootstrap.themeService.getColorTheme());
|
||||
if (this.componentHost) {
|
||||
this.loadComponent();
|
||||
}
|
||||
this._changeref.detectChanges();
|
||||
this.layout();
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
if (this._componentInstance && this._componentInstance.layout) {
|
||||
this._componentInstance.layout();
|
||||
}
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._componentInstance.descriptor.id;
|
||||
}
|
||||
|
||||
private get componentConfig(): IComponentConfig {
|
||||
return {
|
||||
descriptor: this.descriptor,
|
||||
modelStore: this.modelStore
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private loadComponent(): void {
|
||||
if (!this.descriptor || !this.descriptor.type) {
|
||||
error('No descriptor or type defined for this component');
|
||||
return;
|
||||
}
|
||||
|
||||
let selector = componentRegistry.getCtorFromId(this.descriptor.type);
|
||||
|
||||
if (selector === undefined) {
|
||||
error('No selector defined for type {0}', this.descriptor.type);
|
||||
return;
|
||||
}
|
||||
|
||||
let componentFactory = this._componentFactoryResolver.resolveComponentFactory(selector);
|
||||
|
||||
let viewContainerRef = this.componentHost.viewContainerRef;
|
||||
viewContainerRef.clear();
|
||||
|
||||
let injector = ReflectiveInjector.resolveAndCreate([{ provide: COMPONENT_CONFIG, useValue: this.componentConfig }], this._injector);
|
||||
let componentRef: ComponentRef<IComponent>;
|
||||
try {
|
||||
componentRef = viewContainerRef.createComponent(componentFactory, 0, injector);
|
||||
this._componentInstance = componentRef.instance;
|
||||
this._componentInstance.descriptor = this.descriptor;
|
||||
this._componentInstance.modelStore = this.modelStore;
|
||||
this._changeref.detectChanges();
|
||||
} catch (e) {
|
||||
error('Error rendering component: {0}', e);
|
||||
return;
|
||||
}
|
||||
let el = <HTMLElement>componentRef.location.nativeElement;
|
||||
|
||||
// set widget styles to conform to its box
|
||||
el.style.overflow = 'hidden';
|
||||
el.style.position = 'relative';
|
||||
}
|
||||
|
||||
private updateTheme(theme: IColorTheme): void {
|
||||
// TODO handle theming appropriately
|
||||
let el = <HTMLElement>this._ref.nativeElement;
|
||||
let borderColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true);
|
||||
let backgroundColor = theme.getColor(colors.editorBackground, true);
|
||||
let foregroundColor = theme.getColor(themeColors.SIDE_BAR_FOREGROUND, true);
|
||||
let border = theme.getColor(colors.contrastBorder, true);
|
||||
|
||||
if (backgroundColor) {
|
||||
el.style.backgroundColor = backgroundColor.toString();
|
||||
}
|
||||
|
||||
if (foregroundColor) {
|
||||
el.style.color = foregroundColor.toString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
89
src/sql/parts/modelComponents/modelStore.ts
Normal file
89
src/sql/parts/modelComponents/modelStore.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Registry } from 'vs/platform/registry/common/platform';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { IModelStore, IComponentDescriptor, IComponent } from './interfaces';
|
||||
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
|
||||
const componentRegistry = <IComponentRegistry> Registry.as(Extensions.ComponentContribution);
|
||||
|
||||
|
||||
class ComponentDescriptor implements IComponentDescriptor {
|
||||
constructor(public readonly id: string, public readonly type: string) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class ModelStore implements IModelStore {
|
||||
private static baseId = 0;
|
||||
|
||||
private _descriptorMappings: { [x: string]: IComponentDescriptor } = {};
|
||||
private _componentMappings: { [x: string]: IComponent } = {};
|
||||
private _componentActions: { [x: string]: Deferred<IComponent> } = {};
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public createComponentDescriptor(type: string, id: string): IComponentDescriptor {
|
||||
let descriptor = new ComponentDescriptor(id, type);
|
||||
this._descriptorMappings[id] = descriptor;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
getComponentDescriptor(id: string): IComponentDescriptor {
|
||||
return this._descriptorMappings[id];
|
||||
}
|
||||
|
||||
registerComponent(component: IComponent): void {
|
||||
let id = component.descriptor.id;
|
||||
this._componentMappings[id] = component;
|
||||
this.runPendingActions(id, component);
|
||||
}
|
||||
|
||||
unregisterComponent(component: IComponent): void {
|
||||
let id = component.descriptor.id;
|
||||
this._componentMappings[id] = undefined;
|
||||
this._componentActions[id] = undefined;
|
||||
// TODO notify model for cleanup
|
||||
}
|
||||
|
||||
getComponent(componentId: string): IComponent {
|
||||
return this._componentMappings[componentId];
|
||||
}
|
||||
|
||||
eventuallyRunOnComponent<T>(componentId: string, action: (component: IComponent) => T): Promise<T> {
|
||||
let component = this.getComponent(componentId);
|
||||
if (component) {
|
||||
return Promise.resolve(action(component));
|
||||
} else {
|
||||
return this.addPendingAction(componentId, action);
|
||||
}
|
||||
}
|
||||
|
||||
private addPendingAction<T>(componentId: string, action: (component: IComponent) => T): Promise<T> {
|
||||
// We create a promise and chain it onto a tracking promise whose resolve method
|
||||
// will only be called once the component is created
|
||||
let deferredPromise = this._componentActions[componentId];
|
||||
if (!deferredPromise) {
|
||||
deferredPromise = new Deferred();
|
||||
this._componentActions[componentId] = deferredPromise;
|
||||
}
|
||||
let promise = deferredPromise.promise.then((component) => {
|
||||
return action(component);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
private runPendingActions(componentId: string, component: IComponent) {
|
||||
let promiseTracker = this._componentActions[componentId];
|
||||
if (promiseTracker) {
|
||||
promiseTracker.resolve(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
80
src/sql/parts/modelComponents/modelViewContent.component.ts
Normal file
80
src/sql/parts/modelComponents/modelViewContent.component.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./modelViewContent';
|
||||
|
||||
import { Component, forwardRef, Input, OnInit, Inject, ChangeDetectorRef, ElementRef } from '@angular/core';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import nls = require('vs/nls');
|
||||
|
||||
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
|
||||
import { TabConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { IModelView } from 'sql/services/model/modelViewService';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { ViewBase } from 'sql/parts/modelComponents/viewBase';
|
||||
|
||||
@Component({
|
||||
selector: 'modelview-content',
|
||||
template: `
|
||||
<div *ngIf="rootDescriptor">
|
||||
<model-component-wrapper [descriptor]="rootDescriptor" [modelStore]="modelStore">
|
||||
</model-component-wrapper>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class ModelViewContent extends ViewBase implements OnInit, IModelView {
|
||||
@Input() private modelViewId: string;
|
||||
|
||||
private _onResize = new Emitter<void>();
|
||||
public readonly onResize: Event<void> = this._onResize.event;
|
||||
private _onMessage = new Emitter<string>();
|
||||
public readonly onMessage: Event<string> = this._onMessage.event;
|
||||
|
||||
private _onMessageDisposable: IDisposable;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => DashboardServiceInterface)) private _dashboardService: DashboardServiceInterface,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef
|
||||
) {
|
||||
super(changeRef);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._dashboardService.dashboardViewService.registerModelView(this);
|
||||
this._register(addDisposableListener(window, EventType.RESIZE, e => {
|
||||
this.layout();
|
||||
}));
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this.modelViewId;
|
||||
}
|
||||
|
||||
@memoize
|
||||
public get connection(): sqlops.connection.Connection {
|
||||
let currentConnection = this._dashboardService.connectionManagementService.connectionInfo.connectionProfile;
|
||||
let connection: sqlops.connection.Connection = {
|
||||
providerName: currentConnection.providerName,
|
||||
connectionId: currentConnection.id,
|
||||
options: currentConnection.options
|
||||
};
|
||||
return connection;
|
||||
}
|
||||
|
||||
@memoize
|
||||
public get serverInfo(): sqlops.ServerInfo {
|
||||
return this._dashboardService.connectionManagementService.connectionInfo.serverInfo;
|
||||
}
|
||||
}
|
||||
10
src/sql/parts/modelComponents/modelViewContent.css
Normal file
10
src/sql/parts/modelComponents/modelViewContent.css
Normal file
@@ -0,0 +1,10 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
modelview-content {
|
||||
height: 100%;
|
||||
width : 100%;
|
||||
display: block;
|
||||
}
|
||||
94
src/sql/parts/modelComponents/viewBase.ts
Normal file
94
src/sql/parts/modelComponents/viewBase.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ChangeDetectorRef } from '@angular/core';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import nls = require('vs/nls');
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { IModelStore, IComponentDescriptor, IComponent } from './interfaces';
|
||||
import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IModelView } from 'sql/services/model/modelViewService';
|
||||
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
import { ModelStore } from 'sql/parts/modelComponents/modelStore';
|
||||
|
||||
const componentRegistry = <IComponentRegistry> Registry.as(Extensions.ComponentContribution);
|
||||
|
||||
/**
|
||||
* Provides common logic required for any implementation that hooks to a model provided by
|
||||
* the extension host
|
||||
*/
|
||||
export abstract class ViewBase extends AngularDisposable implements IModelView {
|
||||
protected readonly modelStore: IModelStore;
|
||||
protected rootDescriptor: IComponentDescriptor;
|
||||
constructor(protected changeRef: ChangeDetectorRef) {
|
||||
super();
|
||||
this.modelStore = new ModelStore();
|
||||
}
|
||||
|
||||
// Properties needed by the model view code
|
||||
abstract id: string;
|
||||
abstract connection: sqlops.connection.Connection;
|
||||
abstract serverInfo: sqlops.ServerInfo;
|
||||
|
||||
initializeModel(rootComponent: IComponentShape): void {
|
||||
let descriptor = this.defineComponent(rootComponent);
|
||||
this.rootDescriptor = descriptor;
|
||||
// Kick off the build by detecting changes to the model
|
||||
this.changeRef.detectChanges();
|
||||
}
|
||||
|
||||
private defineComponent(component: IComponentShape): IComponentDescriptor {
|
||||
let typeId = componentRegistry.getIdForTypeMapping(component.type);
|
||||
if (!typeId) {
|
||||
// failure case
|
||||
throw new Error(nls.localize('componentTypeNotRegistered', "Could not find component for type {0}", ModelComponentTypes[component.type]));
|
||||
}
|
||||
let descriptor = this.modelStore.createComponentDescriptor(typeId, component.id);
|
||||
this.setProperties(component.id, component.properties);
|
||||
this.setLayout(component.id, component.layout);
|
||||
if (component.itemConfigs) {
|
||||
for(let item of component.itemConfigs) {
|
||||
this.addToContainer(component.id, item);
|
||||
}
|
||||
}
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
clearContainer(componentId: string): void {
|
||||
this.queueAction(componentId, (component) => component.clearContainer());
|
||||
}
|
||||
|
||||
addToContainer(containerId: string, itemConfig: IItemConfig): void {
|
||||
// Do not return the promise as this should be non-blocking
|
||||
this.queueAction(containerId, (component) => {
|
||||
let childDescriptor = this.defineComponent(itemConfig.componentShape);
|
||||
component.addToContainer(childDescriptor, itemConfig.config);
|
||||
});
|
||||
}
|
||||
|
||||
setLayout(componentId: string, layout: any): void {
|
||||
if (!layout) {
|
||||
return;
|
||||
}
|
||||
this.queueAction(componentId, (component) => component.setLayout(layout));
|
||||
}
|
||||
|
||||
setProperties(componentId: string, properties: { [key: string]: any; }): void {
|
||||
if (!properties) {
|
||||
return;
|
||||
}
|
||||
this.queueAction(componentId, (component) => component.setProperties(properties));
|
||||
}
|
||||
|
||||
private queueAction<T>(componentId: string, action: (component: IComponent) => T): void {
|
||||
this.modelStore.eventuallyRunOnComponent(componentId, action).catch(err => {
|
||||
// TODO add error handling
|
||||
});
|
||||
}
|
||||
}
|
||||
65
src/sql/platform/dashboard/common/modelComponentRegistry.ts
Normal file
65
src/sql/platform/dashboard/common/modelComponentRegistry.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { Type } from '@angular/core';
|
||||
import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
import * as platform from 'vs/platform/registry/common/platform';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IComponent } from 'sql/parts/modelComponents/interfaces';
|
||||
|
||||
export type ComponentIdentifier = string;
|
||||
|
||||
export const Extensions = {
|
||||
ComponentContribution: 'dashboard.contributions.components'
|
||||
};
|
||||
|
||||
export interface IComponentRegistry {
|
||||
registerComponentType(id: string, typeMapping: ModelComponentTypes, ctor: Type<IComponent>): ComponentIdentifier;
|
||||
getIdForTypeMapping(typeMapping: ModelComponentTypes): string;
|
||||
getCtorForType(typeMapping: ModelComponentTypes): Type<IComponent>;
|
||||
getCtorFromId(id: string): Type<IComponent>;
|
||||
getAllCtors(): Array<Type<IComponent>>;
|
||||
getAllIds(): Array<string>;
|
||||
}
|
||||
|
||||
class ComponentRegistry implements IComponentRegistry {
|
||||
private _idToCtor: { [x: string]: Type<IComponent> } = {};
|
||||
private _typeNameToId: { [x: string]: string } = {};
|
||||
|
||||
registerComponentType(id: string, typeMapping: ModelComponentTypes, ctor: Type<IComponent>): string {
|
||||
this._idToCtor[id] = ctor;
|
||||
this._typeNameToId[ModelComponentTypes[typeMapping]] = id;
|
||||
return id;
|
||||
}
|
||||
|
||||
public getIdForTypeMapping(typeMapping: ModelComponentTypes): string {
|
||||
return this._typeNameToId[ModelComponentTypes[typeMapping]];
|
||||
}
|
||||
|
||||
public getCtorForType(typeMapping: ModelComponentTypes): Type<IComponent> {
|
||||
let id = this.getIdForTypeMapping(typeMapping);
|
||||
return id ? this._idToCtor[id] : undefined;
|
||||
}
|
||||
public getCtorFromId(id: string): Type<IComponent> {
|
||||
return this._idToCtor[id];
|
||||
}
|
||||
|
||||
public getAllCtors(): Array<Type<IComponent>> {
|
||||
return Object.values(this._idToCtor);
|
||||
}
|
||||
|
||||
public getAllIds(): Array<string> {
|
||||
return Object.keys(this._idToCtor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const componentRegistry = new ComponentRegistry();
|
||||
platform.Registry.add(Extensions.ComponentContribution, componentRegistry);
|
||||
|
||||
export function registerComponentType(id: string, typeMapping: ModelComponentTypes, ctor: Type<IComponent>): ComponentIdentifier {
|
||||
return componentRegistry.registerComponentType(id, typeMapping, ctor);
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import { ISqlOAuthService } from 'sql/common/sqlOAuthService';
|
||||
import { IFileBrowserService, IFileBrowserDialogController } from 'sql/parts/fileBrowser/common/interfaces';
|
||||
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
||||
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
|
||||
import { IDashboardWebviewService } from 'sql/services/dashboardWebview/common/dashboardWebviewService';
|
||||
import { IDashboardViewService } from 'sql/services/dashboard/common/dashboardViewService';
|
||||
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
@@ -93,7 +93,7 @@ export interface IBootstrapService {
|
||||
capabilitiesService: ICapabilitiesService;
|
||||
configurationEditorService: ConfigurationEditingService;
|
||||
commandService: ICommandService;
|
||||
dashboardWebviewService: IDashboardWebviewService;
|
||||
dashboardViewService: IDashboardViewService;
|
||||
jobManagementService: IJobManagementService;
|
||||
environmentService: IEnvironmentService;
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import { ISqlOAuthService } from 'sql/common/sqlOAuthService';
|
||||
import { IFileBrowserService, IFileBrowserDialogController } from 'sql/parts/fileBrowser/common/interfaces';
|
||||
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
||||
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
|
||||
import { IDashboardWebviewService } from 'sql/services/dashboardWebview/common/dashboardWebviewService';
|
||||
import { IDashboardViewService } from 'sql/services/dashboard/common/dashboardViewService';
|
||||
|
||||
import { $ } from 'vs/base/browser/dom';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
@@ -102,7 +102,7 @@ export class BootstrapService implements IBootstrapService {
|
||||
@IClipboardService public clipboardService: IClipboardService,
|
||||
@ICapabilitiesService public capabilitiesService: ICapabilitiesService,
|
||||
@ICommandService public commandService: ICommandService,
|
||||
@IDashboardWebviewService public dashboardWebviewService: IDashboardWebviewService,
|
||||
@IDashboardViewService public dashboardViewService: IDashboardViewService,
|
||||
@IJobManagementService public jobManagementService: IJobManagementService,
|
||||
@IEnvironmentService public environmentService: IEnvironmentService
|
||||
) {
|
||||
|
||||
@@ -9,22 +9,23 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
|
||||
import Event from 'vs/base/common/event';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { IItemConfig, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IView, IModelView } from 'sql/services/model/modelViewService';
|
||||
|
||||
export const SERVICE_ID = 'dashboardWebviewService';
|
||||
export const SERVICE_ID = 'dashboardViewService';
|
||||
|
||||
export interface IDashboardWebview {
|
||||
readonly id: string;
|
||||
readonly connection: sqlops.connection.Connection;
|
||||
readonly serverInfo: sqlops.ServerInfo;
|
||||
export interface IDashboardWebview extends IView {
|
||||
setHtml(html: string): void;
|
||||
onMessage: Event<string>;
|
||||
sendMessage(message: string);
|
||||
}
|
||||
|
||||
export interface IDashboardWebviewService {
|
||||
export interface IDashboardViewService {
|
||||
_serviceBrand: any;
|
||||
onRegisteredWebview: Event<IDashboardWebview>;
|
||||
registerWebview(widget: IDashboardWebview);
|
||||
onRegisteredModelView: Event<IModelView>;
|
||||
registerModelView(widget: IModelView);
|
||||
}
|
||||
|
||||
export const IDashboardWebviewService = createDecorator<IDashboardWebviewService>(SERVICE_ID);
|
||||
export const IDashboardViewService = createDecorator<IDashboardViewService>(SERVICE_ID);
|
||||
@@ -5,17 +5,24 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { IDashboardWebviewService, IDashboardWebview } from 'sql/services/dashboardWebview/common/dashboardWebviewService';
|
||||
import { IDashboardViewService, IDashboardWebview } from 'sql/services/dashboard/common/dashboardViewService';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IModelView } from 'sql/services/model/modelViewService';
|
||||
|
||||
export class DashboardWebviewService implements IDashboardWebviewService {
|
||||
export class DashboardViewService implements IDashboardViewService {
|
||||
_serviceBrand: any;
|
||||
|
||||
private _onRegisteredWebview = new Emitter<IDashboardWebview>();
|
||||
public readonly onRegisteredWebview: Event<IDashboardWebview> = this._onRegisteredWebview.event;
|
||||
|
||||
private _onRegisteredModelView = new Emitter<IModelView>();
|
||||
public readonly onRegisteredModelView: Event<IModelView> = this._onRegisteredModelView.event;
|
||||
|
||||
public registerWebview(widget: IDashboardWebview) {
|
||||
this._onRegisteredWebview.fire(widget);
|
||||
}
|
||||
|
||||
registerModelView(view: IModelView) {
|
||||
this._onRegisteredModelView.fire(view);
|
||||
}
|
||||
}
|
||||
22
src/sql/services/model/modelViewService.ts
Normal file
22
src/sql/services/model/modelViewService.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IItemConfig, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
export interface IView {
|
||||
readonly id: string;
|
||||
readonly connection: sqlops.connection.Connection;
|
||||
readonly serverInfo: sqlops.ServerInfo;
|
||||
}
|
||||
|
||||
export interface IModelView extends IView {
|
||||
initializeModel(rootComponent: IComponentShape): void;
|
||||
clearContainer(componentId: string): void;
|
||||
addToContainer(containerId: string, item: IItemConfig): void;
|
||||
setLayout(componentId: string, layout: any): void;
|
||||
setProperties(componentId: string, properties: { [key: string]: any }): void;
|
||||
}
|
||||
206
src/sql/sqlops.proposed.d.ts
vendored
Normal file
206
src/sql/sqlops.proposed.d.ts
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// This is the place for API experiments and proposal.
|
||||
|
||||
import * as core from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
declare module 'sqlops' {
|
||||
|
||||
/**
|
||||
* Supports defining a model that can be instantiated as a view in the UI
|
||||
* @export
|
||||
* @interface ModelBuilder
|
||||
*/
|
||||
export interface ModelBuilder {
|
||||
navContainer(): ContainerBuilder<NavContainer, any, any>;
|
||||
flexContainer(): FlexBuilder;
|
||||
card(): ComponentBuilder<CardComponent>;
|
||||
dashboardWidget(widgetId: string): ComponentBuilder<WidgetComponent>;
|
||||
dashboardWebview(webviewId: string): ComponentBuilder<WebviewComponent>;
|
||||
}
|
||||
|
||||
export interface ComponentBuilder<T extends Component> {
|
||||
component(): T;
|
||||
withProperties<U>(properties: U): ComponentBuilder<T>;
|
||||
}
|
||||
export interface ContainerBuilder<T extends Component, TLayout,TItemLayout> extends ComponentBuilder<T> {
|
||||
withLayout(layout: TLayout): ContainerBuilder<T, TLayout, TItemLayout>;
|
||||
withItems(components: Array<Component>, itemLayout ?: TItemLayout): ContainerBuilder<T, TLayout, TItemLayout>;
|
||||
}
|
||||
|
||||
export interface FlexBuilder extends ContainerBuilder<FlexContainer, FlexLayout, FlexItemLayout> {
|
||||
|
||||
}
|
||||
|
||||
export interface Component {
|
||||
readonly id: string;
|
||||
|
||||
/**
|
||||
* Sends any updated properties of the component to the UI
|
||||
*
|
||||
* @returns {Thenable<boolean>} Thenable that completes once the update
|
||||
* has been applied in the UI
|
||||
* @memberof Component
|
||||
*/
|
||||
updateProperties(properties: { [key: string]: any }): Thenable<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A component that contains other components
|
||||
*/
|
||||
export interface Container<TLayout,TItemLayout> extends Component {
|
||||
/**
|
||||
* A copy of the child items array. This cannot be added to directly -
|
||||
* components must be created using the create methods instead
|
||||
*/
|
||||
readonly items: Component[];
|
||||
|
||||
/**
|
||||
* Removes all child items from this container
|
||||
*/
|
||||
clearItems(): void;
|
||||
/**
|
||||
* Creates a collection of child components and adds them all to this container
|
||||
*
|
||||
* @param itemConfigs the definitions
|
||||
* @param {*} [itemLayout] Optional layout for the child items
|
||||
*/
|
||||
addItems(itemConfigs: Array<Component>, itemLayout ?: TItemLayout): void;
|
||||
|
||||
/**
|
||||
* Creates a child component and adds it to this container.
|
||||
*
|
||||
* @param {Component} component the component to be added
|
||||
* @param {*} [itemLayout] Optional layout for this child item
|
||||
*/
|
||||
addItem(component: Component, itemLayout ?: TItemLayout): void;
|
||||
|
||||
/**
|
||||
* Defines the layout for this container
|
||||
*
|
||||
* @param {TLayout} layout object
|
||||
*/
|
||||
setLayout(layout: TLayout): void;
|
||||
}
|
||||
|
||||
export interface NavContainer extends Container<any, any> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The config for a FlexBox-based container. This supports easy
|
||||
* addition of content to a container with a flexible layout
|
||||
* and use of space.
|
||||
*/
|
||||
export interface FlexLayout {
|
||||
/**
|
||||
* Matches the flex-flow CSS property and its available values.
|
||||
* To layout as a vertical view use "column", and for horizontal
|
||||
* use "row".
|
||||
*/
|
||||
flexFlow?: string;
|
||||
/**
|
||||
* Matches the justify-content CSS property.
|
||||
*/
|
||||
justifyContent?: string;
|
||||
}
|
||||
|
||||
export interface FlexItemLayout {
|
||||
/**
|
||||
* Matches the order CSS property and its available values.
|
||||
*/
|
||||
order?: number;
|
||||
/**
|
||||
* Matches the flex CSS property and its available values.
|
||||
* Default is "0 1 auto".
|
||||
*/
|
||||
flex?: string;
|
||||
}
|
||||
|
||||
export interface FlexContainer extends Container<FlexLayout, FlexItemLayout> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes an action to be shown in the UI, with a user-readable label
|
||||
* and a callback to execute the action
|
||||
*/
|
||||
export interface ActionDescriptor {
|
||||
/**
|
||||
* User-visible label to display
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* ID of the task to be called when this is clicked on.
|
||||
* These should be registered using the {tasks.registerTask} API.
|
||||
*/
|
||||
taskId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties representing the card component, can be used
|
||||
* when using ModelBuilder to create the comopnent
|
||||
*/
|
||||
export interface CardProperties {
|
||||
label: string;
|
||||
value?: string;
|
||||
actions?: ActionDescriptor[];
|
||||
}
|
||||
|
||||
export interface CardComponent extends Component {
|
||||
label: string;
|
||||
value: string;
|
||||
actions?: ActionDescriptor[];
|
||||
}
|
||||
|
||||
export interface WidgetComponent extends Component {
|
||||
widgetId: string;
|
||||
}
|
||||
|
||||
export interface WebviewComponent extends Component {
|
||||
webviewId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A view backed by a model provided by an extension.
|
||||
* This model contains enough information to lay out the view
|
||||
*/
|
||||
export interface ModelView {
|
||||
/**
|
||||
* Raised when the view closed.
|
||||
*/
|
||||
readonly onClosed: vscode.Event<any>;
|
||||
|
||||
/**
|
||||
* The connection info for the dashboard the webview exists on
|
||||
*/
|
||||
readonly connection: connection.Connection;
|
||||
|
||||
/**
|
||||
* The info on the server for the dashboard
|
||||
*/
|
||||
readonly serverInfo: ServerInfo;
|
||||
|
||||
/**
|
||||
* The model backing the model-based view
|
||||
*/
|
||||
readonly modelBuilder: ModelBuilder;
|
||||
|
||||
/**
|
||||
* Initializes the model with a root component definition.
|
||||
* Once this has been done, the components will be laid out in the UI and
|
||||
* can be accessed and altered as needed.
|
||||
*/
|
||||
initializeModel<T extends Component>(root: T): Thenable<void>;
|
||||
}
|
||||
|
||||
export namespace dashboard {
|
||||
/**
|
||||
* Register a provider for a model-view widget
|
||||
*/
|
||||
export function registerModelViewProvider(widgetId: string, handler: (view: ModelView) => void): void;
|
||||
}
|
||||
}
|
||||
@@ -62,3 +62,24 @@ export enum ScriptOperation {
|
||||
Execute = 5,
|
||||
Alter = 6
|
||||
}
|
||||
|
||||
export enum ModelComponentTypes {
|
||||
NavContainer,
|
||||
FlexContainer,
|
||||
Card,
|
||||
DashboardWidget,
|
||||
DashboardWebview
|
||||
}
|
||||
|
||||
export interface IComponentShape {
|
||||
type: ModelComponentTypes;
|
||||
id: string;
|
||||
properties?: { [key: string]: any };
|
||||
layout?: any;
|
||||
itemConfigs?: IItemConfig[];
|
||||
}
|
||||
|
||||
export interface IItemConfig {
|
||||
componentShape: IComponentShape;
|
||||
config: any;
|
||||
}
|
||||
|
||||
304
src/sql/workbench/api/node/extHostModelView.ts
Normal file
304
src/sql/workbench/api/node/extHostModelView.ts
Normal file
@@ -0,0 +1,304 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
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 } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
class ModelBuilderImpl implements sqlops.ModelBuilder {
|
||||
private nextComponentId: number;
|
||||
|
||||
constructor(private readonly _proxy: MainThreadModelViewShape, private readonly _handle: number) {
|
||||
this.nextComponentId = 0;
|
||||
}
|
||||
|
||||
navContainer(): sqlops.ContainerBuilder<sqlops.NavContainer, any, any> {
|
||||
let id = this.getNextComponentId();
|
||||
return new ContainerBuilderImpl(this._proxy, this._handle, ModelComponentTypes.NavContainer, id);
|
||||
}
|
||||
|
||||
flexContainer(): sqlops.FlexBuilder {
|
||||
let id = this.getNextComponentId();
|
||||
return new ContainerBuilderImpl<sqlops.FlexContainer, sqlops.FlexLayout, sqlops.FlexItemLayout>(this._proxy, this._handle, ModelComponentTypes.FlexContainer, id);
|
||||
}
|
||||
|
||||
card(): sqlops.ComponentBuilder<sqlops.CardComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
return new ComponentBuilderImpl(new CardWrapper(this._proxy, this._handle, id));
|
||||
}
|
||||
|
||||
dashboardWidget(widgetId: string): sqlops.ComponentBuilder<sqlops.WidgetComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
return new ComponentBuilderImpl<sqlops.WidgetComponent>(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWidget, id));
|
||||
}
|
||||
|
||||
dashboardWebview(webviewId: string): sqlops.ComponentBuilder<sqlops.WebviewComponent> {
|
||||
let id = this.getNextComponentId();
|
||||
return new ComponentBuilderImpl(new ComponentWrapper(this._proxy, this._handle, ModelComponentTypes.DashboardWebview, id));
|
||||
}
|
||||
|
||||
private getNextComponentId(): string {
|
||||
return `component${this._handle}_${this.nextComponentId++}`;
|
||||
}
|
||||
}
|
||||
|
||||
class ComponentBuilderImpl<T extends sqlops.Component> implements sqlops.ComponentBuilder<T> {
|
||||
|
||||
constructor(protected _component: ComponentWrapper) {
|
||||
}
|
||||
|
||||
component(): T {
|
||||
return <T><any>this._component;
|
||||
}
|
||||
|
||||
withProperties<U>(properties: U): sqlops.ComponentBuilder<T> {
|
||||
this._component.properties = properties;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class GenericComponentBuilder<T extends sqlops.Component> extends ComponentBuilderImpl<T> {
|
||||
constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) {
|
||||
super(new ComponentWrapper(proxy, handle, type, id));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class ContainerBuilderImpl<T extends sqlops.Component, TLayout, TItemLayout> extends ComponentBuilderImpl<T> implements sqlops.ContainerBuilder<T, TLayout, TItemLayout> {
|
||||
constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) {
|
||||
super(new ComponentWrapper(proxy, handle, type, id));
|
||||
}
|
||||
|
||||
withLayout(layout: TLayout): sqlops.ContainerBuilder<T, TLayout, TItemLayout> {
|
||||
this._component.layout = layout;
|
||||
return this;
|
||||
}
|
||||
|
||||
withItems(components: sqlops.Component[], itemLayout?: TItemLayout): sqlops.ContainerBuilder<T, TLayout, TItemLayout> {
|
||||
this._component.itemConfigs = components.map(item => {
|
||||
let componentWrapper = item as ComponentWrapper;
|
||||
return new InternalItemConfig(componentWrapper, itemLayout);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class InternalItemConfig {
|
||||
constructor(private _component: ComponentWrapper, public config: any) {}
|
||||
|
||||
public toIItemConfig(): IItemConfig {
|
||||
return {
|
||||
config: this.config,
|
||||
componentShape: this._component.toComponentShape()
|
||||
};
|
||||
}
|
||||
|
||||
public get component(): sqlops.Component {
|
||||
return this._component;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ComponentWrapper implements sqlops.Component {
|
||||
public properties: { [key: string]: any } = {};
|
||||
public layout: any;
|
||||
public itemConfigs: InternalItemConfig[];
|
||||
|
||||
private _onErrorEmitter = new Emitter<Error>();
|
||||
public readonly onError: vscode.Event<Error> = this._onErrorEmitter.event;
|
||||
|
||||
constructor(protected readonly _proxy: MainThreadModelViewShape,
|
||||
protected readonly _handle: number,
|
||||
protected _type: ModelComponentTypes,
|
||||
protected _id: string
|
||||
) {
|
||||
this.properties = {};
|
||||
this.itemConfigs = [];
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get type(): ModelComponentTypes {
|
||||
return this._type;
|
||||
}
|
||||
|
||||
public get items(): sqlops.Component[] {
|
||||
return this.itemConfigs.map(itemConfig => itemConfig.component);
|
||||
}
|
||||
|
||||
public toComponentShape(): IComponentShape {
|
||||
return <IComponentShape> {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
layout: this.layout,
|
||||
properties: this.properties,
|
||||
itemConfigs: this.itemConfigs ? this.itemConfigs.map<IItemConfig>(item => item.toIItemConfig()) : undefined
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public clearItems(): Thenable<void> {
|
||||
this.itemConfigs = [];
|
||||
return this._proxy.$clearContainer(this._handle, this.id);
|
||||
}
|
||||
|
||||
public addItems(items: Array<sqlops.Component>, itemLayout ?: any): void {
|
||||
for(let item of items) {
|
||||
this.addItem(item, itemLayout);
|
||||
}
|
||||
}
|
||||
|
||||
public addItem(item: sqlops.Component, itemLayout ?: any): void {
|
||||
let itemImpl = item as ComponentWrapper;
|
||||
if (!itemImpl) {
|
||||
throw new Error(nls.localize('unknownComponentType', 'Unkown component type. Must use ModelBuilder to create objects'));
|
||||
}
|
||||
let config = new InternalItemConfig(itemImpl, itemLayout);
|
||||
this.itemConfigs.push(config);
|
||||
this._proxy.$addToContainer(this._handle, this.id, config.toIItemConfig()).then(undefined, this.handleError);
|
||||
}
|
||||
|
||||
public setLayout(layout: any): Thenable<void> {
|
||||
return this._proxy.$setLayout(this._handle, this.id, layout);
|
||||
}
|
||||
|
||||
public updateProperties(): Thenable<boolean> {
|
||||
return this.notifyPropertyChanged();
|
||||
}
|
||||
|
||||
protected notifyPropertyChanged(): Thenable<boolean> {
|
||||
return this._proxy.$setProperties(this._handle, this._id, this.properties).then(() => true);
|
||||
}
|
||||
|
||||
protected setProperty(key: string, value: any): Thenable<boolean> {
|
||||
if (!this.properties[key] || this.properties[key] !== value) {
|
||||
// Only notify the front end if a value has been updated
|
||||
this.properties[key] = value;
|
||||
return this.notifyPropertyChanged();
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private handleError(err: Error): void {
|
||||
this._onErrorEmitter.fire(err);
|
||||
}
|
||||
}
|
||||
|
||||
class ContainerWrapper<T, U> extends ComponentWrapper implements sqlops.Container<T, U> {
|
||||
|
||||
constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) {
|
||||
super(proxy, handle, type, id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class CardWrapper extends ComponentWrapper implements sqlops.CardComponent {
|
||||
|
||||
constructor(proxy: MainThreadModelViewShape, handle: number, id: string) {
|
||||
super(proxy, handle, ModelComponentTypes.Card, id);
|
||||
this.properties = {};
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this.properties['label'];
|
||||
}
|
||||
public set label(l: string) {
|
||||
this.setProperty('label', l);
|
||||
}
|
||||
public get value(): string {
|
||||
return this.properties['value'];
|
||||
}
|
||||
public set value(v: string) {
|
||||
this.setProperty('value', v);
|
||||
}
|
||||
public get actions(): sqlops.ActionDescriptor[] {
|
||||
return this.properties['actions'];
|
||||
}
|
||||
public set actions(a: sqlops.ActionDescriptor[]) {
|
||||
this.setProperty('actions', a);
|
||||
}
|
||||
}
|
||||
|
||||
class ModelViewImpl implements sqlops.ModelView {
|
||||
|
||||
public onClosedEmitter = new Emitter<any>();
|
||||
|
||||
private _modelBuilder: sqlops.ModelBuilder;
|
||||
|
||||
constructor(
|
||||
private readonly _proxy: MainThreadModelViewShape,
|
||||
private readonly _handle: number,
|
||||
private readonly _connection: sqlops.connection.Connection,
|
||||
private readonly _serverInfo: sqlops.ServerInfo
|
||||
) {
|
||||
this._modelBuilder = new ModelBuilderImpl(this._proxy, this._handle);
|
||||
}
|
||||
|
||||
public get onClosed(): vscode.Event<any> {
|
||||
return this.onClosedEmitter.event;
|
||||
}
|
||||
|
||||
public get connection(): sqlops.connection.Connection {
|
||||
return deepClone(this._connection);
|
||||
}
|
||||
|
||||
public get serverInfo(): sqlops.ServerInfo {
|
||||
return deepClone(this._serverInfo);
|
||||
}
|
||||
|
||||
public get modelBuilder(): sqlops.ModelBuilder {
|
||||
return this._modelBuilder;
|
||||
}
|
||||
|
||||
public initializeModel<T extends sqlops.Component>(component: T): Thenable<void> {
|
||||
let componentImpl = <any>component as ComponentWrapper;
|
||||
if (!componentImpl) {
|
||||
return Promise.reject(nls.localize('unknownConfig', 'Unkown component configuration, must use ModelBuilder to create a configuration object'));
|
||||
}
|
||||
return this._proxy.$initializeModel(this._handle, componentImpl.toComponentShape());
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostModelView implements ExtHostModelViewShape {
|
||||
private readonly _proxy: MainThreadModelViewShape;
|
||||
|
||||
private readonly _modelViews = new Map<number, ModelViewImpl>();
|
||||
private readonly _handlers = new Map<string, (view: sqlops.ModelView) => void>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(SqlMainContext.MainThreadModelView);
|
||||
}
|
||||
|
||||
$onClosed(handle: number): void {
|
||||
const view = this._modelViews.get(handle);
|
||||
view.onClosedEmitter.fire();
|
||||
this._modelViews.delete(handle);
|
||||
}
|
||||
|
||||
$registerProvider(widgetId: string, handler: (webview: sqlops.ModelView) => void): void {
|
||||
this._handlers.set(widgetId, handler);
|
||||
this._proxy.$registerProvider(widgetId);
|
||||
}
|
||||
|
||||
$registerWidget(handle: number, id: string, connection: sqlops.connection.Connection, serverInfo: sqlops.ServerInfo): void {
|
||||
let view = new ModelViewImpl(this._proxy, handle, connection, serverInfo);
|
||||
this._modelViews.set(handle, view);
|
||||
this._handlers.get(id)(view);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
import { MainThreadDashboardWebviewShape, SqlMainContext, ExtHostDashboardWebviewsShape, SqlExtHostContext } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { IDashboardWebviewService, IDashboardWebview } from 'sql/services/dashboardWebview/common/dashboardWebviewService';
|
||||
import { IDashboardViewService, IDashboardWebview } from 'sql/services/dashboard/common/dashboardViewService';
|
||||
|
||||
@extHostNamedCustomer(SqlMainContext.MainThreadDashboardWebview)
|
||||
export class MainThreadDashboardWebview implements MainThreadDashboardWebviewShape {
|
||||
@@ -20,10 +20,10 @@ export class MainThreadDashboardWebview implements MainThreadDashboardWebviewSha
|
||||
|
||||
constructor(
|
||||
context: IExtHostContext,
|
||||
@IDashboardWebviewService webviewService: IDashboardWebviewService
|
||||
@IDashboardViewService viewService: IDashboardViewService
|
||||
) {
|
||||
this._proxy = context.getProxy(SqlExtHostContext.ExtHostDashboardWebviews);
|
||||
webviewService.onRegisteredWebview(e => {
|
||||
viewService.onRegisteredWebview(e => {
|
||||
if (this.knownWidgets.includes(e.id)) {
|
||||
let handle = MainThreadDashboardWebview._handlePool++;
|
||||
this._dialogs.set(handle, e);
|
||||
@@ -36,7 +36,7 @@ export class MainThreadDashboardWebview implements MainThreadDashboardWebviewSha
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
$sendMessage(handle: number, message: string) {
|
||||
|
||||
75
src/sql/workbench/api/node/mainThreadModelView.ts
Normal file
75
src/sql/workbench/api/node/mainThreadModelView.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { MainThreadModelViewShape, SqlMainContext, ExtHostModelViewShape, SqlExtHostContext } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
import { IDashboardViewService } from 'sql/services/dashboard/common/dashboardViewService';
|
||||
import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IModelView } from 'sql/services/model/modelViewService';
|
||||
|
||||
|
||||
@extHostNamedCustomer(SqlMainContext.MainThreadModelView)
|
||||
export class MainThreadModelView implements MainThreadModelViewShape {
|
||||
|
||||
private static _handlePool = 0;
|
||||
private readonly _proxy: ExtHostModelViewShape;
|
||||
private readonly _dialogs = new Map<number, IModelView>();
|
||||
|
||||
private knownWidgets = new Array<string>();
|
||||
|
||||
constructor(
|
||||
context: IExtHostContext,
|
||||
@IDashboardViewService viewService: IDashboardViewService
|
||||
) {
|
||||
this._proxy = context.getProxy(SqlExtHostContext.ExtHostModelView);
|
||||
viewService.onRegisteredModelView(view => {
|
||||
if (this.knownWidgets.includes(view.id)) {
|
||||
let handle = MainThreadModelView._handlePool++;
|
||||
this._dialogs.set(handle, view);
|
||||
this._proxy.$registerWidget(handle, view.id, view.connection, view.serverInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
$registerProvider(id: string) {
|
||||
this.knownWidgets.push(id);
|
||||
}
|
||||
|
||||
$initializeModel(handle: number, rootComponent: IComponentShape): Thenable<void> {
|
||||
return this.execModelViewAction(handle, (modelView) => modelView.initializeModel(rootComponent));
|
||||
}
|
||||
|
||||
$clearContainer(handle: number, componentId: string): Thenable<void> {
|
||||
return this.execModelViewAction(handle, (modelView) => modelView.clearContainer(componentId));
|
||||
}
|
||||
|
||||
$addToContainer(handle: number, containerId: string, item: IItemConfig): Thenable<void> {
|
||||
return this.execModelViewAction(handle,
|
||||
(modelView) => modelView.addToContainer(containerId, item));
|
||||
}
|
||||
|
||||
$setLayout(handle: number, componentId: string, layout: any): Thenable<void> {
|
||||
return this.execModelViewAction(handle, (modelView) => modelView.setLayout(componentId, layout));
|
||||
}
|
||||
|
||||
$setProperties(handle: number, componentId: string, properties: { [key: string]: any; }): Thenable<void> {
|
||||
return this.execModelViewAction(handle, (modelView) => modelView.setProperties(componentId, properties));
|
||||
}
|
||||
|
||||
private execModelViewAction<T>(handle: number, action: (m: IModelView) => T): Thenable<T> {
|
||||
let modelView: IModelView = this._dialogs.get(handle);
|
||||
let result = action(modelView);
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration
|
||||
import { ExtHostModalDialogs } from 'sql/workbench/api/node/extHostModalDialog';
|
||||
import { ExtHostTasks } from 'sql/workbench/api/node/extHostTasks';
|
||||
import { ExtHostDashboardWebviews } from 'sql/workbench/api/node/extHostDashboardWebview';
|
||||
import { ExtHostModelView } from 'sql/workbench/api/node/extHostModelView';
|
||||
import { ExtHostConnectionManagement } from 'sql/workbench/api/node/extHostConnectionManagement';
|
||||
import { ExtHostDashboard } from 'sql/workbench/api/node/extHostDashboard';
|
||||
import { ExtHostObjectExplorer } from 'sql/workbench/api/node/extHostObjectExplorer';
|
||||
@@ -61,8 +62,10 @@ export function createApiFactory(
|
||||
const extHostModalDialogs = rpcProtocol.set(SqlExtHostContext.ExtHostModalDialogs, new ExtHostModalDialogs(rpcProtocol));
|
||||
const extHostTasks = rpcProtocol.set(SqlExtHostContext.ExtHostTasks, new ExtHostTasks(rpcProtocol, logService));
|
||||
const extHostWebviewWidgets = rpcProtocol.set(SqlExtHostContext.ExtHostDashboardWebviews, new ExtHostDashboardWebviews(rpcProtocol));
|
||||
const extHostModelView = rpcProtocol.set(SqlExtHostContext.ExtHostModelView, new ExtHostModelView(rpcProtocol));
|
||||
const extHostDashboard = rpcProtocol.set(SqlExtHostContext.ExtHostDashboard, new ExtHostDashboard(rpcProtocol));
|
||||
|
||||
|
||||
return {
|
||||
vsCodeFactory: vsCodeFactory,
|
||||
sqlopsFactory: function (extension: IExtensionDescription): typeof sqlops {
|
||||
@@ -297,6 +300,9 @@ export function createApiFactory(
|
||||
const dashboard = {
|
||||
registerWebviewProvider(widgetId: string, handler: (webview: sqlops.DashboardWebview) => void) {
|
||||
extHostWebviewWidgets.$registerProvider(widgetId, handler);
|
||||
},
|
||||
registerModelViewProvider(widgetId: string, handler: (view: sqlops.ModelView) => void): void {
|
||||
extHostModelView.$registerProvider(widgetId, handler);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import 'sql/workbench/api/node/mainThreadResourceProvider';
|
||||
import 'sql/workbench/api/electron-browser/mainThreadTasks';
|
||||
import 'sql/workbench/api/electron-browser/mainThreadDashboard';
|
||||
import 'sql/workbench/api/node/mainThreadDashboardWebview';
|
||||
import 'sql/workbench/api/node/mainThreadModelView';
|
||||
import './mainThreadAccountManagement';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import * as sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { ITaskHandlerDescription } from 'sql/platform/tasks/common/tasks';
|
||||
import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
export abstract class ExtHostAccountManagementShape {
|
||||
$autoOAuthCancelled(handle: number): Thenable<void> { throw ni(); }
|
||||
@@ -444,6 +445,7 @@ export const SqlMainContext = {
|
||||
MainThreadModalDialog: createMainId<MainThreadModalDialogShape>('MainThreadModalDialog'),
|
||||
MainThreadTasks: createMainId<MainThreadTasksShape>('MainThreadTasks'),
|
||||
MainThreadDashboardWebview: createMainId<MainThreadDashboardWebviewShape>('MainThreadDashboardWebview'),
|
||||
MainThreadModelView: createMainId<MainThreadModelViewShape>('MainThreadModelView'),
|
||||
MainThreadDashboard: createMainId<MainThreadDashboardShape>('MainThreadDashboard')
|
||||
};
|
||||
|
||||
@@ -458,6 +460,7 @@ export const SqlExtHostContext = {
|
||||
ExtHostModalDialogs: createExtId<ExtHostModalDialogsShape>('ExtHostModalDialogs'),
|
||||
ExtHostTasks: createExtId<ExtHostTasksShape>('ExtHostTasks'),
|
||||
ExtHostDashboardWebviews: createExtId<ExtHostDashboardWebviewsShape>('ExtHostDashboardWebviews'),
|
||||
ExtHostModelView: createExtId<ExtHostModelViewShape>('ExtHostModelView'),
|
||||
ExtHostDashboard: createExtId<ExtHostDashboardShape>('ExtHostDashboard')
|
||||
};
|
||||
|
||||
@@ -507,6 +510,21 @@ export interface MainThreadDashboardWebviewShape extends IDisposable {
|
||||
$setHtml(handle: number, value: string);
|
||||
}
|
||||
|
||||
export interface ExtHostModelViewShape {
|
||||
$registerProvider(widgetId: string, handler: (webview: sqlops.ModelView) => void): void;
|
||||
$onClosed(handle: number): void;
|
||||
$registerWidget(handle: number, id: string, connection: sqlops.connection.Connection, serverInfo: sqlops.ServerInfo): void;
|
||||
}
|
||||
|
||||
export interface MainThreadModelViewShape extends IDisposable {
|
||||
$registerProvider(id: string): void;
|
||||
$initializeModel(handle: number, rootComponent: IComponentShape): Thenable<void>;
|
||||
$clearContainer(handle: number, componentId: string): Thenable<void>;
|
||||
$addToContainer(handle: number, containerId: string, item: IItemConfig): Thenable<void>;
|
||||
$setLayout(handle: number, componentId: string, layout: any): Thenable<void>;
|
||||
$setProperties(handle: number, componentId: string, properties: { [key: string]: any }): Thenable<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostObjectExplorerShape {
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user