diff --git a/src/sql/parts/dashboard/common/dashboardPage.component.html b/src/sql/parts/dashboard/common/dashboardPage.component.html
index 0845ee98e1..dac1282f03 100644
--- a/src/sql/parts/dashboard/common/dashboardPage.component.html
+++ b/src/sql/parts/dashboard/common/dashboardPage.component.html
@@ -16,6 +16,8 @@
+
+
diff --git a/src/sql/parts/dashboard/common/dashboardPage.component.ts b/src/sql/parts/dashboard/common/dashboardPage.component.ts
index 4e174acaa2..f37849b30f 100644
--- a/src/sql/parts/dashboard/common/dashboardPage.component.ts
+++ b/src/sql/parts/dashboard/common/dashboardPage.component.ts
@@ -24,6 +24,7 @@ import { AngularEventType, IAngularEvent } from 'sql/services/angularEventing/an
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
import { error } from 'sql/base/common/log';
import { WIDGETS_TABS } from 'sql/parts/dashboard/tabs/dashboardWidgetTab.contribution';
+import { GRID_TABS } from 'sql/parts/dashboard/tabs/dashboardGridTab.contribution';
import { WEBVIEW_TABS } from 'sql/parts/dashboard/tabs/dashboardWebviewTab.contribution';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -255,7 +256,7 @@ export abstract class DashboardPage extends Disposable implements OnDestroy {
}
let key = Object.keys(v.content)[0];
- if (key === WIDGETS_TABS) {
+ if (key === WIDGETS_TABS || key === GRID_TABS) {
let configs = Object.values(v.content)[0];
this._configModifiers.forEach(cb => {
configs = cb.apply(this, [configs]);
@@ -263,7 +264,12 @@ export abstract class DashboardPage extends Disposable implements OnDestroy {
this._gridModifiers.forEach(cb => {
configs = cb.apply(this, [configs]);
});
- return { id: v.id, title: v.title, content: { 'widgets-tab': configs }, alwaysShow: v.alwaysShow };
+ if (key === WIDGETS_TABS) {
+ return { id: v.id, title: v.title, content: { 'widgets-tab': configs }, alwaysShow: v.alwaysShow };
+
+ } else {
+ return { id: v.id, title: v.title, content: { 'grid-tab': configs }, alwaysShow: v.alwaysShow };
+ }
}
return v;
}).map(v => {
diff --git a/src/sql/parts/dashboard/dashboard.module.ts b/src/sql/parts/dashboard/dashboard.module.ts
index f01373543a..ad0500c1b6 100644
--- a/src/sql/parts/dashboard/dashboard.module.ts
+++ b/src/sql/parts/dashboard/dashboard.module.ts
@@ -28,12 +28,13 @@ import { ComponentHostDirective } from 'sql/parts/dashboard/common/componentHost
import { DashboardComponent, DASHBOARD_SELECTOR } from 'sql/parts/dashboard/dashboard.component';
import { DashboardWidgetWrapper } from 'sql/parts/dashboard/common/dashboardWidgetWrapper.component';
import { DashboardWidgetTab } from 'sql/parts/dashboard/tabs/dashboardWidgetTab.component';
+import { DashboardGridTab } from 'sql/parts/dashboard/tabs/dashboardGridTab.component';
import { DashboardWebviewTab } from 'sql/parts/dashboard/tabs/dashboardWebviewTab.component';
import { WidgetContent } from 'sql/parts/dashboard/contents/widgetContent.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';
-let baseComponents = [DashboardComponent, DashboardWidgetWrapper, DashboardWebviewTab, DashboardWidgetTab, WidgetContent, WebviewContent, ComponentHostDirective, BreadcrumbComponent];
+let baseComponents = [DashboardComponent, DashboardWidgetWrapper, DashboardWebviewTab, DashboardWidgetTab, DashboardGridTab, WidgetContent, WebviewContent, ComponentHostDirective, BreadcrumbComponent];
/* Panel */
import { PanelModule } from 'sql/base/browser/ui/panel/panel.module';
diff --git a/src/sql/parts/dashboard/pages/dashboardPageContribution.ts b/src/sql/parts/dashboard/pages/dashboardPageContribution.ts
index 6e8814f222..220d96428b 100644
--- a/src/sql/parts/dashboard/pages/dashboardPageContribution.ts
+++ b/src/sql/parts/dashboard/pages/dashboardPageContribution.ts
@@ -83,6 +83,55 @@ export function generateDashboardWidgetSchema(type?: 'database' | 'server', exte
};
}
+export function generateDashboardGridLayoutSchema(type?: 'database' | 'server', extension?: boolean): IJSONSchema {
+ let schemas;
+ if (extension) {
+ let extensionSchemas = type === 'server' ? widgetRegistry.serverWidgetSchema.extensionProperties : type === 'database' ? widgetRegistry.databaseWidgetSchema.extensionProperties : widgetRegistry.allSchema.extensionProperties;
+ schemas = type === 'server' ? widgetRegistry.serverWidgetSchema.properties : type === 'database' ? widgetRegistry.databaseWidgetSchema.properties : widgetRegistry.allSchema.properties;
+ schemas = mixin(schemas, extensionSchemas, true);
+ } else {
+ schemas = type === 'server' ? widgetRegistry.serverWidgetSchema.properties : type === 'database' ? widgetRegistry.databaseWidgetSchema.properties : widgetRegistry.allSchema.properties;
+ }
+
+ return {
+ type: 'object',
+ properties: {
+ name: {
+ type: 'string'
+ },
+ icon: {
+ type: 'string'
+ },
+ provider: {
+ anyOf: [
+ {
+ type: 'string'
+ },
+ {
+ type: 'array',
+ items: {
+ type: 'string'
+ }
+ }
+ ]
+ },
+ edition: {
+ anyOf: [
+ {
+ type: 'number'
+ },
+ {
+ type: 'array',
+ items: {
+ type: 'number'
+ }
+ }
+ ]
+ }
+ }
+ };
+}
+
export function generateDashboardTabSchema(type?: 'database' | 'server'): IJSONSchema {
return {
type: 'object',
diff --git a/src/sql/parts/dashboard/tabs/dashboardGridTab.component.html b/src/sql/parts/dashboard/tabs/dashboardGridTab.component.html
new file mode 100644
index 0000000000..b68f52a9ee
--- /dev/null
+++ b/src/sql/parts/dashboard/tabs/dashboardGridTab.component.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+ |
+
+
+
+
+
diff --git a/src/sql/parts/dashboard/tabs/dashboardGridTab.component.ts b/src/sql/parts/dashboard/tabs/dashboardGridTab.component.ts
new file mode 100644
index 0000000000..3d995e4be0
--- /dev/null
+++ b/src/sql/parts/dashboard/tabs/dashboardGridTab.component.ts
@@ -0,0 +1,191 @@
+/*---------------------------------------------------------------------------------------------
+ * 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!./dashboardGridTab';
+
+import { Component, Inject, Input, forwardRef, ViewChild, ElementRef, ViewChildren, QueryList, OnDestroy, ChangeDetectorRef, EventEmitter, OnChanges } from '@angular/core';
+import { NgGridConfig, NgGrid, NgGridItem } from 'angular2-grid';
+
+import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
+import { TabConfig, WidgetConfig } from 'sql/parts/dashboard/common/dashboardWidget';
+import { DashboardWidgetWrapper } from 'sql/parts/dashboard/common/dashboardWidgetWrapper.component';
+import { subscriptionToDisposable } from 'sql/base/common/lifecycle';
+import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
+
+import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
+import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
+import * as objects from 'vs/base/common/objects';
+import Event, { Emitter } from 'vs/base/common/event';
+import { concat } from 'rxjs/operator/concat';
+
+export interface GridCellConfig {
+ id?: string;
+ row?: number;
+ col?: number;
+ colspan?: number;
+ rowspan?: number;
+}
+
+export interface GridWidgetConfig extends GridCellConfig, WidgetConfig {
+}
+
+export interface GridWebviewConfig extends GridCellConfig {
+ webview: {
+ id?: string;
+ };
+}
+
+@Component({
+ selector: 'dashboard-grid-tab',
+ templateUrl: decodeURI(require.toUrl('sql/parts/dashboard/tabs/dashboardGridTab.component.html')),
+ providers: [{ provide: DashboardTab, useExisting: forwardRef(() => DashboardGridTab) }]
+})
+export class DashboardGridTab extends DashboardTab implements OnDestroy, OnChanges {
+ @Input() private tab: TabConfig;
+ private _contents: GridCellConfig[];
+ private _onResize = new Emitter();
+ public readonly onResize: Event = this._onResize.event;
+
+ protected SKELETON_WIDTH = 5;
+
+ protected rows: number[];
+ protected cols: number[];
+
+ protected getContent(row: number, col: number): GridCellConfig {
+ let widget = this._contents.filter(w => w.row === row && w.col === col);
+ return widget ? widget[0] : undefined;
+ }
+
+ protected getWidgetContent(row: number, col: number): GridWidgetConfig {
+ let content = this.getContent(row, col);
+ if (content) {
+ let widgetConfig = content;
+ if (widgetConfig && widgetConfig.widget) {
+ return widgetConfig;
+ }
+ }
+ return undefined;
+ }
+
+ protected getWebviewContent(row: number, col: number): GridWebviewConfig {
+ let content = this.getContent(row, col);
+ if (content) {
+ let webviewConfig = content;
+ if (webviewConfig && webviewConfig.webview) {
+ return webviewConfig;
+ }
+ }
+ return undefined;
+ }
+
+
+ protected isWidget(row: number, col: number): boolean {
+ let widgetConfig = this.getWidgetContent(row, col);
+ return widgetConfig !== undefined;
+ }
+
+ protected isWebview(row: number, col: number): boolean {
+ let webview = this.getWebviewContent(row, col);
+ return webview !== undefined;
+ }
+
+ protected getWebviewId(row: number, col: number): string {
+ let widgetConfig = this.getWebviewContent(row, col);
+ if (widgetConfig && widgetConfig.webview) {
+ return widgetConfig.webview.id;
+ }
+ return undefined;
+ }
+
+ protected getColspan(row: number, col: number): number {
+ let content = this.getContent(row, col);
+ let colspan: number = 1;
+ if (content && content.colspan) {
+ colspan = content.colspan;
+ }
+ return colspan;
+ }
+
+ protected getRowspan(row: number, col: number): number {
+ let content = this.getContent(row, col);
+ if (content && (content.rowspan)) {
+ return content.rowspan;
+ } else {
+ return 1;
+ }
+ }
+
+ @ViewChildren(DashboardWidgetWrapper) private _widgets: QueryList;
+ constructor(
+ @Inject(forwardRef(() => DashboardServiceInterface)) protected dashboardService: DashboardServiceInterface,
+ @Inject(forwardRef(() => ElementRef)) protected _el: ElementRef,
+ @Inject(forwardRef(() => ChangeDetectorRef)) protected _cd: ChangeDetectorRef
+ ) {
+ super();
+ }
+
+ protected init() {
+ }
+
+ ngOnChanges() {
+ if (this.tab.content) {
+ this._contents = Object.values(this.tab.content)[0];
+ this._contents.forEach(widget => {
+ if (!widget.row) {
+ widget.row = 0;
+ }
+ if (!widget.col) {
+ widget.col = 0;
+ }
+ if (!widget.colspan) {
+ widget.colspan = 1;
+ }
+ if (!widget.rowspan) {
+ widget.rowspan = 1;
+ }
+ });
+ this.rows = this.createIndexes(this._contents.map(w => w.row));
+ this.cols = this.createIndexes(this._contents.map(w => w.col));
+
+ this._cd.detectChanges();
+ }
+ }
+
+ private createIndexes(indexes: number[]) {
+ let max = Math.max(...indexes) + 1;
+ return Array(max).fill(0).map((x, i) => i);
+ }
+
+ ngOnDestroy() {
+ this.dispose();
+ }
+
+ public get id(): string {
+ return this.tab.id;
+ }
+
+ public get editable(): boolean {
+ return this.tab.editable;
+ }
+
+ public layout() {
+ if (this._widgets) {
+ this._widgets.forEach(item => {
+ item.layout();
+ });
+ }
+ }
+
+ public refresh(): void {
+ if (this._widgets) {
+ this._widgets.forEach(item => {
+ item.refresh();
+ });
+ }
+ }
+
+ public enableEdit(): void {
+ }
+}
diff --git a/src/sql/parts/dashboard/tabs/dashboardGridTab.contribution.ts b/src/sql/parts/dashboard/tabs/dashboardGridTab.contribution.ts
new file mode 100644
index 0000000000..3f5cd4233c
--- /dev/null
+++ b/src/sql/parts/dashboard/tabs/dashboardGridTab.contribution.ts
@@ -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 { generateDashboardGridLayoutSchema } from 'sql/parts/dashboard/pages/dashboardPageContribution';
+import { registerTabContent } from 'sql/platform/dashboard/common/dashboardRegistry';
+
+export const GRID_TABS = 'grid-tab';
+
+let gridContentsSchema: IJSONSchema = {
+ type: 'array',
+ description: nls.localize('dashboard.gridtab.content.items', "The list of widgets or webviews that will be displayed in this tab."),
+ items: generateDashboardGridLayoutSchema(undefined, true)
+};
+
+registerTabContent(GRID_TABS, gridContentsSchema);
diff --git a/src/sql/parts/dashboard/tabs/dashboardGridTab.css b/src/sql/parts/dashboard/tabs/dashboardGridTab.css
new file mode 100644
index 0000000000..bfd9b141f0
--- /dev/null
+++ b/src/sql/parts/dashboard/tabs/dashboardGridTab.css
@@ -0,0 +1,25 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+dashboard-tab {
+ height: auto;
+ width: auto;
+}
+
+.grid-table {
+ border-spacing: 5px;
+}
+
+.grid-table-row {
+ width: auto;
+ clear: both;
+}
+
+.table-cell {
+ height: 270px;
+ min-width: 300px;
+ vertical-align: top;
+ padding: 7px;
+}
diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts
index 7d0aff5109..22f4a16b22 100644
--- a/src/vs/workbench/workbench.main.ts
+++ b/src/vs/workbench/workbench.main.ts
@@ -162,6 +162,7 @@ import 'sql/parts/dashboard/dashboardConfig.contribution';
/* Tabs */
import 'sql/parts/dashboard/tabs/dashboardWebviewTab.contribution';
import 'sql/parts/dashboard/tabs/dashboardWidgetTab.contribution';
+import 'sql/parts/dashboard/tabs/dashboardGridTab.contribution';
import 'sql/parts/dashboard/common/dashboardTab.contribution';
/* Tasks */
import 'sql/workbench/common/actions.contribution';