mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge dashboardCommandCenter to master (#697)
* Initial work of adding tab in the dashboard (#526) * refactor dashboard to have the home tab * formatting * fix grid layout issue * fix initailize issue in database dashboard * Add action bar to the panel and add close tab to the dashboard (#562) * add action bar to the panel and add close tab to the dashboard * formatting * Tab contribution (#564) * added contrib * disabled edit for extensions; fixed new name for insights contrib * fix merge issue * move file * formatting * fix builds * moving imports * Expand on tab contrib (#581) * added contrib * disabled edit for extensions; fixed new name for insights contrib * fix merge issue * move file * formatting * fix builds * adding to contrib * updated contrib * format * moving imports * updated contribution to map to current design * implemented actually using provider and edition filtering * Refactor and fix issues in close tab and add the placeholder for pin tab (#588) * refactor and fix issues in close tab and add the placeholder for pin tab * formatting * remove the redundant code * add clear all tabs in dashboard page init * Initial work for adding a feature tab dialog (#594) * initial work for add new dashboard tab * formatting * fix add panel action issue * fix breaking change * fix issues and tab and panels * formatting * minor fix * address comments * Add tab status to add extension tab dialog (#610) * add tab status to add extension tab dialog * add tab status to add extension tab dialog * rename add feature tab action * address comments * Webview widget (#618) * getting closer * webview widget now works * fix problem with rerendering webview * formatting * ensure that webview only shows up for extensions * formatting * comments * fix more compile issues * Change dashboard page init (#640) * changed init of serverpage * formatting * Webview tab (#638) * getting closer * webview widget now works * fix problem with rerendering webview * formatting * ensure that webview only shows up for extensions * formatting * comments * fix more compile issues * refacting stuff * added inital webview tab * piped through messaging and tested * Implement pin/unpin feature and always on tabs (#629) * implement pin/unpin feature * fix issue where insight can't be loaded after reopen * fix tab look and feel * implement always show tabs * make AddFeatureTabAction to track always show and pinned tabs * formatting * make dashboard tabs looks like the UX design * load always show before pinned tab * fix regression in panel for restore and connection dialog * fix merge conflict * don't worry about no widgets if its a webview (#656) * expose the dashboard server info when a webview is rendering (#644) * Fix few issues in dashboard command center (#655) * fix reloading insight wigets and create new tab when there is no extension * show possible tabIDs in the setting file * formatting * address comment * fix import name * fixes problem with size of webview widget being wrong (#654) * Refactor tab contribution to support content type (#685) * refactor tab contribution to support content type * formatting * address comment * fix rendering tab issue (#694) * Add layout option to panel for supporting horizontal and vertical navigation bar (#700) * Add left navigation panel for inner tab in the dashboard * add layout option in panel * remove panel option in dashboard Page
This commit is contained in:
@@ -7,8 +7,12 @@ import * as nls from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IAngularEventingService, AngularEventType } from 'sql/services/angularEventing/angularEventingService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
import { IAngularEventingService, AngularEventType, IAngularEvent } from 'sql/services/angularEventing/angularEventingService';
|
||||
import { INewDashboardTabDialogService } from 'sql/parts/dashboard/newDashboardTabDialog/interface';
|
||||
import { IDashboardTab } from 'sql/platform/dashboard/common/dashboardRegistry';
|
||||
import { toDisposableSubscription } from 'sql/parts/common/rxjsUtils';
|
||||
export class EditDashboardAction extends Action {
|
||||
|
||||
private static readonly ID = 'editDashboard';
|
||||
@@ -111,3 +115,85 @@ export class DeleteWidgetAction extends Action {
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class PinUnpinTabAction extends Action {
|
||||
private static readonly ID = 'pinTab';
|
||||
private static readonly PINLABEL = nls.localize('clickToUnpin', "Click to unpin");
|
||||
private static readonly UNPINLABEL = nls.localize('clickToPin', "Click to pin");
|
||||
private static readonly PINICON = 'pin';
|
||||
private static readonly UNPINICON = 'unpin';
|
||||
|
||||
constructor(
|
||||
private _tabId: string,
|
||||
private _uri: string,
|
||||
private _isPinned: boolean,
|
||||
@IAngularEventingService private angularEventService: IAngularEventingService
|
||||
) {
|
||||
super(PinUnpinTabAction.ID, PinUnpinTabAction.PINLABEL, PinUnpinTabAction.PINICON);
|
||||
this.updatePinStatus();
|
||||
}
|
||||
|
||||
private updatePinStatus() {
|
||||
if (this._isPinned) {
|
||||
this.label = PinUnpinTabAction.PINLABEL;
|
||||
this.class = PinUnpinTabAction.PINICON;
|
||||
} else {
|
||||
this.label = PinUnpinTabAction.UNPINLABEL;
|
||||
this.class = PinUnpinTabAction.UNPINICON;
|
||||
}
|
||||
}
|
||||
|
||||
public run(): TPromise<boolean> {
|
||||
this._isPinned = !this._isPinned;
|
||||
this.updatePinStatus();
|
||||
this.angularEventService.sendAngularEvent(this._uri, AngularEventType.PINUNPIN_TAB, { tabId: this._tabId, isPinned: this._isPinned });
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class AddFeatureTabAction extends Action {
|
||||
private static readonly ID = 'openInstalledFeatures';
|
||||
private static readonly LABEL = nls.localize('openInstalledFeatures', "Open installed features");
|
||||
private static readonly ICON = 'new';
|
||||
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private _dashboardTabs: Array<IDashboardTab>,
|
||||
private _openedTabs: Array<IDashboardTab>,
|
||||
private _uri: string,
|
||||
@INewDashboardTabDialogService private _newDashboardTabService: INewDashboardTabDialogService,
|
||||
@IAngularEventingService private _angularEventService: IAngularEventingService
|
||||
) {
|
||||
super(AddFeatureTabAction.ID, AddFeatureTabAction.LABEL, AddFeatureTabAction.ICON);
|
||||
this._disposables.push(toDisposableSubscription(this._angularEventService.onAngularEvent(this._uri, (event) => this.handleDashboardEvent(event))));
|
||||
}
|
||||
|
||||
run(): TPromise<boolean> {
|
||||
this._newDashboardTabService.showDialog(this._dashboardTabs, this._openedTabs, this._uri);
|
||||
return TPromise.as(true);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
this._disposables.forEach((item) => item.dispose());
|
||||
}
|
||||
|
||||
private handleDashboardEvent(event: IAngularEvent): void {
|
||||
switch (event.event) {
|
||||
case AngularEventType.NEW_TABS:
|
||||
let openedTabs = <IDashboardTab[]>event.payload.dashboardTabs;
|
||||
openedTabs.forEach(tab => {
|
||||
let existedTab = this._openedTabs.find(i => i === tab);
|
||||
if (!existedTab) {
|
||||
this._openedTabs.push(tab);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case AngularEventType.CLOSE_TAB:
|
||||
let index = this._openedTabs.findIndex(i => i.id === event.payload.id);
|
||||
this._openedTabs.splice(index, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,18 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
<div #scrollContainer style="height: 100%">
|
||||
<div #scrollable style="position: relative">
|
||||
<div #propertiesContainer>
|
||||
<div #scrollable style="position: relative; display: flex; flex-direction: column; height: 100%">
|
||||
<div style="flex: 0 0 auto" #propertiesContainer>
|
||||
<dashboard-widget-wrapper #properties *ngIf="propertiesWidget" [_config]="propertiesWidget" style="padding-left: 10px; padding-right: 10px; height: 90px; display: block">
|
||||
</dashboard-widget-wrapper>
|
||||
</div>
|
||||
<div [ngGrid]="gridConfig">
|
||||
<dashboard-widget-wrapper *ngFor="let widget of widgets" [(ngGridItem)]="widget.gridItemConfig" [_config]="widget">
|
||||
</dashboard-widget-wrapper>
|
||||
</div>
|
||||
<panel style="flex: 1 1 auto; position: relative" class="dashboard-panel" (onTabChange)="handleTabChange($event)" (onTabClose)="handleTabClose($event)" [actions]="panelActions">
|
||||
<tab *ngFor="let tab of tabs" [title]="tab.title" class="fullsize" [identifier]="tab.id" [canClose]="tab.canClose" [actions]="tab.actions">
|
||||
<dashboard-webview-tab *ngIf="getContentType(tab) === 'webview-tab'" [tab]="tab">
|
||||
</dashboard-webview-tab>
|
||||
<dashboard-widget-tab *ngIf="getContentType(tab) === 'widgets-tab'" [tab]="tab">
|
||||
</dashboard-widget-tab>
|
||||
</tab>
|
||||
</panel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,17 +4,27 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./dashboardPage';
|
||||
import './dashboardPanelStyles';
|
||||
|
||||
import { Component, Inject, forwardRef, ViewChild, ElementRef, ViewChildren, QueryList, OnDestroy, ChangeDetectorRef } from '@angular/core';
|
||||
import { NgGridConfig, NgGrid, NgGridItem } from 'angular2-grid';
|
||||
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { WidgetConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { WidgetConfig, TabConfig, PinConfig } from 'sql/parts/dashboard/common/dashboardWidget';
|
||||
import { ConnectionManagementInfo } from 'sql/parts/connection/common/connectionManagementInfo';
|
||||
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
|
||||
import { DashboardWidgetWrapper } from 'sql/parts/dashboard/common/dashboardWidgetWrapper.component';
|
||||
import { subscriptionToDisposable } from 'sql/base/common/lifecycle';
|
||||
import { IPropertiesConfig } from 'sql/parts/dashboard/pages/serverDashboardPage.contribution';
|
||||
import { PanelComponent } from 'sql/base/browser/ui/panel/panel.component';
|
||||
import { subscriptionToDisposable } from 'sql/base/common/lifecycle';
|
||||
import { IDashboardRegistry, Extensions as DashboardExtensions, IDashboardTab } from 'sql/platform/dashboard/common/dashboardRegistry';
|
||||
import { PinUnpinTabAction, AddFeatureTabAction } from './actions';
|
||||
import { TabComponent } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { AngularEventType, IAngularEvent } from 'sql/services/angularEventing/angularEventingService';
|
||||
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 { WEBVIEW_TABS } from 'sql/parts/dashboard/tabs/dashboardWebviewTab.contribution';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import * as types from 'vs/base/common/types';
|
||||
@@ -28,9 +38,13 @@ import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeS
|
||||
import * as colors from 'vs/platform/theme/common/colorRegistry';
|
||||
import * as themeColors from 'vs/workbench/common/theme';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as objects from 'sql/base/common/objects';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
const dashboardRegistry = Registry.as<IDashboardRegistry>(DashboardExtensions.DashboardContributions);
|
||||
|
||||
/**
|
||||
* @returns whether the provided parameter is a JavaScript Array and each element in the array is a number.
|
||||
*/
|
||||
@@ -38,51 +52,6 @@ function isNumberArray(value: any): value is number[] {
|
||||
return types.isArray(value) && (<any[]>value).every(elem => types.isNumber(elem));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorting function for dashboard widgets
|
||||
* In order of priority;
|
||||
* If neither have defined grid positions, they are equivalent
|
||||
* If a has a defined grid position and b does not; a should come first
|
||||
* If both have defined grid positions and have the same row; the one with the smaller col position should come first
|
||||
* If both have defined grid positions but different rows (it doesn't really matter in this case) the lowers row should come first
|
||||
*/
|
||||
function configSorter(a, b): number {
|
||||
if ((!a.gridItemConfig || !a.gridItemConfig.col)
|
||||
&& (!b.gridItemConfig || !b.gridItemConfig.col)) {
|
||||
return 0;
|
||||
} else if (!a.gridItemConfig || !a.gridItemConfig.col) {
|
||||
return 1;
|
||||
} else if (!b.gridItemConfig || !b.gridItemConfig.col) {
|
||||
return -1;
|
||||
} else if (a.gridItemConfig.row === b.gridItemConfig.row) {
|
||||
if (a.gridItemConfig.col < b.gridItemConfig.col) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.gridItemConfig.col === b.gridItemConfig.col) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (a.gridItemConfig.col > b.gridItemConfig.col) {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if (a.gridItemConfig.row < b.gridItemConfig.row) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.gridItemConfig.row === b.gridItemConfig.row) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (a.gridItemConfig.row > b.gridItemConfig.row) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return void 0; // this should never be reached
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'dashboard-page',
|
||||
templateUrl: decodeURI(require.toUrl('sql/parts/dashboard/common/dashboardPage.component.html'))
|
||||
@@ -90,43 +59,31 @@ function configSorter(a, b): number {
|
||||
export abstract class DashboardPage extends Disposable implements OnDestroy {
|
||||
|
||||
protected SKELETON_WIDTH = 5;
|
||||
protected widgets: Array<WidgetConfig> = [];
|
||||
protected gridConfig: NgGridConfig = {
|
||||
'margins': [10], // The size of the margins of each item. Supports up to four values in the same way as CSS margins. Can be updated using setMargins()
|
||||
'draggable': false, // Whether the items can be dragged. Can be updated using enableDrag()/disableDrag()
|
||||
'resizable': false, // Whether the items can be resized. Can be updated using enableResize()/disableResize()
|
||||
'max_cols': this.SKELETON_WIDTH, // The maximum number of columns allowed. Set to 0 for infinite. Cannot be used with max_rows
|
||||
'max_rows': 0, // The maximum number of rows allowed. Set to 0 for infinite. Cannot be used with max_cols
|
||||
'visible_cols': 0, // The number of columns shown on screen when auto_resize is set to true. Set to 0 to not auto_resize. Will be overriden by max_cols
|
||||
'visible_rows': 0, // The number of rows shown on screen when auto_resize is set to true. Set to 0 to not auto_resize. Will be overriden by max_rows
|
||||
'min_cols': 0, // The minimum number of columns allowed. Can be any number greater than or equal to 1.
|
||||
'min_rows': 0, // The minimum number of rows allowed. Can be any number greater than or equal to 1.
|
||||
'col_width': 250, // The width of each column
|
||||
'row_height': 250, // The height of each row
|
||||
'cascade': 'left', // The direction to cascade grid items ('up', 'right', 'down', 'left')
|
||||
'min_width': 100, // The minimum width of an item. If greater than col_width, this will update the value of min_cols
|
||||
'min_height': 100, // The minimum height of an item. If greater than row_height, this will update the value of min_rows
|
||||
'fix_to_grid': false, // Fix all item movements to the grid
|
||||
'auto_style': true, // Automatically add required element styles at run-time
|
||||
'auto_resize': false, // Automatically set col_width/row_height so that max_cols/max_rows fills the screen. Only has effect is max_cols or max_rows is set
|
||||
'maintain_ratio': false, // Attempts to maintain aspect ratio based on the colWidth/rowHeight values set in the config
|
||||
'prefer_new': false, // When adding new items, will use that items position ahead of existing items
|
||||
'limit_to_screen': true, // When resizing the screen, with this true and auto_resize false, the grid will re-arrange to fit the screen size. Please note, at present this only works with cascade direction up.
|
||||
};
|
||||
protected tabs: Array<TabConfig> = [];
|
||||
|
||||
private _originalConfig: WidgetConfig[];
|
||||
private _editDispose: Array<IDisposable> = [];
|
||||
private _scrollableElement: ScrollableElement;
|
||||
|
||||
private _widgetConfigLocation: string;
|
||||
private _propertiesConfigLocation: string;
|
||||
|
||||
protected panelActions: Action[];
|
||||
private _tabsDispose: Array<IDisposable> = [];
|
||||
private _pinnedTabs: Array<PinConfig> = [];
|
||||
|
||||
@ViewChild('properties') private _properties: DashboardWidgetWrapper;
|
||||
@ViewChild(NgGrid) private _grid: NgGrid;
|
||||
@ViewChild('scrollable', { read: ElementRef }) private _scrollable: ElementRef;
|
||||
@ViewChild('scrollContainer', { read: ElementRef }) private _scrollContainer: ElementRef;
|
||||
@ViewChild('propertiesContainer', { read: ElementRef }) private _propertiesContainer: ElementRef;
|
||||
@ViewChildren(DashboardWidgetWrapper) private _widgets: QueryList<DashboardWidgetWrapper>;
|
||||
@ViewChildren(NgGridItem) private _items: QueryList<NgGridItem>;
|
||||
@ViewChildren(DashboardTab) private _tabs: QueryList<DashboardTab>;
|
||||
@ViewChild(PanelComponent) private _panel: PanelComponent;
|
||||
|
||||
private _editEnabled = new Emitter<boolean>();
|
||||
public readonly editEnabled: Event<boolean> = this._editEnabled.event;
|
||||
|
||||
|
||||
// tslint:disable:no-unused-variable
|
||||
private readonly homeTabTitle: string = nls.localize('home', 'Home');
|
||||
|
||||
// a set of config modifiers
|
||||
private readonly _configModifiers: Array<(item: Array<WidgetConfig>) => Array<WidgetConfig>> = [
|
||||
@@ -135,7 +92,7 @@ export abstract class DashboardPage extends Disposable implements OnDestroy {
|
||||
this.addProvider,
|
||||
this.addEdition,
|
||||
this.addContext,
|
||||
this.filterWidgets
|
||||
this.filterConfigs
|
||||
];
|
||||
|
||||
private readonly _gridModifiers: Array<(item: Array<WidgetConfig>) => Array<WidgetConfig>> = [
|
||||
@@ -144,6 +101,7 @@ export abstract class DashboardPage extends Disposable implements OnDestroy {
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => DashboardServiceInterface)) protected dashboardService: DashboardServiceInterface,
|
||||
@Inject(BOOTSTRAP_SERVICE_ID) protected bootstrapService: IBootstrapService,
|
||||
@Inject(forwardRef(() => ElementRef)) protected _el: ElementRef,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) protected _cd: ChangeDetectorRef
|
||||
) {
|
||||
@@ -156,7 +114,7 @@ export abstract class DashboardPage extends Disposable implements OnDestroy {
|
||||
} else {
|
||||
let tempWidgets = this.dashboardService.getSettings<Array<WidgetConfig>>([this.context, 'widgets'].join('.'));
|
||||
this._widgetConfigLocation = 'default';
|
||||
this._originalConfig = objects.clone(tempWidgets);
|
||||
this._originalConfig = objects.deepClone(tempWidgets);
|
||||
let properties = this.getProperties();
|
||||
this._configModifiers.forEach((cb) => {
|
||||
tempWidgets = cb.apply(this, [tempWidgets]);
|
||||
@@ -165,8 +123,10 @@ export abstract class DashboardPage extends Disposable implements OnDestroy {
|
||||
this._gridModifiers.forEach(cb => {
|
||||
tempWidgets = cb.apply(this, [tempWidgets]);
|
||||
});
|
||||
this.widgets = tempWidgets;
|
||||
this.propertiesWidget = properties ? properties[0] : undefined;
|
||||
|
||||
this.createTabs(tempWidgets);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,15 +149,18 @@ export abstract class DashboardPage extends Disposable implements OnDestroy {
|
||||
container.appendChild(this._scrollableElement.getDomNode());
|
||||
let initalHeight = getContentHeight(scrollable);
|
||||
this._scrollableElement.setScrollDimensions({
|
||||
scrollHeight: getContentHeight(scrollable),
|
||||
scrollHeight: Math.max(getContentHeight(scrollable), getContentHeight(container)),
|
||||
height: getContentHeight(container)
|
||||
});
|
||||
|
||||
this._register(addDisposableListener(window, EventType.RESIZE, () => {
|
||||
this._scrollableElement.setScrollDimensions({
|
||||
scrollHeight: getContentHeight(scrollable),
|
||||
height: getContentHeight(container)
|
||||
});
|
||||
// Todo: Need to set timeout because we have to make sure that the grids have already rearraged before the getContentHeight gets called.
|
||||
setTimeout(() => {
|
||||
this._scrollableElement.setScrollDimensions({
|
||||
scrollHeight: Math.max(getContentHeight(scrollable), getContentHeight(container)),
|
||||
height: getContentHeight(container)
|
||||
});
|
||||
}, 100);
|
||||
}));
|
||||
|
||||
// unforunately because of angular rendering behavior we need to do a double check to make sure nothing changed after this point
|
||||
@@ -205,13 +168,147 @@ export abstract class DashboardPage extends Disposable implements OnDestroy {
|
||||
let currentheight = getContentHeight(scrollable);
|
||||
if (initalHeight !== currentheight) {
|
||||
this._scrollableElement.setScrollDimensions({
|
||||
scrollHeight: getContentHeight(scrollable),
|
||||
scrollHeight: Math.max(getContentHeight(scrollable), getContentHeight(container)),
|
||||
height: getContentHeight(container)
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
private createTabs(homeWidgets: WidgetConfig[]) {
|
||||
// Clear all tabs
|
||||
this.tabs = [];
|
||||
this._pinnedTabs = [];
|
||||
this._tabsDispose.forEach(i => i.dispose());
|
||||
this._tabsDispose = [];
|
||||
|
||||
// Create home tab
|
||||
let homeTab: TabConfig = {
|
||||
id: 'homeTab',
|
||||
publisher: undefined,
|
||||
title: this.homeTabTitle,
|
||||
content: { 'widgets-tab': homeWidgets },
|
||||
context: this.context,
|
||||
originalConfig: this._originalConfig,
|
||||
editable: true,
|
||||
canClose: false,
|
||||
actions: []
|
||||
};
|
||||
this.addNewTab(homeTab);
|
||||
this._panel.selectTab(homeTab.id);
|
||||
|
||||
let allTabs = this.filterConfigs(dashboardRegistry.tabs);
|
||||
|
||||
// Load always show tabs
|
||||
let alwaysShowTabs = allTabs.filter(tab => tab.alwaysShow);
|
||||
this.loadNewTabs(alwaysShowTabs);
|
||||
|
||||
// Load pinned tabs
|
||||
this._pinnedTabs = this.dashboardService.getSettings<Array<PinConfig>>([this.context, 'tabs'].join('.'));
|
||||
let pinnedDashboardTabs: IDashboardTab[] = [];
|
||||
this._pinnedTabs.forEach(pinnedTab => {
|
||||
let tab = allTabs.find(i => i.id === pinnedTab.tabId);
|
||||
if (tab) {
|
||||
pinnedDashboardTabs.push(tab);
|
||||
}
|
||||
});
|
||||
this.loadNewTabs(pinnedDashboardTabs);
|
||||
|
||||
// Set panel actions
|
||||
let openedTabs = [...pinnedDashboardTabs, ...alwaysShowTabs];
|
||||
let addNewTabAction = this.dashboardService.instantiationService.createInstance(AddFeatureTabAction, allTabs, openedTabs, this.dashboardService.getUnderlyingUri());
|
||||
this._tabsDispose.push(addNewTabAction);
|
||||
this.panelActions = [addNewTabAction];
|
||||
this._cd.detectChanges();
|
||||
|
||||
this._tabsDispose.push(this.dashboardService.onPinUnpinTab(e => {
|
||||
if (e.isPinned) {
|
||||
this._pinnedTabs.push(e);
|
||||
} else {
|
||||
let index = this._pinnedTabs.findIndex(i => i.tabId === e.tabId);
|
||||
this._pinnedTabs.splice(index, 1);
|
||||
}
|
||||
this.rewriteConfig();
|
||||
}));
|
||||
|
||||
this._tabsDispose.push(this.dashboardService.onAddNewTabs(e => {
|
||||
this.loadNewTabs(e);
|
||||
}));
|
||||
}
|
||||
|
||||
private rewriteConfig(): void {
|
||||
let writeableConfig = objects.deepClone(this._pinnedTabs);
|
||||
|
||||
writeableConfig.forEach(i => {
|
||||
delete i.isPinned;
|
||||
});
|
||||
let target: ConfigurationTarget = ConfigurationTarget.USER;
|
||||
this.dashboardService.writeSettings([this.context, 'tabs'].join('.'), writeableConfig, target);
|
||||
}
|
||||
|
||||
private loadNewTabs(dashboardTabs: IDashboardTab[]) {
|
||||
if (dashboardTabs && dashboardTabs.length > 0) {
|
||||
let selectedTabs = dashboardTabs.map(v => {
|
||||
|
||||
if (Object.keys(v.content).length !== 1) {
|
||||
error('Exactly 1 widget must be defined per space');
|
||||
}
|
||||
|
||||
let key = Object.keys(v.content)[0];
|
||||
if (key === WIDGETS_TABS) {
|
||||
let configs = <WidgetConfig[]>Object.values(v.content)[0];
|
||||
this._configModifiers.forEach(cb => {
|
||||
configs = cb.apply(this, [configs]);
|
||||
});
|
||||
this._gridModifiers.forEach(cb => {
|
||||
configs = cb.apply(this, [configs]);
|
||||
});
|
||||
return { id: v.id, title: v.title, content: { 'widgets-tab': configs }, alwaysShow: v.alwaysShow };
|
||||
}
|
||||
return v;
|
||||
}).map(v => {
|
||||
let actions = [];
|
||||
if (!v.alwaysShow) {
|
||||
let pinnedTab = this._pinnedTabs.find(i => i.tabId === v.id);
|
||||
actions.push(this.dashboardService.instantiationService.createInstance(PinUnpinTabAction, v.id, this.dashboardService.getUnderlyingUri(), !!pinnedTab));
|
||||
}
|
||||
|
||||
let config = v as TabConfig;
|
||||
config.context = this.context;
|
||||
config.editable = false;
|
||||
config.canClose = true;
|
||||
config.actions = actions;
|
||||
this.addNewTab(config);
|
||||
return config;
|
||||
});
|
||||
|
||||
// put this immediately on the stack so that is ran *after* the tab is rendered
|
||||
setTimeout(() => {
|
||||
this._panel.selectTab(selectedTabs.pop().id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private getContentType(tab: TabConfig): string {
|
||||
return tab.content ? Object.keys(tab.content)[0] : '';
|
||||
}
|
||||
|
||||
private addNewTab(tab: TabConfig): void {
|
||||
let existedTab = this.tabs.find(i => i.id === tab.id);
|
||||
if (!existedTab) {
|
||||
this.tabs.push(tab);
|
||||
this._cd.detectChanges();
|
||||
let tabComponents = this._tabs.find(i => i.id === tab.id);
|
||||
this._register(tabComponents.onResize(() => {
|
||||
this._scrollableElement.setScrollDimensions({
|
||||
scrollHeight: Math.max(getContentHeight(this._scrollable.nativeElement), getContentHeight(this._scrollContainer.nativeElement)),
|
||||
height: getContentHeight(this._scrollContainer.nativeElement)
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private updateTheme(theme: IColorTheme): void {
|
||||
let el = this._propertiesContainer.nativeElement as HTMLElement;
|
||||
let border = theme.getColor(colors.contrastBorder, true);
|
||||
@@ -240,14 +337,18 @@ export abstract class DashboardPage extends Disposable implements OnDestroy {
|
||||
* Returns a filtered version of the widgets passed based on edition and provider
|
||||
* @param config widgets to filter
|
||||
*/
|
||||
private filterWidgets(config: WidgetConfig[]): Array<WidgetConfig> {
|
||||
private filterConfigs<T extends { provider?: string | string[], edition?: number | number[] }>(config: T[]): Array<T> {
|
||||
let connectionInfo: ConnectionManagementInfo = this.dashboardService.connectionManagementService.connectionInfo;
|
||||
let edition = connectionInfo.serverInfo.engineEditionId;
|
||||
let provider = connectionInfo.providerId;
|
||||
|
||||
// filter by provider
|
||||
return config.filter((item) => {
|
||||
return this.stringCompare(item.provider, provider);
|
||||
if (item.provider) {
|
||||
return this.stringCompare(item.provider, provider);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}).filter((item) => {
|
||||
if (item.edition) {
|
||||
if (edition) {
|
||||
@@ -407,96 +508,42 @@ export abstract class DashboardPage extends Disposable implements OnDestroy {
|
||||
public refresh(refreshConfig: boolean = false): void {
|
||||
if (refreshConfig) {
|
||||
this.init();
|
||||
if (this._properties) {
|
||||
this._properties.refresh();
|
||||
}
|
||||
this.refreshProperties();
|
||||
} else {
|
||||
if (this._widgets) {
|
||||
this._widgets.forEach(item => {
|
||||
item.refresh();
|
||||
this.refreshProperties();
|
||||
if (this._tabs) {
|
||||
this._tabs.forEach(tabContent => {
|
||||
tabContent.refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private refreshProperties(): void {
|
||||
if (this._properties) {
|
||||
this._properties.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public enableEdit(): void {
|
||||
if (this._grid.dragEnable) {
|
||||
this._grid.disableDrag();
|
||||
this._grid.disableResize();
|
||||
this._editDispose.forEach(i => i.dispose());
|
||||
this._widgets.forEach(i => {
|
||||
if (i.id) {
|
||||
i.disableEdit();
|
||||
}
|
||||
});
|
||||
this._editDispose = [];
|
||||
} else {
|
||||
this._grid.enableResize();
|
||||
this._grid.enableDrag();
|
||||
this._editDispose.push(this.dashboardService.onDeleteWidget(e => {
|
||||
let index = this.widgets.findIndex(i => i.id === e);
|
||||
this.widgets.splice(index, 1);
|
||||
|
||||
index = this._originalConfig.findIndex(i => i.id === e);
|
||||
this._originalConfig.splice(index, 1);
|
||||
|
||||
this._rewriteConfig();
|
||||
this._cd.detectChanges();
|
||||
}));
|
||||
this._editDispose.push(subscriptionToDisposable(this._grid.onResizeStop.subscribe((e: NgGridItem) => {
|
||||
this._scrollableElement.setScrollDimensions({
|
||||
scrollHeight: getContentHeight(this._scrollable.nativeElement),
|
||||
height: getContentHeight(this._scrollContainer.nativeElement)
|
||||
});
|
||||
let event = e.getEventOutput();
|
||||
let config = this._originalConfig.find(i => i.id === event.payload.id);
|
||||
|
||||
if (!config.gridItemConfig) {
|
||||
config.gridItemConfig = {};
|
||||
}
|
||||
config.gridItemConfig.sizex = e.sizex;
|
||||
config.gridItemConfig.sizey = e.sizey;
|
||||
|
||||
let component = this._widgets.find(i => i.id === event.payload.id);
|
||||
|
||||
component.layout();
|
||||
this._rewriteConfig();
|
||||
})));
|
||||
this._editDispose.push(subscriptionToDisposable(this._grid.onDragStop.subscribe((e: NgGridItem) => {
|
||||
this._scrollableElement.setScrollDimensions({
|
||||
scrollHeight: getContentHeight(this._scrollable.nativeElement),
|
||||
height: getContentHeight(this._scrollContainer.nativeElement)
|
||||
});
|
||||
let event = e.getEventOutput();
|
||||
this._items.forEach(i => {
|
||||
let config = this._originalConfig.find(j => j.id === i.getEventOutput().payload.id);
|
||||
if ((config.gridItemConfig && config.gridItemConfig.col) || config.id === event.payload.id) {
|
||||
if (!config.gridItemConfig) {
|
||||
config.gridItemConfig = {};
|
||||
}
|
||||
config.gridItemConfig.col = i.col;
|
||||
config.gridItemConfig.row = i.row;
|
||||
}
|
||||
});
|
||||
this._originalConfig.sort(configSorter);
|
||||
|
||||
this._rewriteConfig();
|
||||
})));
|
||||
this._widgets.forEach(i => {
|
||||
if (i.id) {
|
||||
i.enableEdit();
|
||||
}
|
||||
if (this._tabs) {
|
||||
this._tabs.forEach(tabContent => {
|
||||
tabContent.enableEdit();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _rewriteConfig(): void {
|
||||
let writeableConfig = objects.clone(this._originalConfig);
|
||||
public handleTabChange(tab: TabComponent): void {
|
||||
let localtab = this._tabs.find(i => i.id === tab.identifier);
|
||||
this._editEnabled.fire(localtab.editable);
|
||||
this._cd.detectChanges();
|
||||
localtab.layout();
|
||||
}
|
||||
|
||||
writeableConfig.forEach(i => {
|
||||
delete i.id;
|
||||
});
|
||||
let target: ConfigurationTarget = ConfigurationTarget.USER;
|
||||
this.dashboardService.writeSettings(this.context, writeableConfig, target);
|
||||
public handleTabClose(tab: TabComponent): void {
|
||||
let index = this.tabs.findIndex(i => i.id === tab.identifier);
|
||||
this.tabs.splice(index, 1);
|
||||
this._cd.detectChanges();
|
||||
this.bootstrapService.angularEventingService.sendAngularEvent(this.dashboardService.getUnderlyingUri(), AngularEventType.CLOSE_TAB, { id: tab.identifier });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,3 +8,8 @@ dashboard-page {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
dashboard-page .monaco-scrollable-element {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
19
src/sql/parts/dashboard/common/dashboardPanel.css
Normal file
19
src/sql/parts/dashboard/common/dashboardPanel.css
Normal file
@@ -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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel {
|
||||
border-top-width: 0px;
|
||||
border-top-style: solid;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .tabList .tab-header .tab > .tabLabel.active {
|
||||
border-bottom: 0px solid;
|
||||
}
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .tabList .tab-header {
|
||||
box-sizing: border-box;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
95
src/sql/parts/dashboard/common/dashboardPanelStyles.ts
Normal file
95
src/sql/parts/dashboard/common/dashboardPanelStyles.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!./dashboardPanel';
|
||||
|
||||
import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_ACTIVE_BORDER, TAB_INACTIVE_BACKGROUND, TAB_INACTIVE_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BACKGROUND, TAB_BORDER } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
|
||||
// Title Active
|
||||
const tabActiveBackground = theme.getColor(TAB_ACTIVE_BACKGROUND);
|
||||
const tabActiveForeground = theme.getColor(TAB_ACTIVE_FOREGROUND);
|
||||
if (tabActiveBackground || tabActiveForeground) {
|
||||
collector.addRule(`
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .tabList .tab:hover .tabLabel,
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .tabList .tab .tabLabel.active {
|
||||
color: ${tabActiveForeground};
|
||||
border-bottom: 0px solid;
|
||||
}
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .tabList .tab-header.active {
|
||||
background-color: ${tabActiveBackground};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const activeTabBorderColor = theme.getColor(TAB_ACTIVE_BORDER);
|
||||
if (activeTabBorderColor) {
|
||||
collector.addRule(`
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .tabList .tab-header.active {
|
||||
box-shadow: ${activeTabBorderColor} 0 -1px inset;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Title Inactive
|
||||
const tabInactiveBackground = theme.getColor(TAB_INACTIVE_BACKGROUND);
|
||||
const tabInactiveForeground = theme.getColor(TAB_INACTIVE_FOREGROUND);
|
||||
if (tabInactiveBackground || tabInactiveForeground) {
|
||||
collector.addRule(`
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .tabList .tab .tabLabel {
|
||||
color: ${tabInactiveForeground};
|
||||
}
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .tabList .tab-header {
|
||||
background-color: ${tabInactiveBackground};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Panel title background
|
||||
const panelTitleBackground = theme.getColor(EDITOR_GROUP_HEADER_TABS_BACKGROUND);
|
||||
if (panelTitleBackground) {
|
||||
collector.addRule(`
|
||||
panel.dashboard-panel > .tabbedPanel > .title {
|
||||
background-color: ${panelTitleBackground};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Panel title background
|
||||
const tabBoarder = theme.getColor(TAB_BORDER);
|
||||
if (tabBoarder) {
|
||||
collector.addRule(`
|
||||
panel.dashboard-panel > .tabbedPanel.horizontal > .title > .tabList .tab-header {
|
||||
border-right-color: ${tabBoarder};
|
||||
}
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel.vertical > .title > .tabList .tab-header {
|
||||
border-bottom-color: ${tabBoarder};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Styling with Outline color (e.g. high contrast theme)
|
||||
const outline = theme.getColor(activeContrastBorder);
|
||||
if (outline) {
|
||||
collector.addRule(`
|
||||
panel.dashboard-panel > .tabbedPanel > .title {
|
||||
border-bottom-color: ${tabBoarder};
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel.vertical > .title {
|
||||
border-right-color: ${tabBoarder};
|
||||
border-right-width: 1px;
|
||||
border-right-style: solid;
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
116
src/sql/parts/dashboard/common/dashboardTab.contribution.ts
Normal file
116
src/sql/parts/dashboard/common/dashboardTab.contribution.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { IExtensionPointUser, ExtensionsRegistry } from 'vs/platform/extensions/common/extensionsRegistry';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
import { registerTab, generateTabContentSchemaProperties } from 'sql/platform/dashboard/common/dashboardRegistry';
|
||||
|
||||
export interface IDashboardTabContrib {
|
||||
id: string;
|
||||
title: string;
|
||||
content: object;
|
||||
description?: string;
|
||||
provider?: string | string[];
|
||||
edition?: number | number[];
|
||||
alwaysShow?: boolean;
|
||||
}
|
||||
|
||||
const tabSchema: IJSONSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: localize('sqlops.extension.contributes.dashboard.tab.id', "Unique identifier for this tab. Will be passed to the extension for any requests.")
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: localize('sqlops.extension.contributes.dashboard.tab.title', "Title of the tab to show the user.")
|
||||
},
|
||||
description: {
|
||||
description: localize('sqlops.extension.contributes.dashboard.tab.description', "Description of this tab that will be shown to the user."),
|
||||
type: 'string'
|
||||
},
|
||||
provider: {
|
||||
description: localize('sqlops.extension.contributes.dashboard.tab.provider', "Providers for which this tab should be allowed for."),
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
edition: {
|
||||
description: localize('sqlops.extension.contributes.dashboard.tab.edition', "Editions for which this tab should be allowed for."),
|
||||
anyOf: [
|
||||
{
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'number'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
content: {
|
||||
description: localize('sqlops.extension.contributes.dashboard.tab.content', "The content that will be displayed in this tab."),
|
||||
type: 'object',
|
||||
properties: generateTabContentSchemaProperties()
|
||||
},
|
||||
alwaysShow: {
|
||||
description: localize('sqlops.extension.contributes.dashboard.tab.alwaysShow', "Whether or not this tab should always be shown or only when the user adds it."),
|
||||
type: 'boolean'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const tabContributionSchema: IJSONSchema = {
|
||||
description: localize('sqlops.extension.contributes.tabs', "Contributes a single or multiple tabs for users to add to their dashboard."),
|
||||
oneOf: [
|
||||
tabSchema,
|
||||
{
|
||||
type: 'array',
|
||||
items: tabSchema
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
ExtensionsRegistry.registerExtensionPoint<IDashboardTabContrib | IDashboardTabContrib[]>('dashboard.tabs', [], tabContributionSchema).setHandler(extensions => {
|
||||
|
||||
function handleCommand(tab: IDashboardTabContrib, extension: IExtensionPointUser<any>) {
|
||||
let { description, content, title, edition, provider, id, alwaysShow } = tab;
|
||||
alwaysShow = alwaysShow || false;
|
||||
let publisher = extension.description.publisher;
|
||||
if (!title) {
|
||||
extension.collector.error('No title specified for extension.');
|
||||
return;
|
||||
}
|
||||
if (!description) {
|
||||
extension.collector.warn('No description specified to show.');
|
||||
}
|
||||
if (!content) {
|
||||
extension.collector.warn('No content specified to show.');
|
||||
}
|
||||
registerTab({ description, title, content, edition, provider, id, alwaysShow, publisher });
|
||||
}
|
||||
|
||||
for (let extension of extensions) {
|
||||
const { value } = extension;
|
||||
if (Array.isArray<IDashboardTabContrib>(value)) {
|
||||
for (let command of value) {
|
||||
handleCommand(command, extension);
|
||||
}
|
||||
} else {
|
||||
handleCommand(value, extension);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -6,6 +6,7 @@ import { InjectionToken, OnDestroy } from '@angular/core';
|
||||
import { NgGridItemConfig } from 'angular2-grid';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IDashboardTab } from 'sql/platform/dashboard/common/dashboardRegistry';
|
||||
|
||||
export interface IDashboardWidget {
|
||||
actions: Array<Action>;
|
||||
@@ -32,6 +33,19 @@ export interface WidgetConfig {
|
||||
padding?: string;
|
||||
}
|
||||
|
||||
export interface TabConfig extends IDashboardTab {
|
||||
context: string;
|
||||
originalConfig: Array<WidgetConfig>;
|
||||
editable: boolean;
|
||||
canClose: boolean;
|
||||
actions?: Array<Action>;
|
||||
}
|
||||
|
||||
export interface PinConfig {
|
||||
tabId: string;
|
||||
isPinned?: boolean;
|
||||
}
|
||||
|
||||
export abstract class DashboardWidget extends Disposable implements OnDestroy {
|
||||
protected _config: WidgetConfig;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div style="display: flex; flex-flow: column; overflow: hidden; height: 100%; width: 100%">
|
||||
|
||||
<div #header>
|
||||
<div *ngIf="_config.name || _config.loadedIcon || _actions" style="display: flex; flex: 0 0; padding: 3px 0 3px 0; flex-direction: row-reverse; justify-content: space-between">
|
||||
<div style="display: flex; flex: 0 0; padding: 3px 0 3px 0; flex-direction: row-reverse; justify-content: space-between">
|
||||
<span #actionbar style="flex: 0 0 auto; align-self: end"></span>
|
||||
<span *ngIf="_config.name" style="margin-left: 5px">{{_config.name}}</span>
|
||||
<span *ngIf="_config.icon" [ngClass]="['icon', _config.icon]" style="display: inline-block; padding: 10px; margin-left: 5px"></span>
|
||||
|
||||
@@ -21,6 +21,7 @@ import { PropertiesWidgetComponent } from 'sql/parts/dashboard/widgets/propertie
|
||||
import { ExplorerWidget } from 'sql/parts/dashboard/widgets/explorer/explorerWidget.component';
|
||||
import { TasksWidget } from 'sql/parts/dashboard/widgets/tasks/tasksWidget.component';
|
||||
import { InsightsWidget } from 'sql/parts/dashboard/widgets/insights/insightsWidget.component';
|
||||
import { WebviewWidget } from 'sql/parts/dashboard/widgets/webview/webviewWidget.component';
|
||||
|
||||
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
|
||||
@@ -36,7 +37,8 @@ const componentMap: { [x: string]: Type<IDashboardWidget> } = {
|
||||
'properties-widget': PropertiesWidgetComponent,
|
||||
'explorer-widget': ExplorerWidget,
|
||||
'tasks-widget': TasksWidget,
|
||||
'insights-widget': InsightsWidget
|
||||
'insights-widget': InsightsWidget,
|
||||
'webview-widget': WebviewWidget
|
||||
};
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import Event from 'vs/base/common/event';
|
||||
|
||||
export enum Conditional {
|
||||
'equals',
|
||||
'notEquals',
|
||||
@@ -11,4 +14,15 @@ export enum Conditional {
|
||||
'lessThanOrEquals',
|
||||
'lessThan',
|
||||
'always'
|
||||
};
|
||||
}
|
||||
|
||||
export abstract class DashboardTab extends Disposable {
|
||||
public abstract layout(): void;
|
||||
public abstract readonly id: string;
|
||||
public abstract readonly editable: boolean;
|
||||
public abstract refresh(): void;
|
||||
public abstract readonly onResize: Event<void>;
|
||||
public enableEdit(): void {
|
||||
// no op
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user