mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-13 11:38:36 -05:00
Code Layering dashboard (#4883)
* move dashboard to workbench * revert xlf file changes * 💄 * 💄 * add back removed functions
This commit is contained in:
244
src/sql/workbench/parts/dashboard/common/actions.ts
Normal file
244
src/sql/workbench/parts/dashboard/common/actions.ts
Normal file
@@ -0,0 +1,244 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
import { IAngularEventingService, AngularEventType, IAngularEvent } from 'sql/platform/angularEventing/common/angularEventingService';
|
||||
import { INewDashboardTabDialogService } from 'sql/workbench/services/dashboard/common/newDashboardTabDialog';
|
||||
import { IDashboardTab } from 'sql/platform/dashboard/common/dashboardRegistry';
|
||||
import { toDisposableSubscription } from 'sql/base/node/rxjsUtils';
|
||||
|
||||
export class EditDashboardAction extends Action {
|
||||
|
||||
private static readonly ID = 'editDashboard';
|
||||
private static readonly EDITLABEL = nls.localize('editDashboard', "Edit");
|
||||
private static readonly EXITLABEL = nls.localize('editDashboardExit', "Exit");
|
||||
private static readonly ICON = 'edit';
|
||||
|
||||
private _state = 0;
|
||||
|
||||
constructor(
|
||||
private editFn: () => void,
|
||||
private context: any //this
|
||||
) {
|
||||
super(EditDashboardAction.ID, EditDashboardAction.EDITLABEL, EditDashboardAction.ICON);
|
||||
}
|
||||
|
||||
run(): Promise<boolean> {
|
||||
try {
|
||||
this.editFn.apply(this.context);
|
||||
this.toggleLabel();
|
||||
return Promise.resolve(true);
|
||||
} catch (e) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
private toggleLabel(): void {
|
||||
if (this._state === 0) {
|
||||
this.label = EditDashboardAction.EXITLABEL;
|
||||
this._state = 1;
|
||||
} else {
|
||||
this.label = EditDashboardAction.EDITLABEL;
|
||||
this._state = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class RefreshWidgetAction extends Action {
|
||||
|
||||
private static readonly ID = 'refreshWidget';
|
||||
private static readonly LABEL = nls.localize('refreshWidget', 'Refresh');
|
||||
private static readonly ICON = 'refresh';
|
||||
|
||||
constructor(
|
||||
private refreshFn: () => void,
|
||||
private context: any // this
|
||||
) {
|
||||
super(RefreshWidgetAction.ID, RefreshWidgetAction.LABEL, RefreshWidgetAction.ICON);
|
||||
}
|
||||
|
||||
run(): Promise<boolean> {
|
||||
try {
|
||||
this.refreshFn.apply(this.context);
|
||||
return Promise.resolve(true);
|
||||
} catch (e) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ToggleMoreWidgetAction extends Action {
|
||||
|
||||
private static readonly ID = 'toggleMore';
|
||||
private static readonly LABEL = nls.localize('toggleMore', 'Toggle More');
|
||||
private static readonly ICON = 'toggle-more';
|
||||
|
||||
constructor(
|
||||
private _actions: Array<IAction>,
|
||||
private _context: any,
|
||||
@IContextMenuService private _contextMenuService: IContextMenuService
|
||||
) {
|
||||
super(ToggleMoreWidgetAction.ID, ToggleMoreWidgetAction.LABEL, ToggleMoreWidgetAction.ICON);
|
||||
}
|
||||
|
||||
run(context: StandardKeyboardEvent): Promise<boolean> {
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => context.target,
|
||||
getActions: () => this._actions,
|
||||
getActionsContext: () => this._context
|
||||
});
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class DeleteWidgetAction extends Action {
|
||||
private static readonly ID = 'deleteWidget';
|
||||
private static readonly LABEL = nls.localize('deleteWidget', "Delete Widget");
|
||||
private static readonly ICON = 'close';
|
||||
|
||||
constructor(
|
||||
private _widgetId,
|
||||
private _uri,
|
||||
@IAngularEventingService private angularEventService: IAngularEventingService
|
||||
) {
|
||||
super(DeleteWidgetAction.ID, DeleteWidgetAction.LABEL, DeleteWidgetAction.ICON);
|
||||
}
|
||||
|
||||
run(): Promise<boolean> {
|
||||
this.angularEventService.sendAngularEvent(this._uri, AngularEventType.DELETE_WIDGET, { id: this._widgetId });
|
||||
return Promise.resolve(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(): Promise<boolean> {
|
||||
this._isPinned = !this._isPinned;
|
||||
this.updatePinStatus();
|
||||
this.angularEventService.sendAngularEvent(this._uri, AngularEventType.PINUNPIN_TAB, { tabId: this._tabId, isPinned: this._isPinned });
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class AddFeatureTabAction extends Action {
|
||||
private static readonly ID = 'openInstalledFeatures';
|
||||
private static readonly LABEL = nls.localize('addFeatureAction.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(): Promise<boolean> {
|
||||
this._newDashboardTabService.showDialog(this._dashboardTabs, this._openedTabs, this._uri);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
this._disposables.forEach((item) => item.dispose());
|
||||
}
|
||||
|
||||
private handleDashboardEvent(event: IAngularEvent): void {
|
||||
switch (event.event) {
|
||||
case AngularEventType.NEW_TABS:
|
||||
const openedTabs = <IDashboardTab[]>event.payload.dashboardTabs;
|
||||
openedTabs.forEach(tab => {
|
||||
const existedTab = this._openedTabs.find(i => i === tab);
|
||||
if (!existedTab) {
|
||||
this._openedTabs.push(tab);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case AngularEventType.CLOSE_TAB:
|
||||
const index = this._openedTabs.findIndex(i => i.id === event.payload.id);
|
||||
this._openedTabs.splice(index, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CollapseWidgetAction extends Action {
|
||||
private static readonly ID = 'collapseWidget';
|
||||
private static readonly COLLPASE_LABEL = nls.localize('collapseWidget', "Collapse");
|
||||
private static readonly EXPAND_LABEL = nls.localize('expandWidget', "Expand");
|
||||
private static readonly COLLAPSE_ICON = 'maximize-panel-action';
|
||||
private static readonly EXPAND_ICON = 'minimize-panel-action';
|
||||
|
||||
constructor(
|
||||
private _uri: string,
|
||||
private _widgetUuid: string,
|
||||
private collpasedState: boolean,
|
||||
@IAngularEventingService private _angularEventService: IAngularEventingService
|
||||
) {
|
||||
super(
|
||||
CollapseWidgetAction.ID,
|
||||
collpasedState ? CollapseWidgetAction.EXPAND_LABEL : CollapseWidgetAction.COLLPASE_LABEL,
|
||||
collpasedState ? CollapseWidgetAction.EXPAND_ICON : CollapseWidgetAction.COLLAPSE_ICON
|
||||
);
|
||||
}
|
||||
|
||||
run(): Promise<boolean> {
|
||||
this._toggleState();
|
||||
this._angularEventService.sendAngularEvent(this._uri, AngularEventType.COLLAPSE_WIDGET, this._widgetUuid);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private _toggleState(): void {
|
||||
this._updateState(!this.collpasedState);
|
||||
}
|
||||
|
||||
private _updateState(collapsed: boolean): void {
|
||||
if (collapsed === this.collpasedState) {
|
||||
return;
|
||||
}
|
||||
this.collpasedState = collapsed;
|
||||
this._setClass(this.collpasedState ? CollapseWidgetAction.EXPAND_ICON : CollapseWidgetAction.COLLAPSE_ICON);
|
||||
this._setLabel(this.collpasedState ? CollapseWidgetAction.EXPAND_LABEL : CollapseWidgetAction.COLLPASE_LABEL);
|
||||
}
|
||||
|
||||
public set state(collapsed: boolean) {
|
||||
this._updateState(collapsed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Directive, ViewContainerRef, Inject, forwardRef } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[component-host]',
|
||||
})
|
||||
export class ComponentHostDirective {
|
||||
constructor( @Inject(forwardRef(() => ViewContainerRef)) public viewContainerRef: ViewContainerRef) { }
|
||||
}
|
||||
190
src/sql/workbench/parts/dashboard/common/dashboardHelper.ts
Normal file
190
src/sql/workbench/parts/dashboard/common/dashboardHelper.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import * as nls from 'vs/nls';
|
||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
import { error } from 'sql/base/common/log';
|
||||
import { WidgetConfig } from 'sql/workbench/parts/dashboard/common/dashboardWidget';
|
||||
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
|
||||
import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo';
|
||||
import { DashboardServiceInterface } from 'sql/workbench/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { WIDGETS_CONTAINER } from 'sql/workbench/parts/dashboard/containers/dashboardWidgetContainer.contribution';
|
||||
import { GRID_CONTAINER } from 'sql/workbench/parts/dashboard/containers/dashboardGridContainer.contribution';
|
||||
import { WEBVIEW_CONTAINER } from 'sql/workbench/parts/dashboard/containers/dashboardWebviewContainer.contribution';
|
||||
import { MODELVIEW_CONTAINER } from 'sql/workbench/parts/dashboard/containers/dashboardModelViewContainer.contribution';
|
||||
import { CONTROLHOST_CONTAINER } from 'sql/workbench/parts/dashboard/containers/dashboardControlHostContainer.contribution';
|
||||
import { NAV_SECTION } from 'sql/workbench/parts/dashboard/containers/dashboardNavSection.contribution';
|
||||
import { IDashboardContainerRegistry, Extensions as DashboardContainerExtensions } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
|
||||
import { SingleConnectionManagementService } from 'sql/services/common/commonServiceInterface.service';
|
||||
import * as Constants from 'sql/platform/connection/common/constants';
|
||||
|
||||
const dashboardcontainerRegistry = Registry.as<IDashboardContainerRegistry>(DashboardContainerExtensions.dashboardContainerContributions);
|
||||
const containerTypes = [
|
||||
WIDGETS_CONTAINER,
|
||||
GRID_CONTAINER,
|
||||
WEBVIEW_CONTAINER,
|
||||
MODELVIEW_CONTAINER,
|
||||
CONTROLHOST_CONTAINER,
|
||||
NAV_SECTION
|
||||
];
|
||||
|
||||
/**
|
||||
* Validates configs to make sure nothing will error out and returns the modified widgets
|
||||
* @param config Array of widgets to validate
|
||||
*/
|
||||
export function removeEmpty(config: WidgetConfig[]): Array<WidgetConfig> {
|
||||
return config.filter(widget => {
|
||||
return !types.isUndefinedOrNull(widget);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates configs to make sure nothing will error out and returns the modified widgets
|
||||
* @param config Array of widgets to validate
|
||||
*/
|
||||
export function validateGridConfig(config: WidgetConfig[], originalConfig: WidgetConfig[]): Array<WidgetConfig> {
|
||||
return config.map((widget, index) => {
|
||||
if (widget.gridItemConfig === undefined) {
|
||||
widget.gridItemConfig = {};
|
||||
}
|
||||
const id = generateUuid();
|
||||
widget.gridItemConfig.payload = { id };
|
||||
widget.id = id;
|
||||
if (originalConfig && originalConfig[index]) {
|
||||
originalConfig[index].id = id;
|
||||
}
|
||||
return widget;
|
||||
});
|
||||
}
|
||||
|
||||
export function initExtensionConfigs(configurations: WidgetConfig[]): Array<WidgetConfig> {
|
||||
const widgetRegistry = <IInsightRegistry>Registry.as(Extensions.InsightContribution);
|
||||
return configurations.map((config) => {
|
||||
if (config.widget && Object.keys(config.widget).length === 1) {
|
||||
const key = Object.keys(config.widget)[0];
|
||||
const insightConfig = widgetRegistry.getRegisteredExtensionInsights(key);
|
||||
if (insightConfig !== undefined) {
|
||||
// Setup the default properties for this extension if needed
|
||||
if (!config.when && insightConfig.when) {
|
||||
config.when = insightConfig.when;
|
||||
}
|
||||
if (!config.gridItemConfig && insightConfig.gridItemConfig) {
|
||||
config.gridItemConfig = {
|
||||
sizex: insightConfig.gridItemConfig.x,
|
||||
sizey: insightConfig.gridItemConfig.y
|
||||
};
|
||||
}
|
||||
if (config.gridItemConfig && !config.gridItemConfig.sizex && insightConfig.gridItemConfig && insightConfig.gridItemConfig.x) {
|
||||
config.gridItemConfig.sizex = insightConfig.gridItemConfig.x;
|
||||
}
|
||||
if (config.gridItemConfig && !config.gridItemConfig.sizey && insightConfig.gridItemConfig && insightConfig.gridItemConfig.y) {
|
||||
config.gridItemConfig.sizey = insightConfig.gridItemConfig.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
return config;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add provider to the passed widgets and returns the new widgets
|
||||
* @param widgets Array of widgets to add provider onto
|
||||
*/
|
||||
export function addProvider<T extends { connectionManagementService: SingleConnectionManagementService }>(config: WidgetConfig[], collection: T): Array<WidgetConfig> {
|
||||
const provider = collection.connectionManagementService.connectionInfo.providerId;
|
||||
return config.map((item) => {
|
||||
if (item.provider === undefined) {
|
||||
item.provider = provider;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the edition to the passed widgets and returns the new widgets
|
||||
* @param widgets Array of widgets to add edition onto
|
||||
*/
|
||||
export function addEdition<T extends { connectionManagementService: SingleConnectionManagementService }>(config: WidgetConfig[], collection: DashboardServiceInterface): Array<WidgetConfig> {
|
||||
const connectionInfo: ConnectionManagementInfo = collection.connectionManagementService.connectionInfo;
|
||||
if (connectionInfo.serverInfo) {
|
||||
const edition = connectionInfo.serverInfo.engineEditionId;
|
||||
return config.map((item) => {
|
||||
if (item.edition === undefined) {
|
||||
item.edition = edition;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
} else {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the context to the passed widgets and returns the new widgets
|
||||
* @param widgets Array of widgets to add context to
|
||||
*/
|
||||
export function addContext(config: WidgetConfig[], collection: any, context: string): Array<WidgetConfig> {
|
||||
return config.map((item) => {
|
||||
if (item.context === undefined) {
|
||||
item.context = context;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a filtered version of the widgets passed based on edition and provider
|
||||
* @param config widgets to filter
|
||||
*/
|
||||
export function filterConfigs<T extends { provider?: string | string[], when?: string }, K extends { contextKeyService: IContextKeyService }>(config: T[], collection: K): Array<T> {
|
||||
return config.filter((item) => {
|
||||
if (!hasCompatibleProvider(item.provider, collection.contextKeyService)) {
|
||||
return false;
|
||||
} else if (!item.when) {
|
||||
return true;
|
||||
} else {
|
||||
return collection.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(item.when));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the listed providers contain '*' indicating any provider will do, or that they are a match
|
||||
* for the currently scoped 'connectionProvider' context key.
|
||||
*/
|
||||
function hasCompatibleProvider(provider: string | string[], contextKeyService: IContextKeyService): boolean {
|
||||
let isCompatible = true;
|
||||
const connectionProvider = contextKeyService.getContextKeyValue<string>(Constants.connectionProviderContextKey);
|
||||
if (connectionProvider) {
|
||||
const providers = (provider instanceof Array) ? provider : [provider];
|
||||
const matchingProvider = providers.find((p) => p === connectionProvider || p === Constants.anyProviderName);
|
||||
isCompatible = (matchingProvider !== undefined);
|
||||
} // Else there's no connection context so skip the check
|
||||
return isCompatible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get registered container if it is specified as the key
|
||||
* @param container dashboard container
|
||||
*/
|
||||
export function getDashboardContainer(container: object): { result: boolean, message: string, container: object } {
|
||||
const key = Object.keys(container)[0];
|
||||
const containerTypeFound = containerTypes.find(c => (c === key));
|
||||
if (!containerTypeFound) {
|
||||
const dashboardContainer = dashboardcontainerRegistry.getRegisteredContainer(key);
|
||||
if (!dashboardContainer) {
|
||||
const errorMessage = nls.localize('unknownDashboardContainerError', '{0} is an unknown container.', key);
|
||||
error(errorMessage);
|
||||
return { result: false, message: errorMessage, container: undefined };
|
||||
} else {
|
||||
container = dashboardContainer.container;
|
||||
}
|
||||
}
|
||||
return { result: true, message: undefined, container: container };
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<!--
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
<panel class="dashboard-panel" (onTabChange)="handleTabChange($event)" (onTabClose)="handleTabClose($event)"
|
||||
[actions]="panelActions">
|
||||
<tab [visibilityType]="'visibility'" *ngFor="let tab of tabs" [title]="tab.title" class="fullsize"
|
||||
[identifier]="tab.id" [canClose]="tab.canClose" [actions]="tab.actions">
|
||||
<ng-template>
|
||||
<dashboard-home-container *ngIf="tab.id === 'homeTab'; else not_home" [properties]="propertiesWidget"
|
||||
[tab]="tab">
|
||||
</dashboard-home-container>
|
||||
<ng-template #not_home>
|
||||
<dashboard-webview-container *ngIf="getContentType(tab) === 'webview-container'" [tab]="tab">
|
||||
</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">
|
||||
</dashboard-nav-section>
|
||||
<dashboard-grid-container *ngIf="getContentType(tab) === 'grid-container'" [tab]="tab">
|
||||
</dashboard-grid-container>
|
||||
<dashboard-error-container *ngIf="getContentType(tab) === 'error-container'" [tab]="tab">
|
||||
</dashboard-error-container>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</tab>
|
||||
</panel>
|
||||
@@ -0,0 +1,353 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/workbench/parts/dashboard/common/dashboardPage';
|
||||
import 'sql/workbench/parts/dashboard/common/dashboardPanelStyles';
|
||||
|
||||
import { Component, Inject, forwardRef, ViewChild, ElementRef, ViewChildren, QueryList, ChangeDetectorRef } from '@angular/core';
|
||||
|
||||
import { DashboardServiceInterface } from 'sql/workbench/parts/dashboard/services/dashboardServiceInterface.service';
|
||||
import { CommonServiceInterface, SingleConnectionManagementService } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { WidgetConfig, TabConfig, TabSettingConfig } from 'sql/workbench/parts/dashboard/common/dashboardWidget';
|
||||
import { IPropertiesConfig } from 'sql/workbench/parts/dashboard/pages/serverDashboardPage.contribution';
|
||||
import { PanelComponent } from 'sql/base/browser/ui/panel/panel.component';
|
||||
import { IDashboardRegistry, Extensions as DashboardExtensions, IDashboardTab } from 'sql/platform/dashboard/common/dashboardRegistry';
|
||||
import { PinUnpinTabAction, AddFeatureTabAction } from './actions';
|
||||
import { TabComponent, TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { AngularEventType, IAngularEventingService } from 'sql/platform/angularEventing/common/angularEventingService';
|
||||
import { DashboardTab, IConfigModifierCollection } from 'sql/workbench/parts/dashboard/common/interfaces';
|
||||
import * as dashboardHelper from 'sql/workbench/parts/dashboard/common/dashboardHelper';
|
||||
import { WIDGETS_CONTAINER } from 'sql/workbench/parts/dashboard/containers/dashboardWidgetContainer.contribution';
|
||||
import { GRID_CONTAINER } from 'sql/workbench/parts/dashboard/containers/dashboardGridContainer.contribution';
|
||||
import { AngularDisposable } from 'sql/base/node/lifecycle';
|
||||
import * as Constants from 'sql/platform/connection/common/constants';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as nls from 'vs/nls';
|
||||
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, IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
const dashboardRegistry = Registry.as<IDashboardRegistry>(DashboardExtensions.DashboardContributions);
|
||||
|
||||
@Component({
|
||||
selector: 'dashboard-page',
|
||||
templateUrl: decodeURI(require.toUrl('sql/workbench/parts/dashboard/common/dashboardPage.component.html'))
|
||||
})
|
||||
export abstract class DashboardPage extends AngularDisposable implements IConfigModifierCollection {
|
||||
|
||||
protected tabs: Array<TabConfig> = [];
|
||||
|
||||
private _originalConfig: WidgetConfig[];
|
||||
|
||||
private _widgetConfigLocation: string;
|
||||
private _propertiesConfigLocation: string;
|
||||
|
||||
protected panelActions: Action[];
|
||||
private _tabsDispose: Array<IDisposable> = [];
|
||||
private _tabSettingConfigs: Array<TabSettingConfig> = [];
|
||||
|
||||
@ViewChildren(TabChild) 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>, collection: IConfigModifierCollection, context: string) => Array<WidgetConfig>> = [
|
||||
dashboardHelper.removeEmpty,
|
||||
dashboardHelper.initExtensionConfigs,
|
||||
dashboardHelper.addProvider,
|
||||
dashboardHelper.addEdition,
|
||||
dashboardHelper.addContext,
|
||||
dashboardHelper.filterConfigs
|
||||
];
|
||||
|
||||
public get connectionManagementService(): SingleConnectionManagementService {
|
||||
return this.dashboardService.connectionManagementService;
|
||||
}
|
||||
|
||||
public get contextKeyService(): IContextKeyService {
|
||||
return this.dashboardService.scopedContextKeyService;
|
||||
}
|
||||
|
||||
private readonly _gridModifiers: Array<(item: Array<WidgetConfig>, originalConfig: Array<WidgetConfig>) => Array<WidgetConfig>> = [
|
||||
dashboardHelper.validateGridConfig
|
||||
];
|
||||
|
||||
protected abstract propertiesWidget: WidgetConfig;
|
||||
protected abstract get context(): string;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) protected dashboardService: DashboardServiceInterface,
|
||||
@Inject(forwardRef(() => ElementRef)) protected _el: ElementRef,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) protected _cd: ChangeDetectorRef,
|
||||
@Inject(IInstantiationService) private instantiationService: IInstantiationService,
|
||||
@Inject(INotificationService) private notificationService: INotificationService,
|
||||
@Inject(IAngularEventingService) private angularEventingService: IAngularEventingService,
|
||||
@Inject(IConfigurationService) private configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected init() {
|
||||
this.dashboardService.dashboardContextKey.set(this.context);
|
||||
if (!this.dashboardService.connectionManagementService.connectionInfo) {
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: nls.localize('missingConnectionInfo', 'No connection information could be found for this dashboard')
|
||||
});
|
||||
} else {
|
||||
let tempWidgets = this.dashboardService.getSettings<Array<WidgetConfig>>([this.context, 'widgets'].join('.'));
|
||||
this._widgetConfigLocation = 'default';
|
||||
this._originalConfig = objects.deepClone(tempWidgets);
|
||||
let properties = this.getProperties();
|
||||
this._configModifiers.forEach((cb) => {
|
||||
tempWidgets = cb.apply(this, [tempWidgets, this, this.context]);
|
||||
properties = properties ? cb.apply(this, [properties, this, this.context]) : undefined;
|
||||
});
|
||||
this._gridModifiers.forEach(cb => {
|
||||
tempWidgets = cb.apply(this, [tempWidgets, this._originalConfig]);
|
||||
});
|
||||
this.propertiesWidget = properties ? properties[0] : undefined;
|
||||
|
||||
this.createTabs(tempWidgets);
|
||||
}
|
||||
}
|
||||
|
||||
private createTabs(homeWidgets: WidgetConfig[]) {
|
||||
// Clear all tabs
|
||||
this.tabs = [];
|
||||
this._tabSettingConfigs = [];
|
||||
this._tabsDispose.forEach(i => i.dispose());
|
||||
this._tabsDispose = [];
|
||||
|
||||
let allTabs = dashboardHelper.filterConfigs(dashboardRegistry.tabs, this);
|
||||
|
||||
// Before separating tabs into pinned / shown, ensure that the home tab is always set up as expected
|
||||
allTabs = this.setAndRemoveHomeTab(allTabs, homeWidgets);
|
||||
|
||||
// If preview features are disabled only show the home tab
|
||||
const extensionTabsEnabled = this.configurationService.getValue('workbench')['enablePreviewFeatures'];
|
||||
if (!extensionTabsEnabled) {
|
||||
allTabs = [];
|
||||
}
|
||||
|
||||
// Load tab setting configs
|
||||
this._tabSettingConfigs = this.dashboardService.getSettings<Array<TabSettingConfig>>([this.context, 'tabs'].join('.'));
|
||||
|
||||
const pinnedDashboardTabs: IDashboardTab[] = [];
|
||||
const alwaysShowTabs = allTabs.filter(tab => tab.alwaysShow);
|
||||
|
||||
this._tabSettingConfigs.forEach(config => {
|
||||
if (config.tabId && types.isBoolean(config.isPinned)) {
|
||||
const tab = allTabs.find(i => i.id === config.tabId);
|
||||
if (tab) {
|
||||
if (config.isPinned) {
|
||||
pinnedDashboardTabs.push(tab);
|
||||
} else {
|
||||
// overwrite always show if specify in user settings
|
||||
const index = alwaysShowTabs.findIndex(i => i.id === tab.id);
|
||||
alwaysShowTabs.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.loadNewTabs(pinnedDashboardTabs);
|
||||
this.loadNewTabs(alwaysShowTabs);
|
||||
|
||||
// Set panel actions
|
||||
const openedTabs = [...pinnedDashboardTabs, ...alwaysShowTabs];
|
||||
if (extensionTabsEnabled) {
|
||||
const addNewTabAction = this.instantiationService.createInstance(AddFeatureTabAction, allTabs, openedTabs, this.dashboardService.getUnderlyingUri());
|
||||
this._tabsDispose.push(addNewTabAction);
|
||||
this.panelActions = [addNewTabAction];
|
||||
} else {
|
||||
this.panelActions = [];
|
||||
}
|
||||
this._cd.detectChanges();
|
||||
|
||||
this._tabsDispose.push(this.dashboardService.onPinUnpinTab(e => {
|
||||
const tabConfig = this._tabSettingConfigs.find(i => i.tabId === e.tabId);
|
||||
if (tabConfig) {
|
||||
tabConfig.isPinned = e.isPinned;
|
||||
} else {
|
||||
this._tabSettingConfigs.push(e);
|
||||
}
|
||||
this.rewriteConfig();
|
||||
}));
|
||||
|
||||
this._tabsDispose.push(this.dashboardService.onAddNewTabs(e => {
|
||||
this.loadNewTabs(e, true);
|
||||
}));
|
||||
}
|
||||
|
||||
private setAndRemoveHomeTab(allTabs: IDashboardTab[], homeWidgets: WidgetConfig[]): IDashboardTab[] {
|
||||
const homeTabConfig: TabConfig = {
|
||||
id: 'homeTab',
|
||||
provider: Constants.anyProviderName,
|
||||
publisher: undefined,
|
||||
title: this.homeTabTitle,
|
||||
container: { 'widgets-container': homeWidgets },
|
||||
context: this.context,
|
||||
originalConfig: this._originalConfig,
|
||||
editable: true,
|
||||
canClose: false,
|
||||
actions: []
|
||||
};
|
||||
|
||||
const homeTabIndex = allTabs.findIndex((tab) => tab.isHomeTab === true);
|
||||
if (homeTabIndex !== undefined && homeTabIndex > -1) {
|
||||
// Have a tab: get its information and copy over to the home tab definition
|
||||
const homeTab = allTabs.splice(homeTabIndex, 1)[0];
|
||||
const tabConfig = this.initTabComponents(homeTab);
|
||||
homeTabConfig.id = tabConfig.id;
|
||||
homeTabConfig.container = tabConfig.container;
|
||||
}
|
||||
this.addNewTab(homeTabConfig);
|
||||
return allTabs;
|
||||
}
|
||||
|
||||
private rewriteConfig(): void {
|
||||
const writeableConfig = objects.deepClone(this._tabSettingConfigs);
|
||||
|
||||
const target: ConfigurationTarget = ConfigurationTarget.USER;
|
||||
this.dashboardService.writeSettings([this.context, 'tabs'].join('.'), writeableConfig, target);
|
||||
}
|
||||
|
||||
private loadNewTabs(dashboardTabs: IDashboardTab[], openLastTab: boolean = false) {
|
||||
if (dashboardTabs && dashboardTabs.length > 0) {
|
||||
const selectedTabs = dashboardTabs.map(v => this.initTabComponents(v)).map(v => {
|
||||
const actions = [];
|
||||
const tabSettingConfig = this._tabSettingConfigs.find(i => i.tabId === v.id);
|
||||
let isPinned = false;
|
||||
if (tabSettingConfig) {
|
||||
isPinned = tabSettingConfig.isPinned;
|
||||
} else if (v.alwaysShow) {
|
||||
isPinned = true;
|
||||
}
|
||||
actions.push(this.instantiationService.createInstance(PinUnpinTabAction, v.id, this.dashboardService.getUnderlyingUri(), isPinned));
|
||||
|
||||
const config = v as TabConfig;
|
||||
config.context = this.context;
|
||||
config.editable = false;
|
||||
config.canClose = true;
|
||||
config.actions = actions;
|
||||
this.addNewTab(config);
|
||||
return config;
|
||||
});
|
||||
|
||||
if (openLastTab) {
|
||||
// put this immediately on the stack so that is ran *after* the tab is rendered
|
||||
setTimeout(() => {
|
||||
const selectedLastTab = selectedTabs.pop();
|
||||
this._panel.selectTab(selectedLastTab.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private initTabComponents(value: IDashboardTab): { id: string; title: string; container: object; alwaysShow: boolean; } {
|
||||
const containerResult = dashboardHelper.getDashboardContainer(value.container);
|
||||
if (!containerResult.result) {
|
||||
return { id: value.id, title: value.title, container: { 'error-container': undefined }, alwaysShow: value.alwaysShow };
|
||||
}
|
||||
const key = Object.keys(containerResult.container)[0];
|
||||
if (key === WIDGETS_CONTAINER || key === GRID_CONTAINER) {
|
||||
let configs = <WidgetConfig[]>Object.values(containerResult.container)[0];
|
||||
this._configModifiers.forEach(cb => {
|
||||
configs = cb.apply(this, [configs, this, this.context]);
|
||||
});
|
||||
this._gridModifiers.forEach(cb => {
|
||||
configs = cb.apply(this, [configs]);
|
||||
});
|
||||
if (key === WIDGETS_CONTAINER) {
|
||||
return { id: value.id, title: value.title, container: { 'widgets-container': configs }, alwaysShow: value.alwaysShow };
|
||||
}
|
||||
else {
|
||||
return { id: value.id, title: value.title, container: { 'grid-container': configs }, alwaysShow: value.alwaysShow };
|
||||
}
|
||||
}
|
||||
return { id: value.id, title: value.title, container: containerResult.container, alwaysShow: value.alwaysShow };
|
||||
}
|
||||
|
||||
protected getContentType(tab: TabConfig): string {
|
||||
return tab.container ? Object.keys(tab.container)[0] : '';
|
||||
}
|
||||
|
||||
private addNewTab(tab: TabConfig): void {
|
||||
const existedTab = this.tabs.find(i => i.id === tab.id);
|
||||
if (!existedTab) {
|
||||
this.tabs.push(tab);
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
private getProperties(): Array<WidgetConfig> {
|
||||
const properties = this.dashboardService.getSettings<IPropertiesConfig[] | string | boolean>([this.context, 'properties'].join('.'));
|
||||
this._propertiesConfigLocation = 'default';
|
||||
if (types.isUndefinedOrNull(properties)) {
|
||||
return [this.propertiesWidget];
|
||||
} else if (types.isBoolean(properties)) {
|
||||
return properties ? [this.propertiesWidget] : [];
|
||||
} else if (types.isString(properties) && properties === 'collapsed') {
|
||||
return [this.propertiesWidget];
|
||||
} else if (types.isArray(properties)) {
|
||||
return properties.map((item) => {
|
||||
const retVal = Object.assign({}, this.propertiesWidget);
|
||||
retVal.edition = item.edition;
|
||||
retVal.provider = item.provider;
|
||||
retVal.widget = { 'properties-widget': { properties: item.properties } };
|
||||
return retVal;
|
||||
});
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public refresh(refreshConfig: boolean = false): void {
|
||||
if (refreshConfig) {
|
||||
this.init();
|
||||
} else {
|
||||
if (this._tabs) {
|
||||
this._tabs.forEach(tabContent => {
|
||||
tabContent.refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enableEdit(): void {
|
||||
if (this._tabs) {
|
||||
this._tabs.forEach(tabContent => {
|
||||
tabContent.enableEdit();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public handleTabChange(tab: TabComponent): void {
|
||||
this._cd.detectChanges();
|
||||
const localtab = this._tabs.find(i => i.id === tab.identifier);
|
||||
this._editEnabled.fire(localtab.editable);
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
|
||||
public handleTabClose(tab: TabComponent): void {
|
||||
const index = this.tabs.findIndex(i => i.id === tab.identifier);
|
||||
this.tabs.splice(index, 1);
|
||||
this.angularEventingService.sendAngularEvent(this.dashboardService.getUnderlyingUri(), AngularEventType.CLOSE_TAB, { id: tab.identifier });
|
||||
}
|
||||
}
|
||||
25
src/sql/workbench/parts/dashboard/common/dashboardPage.css
Normal file
25
src/sql/workbench/parts/dashboard/common/dashboardPage.css
Normal file
@@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
dashboard-page {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
dashboard-page .monaco-scrollable-element {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
dashboard-page .dashboard-panel .tab-header .action-item .action-label.unpin,
|
||||
dashboard-page .dashboard-panel .tab-header .action-item .action-label.pin {
|
||||
padding: 6px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
dashboard-page .dashboard-panel .tab-header .action-item .action-label.close {
|
||||
padding: 5px;
|
||||
}
|
||||
24
src/sql/workbench/parts/dashboard/common/dashboardPanel.css
Normal file
24
src/sql/workbench/parts/dashboard/common/dashboardPanel.css
Normal file
@@ -0,0 +1,24 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 > .monaco-scrollable-element > .tabList .tab-header .tab > .tabLabel.active {
|
||||
border-bottom: 0px solid;
|
||||
}
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel .tabList .tab .tabLabel {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .title-actions,
|
||||
panel.dashboard-panel > .tabbedPanel > .title > .monaco-scrollable-element > .tabList .tab-header {
|
||||
box-sizing: border-box;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
115
src/sql/workbench/parts/dashboard/common/dashboardPanelStyles.ts
Normal file
115
src/sql/workbench/parts/dashboard/common/dashboardPanelStyles.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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, EDITOR_GROUP_BORDER
|
||||
} from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder } 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};
|
||||
outline-color: ${tabActiveBackground};
|
||||
}
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel.horizontal > .title .tabList .tab-header.active {
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel.vertical > .title .tabList .tab-header.active {
|
||||
border-right-color: transparent;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
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 tabBorder = theme.getColor(TAB_BORDER);
|
||||
if (tabBorder) {
|
||||
collector.addRule(`
|
||||
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab-header {
|
||||
border-right-color: ${tabBorder};
|
||||
border-bottom-color: ${tabBorder};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// 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: ${tabBorder};
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
panel.dashboard-panel > .tabbedPanel.vertical > .title {
|
||||
border-right-color: ${tabBorder};
|
||||
border-right-width: 1px;
|
||||
border-right-style: solid;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const divider = theme.getColor(EDITOR_GROUP_BORDER);
|
||||
if (divider) {
|
||||
collector.addRule(`
|
||||
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab-header {
|
||||
border-right-width: 1px;
|
||||
border-right-style: solid;
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,147 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { localize } from 'vs/nls';
|
||||
import * as types from 'vs/base/common/types';
|
||||
|
||||
import * as Constants from 'sql/platform/connection/common/constants';
|
||||
import { registerTab } from 'sql/platform/dashboard/common/dashboardRegistry';
|
||||
import { generateContainerTypeSchemaProperties } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
|
||||
import { NAV_SECTION, validateNavSectionContributionAndRegisterIcon } from 'sql/workbench/parts/dashboard/containers/dashboardNavSection.contribution';
|
||||
import { WIDGETS_CONTAINER, validateWidgetContainerContribution } from 'sql/workbench/parts/dashboard/containers/dashboardWidgetContainer.contribution';
|
||||
import { GRID_CONTAINER, validateGridContainerContribution } from 'sql/workbench/parts/dashboard/containers/dashboardGridContainer.contribution';
|
||||
|
||||
export interface IDashboardTabContrib {
|
||||
id: string;
|
||||
title: string;
|
||||
container: object;
|
||||
provider: string | string[];
|
||||
when?: string;
|
||||
description?: string;
|
||||
alwaysShow?: boolean;
|
||||
isHomeTab?: boolean;
|
||||
}
|
||||
|
||||
const tabSchema: IJSONSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: localize('azdata.extension.contributes.dashboard.tab.id', "Unique identifier for this tab. Will be passed to the extension for any requests.")
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
description: localize('azdata.extension.contributes.dashboard.tab.title', "Title of the tab to show the user.")
|
||||
},
|
||||
description: {
|
||||
description: localize('azdata.extension.contributes.dashboard.tab.description', "Description of this tab that will be shown to the user."),
|
||||
type: 'string'
|
||||
},
|
||||
when: {
|
||||
description: localize('azdata.extension.contributes.tab.when', 'Condition which must be true to show this item'),
|
||||
type: 'string'
|
||||
},
|
||||
provider: {
|
||||
description: localize('azdata.extension.contributes.tab.provider', 'Defines the connection types this tab is compatible with. Defaults to "MSSQL" if not set'),
|
||||
type: ['string', 'array']
|
||||
|
||||
},
|
||||
container: {
|
||||
description: localize('azdata.extension.contributes.dashboard.tab.container', "The container that will be displayed in this tab."),
|
||||
type: 'object',
|
||||
properties: generateContainerTypeSchemaProperties()
|
||||
},
|
||||
alwaysShow: {
|
||||
description: localize('azdata.extension.contributes.dashboard.tab.alwaysShow', "Whether or not this tab should always be shown or only when the user adds it."),
|
||||
type: 'boolean'
|
||||
},
|
||||
isHomeTab: {
|
||||
description: localize('azdata.extension.contributes.dashboard.tab.isHomeTab', "Whether or not this tab should be used as the Home tab for a connection type."),
|
||||
type: 'boolean'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const tabContributionSchema: IJSONSchema = {
|
||||
description: localize('azdata.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[]>({ extensionPoint: 'dashboard.tabs', jsonSchema: tabContributionSchema }).setHandler(extensions => {
|
||||
|
||||
function handleCommand(tab: IDashboardTabContrib, extension: IExtensionPointUser<any>) {
|
||||
let { description, container, provider, title, when, id, alwaysShow, isHomeTab } = tab;
|
||||
|
||||
// If always show is not specified, set it to true by default.
|
||||
if (!types.isBoolean(alwaysShow)) {
|
||||
alwaysShow = true;
|
||||
}
|
||||
const publisher = extension.description.publisher;
|
||||
if (!title) {
|
||||
extension.collector.error(localize('dashboardTab.contribution.noTitleError', 'No title specified for extension.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!description) {
|
||||
extension.collector.warn(localize('dashboardTab.contribution.noDescriptionWarning', 'No description specified to show.'));
|
||||
}
|
||||
|
||||
if (!container) {
|
||||
extension.collector.error(localize('dashboardTab.contribution.noContainerError', 'No container specified for extension.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!provider) {
|
||||
// Use a default. Consider warning extension developers about this in the future if in development mode
|
||||
provider = Constants.mssqlProviderName;
|
||||
// Cannot be a home tab if it did not specify a provider
|
||||
isHomeTab = false;
|
||||
}
|
||||
|
||||
if (Object.keys(container).length !== 1) {
|
||||
extension.collector.error(localize('dashboardTab.contribution.moreThanOneDashboardContainersError', 'Exactly 1 dashboard container must be defined per space'));
|
||||
return;
|
||||
}
|
||||
|
||||
let result = true;
|
||||
const containerkey = Object.keys(container)[0];
|
||||
const containerValue = Object.values(container)[0];
|
||||
|
||||
switch (containerkey) {
|
||||
case WIDGETS_CONTAINER:
|
||||
result = validateWidgetContainerContribution(extension, containerValue);
|
||||
break;
|
||||
case GRID_CONTAINER:
|
||||
result = validateGridContainerContribution(extension, containerValue);
|
||||
break;
|
||||
case NAV_SECTION:
|
||||
result = validateNavSectionContributionAndRegisterIcon(extension, containerValue);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
registerTab({ description, title, container, provider, when, id, alwaysShow, publisher, isHomeTab });
|
||||
}
|
||||
}
|
||||
|
||||
for (const extension of extensions) {
|
||||
const { value } = extension;
|
||||
if (Array.isArray<IDashboardTabContrib>(value)) {
|
||||
for (const command of value) {
|
||||
handleCommand(command, extension);
|
||||
}
|
||||
} else {
|
||||
handleCommand(value, extension);
|
||||
}
|
||||
}
|
||||
});
|
||||
72
src/sql/workbench/parts/dashboard/common/dashboardWidget.ts
Normal file
72
src/sql/workbench/parts/dashboard/common/dashboardWidget.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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>;
|
||||
actionsContext?: any;
|
||||
refresh?: () => void;
|
||||
layout?: () => void;
|
||||
}
|
||||
|
||||
export const WIDGET_CONFIG = new InjectionToken<WidgetConfig>('widget_config');
|
||||
|
||||
export interface WidgetConfig {
|
||||
id?: string; // used to track the widget lifespan operations
|
||||
name?: string;
|
||||
icon?: string;
|
||||
context: string;
|
||||
provider: string | Array<string>;
|
||||
edition: number | Array<number>;
|
||||
when?: string;
|
||||
gridItemConfig?: NgGridItemConfig;
|
||||
widget: Object;
|
||||
background_color?: string;
|
||||
border?: string;
|
||||
fontSize?: string;
|
||||
fontWeight?: string;
|
||||
padding?: string;
|
||||
}
|
||||
|
||||
export interface TabConfig extends IDashboardTab {
|
||||
context: string;
|
||||
originalConfig: Array<WidgetConfig>;
|
||||
editable: boolean;
|
||||
canClose: boolean;
|
||||
actions?: Array<Action>;
|
||||
iconClass?: string;
|
||||
}
|
||||
|
||||
export type IUserFriendlyIcon = string | { light: string; dark: string; };
|
||||
|
||||
export interface NavSectionConfig {
|
||||
id: string;
|
||||
title: string;
|
||||
iconClass?: string;
|
||||
icon?: IUserFriendlyIcon;
|
||||
container: object;
|
||||
}
|
||||
|
||||
export interface TabSettingConfig {
|
||||
tabId: string;
|
||||
isPinned: boolean;
|
||||
}
|
||||
|
||||
export abstract class DashboardWidget extends Disposable implements OnDestroy {
|
||||
protected _config: WidgetConfig;
|
||||
|
||||
get actions(): Array<Action> {
|
||||
return [];
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
45
src/sql/workbench/parts/dashboard/common/interfaces.ts
Normal file
45
src/sql/workbench/parts/dashboard/common/interfaces.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { OnDestroy } from '@angular/core';
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||
import { SingleConnectionManagementService } from 'sql/services/common/commonServiceInterface.service';
|
||||
|
||||
export enum Conditional {
|
||||
'equals',
|
||||
'notEquals',
|
||||
'greaterThanOrEquals',
|
||||
'greaterThan',
|
||||
'lessThanOrEquals',
|
||||
'lessThan',
|
||||
'always'
|
||||
}
|
||||
|
||||
export abstract class DashboardTab extends TabChild implements OnDestroy {
|
||||
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
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export interface IConfigModifierCollection {
|
||||
connectionManagementService: SingleConnectionManagementService;
|
||||
contextKeyService: IContextKeyService;
|
||||
}
|
||||
Reference in New Issue
Block a user