dashboard improvement (#9730)

* dashboard improvement - WIP (#8836)

* wip

* wip

* tabgroup

* tab group

* agent views

* clean up

* formats

* feedback

* fix error

* contribute top level server/db dashboard tab (#8868)

* tabbedPanel component (#8861)

* tabbed panel

* tabbed panel

* fix errors

* revert main.ts changes

* use margin

* address comments

* remove orientation property

* content tab group (#8878)

* add databases tab

* use more extensible approach

* remove unnecessary code

* add when expression

* objects tab for database dashboard (#8892)

* fix build errors

* fix build error

* Dashboard toolbar (#9118)

* remove old toolbar with only edit and refresh

* remove tasks widgets from server and databases dashboards

* adding toolbar to dashboardpage and clicking new query works

* restore and new notebook now do something

* add backup to toolbar for database dashboards

* new notebook connects to db

* only show backup and restore for non-azure

* new backup and restore svgs

* clean up

* got toolbar actions to show up from contribution

* some cleanup and add database dashboard toolbar contributions

* don't show all tasks when there should be no tasks

* fix toolbar showing multiple times when switching opening another dashboard from OE

* only show toolbar for home page

* update to new icons - same icons for light and dark theme

* don't show separator if there aren't any actions

* read toolbar actions from tasks-widget

* remove tasks widget from home dashboard page

* show extension's actions in toolbar

* clean up

* more cleaning up

* fix extension actions not always loading the first time

* add configure dashboard

* remove old edit icon css

* change tasks back to original order

* make sure tasks widget is the one being removed

* collapsible tab panel (#9221)

* collapsible vertical tab panel

* fix lint error

* comments batch 1

* pr comments

* update new query icon (#9351)

* Update toolbar actions (#9313)

* remove edit and configure dashboard and add refresh to toolbar for other dashboard pages too

* Add refresh for tabs that have container type with refresh implemented

* change refresh to only refresh the current tab

* remove map for tab to actions

* add back configure dashboard to home toolbar

* check if index is -1 before trying to remove tasks widget from widgets

* Move objects widget back to database home tab (#9432)

* move objects widget back to database home tab and reorder toolbar

* change order of actions back to previous order

* Allow extensions to add actions to home toolbar (#9269)

* add support for extensions to add actions to home toolbar

* fix spacing

* use menu contribution point

* undo previous changes that added dashboardToolbarHomeAction contribution

* remove home from name

* add context key for tab name

* allow actions to also be added to the toolbar of other tabs

* add extension contributed actions even if no tasks-widget

* fix refresh being added twice after merging

* hide the tab list when collapsed (#9529)

* update the order of css selectors (#9606)

* Update dashboard style to be closer to mockups (#9570)

* update style to be closer to mockups

* tab panel styling

* change back tab styling for tabs in a tab contributed by an extension

* change color of borders when theme changes

* set dark theme active tab background to same as OE for now

* update border colors

* move colors to theme file

* fix a few issues (#9690)

* couple fixes

* comments

* small dashboard toolbar fixes  (#9695)

* fix backup icon in toolbar

* fix database page toolbar border color

* add back center center in common-icons.css (#9703)

* change padding so bottom border shows again (#9710)

* tab panel fixes (#9724)

* tab panel fixes

* fix package.nls.json

* feedbacks (#9761)

* feedbacks

* remove comments

Co-authored-by: Kim Santiago <31145923+kisantia@users.noreply.github.com>
This commit is contained in:
Alan Ren
2020-03-26 20:41:09 -07:00
committed by GitHub
parent fa43e26650
commit be83b31e37
77 changed files with 1187 additions and 302 deletions

View File

@@ -10,8 +10,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { IModelViewService } from 'sql/platform/modelComponents/browser/modelViewService';
import { IItemConfig, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IModelView } from 'sql/platform/model/browser/modelViewService';
import { IItemConfig, IComponentShape, IModelView } from 'sql/platform/model/browser/modelViewService';
import { find } from 'vs/base/common/arrays';

View File

@@ -249,6 +249,13 @@ class ModelBuilderImpl implements azdata.ModelBuilder {
return builder;
}
tabbedPanel(): azdata.TabbedPanelComponentBuilder {
let id = this.getNextComponentId();
let builder = new TabbedPanelComponentBuilder(new TabbedPanelComponentWrapper(this._proxy, this._handle, id));
this._componentBuilders.set(id, builder);
return builder;
}
getComponentBuilder<T extends azdata.Component>(component: ComponentWrapper, id: string): ComponentBuilderImpl<T> {
let componentBuilder: ComponentBuilderImpl<T> = new ComponentBuilderImpl<T>(component);
this._componentBuilders.set(id, componentBuilder);
@@ -486,6 +493,32 @@ class ToolbarContainerBuilder extends GenericContainerBuilder<azdata.ToolbarCont
}
}
class TabbedPanelComponentBuilder extends ContainerBuilderImpl<azdata.TabbedPanelComponent, azdata.TabbedPanelLayout, any> implements azdata.TabbedPanelComponentBuilder {
withTabs(items: (azdata.Tab | azdata.TabGroup)[]): azdata.ContainerBuilder<azdata.TabbedPanelComponent, azdata.TabbedPanelLayout, any> {
const itemConfigs = [];
items.forEach(item => {
if (item && 'tabs' in item) {
item.tabs.forEach(tab => {
itemConfigs.push(this.toItemConfig(tab.content, tab.title, tab.id, item.title));
});
} else {
const tab = <azdata.Tab>item;
itemConfigs.push(this.toItemConfig(tab.content, tab.title, tab.id));
}
});
this._component.itemConfigs = itemConfigs;
return this;
}
toItemConfig(content: azdata.Component, title: string, id?: string, group?: string): InternalItemConfig {
return new InternalItemConfig(content as ComponentWrapper, {
title: title,
group: group,
id: id
});
}
}
class LoadingComponentBuilder extends ComponentBuilderImpl<azdata.LoadingComponent> implements azdata.LoadingComponentBuilder {
withItem(component: azdata.Component) {
this.component().component = component;
@@ -1688,6 +1721,19 @@ class RadioCardGroupComponentWrapper extends ComponentWrapper implements azdata.
}
}
class TabbedPanelComponentWrapper extends ComponentWrapper implements azdata.TabbedPanelComponent {
constructor(proxy: MainThreadModelViewShape, handle: number, id: string) {
super(proxy, handle, ModelComponentTypes.TabbedPanel, id);
this.properties = {};
this._emitterMap.set(ComponentEventType.onDidChange, new Emitter<string>());
}
public get onTabChanged(): vscode.Event<string> {
let emitter = this._emitterMap.get(ComponentEventType.onDidChange);
return emitter && emitter.event;
}
}
class GroupContainerComponentWrapper extends ComponentWrapper implements azdata.GroupContainer {
constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) {
super(proxy, handle, type, id);

View File

@@ -552,7 +552,8 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
AgentSubSystem: sqlExtHostTypes.AgentSubSystem,
ExtensionNodeType: sqlExtHostTypes.ExtensionNodeType,
ColumnSizingMode: sqlExtHostTypes.ColumnSizingMode,
DatabaseEngineEdition: sqlExtHostTypes.DatabaseEngineEdition
DatabaseEngineEdition: sqlExtHostTypes.DatabaseEngineEdition,
TabOrientation: sqlExtHostTypes.TabOrientation
};
}
};

View File

@@ -174,6 +174,7 @@ export enum ModelComponentTypes {
Hyperlink,
Image,
RadioCardGroup,
TabbedPanel,
Separator
}
@@ -828,3 +829,13 @@ export type QueryEventType =
| 'queryStop'
| 'executionPlan'
| 'visualize';
export enum TabOrientation {
Vertical = 'vertical',
Horizontal = 'horizontal'
}
export interface TabbedPanelLayout {
orientation: TabOrientation;
}

View File

@@ -73,7 +73,7 @@ export class InsightAction extends Action {
export class ConfigureDashboardAction extends Task {
public static readonly ID = 'configureDashboard';
public static readonly LABEL = nls.localize('configureDashboard', "Learn How To Configure The Dashboard");
public static readonly LABEL = nls.localize('configureDashboardLearnMore', "Learn More");
public static readonly ICON = 'configure-dashboard';
private static readonly configHelpUri = 'https://aka.ms/sqldashboardconfig';

View File

@@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.tabbedpanel-component {
width: 100%;
height: 100%;
}
.tabbedpanel-component .tabbedPanel .tabContainer {
border-style: solid;
border-color: rgb(237, 235, 233);
border-width: 0px;
}
.vs-dark .tabbedpanel-component .tabbedPanel .tabContainer, .hc-black .tabbedpanel-component .tabbedPanel .tabContainer {
border-color: rgba(128, 128, 128, 0.5);;
}
.tabbedpanel-component .tabbedPanel.vertical .tabContainer {
border-right-width: 1px;
}
.tabbedpanel-component .tabbedPanel.horizontal .tabContainer {
border-bottom-width: 1px;
}
.tabbedpanel-component .tabbedPanel .tab>.tabLabel.active {
border-bottom: 0px solid;
}
.tabbedpanel-component .tabbedPanel.vertical .tabList .tab-header {
line-height: 35px;
}
.tabbedpanel-component .tabbedPanel.horizontal .tabList .tab-header {
border-color: rgb(214, 214, 214);
border-width: 0 1px 0 0;
border-style: solid;
}
.tabbedpanel-component .tabbedPanel .tabList .tab .tabLabel {
font-weight: normal;
}
.tabbedpanel-component .tabList .tab-header.active {
background-color: rgb(237, 235, 233);
}
.vs-dark .tabbedpanel-component .tabList .tab-header.active, .hc-black .tabbedpanel-component .tabList .tab-header.active {
background-color: rgba(128, 128, 128, 0.5);
}

View File

@@ -0,0 +1,20 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div class="tabbedpanel-component">
<panel (onTabChange)="handleTabChange($event)">
<tab [visibilityType]="'visibility'" *ngFor="let tab of tabs" [title]="tab.title" class="fullsize"
[identifier]="tab.id" [type]="tab.type">
<ng-template>
<ng-container *ngIf="tab.type === 'tab'">
{{tab.title}}
<model-component-wrapper [descriptor]="tab.content" [modelStore]="modelStore">
</model-component-wrapper>
</ng-container>
</ng-template>
</tab>
</panel>
</div>

View File

@@ -0,0 +1,96 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, forwardRef, Inject, Input, OnDestroy, ViewChild } from '@angular/core';
import { NavigationBarLayout, PanelComponent } from 'sql/base/browser/ui/panel/panel.component';
import { TabType } from 'sql/base/browser/ui/panel/tab.component';
// eslint-disable-next-line code-import-patterns
import { TabOrientation, TabbedPanelLayout } from 'sql/workbench/api/common/sqlExtHostTypes';
import { ContainerBase } from 'sql/workbench/browser/modelComponents/componentBase';
import { ComponentEventType, IComponent, IComponentDescriptor, IModelStore } from 'sql/platform/dashboard/browser/interfaces';
import 'vs/css!./media/tabbedPanel';
export interface TabConfig {
title: string;
id?: string;
group: string;
}
interface Tab {
title: string;
content?: IComponentDescriptor;
id?: string;
type: TabType;
}
@Component({
templateUrl: decodeURI(require.toUrl('./tabbedPanel.component.html'))
})
export default class TabbedPanelComponent extends ContainerBase<TabConfig> implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
@ViewChild(PanelComponent) private _panel: PanelComponent;
private _tabs: Tab[] = [];
private _itemIndexToProcess: number = 0;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef) {
super(changeRef, el);
}
ngOnInit(): void {
this.baseInit();
}
ngAfterViewInit(): void {
}
ngOnDestroy(): void {
this.baseDestroy();
}
setLayout(layout: TabbedPanelLayout): void {
this._panel.options = {
showTabsWhenOne: true,
layout: layout.orientation === TabOrientation.Horizontal ? NavigationBarLayout.horizontal : NavigationBarLayout.vertical,
showIcon: false
};
}
handleTabChange(event: any): void {
this.fireEvent({
eventType: ComponentEventType.onDidChange,
args: event.identifier
});
}
get tabs(): Tab[] {
if (this.items.length > this._itemIndexToProcess) {
let currentGroup: string | undefined = this.items.length === 1 ? undefined : this.items[this._itemIndexToProcess - 1].config.group;
for (let i = this._itemIndexToProcess; i < this.items.length; i++) {
const item = this.items[i];
if (item.config.group !== currentGroup) {
currentGroup = item.config.group;
if (currentGroup) {
this._tabs.push({
title: currentGroup,
type: 'group-header'
});
}
}
this._tabs.push({
title: item.config.title,
id: item.config.id,
content: item.descriptor,
type: 'tab'
});
}
this._itemIndexToProcess = this.items.length;
}
return this._tabs;
}
}

View File

@@ -35,7 +35,7 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh
// tslint:disable-next-line:no-unused-variable
private readonly panelOpt: IPanelOptions = {
layout: NavigationBarLayout.vertical
layout: NavigationBarLayout.horizontal
};
// a set of config modifiers

View File

@@ -6,15 +6,12 @@
import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import * as nls from 'vs/nls';
import { createCSSRule, asCSSUrl } from 'vs/base/browser/dom';
import { IdGenerator } from 'vs/base/common/idGenerator';
import * as resources from 'vs/base/common/resources';
import { NavSectionConfig, IUserFriendlyIcon } from 'sql/workbench/contrib/dashboard/browser/core/dashboardWidget';
import { NavSectionConfig } from 'sql/workbench/contrib/dashboard/browser/core/dashboardWidget';
import { registerContainerType, generateNavSectionContainerTypeSchemaProperties } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
import { WIDGETS_CONTAINER, validateWidgetContainerContribution } from 'sql/workbench/contrib/dashboard/browser/containers/dashboardWidgetContainer.contribution';
import { GRID_CONTAINER, validateGridContainerContribution } from 'sql/workbench/contrib/dashboard/browser/containers/dashboardGridContainer.contribution';
import { values } from 'vs/base/common/collections';
import { createCSSRuleForIcon, isValidIcon } from 'sql/workbench/contrib/dashboard/browser/dashboardIconUtil';
export const NAV_SECTION = 'nav-section';
@@ -64,38 +61,6 @@ const NavSectionSchema: IJSONSchema = {
registerContainerType(NAV_SECTION, NavSectionSchema);
function isValidIcon(icon: IUserFriendlyIcon, extension: IExtensionPointUser<any>): boolean {
if (typeof icon === 'undefined') {
return false;
}
if (typeof icon === 'string') {
return true;
} else if (typeof icon.dark === 'string' && typeof icon.light === 'string') {
return true;
}
extension.collector.error(nls.localize('opticon', "property `icon` can be omitted or must be either a string or a literal like `{dark, light}`"));
return false;
}
const ids = new IdGenerator('contrib-dashboardNavSection-icon-');
function createCSSRuleForIcon(icon: IUserFriendlyIcon, extension: IExtensionPointUser<any>): string {
let iconClass: string;
if (icon) {
iconClass = ids.nextId();
if (typeof icon === 'string') {
const path = resources.joinPath(extension.description.extensionLocation, icon);
createCSSRule(`.codicon.${iconClass}`, `background-image: ${asCSSUrl(path)}`);
} else {
const light = resources.joinPath(extension.description.extensionLocation, icon.light);
const dark = resources.joinPath(extension.description.extensionLocation, icon.dark);
createCSSRule(`.codicon.${iconClass}`, `background-image: ${asCSSUrl(light)}`);
createCSSRule(`.vs-dark .codicon.${iconClass}, .hc-black .codicon.${iconClass}`, `background-image: ${asCSSUrl(dark)}`);
}
}
return iconClass;
}
export function validateNavSectionContributionAndRegisterIcon(extension: IExtensionPointUser<any>, navSectionConfigs: NavSectionConfig[]): boolean {
let result = true;
navSectionConfigs.forEach(section => {

View File

@@ -7,9 +7,10 @@
<div style="display: flex; flex-flow: column; overflow: hidden; height: 100%; width: 100%">
<div #header>
<div style="display: flex; flex: 0 0; padding: 3px 0 3px 0;flex: 0 0;">
<div style="display: flex; flex: 0 0; padding: 3px 0 3px 0;">
<span *ngIf="_config.icon" [ngClass]="['icon', _config.icon]" style="display: inline-block; padding: 10px; margin-left: 5px"></span>
<span *ngIf="_config.name" style="margin-left: 5px;flex:1 1;">{{_config.name}}</span>
<span *ngIf="!_config.name" style="flex:1 1"></span>
<span #actionbar style="flex: 0 0 auto; align-self: end"></span>
</div>
</div>

View File

@@ -162,7 +162,7 @@ export class DashboardWidgetWrapper extends AngularDisposable implements OnInit
// If _config.name is not set, set it to _config.widget.name
if (!this._config.name) {
const widget = values(this._config.widget)[0];
if (widget.name) {
if (widget && widget.name) {
this._config.name = widget.name;
}
}
@@ -221,7 +221,7 @@ export class DashboardWidgetWrapper extends AngularDisposable implements OnInit
private updateTheme(theme: IColorTheme): void {
const el = <HTMLElement>this._ref.nativeElement;
const headerEl: HTMLElement = this.header.nativeElement;
let borderColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true);
let borderColor = theme.getColor(themeColors.DASHBOARD_BORDER);
let backgroundColor = theme.getColor(colors.editorBackground, true);
const foregroundColor = theme.getColor(themeColors.SIDE_BAR_FOREGROUND, true);
const border = theme.getColor(colors.contrastBorder, true);
@@ -249,18 +249,12 @@ export class DashboardWidgetWrapper extends AngularDisposable implements OnInit
el.style.borderWidth = '1px';
el.style.borderStyle = 'solid';
} else if (borderColor) {
borderString = borderColor.toString();
el.style.border = '3px solid ' + borderColor.toString();
borderString = borderColor;
el.style.border = '1px solid ' + borderColor;
} else {
el.style.border = 'none';
}
if (borderString) {
headerEl.style.backgroundColor = borderString;
} else {
headerEl.style.backgroundColor = '';
}
if (this._config.fontSize) {
headerEl.style.fontSize = this._config.fontSize;
}

View File

@@ -13,6 +13,7 @@ import { INewDashboardTabDialogService } from 'sql/workbench/services/dashboard/
import { IDashboardTab } from 'sql/workbench/services/dashboard/browser/common/interfaces';
import { find, firstIndex } from 'vs/base/common/arrays';
import { CellContext } from 'sql/workbench/contrib/notebook/browser/cellViews/codeActions';
import { ILogService } from 'vs/platform/log/common/log';
export class EditDashboardAction extends Action {
@@ -25,7 +26,7 @@ export class EditDashboardAction extends Action {
constructor(
private editFn: () => void,
private context: any //this
private context: any
) {
super(EditDashboardAction.ID, EditDashboardAction.EDITLABEL, EditDashboardAction.ICON);
}
@@ -59,7 +60,7 @@ export class RefreshWidgetAction extends Action {
constructor(
private refreshFn: () => void,
private context: any // this
private context: any
) {
super(RefreshWidgetAction.ID, RefreshWidgetAction.LABEL, RefreshWidgetAction.ICON);
}
@@ -74,6 +75,29 @@ export class RefreshWidgetAction extends Action {
}
}
export class ToolbarAction extends Action {
constructor(
id: string,
label: string,
cssClass: string,
private runFn: (id: string) => void,
private context: any, // this
private logService: ILogService
) {
super(id, label, cssClass);
}
run(): Promise<boolean> {
try {
this.runFn.apply(this.context, [this.id]);
return Promise.resolve(true);
} catch (e) {
this.logService.error(e);
return Promise.resolve(false);
}
}
}
export class ToggleMoreWidgetAction extends Action {
private static readonly ID = 'toggleMore';

View File

@@ -6,8 +6,10 @@
-->
<panel class="dashboard-panel" (onTabChange)="handleTabChange($event)" (onTabClose)="handleTabClose($event)"
[actions]="panelActions">
<div #toolbar [style.display]="showToolbar ? 'block': 'none'" class="editor-toolbar">
</div>
<tab [visibilityType]="'visibility'" *ngFor="let tab of tabs" [title]="tab.title" class="fullsize"
[identifier]="tab.id" [canClose]="tab.canClose" [actions]="tab.actions">
[identifier]="tab.id" [canClose]="tab.canClose" [actions]="tab.actions" [type]="tab.type" [iconClass]="tab.iconClass">
<ng-template>
<dashboard-home-container *ngIf="tab.id === 'homeTab'; else not_home" [properties]="propertiesWidget"
[tab]="tab">
@@ -30,4 +32,4 @@
</ng-template>
</ng-template>
</tab>
</panel>
</panel>

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./dashboardPage';
import 'vs/css!sql/media/icons/common-icons';
import 'sql/workbench/contrib/dashboard/browser/core/dashboardPanelStyles';
import { Component, Inject, forwardRef, ViewChild, ElementRef, ViewChildren, QueryList, ChangeDetectorRef } from '@angular/core';
@@ -12,10 +13,9 @@ import { DashboardServiceInterface } from 'sql/workbench/contrib/dashboard/brows
import { CommonServiceInterface, SingleConnectionManagementService } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service';
import { WidgetConfig, TabConfig, TabSettingConfig } from 'sql/workbench/contrib/dashboard/browser/core/dashboardWidget';
import { IPropertiesConfig } from 'sql/workbench/contrib/dashboard/browser/pages/serverDashboardPage.contribution';
import { PanelComponent } from 'sql/base/browser/ui/panel/panel.component';
import { PanelComponent, NavigationBarLayout } from 'sql/base/browser/ui/panel/panel.component';
import { IDashboardRegistry, Extensions as DashboardExtensions } from 'sql/workbench/contrib/dashboard/browser/dashboardRegistry';
import { IDashboardTab } from 'sql/workbench/services/dashboard/browser/common/interfaces';
import { PinUnpinTabAction, AddFeatureTabAction } from './actions';
import { TabComponent, TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { AngularEventType, IAngularEventingService } from 'sql/platform/angularEventing/browser/angularEventingService';
import { DashboardTab, IConfigModifierCollection } from 'sql/workbench/contrib/dashboard/browser/core/interfaces';
@@ -31,17 +31,31 @@ 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 { Action, IAction, IActionViewItem } 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';
import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ILogService } from 'vs/platform/log/common/log';
import { firstIndex, find } from 'vs/base/common/arrays';
import { values } from 'vs/base/common/collections';
import { RefreshWidgetAction, ToolbarAction } from 'sql/workbench/contrib/dashboard/browser/core/actions';
import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar';
import * as DOM from 'vs/base/browser/dom';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { TaskRegistry } from 'sql/workbench/services/tasks/browser/tasksRegistry';
import { MenuRegistry, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
import { fillInActions, LabeledMenuItemActionItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { NAV_SECTION } from 'sql/workbench/contrib/dashboard/browser/containers/dashboardNavSection.contribution';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { DASHBOARD_BORDER } from 'vs/workbench/common/theme';
import { IColorTheme } from 'vs/platform/theme/common/themeService';
const dashboardRegistry = Registry.as<IDashboardRegistry>(DashboardExtensions.DashboardContributions);
const homeTabGroupId = 'home';
@Component({
selector: 'dashboard-page',
@@ -59,12 +73,20 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
@ViewChildren(TabChild) private _tabs: QueryList<DashboardTab>;
@ViewChild(PanelComponent) private _panel: PanelComponent;
@ViewChild('toolbar', { read: ElementRef }) private toolbarContainer: ElementRef;
protected toolbar: Taskbar;
public showToolbar: boolean;
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");
private readonly homeTabId: string = 'homeTab';
private tabToolbarActionsConfig = new Map<string, WidgetConfig>();
private tabContents = new Map<string, string>();
static tabName = new RawContextKey<string>('tabName', undefined);
private _tabName: IContextKey<string>;
// a set of config modifiers
private readonly _configModifiers: Array<(item: Array<WidgetConfig>, collection: IConfigModifierCollection, context: string) => Array<WidgetConfig>> = [
@@ -95,13 +117,23 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
@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,
@Inject(ILogService) private logService: ILogService
@Inject(ILogService) private logService: ILogService,
@Inject(ICommandService) private commandService: ICommandService,
@Inject(IContextKeyService) contextKeyService: IContextKeyService,
@Inject(IMenuService) private menuService: IMenuService,
@Inject(IKeybindingService) private keybindingService: IKeybindingService,
@Inject(IContextMenuService) private contextMenuService: IContextMenuService,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService
) {
super();
this._tabName = DashboardPage.tabName.bindTo(contextKeyService);
}
ngAfterViewInit(): void {
this.updateTheme(this.themeService.getColorTheme());
}
protected init() {
@@ -113,6 +145,12 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
});
} else {
let tempWidgets = this.dashboardService.getSettings<Array<WidgetConfig>>([this.context, 'widgets'].join('.'));
// remove tasks widget because those will be shown in the toolbar
const index = tempWidgets.findIndex(c => c.widget['tasks-widget']);
if (index !== -1) {
tempWidgets.splice(index, 1);
}
this._originalConfig = objects.deepClone(tempWidgets);
let properties = this.getProperties();
this._configModifiers.forEach((cb) => {
@@ -123,9 +161,134 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
tempWidgets = cb.apply(this, [tempWidgets, this._originalConfig]);
});
this.propertiesWidget = properties ? properties[0] : undefined;
this._panel.options = {
showTabsWhenOne: true,
layout: NavigationBarLayout.vertical,
showIcon: true
};
this.createTabs(tempWidgets);
}
this.showToolbar = true;
const homeToolbarConfig = this.dashboardService.getSettings<Array<WidgetConfig>>([this.context, 'widgets'].join('.'))[0].widget['tasks-widget'];
this.tabToolbarActionsConfig.set(this.homeTabId, homeToolbarConfig);
this.createToolbar(this.toolbarContainer.nativeElement, this.homeTabId);
this._register(this.themeService.onDidColorThemeChange((event: IColorTheme) => {
this.updateTheme(event);
}));
}
private getExtensionContributedHomeToolbarContent(content: ITaskbarContent[]): void {
let primary: IAction[] = [];
let secondary: IAction[] = [];
const menu = this.menuService.createMenu(MenuId.DashboardToolbar, this.contextKeyService);
let groups = menu.getActions({ arg: null, shouldForwardArgs: true });
fillInActions(groups, { primary, secondary }, false, (group: string) => group === undefined || group === '');
primary.forEach(a => {
if (a instanceof MenuItemAction) {
// Need to ensure that we don't add the same action multiple times
let foundIndex = firstIndex(content, act => act.action && act.action.id === a.id);
if (foundIndex < 0) {
content.push({ action: a });
}
}
});
if (primary.length > 0) {
let separator: HTMLElement = Taskbar.createTaskbarSeparator();
content.push({ element: separator });
}
}
private hasExtensionContributedToolbarContent(): boolean {
let primary: IAction[] = [];
let secondary: IAction[] = [];
const menu = this.menuService.createMenu(MenuId.DashboardToolbar, this.contextKeyService);
let groups = menu.getActions({ arg: null, shouldForwardArgs: true });
fillInActions(groups, { primary, secondary }, false, (group: string) => group === undefined || group === '');
return primary.length > 0 || secondary.length > 0;
}
private createToolbar(parentElement: HTMLElement, tabName: string): void {
// clear out toolbar
DOM.clearNode(parentElement);
this.toolbar = this._register(new Taskbar(parentElement, { actionViewItemProvider: action => this.createActionItemProvider(action as Action) }));
let content = [];
content = this.getToolbarContent(this.tabToolbarActionsConfig.get(tabName));
if (tabName === this.homeTabId) {
const configureDashboardCommand = MenuRegistry.getCommand('configureDashboard');
const configureDashboardAction = new ToolbarAction(configureDashboardCommand.id, configureDashboardCommand.title.toString(), TaskRegistry.getOrCreateTaskIconClassName(configureDashboardCommand), this.runAction, this, this.logService);
content.push({ action: configureDashboardAction });
}
this.toolbar.setContent(content);
}
private getToolbarContent(toolbarTasks: WidgetConfig): ITaskbarContent[] {
let tasks = TaskRegistry.getTasks();
let content;
if (types.isArray(toolbarTasks) && toolbarTasks.length > 0) {
tasks = toolbarTasks.map(i => {
if (types.isString(i)) {
if (tasks.some(x => x === i)) {
return i;
}
} else {
if (tasks.some(x => x === i.name) && this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(i.when))) {
return i.name;
}
}
return undefined;
}).filter(i => !!i);
content = this.convertTasksToToolbarContent(tasks);
} else {
content = [];
}
// get extension actions contributed to the page's toolbar
this.getExtensionContributedHomeToolbarContent(content);
const refreshAction = new RefreshWidgetAction(this.refresh, this);
content.push({ action: refreshAction });
return content;
}
private convertTasksToToolbarContent(tasks: string[]): ITaskbarContent[] {
let _tasks = tasks.map(i => MenuRegistry.getCommand(i)).filter(v => !!v);
let toolbarActions = [];
_tasks.forEach(a => {
let iconClassName = TaskRegistry.getOrCreateTaskIconClassName(a);
toolbarActions.push(new ToolbarAction(a.id, a.title.toString(), iconClassName, this.runAction, this, this.logService));
});
let content: ITaskbarContent[] = [];
toolbarActions.forEach(a => {
content.push({ action: a });
});
if (content.length > 0) {
let separator: HTMLElement = Taskbar.createTaskbarSeparator();
content.push({ element: separator });
}
return content;
}
private runAction(id: string): Promise<void> {
return this.commandService.executeCommand(id, this.connectionManagementService.connectionInfo.connectionProfile);
}
private createActionItemProvider(action: Action): IActionViewItem {
// Create ActionItem for actions contributed by extensions
if (action instanceof MenuItemAction) {
return new LabeledMenuItemActionItem(action, this.keybindingService, this.contextMenuService, this.notificationService);
}
return undefined;
}
private createTabs(homeWidgets: WidgetConfig[]) {
@@ -140,6 +303,8 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
// Before separating tabs into pinned / shown, ensure that the home tab is always set up as expected
allTabs = this.setAndRemoveHomeTab(allTabs, homeWidgets);
this.loadNewTabs(allTabs.filter((tab) => tab.group === homeTabGroupId));
// If preview features are disabled only show the home tab
const extensionTabsEnabled = this.configurationService.getValue('workbench')['enablePreviewFeatures'];
if (!extensionTabsEnabled) {
@@ -149,36 +314,11 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
// 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.addCustomTabGroups(allTabs);
this.addExtensionsTabGroup(allTabs);
this._tabSettingConfigs.forEach(config => {
if (config.tabId && types.isBoolean(config.isPinned)) {
const tab = find(allTabs, i => i.id === config.tabId);
if (tab) {
if (config.isPinned) {
pinnedDashboardTabs.push(tab);
} else {
// overwrite always show if specify in user settings
const index = firstIndex(alwaysShowTabs, i => i.id === tab.id);
alwaysShowTabs.splice(index, 1);
}
}
}
});
this.panelActions = [];
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 => {
@@ -196,18 +336,67 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
}));
}
/**
* Add the custom tab groups and their child tabs.
* @param allTabs The available tabs
*/
private addCustomTabGroups(allTabs: IDashboardTab[]): void {
dashboardRegistry.tabGroups.forEach((tabGroup) => {
const tabs = allTabs.filter(tab => tab.group === tabGroup.id);
if (tabs.length > 0) {
this.addNewTab({
id: tabGroup.id,
provider: Constants.anyProviderName,
originalConfig: [],
publisher: undefined,
title: tabGroup.title,
context: this.context,
type: 'group-header',
editable: false,
canClose: false,
actions: []
});
this.loadNewTabs(tabs);
}
});
}
/**
* Add the "Extensions" tab group, tabs without a group will be added here.
* @param allTabs The available tabs
*/
private addExtensionsTabGroup(allTabs: IDashboardTab[]): void {
const tabs = allTabs.filter(tab => !tab.group);
if (tabs.length > 0) {
this.addNewTab({
id: 'generalTabGroupHeader',
provider: Constants.anyProviderName,
originalConfig: [],
publisher: undefined,
title: nls.localize('dashboard.generalTabGroupHeader', "General"),
context: this.context,
type: 'group-header',
editable: false,
canClose: false,
actions: []
});
this.loadNewTabs(tabs);
}
}
private setAndRemoveHomeTab(allTabs: IDashboardTab[], homeWidgets: WidgetConfig[]): IDashboardTab[] {
const homeTabConfig: TabConfig = {
id: 'homeTab',
id: this.homeTabId,
provider: Constants.anyProviderName,
publisher: undefined,
title: this.homeTabTitle,
container: { 'widgets-container': homeWidgets },
context: this.context,
originalConfig: this._originalConfig,
originalConfig: [],
editable: true,
canClose: false,
actions: []
actions: [],
iconClass: 'home-tab-icon'
};
const homeTabIndex = firstIndex(allTabs, (tab) => tab.isHomeTab === true);
@@ -232,21 +421,11 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
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 = find(this._tabSettingConfigs, 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;
config.canClose = false;
config.actions = [];
this.addNewTab(config);
return config;
});
@@ -261,12 +440,13 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
}
}
private initTabComponents(value: IDashboardTab): { id: string; title: string; container: object; alwaysShow: boolean; } {
private initTabComponents(value: IDashboardTab): { id: string; title: string; container: object; alwaysShow: boolean; iconClass?: string } {
const containerResult = dashboardHelper.getDashboardContainer(value.container, this.logService);
if (!containerResult.result) {
return { id: value.id, title: value.title, container: { 'error-container': undefined }, alwaysShow: value.alwaysShow };
return { id: value.id, title: value.title, container: { 'error-container': undefined }, alwaysShow: value.alwaysShow, iconClass: value.iconClass };
}
const key = Object.keys(containerResult.container)[0];
this.tabContents.set(value.id, key);
if (key === WIDGETS_CONTAINER || key === GRID_CONTAINER) {
let configs = <WidgetConfig[]>values(containerResult.container)[0];
this._configModifiers.forEach(cb => {
@@ -275,14 +455,22 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
this._gridModifiers.forEach(cb => {
configs = cb.apply(this, [configs]);
});
// remove tasks widget because the tasks will be shown in the toolbar
const index = configs.findIndex(c => c.widget['tasks-widget']);
if (index !== -1) {
this.tabToolbarActionsConfig.set(value.id, configs[index].widget['tasks-widget']);
configs.splice(index, 1);
}
if (key === WIDGETS_CONTAINER) {
return { id: value.id, title: value.title, container: { 'widgets-container': configs }, alwaysShow: value.alwaysShow };
return { id: value.id, title: value.title, container: { 'widgets-container': configs }, alwaysShow: value.alwaysShow, iconClass: value.iconClass };
}
else {
return { id: value.id, title: value.title, container: { 'grid-container': configs }, alwaysShow: value.alwaysShow };
return { id: value.id, title: value.title, container: { 'grid-container': configs }, alwaysShow: value.alwaysShow, iconClass: value.iconClass };
}
}
return { id: value.id, title: value.title, container: containerResult.container, alwaysShow: value.alwaysShow };
return { id: value.id, title: value.title, container: containerResult.container, alwaysShow: value.alwaysShow, iconClass: value.iconClass };
}
protected getContentType(tab: TabConfig): string {
@@ -292,6 +480,9 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
private addNewTab(tab: TabConfig): void {
const existedTab = find(this.tabs, i => i.id === tab.id);
if (!existedTab) {
if (!tab.iconClass && tab.type !== 'group-header') {
tab.iconClass = 'default-tab-icon';
}
this.tabs.push(tab);
this._cd.detectChanges();
}
@@ -323,22 +514,25 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
this.init();
} else {
if (this._tabs) {
this._tabs.forEach(tabContent => {
tabContent.refresh();
});
const tab = this._tabs.find(t => t.id === this._tabName.get());
if (tab) {
tab.refresh();
}
}
}
}
public enableEdit(): void {
if (this._tabs) {
this._tabs.forEach(tabContent => {
tabContent.enableEdit();
});
}
}
public handleTabChange(tab: TabComponent): void {
this._tabName.set(tab.identifier);
const tabContent = this.tabContents.get(tab.identifier);
if (tab.identifier === this.homeTabId || tabContent === WIDGETS_CONTAINER || tabContent === GRID_CONTAINER || tabContent === NAV_SECTION
|| this.hasExtensionContributedToolbarContent()) {
this.showToolbar = true;
this.createToolbar(this.toolbarContainer.nativeElement, tab.identifier);
} else { // hide toolbar
this.showToolbar = false;
}
this._cd.detectChanges();
const localtab = this._tabs.find(i => i.id === tab.identifier);
this._editEnabled.fire(localtab.editable);
@@ -350,4 +544,9 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig
this.tabs.splice(index, 1);
this.angularEventingService.sendAngularEvent(this.dashboardService.getUnderlyingUri(), AngularEventType.CLOSE_TAB, { id: tab.identifier });
}
private updateTheme(theme: IColorTheme): void {
const border = theme.getColor(DASHBOARD_BORDER);
this.toolbarContainer.nativeElement.style.borderBottomColor = border.toString();
}
}

View File

@@ -23,3 +23,43 @@ dashboard-page .dashboard-panel .tab-header .action-item .action-label.pin {
dashboard-page .dashboard-panel .tab-header .action-item .action-label.close {
padding: 5px;
}
dashboard-page .home-tab-icon {
background-image: url("media/home.svg");
}
.vs-dark dashboard-page .home-tab-icon,
.hc-black dashboard-page .home-tab-icon {
background-image: url("media/home_inverse.svg");
}
dashboard-page .default-tab-icon {
background-image: url("media/default.svg");
}
.vs-dark dashboard-page .default-tab-icon,
.hc-black dashboard-page .default-tab-icon {
background-image: url("media/default_inverse.svg");
}
dashboard-page .actions-container .action-item .action-label{
padding-left: 20px;
padding-right: 5px;
background-size: 16px;
background-position: left;
}
dashboard-page .actions-container .taskbarSeparator {
height: 14px;
margin-right: 16px;
}
dashboard-page .editor-toolbar {
flex: 0 0 auto;
flex-flow: row;
width: 100%;
align-items: center;
border-bottom-width: 1px;
border-bottom-style: solid;
padding: 3px 0;
}

View File

@@ -21,4 +21,4 @@ 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;
}
}

View File

@@ -4,19 +4,19 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./dashboardPanel';
import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { registerThemingParticipant, IColorTheme, ICssStyleCollector, HIGH_CONTRAST } 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
TAB_INACTIVE_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BACKGROUND, TAB_BORDER, EDITOR_GROUP_BORDER, DASHBOARD_TAB_ACTIVE_BACKGROUND, DASHBOARD_BORDER
} from 'vs/workbench/common/theme';
import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
// Title Active
const tabActiveBackground = theme.getColor(TAB_ACTIVE_BACKGROUND);
const tabActiveForeground = theme.getColor(TAB_ACTIVE_FOREGROUND);
let tabActiveBackgroundVertical = theme.getColor(DASHBOARD_TAB_ACTIVE_BACKGROUND);
if (tabActiveBackground || tabActiveForeground) {
collector.addRule(`
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab:hover .tabLabel,
@@ -25,9 +25,12 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
border-bottom: 0px solid;
}
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab-header.active {
panel.dashboard-panel > .tabbedPanel.vertical > .title .tabList .tab-header.active {
background-color: ${tabActiveBackgroundVertical};
}
panel.dashboard-panel > .tabbedPanel.horizontal > .title .tabList .tab-header.active {
background-color: ${tabActiveBackground};
outline-color: ${tabActiveBackground};
}
panel.dashboard-panel > .tabbedPanel.horizontal > .title .tabList .tab-header.active {
@@ -40,7 +43,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
`);
}
const activeTabBorderColor = theme.getColor(TAB_ACTIVE_BORDER);
const activeTabBorderColor = theme.type === HIGH_CONTRAST ? theme.getColor(activeContrastBorder) : theme.getColor(TAB_ACTIVE_BORDER);
if (activeTabBorderColor) {
collector.addRule(`
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab-header.active {
@@ -54,11 +57,10 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
const tabInactiveForeground = theme.getColor(TAB_INACTIVE_FOREGROUND);
if (tabInactiveBackground || tabInactiveForeground) {
collector.addRule(`
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab .tabLabel {
panel.dashboard-panel > .tabbedPanel.horizontal > .title .tabList .tab .tabLabel {
color: ${tabInactiveForeground};
}
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab-header {
panel.dashboard-panel > .tabbedPanel.horizontal > .title .tabList .tab-header {
background-color: ${tabInactiveBackground};
}
`);
@@ -68,7 +70,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
const panelTitleBackground = theme.getColor(EDITOR_GROUP_HEADER_TABS_BACKGROUND);
if (panelTitleBackground) {
collector.addRule(`
panel.dashboard-panel > .tabbedPanel > .title {
panel.dashboard-panel > .tabbedPanel.horizontal > .title {
background-color: ${panelTitleBackground};
}
`);
@@ -78,7 +80,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
const tabBorder = theme.getColor(TAB_BORDER);
if (tabBorder) {
collector.addRule(`
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab-header {
panel.dashboard-panel > .tabbedPanel.horizontal > .title .tabList .tab-header {
border-right-color: ${tabBorder};
border-bottom-color: ${tabBorder};
}
@@ -89,7 +91,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
const outline = theme.getColor(activeContrastBorder);
if (outline) {
collector.addRule(`
panel.dashboard-panel > .tabbedPanel > .title {
panel.dashboard-panel > .tabbedPanel.horizontal > .title {
border-bottom-color: ${tabBorder};
border-bottom-width: 1px;
border-bottom-style: solid;
@@ -106,10 +108,19 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
const divider = theme.getColor(EDITOR_GROUP_BORDER);
if (divider) {
collector.addRule(`
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab-header {
panel.dashboard-panel > .tabbedPanel.horizontal > .title .tabList .tab-header {
border-right-width: 1px;
border-right-style: solid;
}
`);
}
const sideBorder = theme.getColor(DASHBOARD_BORDER);
if (divider) {
collector.addRule(`panel.dashboard-panel > .tabbedPanel.vertical > .title > .tabContainer {
border-right-width: 1px;
border-right-style: solid;
border-right-color: ${sideBorder};
}`);
}
});

View File

@@ -9,12 +9,14 @@ 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/workbench/contrib/dashboard/browser/dashboardRegistry';
import { registerTab, registerTabGroup } from 'sql/workbench/contrib/dashboard/browser/dashboardRegistry';
import { generateContainerTypeSchemaProperties } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
import { NAV_SECTION, validateNavSectionContributionAndRegisterIcon } from 'sql/workbench/contrib/dashboard/browser/containers/dashboardNavSection.contribution';
import { WIDGETS_CONTAINER, validateWidgetContainerContribution } from 'sql/workbench/contrib/dashboard/browser/containers/dashboardWidgetContainer.contribution';
import { GRID_CONTAINER, validateGridContainerContribution } from 'sql/workbench/contrib/dashboard/browser/containers/dashboardGridContainer.contribution';
import { values } from 'vs/base/common/collections';
import { IUserFriendlyIcon } from 'sql/workbench/contrib/dashboard/browser/core/dashboardWidget';
import { isValidIcon, createCSSRuleForIcon } from 'sql/workbench/contrib/dashboard/browser/dashboardIconUtil';
export interface IDashboardTabContrib {
id: string;
@@ -25,6 +27,13 @@ export interface IDashboardTabContrib {
description?: string;
alwaysShow?: boolean;
isHomeTab?: boolean;
group?: string;
icon?: IUserFriendlyIcon;
}
export interface IDashboardTabGroupContrib {
id: string;
title: string;
}
const tabSchema: IJSONSchema = {
@@ -63,6 +72,29 @@ const tabSchema: IJSONSchema = {
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'
},
group: {
description: localize('azdata.extension.contributes.dashboard.tab.group', "The unique identifier of the group this tab belongs to, value for home group: home."),
type: 'string'
},
icon: {
description: localize('dazdata.extension.contributes.dashboard.tab.icon', "(Optional) Icon which is used to represent this tab in the UI. Either a file path or a themeable configuration"),
anyOf: [{
type: 'string'
},
{
type: 'object',
properties: {
light: {
description: localize('azdata.extension.contributes.dashboard.tab.icon.light', "Icon path when a light theme is used"),
type: 'string'
},
dark: {
description: localize('azdata.extension.contributes.dashboard.tab.icon.dark', "Icon path when a dark theme is used"),
type: 'string'
}
}
}]
}
}
};
@@ -80,8 +112,8 @@ const tabContributionSchema: IJSONSchema = {
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;
function handleTab(tab: IDashboardTabContrib, extension: IExtensionPointUser<any>) {
let { description, container, provider, title, when, id, alwaysShow, isHomeTab, group, icon } = tab;
// If always show is not specified, set it to true by default.
if (!types.isBoolean(alwaysShow)) {
@@ -130,8 +162,13 @@ ExtensionsRegistry.registerExtensionPoint<IDashboardTabContrib | IDashboardTabCo
break;
}
let iconClass = undefined;
if (isValidIcon(icon, extension)) {
iconClass = createCSSRuleForIcon(icon, extension);
}
if (result) {
registerTab({ description, title, container, provider, when, id, alwaysShow, publisher, isHomeTab });
registerTab({ description, title, container, provider, when, id, alwaysShow, publisher, isHomeTab, group, iconClass });
}
}
@@ -139,10 +176,64 @@ ExtensionsRegistry.registerExtensionPoint<IDashboardTabContrib | IDashboardTabCo
const { value } = extension;
if (Array.isArray<IDashboardTabContrib>(value)) {
for (const command of value) {
handleCommand(command, extension);
handleTab(command, extension);
}
} else {
handleCommand(value, extension);
handleTab(value, extension);
}
}
});
const tabGroupSchema: IJSONSchema = {
type: 'object',
properties: {
id: {
type: 'string',
description: localize('azdata.extension.contributes.dashboard.tabGroup.id', "Unique identifier for this tab group.")
},
title: {
type: 'string',
description: localize('azdata.extension.contributes.dashboard.tabGroup.title', "Title of the tab group.")
}
}
};
const tabGroupContributionSchema: IJSONSchema = {
description: localize('azdata.extension.contributes.tabGroups', "Contributes a single or multiple tab groups for users to add to their dashboard."),
oneOf: [
tabGroupSchema,
{
type: 'array',
items: tabGroupSchema
}
]
};
ExtensionsRegistry.registerExtensionPoint<IDashboardTabContrib | IDashboardTabContrib[]>({ extensionPoint: 'dashboard.tabGroups', jsonSchema: tabGroupContributionSchema }).setHandler(extensions => {
function handleTabGroup(tabgroup: IDashboardTabGroupContrib, extension: IExtensionPointUser<any>) {
let { id, title } = tabgroup;
if (!id) {
extension.collector.error(localize('dashboardTabGroup.contribution.noIdError', "No id specified for tab group."));
return;
}
if (!title) {
extension.collector.error(localize('dashboardTabGroup.contribution.noTitleError', "No title specified for tab group."));
return;
}
registerTabGroup({ id, title });
}
for (const extension of extensions) {
const { value } = extension;
if (Array.isArray<IDashboardTabGroupContrib>(value)) {
for (const command of value) {
handleTabGroup(command, extension);
}
} else {
handleTabGroup(value, extension);
}
}
});

View File

@@ -7,6 +7,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 { TabType } from 'sql/base/browser/ui/panel/tab.component';
import { IDashboardTab } from 'sql/workbench/services/dashboard/browser/common/interfaces';
export interface IDashboardWidget {
@@ -41,9 +42,10 @@ export interface TabConfig extends IDashboardTab {
editable: boolean;
canClose: boolean;
actions?: Array<Action>;
iconClass?: string;
type?: TabType;
}
export type IUserFriendlyIcon = string | { light: string; dark: string; };
export interface NavSectionConfig {

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048" width="16" height="16">
<path d="M1792 569v1038l-832 417-832-417V569l832-417 832 417zM960 296L335 608l625 312 625-312-625-312zM256 1528l640 321v-817L256 711v817zm1408 0V711l-640 321v817l640-321z" />
</svg>

After

Width:  |  Height:  |  Size: 271 B

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M14,4.4v8.1l-6.5,3.3L1,12.6V4.4l6.5-3.3L14,4.4z M7.5,2.3L2.6,4.8l4.9,2.4l4.9-2.4L7.5,2.3z M2,11.9l5,2.5V8.1
L2,5.6V11.9z M13,11.9V5.6L8,8.1v6.4L13,11.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 584 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 50 50" height="50px" id="Layer_1" version="1.1" viewBox="0 0 50 50" width="50px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect fill="none" height="50" width="50"/><polyline fill="none" points="44,21 44,49 6,49 6,21 " stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2"/><polyline fill="none" points="19,49 19,28 31,28 31,49 " stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2"/><polygon points="35,5 35,8.016 37,10.094 37,7 39,7 39,12.203 41,14.266 41,5 "/><polyline fill="none" points=" 1.11,25.942 25,1.053 48.89,25.943 " stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/></svg>

After

Width:  |  Height:  |  Size: 910 B

View File

@@ -0,0 +1,15 @@
<svg width="50" height="50" xmlns="http://www.w3.org/2000/svg">
<g>
<title>background</title>
<rect fill="none" id="canvas_background" height="402" width="582" y="-1" x="-1"/>
</g>
<g>
<title>Layer 1</title>
<rect id="svg_1" width="50" height="50" fill="none"/>
<polyline id="svg_2" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke="#ffffff" points="44,21 44,49 6,49 6,21 " fill="none"/>
<polyline id="svg_3" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke="#ffffff" points="18.9292186871171,49 18.9292186871171,28 30.9292186871171,28 30.9292186871171,49 " fill="none"/>
<polygon fill="#ffffff" stroke="#000000" stroke-opacity="0" id="svg_4" points="35,5 35,8.016 37,10.094 37,7 39,7 39,12.203 41,14.266 41,5 "/>
<polyline id="svg_5" stroke-width="2" stroke-miterlimit="10" stroke-linejoin="round" stroke-linecap="round" stroke="#ffffff" points=" 1.11,25.942 25,1.053 48.89,25.943 " fill="none"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 975 B

View File

@@ -9,8 +9,6 @@
<div style="flex: 1 1 auto">
<breadcrumb></breadcrumb>
</div>
<div style="flex: 0 0 auto" #actionBar>
</div>
</div>
<div style="flex: 1 1 auto; position: relative">
<router-outlet (activate)="onActivate($event)"></router-outlet>

View File

@@ -2,23 +2,17 @@
* 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!./dashboard';
import { OnInit, Component, Inject, forwardRef, ElementRef, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import * as Utils from 'sql/platform/connection/common/utils';
import { RefreshWidgetAction, EditDashboardAction } from 'sql/workbench/contrib/dashboard/browser/core/actions';
import { DashboardPage } from 'sql/workbench/contrib/dashboard/browser/core/dashboardPage.component';
import { AngularDisposable } from 'sql/base/browser/lifecycle';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IDisposable } from 'vs/base/common/lifecycle';
import * as themeColors from 'vs/workbench/common/theme';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IColorTheme } from 'vs/platform/theme/common/themeService';
import { onUnexpectedError } from 'vs/base/common/errors';
@@ -32,10 +26,6 @@ export class DashboardComponent extends AngularDisposable implements OnInit {
private _currentPage: DashboardPage;
@ViewChild('header', { read: ElementRef }) private header: ElementRef;
@ViewChild('actionBar', { read: ElementRef }) private actionbarContainer: ElementRef;
private actionbar: ActionBar;
private editAction: EditDashboardAction;
private editDisposable: IDisposable;
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrapService: CommonServiceInterface,
@@ -49,16 +39,6 @@ export class DashboardComponent extends AngularDisposable implements OnInit {
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme());
const profile: IConnectionProfile = this._bootstrapService.getOriginalConnectionProfile();
this.actionbar = new ActionBar(this.actionbarContainer.nativeElement);
this.actionbar.push(new RefreshWidgetAction(this.refresh, this), {
icon: true,
label: false,
});
this.editAction = new EditDashboardAction(this.edit, this);
this.actionbar.push(this.editAction, {
icon: true,
label: false,
});
if (profile && (!profile.databaseName || Utils.isMaster(profile))) {
// Route to the server page as this is the default database
this._router.navigate(['server-dashboard']).catch(onUnexpectedError);
@@ -73,11 +53,7 @@ export class DashboardComponent extends AngularDisposable implements OnInit {
}
onActivate(page: DashboardPage) {
if (this.editDisposable) {
this.editDisposable.dispose();
}
this._currentPage = page;
this.editDisposable = page.editEnabled(e => this.editEnabled = e, this);
}
refresh(): void {
@@ -85,12 +61,4 @@ export class DashboardComponent extends AngularDisposable implements OnInit {
this._currentPage.refresh();
}
}
edit(): void {
this._currentPage.enableEdit();
}
set editEnabled(val: boolean) {
this.editAction.enabled = val;
}
}

View File

@@ -1,16 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.dashboardEditor .header .monaco-action-bar .action-label {
padding: 8px;
}
.dashboardEditor .header .monaco-action-bar .action-item {
margin-right: 5px;
}
.dashboardEditor .monaco-action-bar {
overflow: visible;
}

View File

@@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IUserFriendlyIcon } from 'sql/workbench/contrib/dashboard/browser/core/dashboardWidget';
import { asCSSUrl, createCSSRule } from 'vs/base/browser/dom';
import { IdGenerator } from 'vs/base/common/idGenerator';
import * as resources from 'vs/base/common/resources';
import * as nls from 'vs/nls';
import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
const ids = new IdGenerator('contrib-dashboard-icon-');
export function createCSSRuleForIcon(icon: IUserFriendlyIcon, extension: IExtensionPointUser<any>): string {
let iconClass: string;
if (icon) {
iconClass = ids.nextId();
if (typeof icon === 'string') {
const path = resources.joinPath(extension.description.extensionLocation, icon);
createCSSRule(`.codicon.${iconClass}`, `background-image: ${asCSSUrl(path)}`);
} else {
const light = resources.joinPath(extension.description.extensionLocation, icon.light);
const dark = resources.joinPath(extension.description.extensionLocation, icon.dark);
createCSSRule(`.codicon.${iconClass}`, `background-image: ${asCSSUrl(light)}`);
createCSSRule(`.vs-dark .codicon.${iconClass}, .hc-black .codicon.${iconClass}`, `background-image: ${asCSSUrl(dark)}`);
}
}
return iconClass;
}
export function isValidIcon(icon: IUserFriendlyIcon, extension: IExtensionPointUser<any>): boolean {
if (typeof icon === 'undefined') {
return false;
}
if (typeof icon === 'string') {
return true;
} else if (typeof icon.dark === 'string' && typeof icon.light === 'string') {
return true;
}
extension.collector.error(nls.localize('opticon', "property `icon` can be omitted or must be either a string or a literal like `{dark, light}`"));
return false;
}

View File

@@ -14,7 +14,7 @@ import { DATABASE_DASHBOARD_TABS } from 'sql/workbench/contrib/dashboard/browser
import { SERVER_DASHBOARD_TABS } from 'sql/workbench/contrib/dashboard/browser/pages/serverDashboardPage.contribution';
import { DASHBOARD_CONFIG_ID, DASHBOARD_TABS_KEY_PROPERTY } from 'sql/workbench/contrib/dashboard/browser/pages/dashboardPageContribution';
import { find } from 'vs/base/common/arrays';
import { IDashboardTab } from 'sql/workbench/services/dashboard/browser/common/interfaces';
import { IDashboardTab, IDashboardTabGroup } from 'sql/workbench/services/dashboard/browser/common/interfaces';
export const Extensions = {
DashboardContributions: 'dashboard.contributions'
@@ -24,12 +24,15 @@ export interface IDashboardRegistry {
registerDashboardProvider(id: string, properties: ProviderProperties): void;
getProperties(id: string): ProviderProperties;
registerTab(tab: IDashboardTab): void;
registerTabGroup(tabGroup: IDashboardTabGroup): void;
tabs: Array<IDashboardTab>;
tabGroups: Array<IDashboardTabGroup>;
}
class DashboardRegistry implements IDashboardRegistry {
private _properties = new Map<string, ProviderProperties>();
private _tabs = new Array<IDashboardTab>();
private _tabGroups = new Array<IDashboardTabGroup>();
private _configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtension.Configuration);
/**
@@ -61,9 +64,19 @@ class DashboardRegistry implements IDashboardRegistry {
}
}
registerTabGroup(tabGroup: IDashboardTabGroup): void {
if (this.tabGroups.findIndex(group => group.id === tabGroup.id) === -1) {
this.tabGroups.push(tabGroup);
}
}
public get tabs(): Array<IDashboardTab> {
return this._tabs;
}
public get tabGroups(): Array<IDashboardTabGroup> {
return this._tabGroups;
}
}
const dashboardRegistry = new DashboardRegistry();
@@ -73,6 +86,10 @@ export function registerTab(tab: IDashboardTab): void {
dashboardRegistry.registerTab(tab);
}
export function registerTabGroup(tabGroup: IDashboardTabGroup): void {
dashboardRegistry.registerTabGroup(tabGroup);
}
const dashboardPropertiesPropertyContrib: IJSONSchema = {
description: nls.localize('dashboard.properties.property', "Defines a property to show on the dashboard"),
type: 'object',

View File

@@ -144,4 +144,4 @@ export function generateDashboardTabSchema(type?: 'database' | 'server'): IJSONS
}
export const DASHBOARD_CONFIG_ID = 'Dashboard';
export const DASHBOARD_TABS_KEY_PROPERTY = 'tabId';
export const DASHBOARD_TABS_KEY_PROPERTY = 'tabId';

View File

@@ -15,10 +15,15 @@ import { IAngularEventingService } from 'sql/platform/angularEventing/browser/an
import * as colors from 'vs/platform/theme/common/colorRegistry';
import * as nls from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IMenuService } from 'vs/platform/actions/common/actions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
export class DatabaseDashboardPage extends DashboardPage implements OnInit {
protected propertiesWidget: WidgetConfig = {
@@ -42,13 +47,18 @@ export class DatabaseDashboardPage extends DashboardPage implements OnInit {
@Inject(forwardRef(() => CommonServiceInterface)) dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) _cd: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(IInstantiationService) instantiationService: IInstantiationService,
@Inject(INotificationService) notificationService: INotificationService,
@Inject(IAngularEventingService) angularEventingService: IAngularEventingService,
@Inject(IConfigurationService) configurationService: IConfigurationService,
@Inject(ILogService) logService: ILogService
@Inject(ILogService) logService: ILogService,
@Inject(ICommandService) commandService: ICommandService,
@Inject(IContextKeyService) contextKeyService: IContextKeyService,
@Inject(IMenuService) menuService: IMenuService,
@Inject(IKeybindingService) keybindingService: IKeybindingService,
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
@Inject(IWorkbenchThemeService) themeService: IWorkbenchThemeService
) {
super(dashboardService, el, _cd, instantiationService, notificationService, angularEventingService, configurationService, logService);
super(dashboardService, el, _cd, notificationService, angularEventingService, configurationService, logService, commandService, contextKeyService, menuService, keybindingService, contextMenuService, themeService);
this._register(dashboardService.onUpdatePage(() => {
this.refresh(true);
this._cd.detectChanges();
@@ -58,5 +68,6 @@ export class DatabaseDashboardPage extends DashboardPage implements OnInit {
ngOnInit() {
this.init();
this._breadcrumbService.setBreadcrumbs(BreadcrumbClass.DatabasePage);
super.ngAfterViewInit();
}
}

View File

@@ -99,15 +99,14 @@ export const databaseDashboardSettingSchema: IJSONSchema = {
'newQuery',
'mssqlCluster.task.newNotebook',
{ name: 'backup', when: '!mssql:iscloud && mssql:engineedition != 11' },
{ name: 'restore', when: '!mssql:iscloud && mssql:engineedition != 11' },
'configureDashboard'
{ name: 'restore', when: '!mssql:iscloud && mssql:engineedition != 11' }
]
}
},
{
name: 'Search',
name: nls.localize('objectsWidgetTitle', "Search"),
gridItemConfig: {
sizex: 1,
sizex: 3,
sizey: 2
},
widget: {

View File

@@ -16,11 +16,16 @@ import { IAngularEventingService } from 'sql/platform/angularEventing/browser/an
import * as colors from 'vs/platform/theme/common/colorRegistry';
import * as nls from 'vs/nls';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IMenuService } from 'vs/platform/actions/common/actions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
export class ServerDashboardPage extends DashboardPage implements OnInit {
protected propertiesWidget: WidgetConfig = {
@@ -45,13 +50,18 @@ export class ServerDashboardPage extends DashboardPage implements OnInit {
@Inject(forwardRef(() => CommonServiceInterface)) dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) _cd: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(IInstantiationService) instantiationService: IInstantiationService,
@Inject(INotificationService) notificationService: INotificationService,
@Inject(IAngularEventingService) angularEventingService: IAngularEventingService,
@Inject(IConfigurationService) configurationService: IConfigurationService,
@Inject(ILogService) logService: ILogService
@Inject(ILogService) logService: ILogService,
@Inject(ICommandService) commandService: ICommandService,
@Inject(IContextKeyService) contextKeyService: IContextKeyService,
@Inject(IMenuService) menuService: IMenuService,
@Inject(IKeybindingService) keybindingService: IKeybindingService,
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
@Inject(IWorkbenchThemeService) themeService: IWorkbenchThemeService
) {
super(dashboardService, el, _cd, instantiationService, notificationService, angularEventingService, configurationService, logService);
super(dashboardService, el, _cd, notificationService, angularEventingService, configurationService, logService, commandService, contextKeyService, menuService, keybindingService, contextMenuService, themeService);
// special-case handling for MSSQL data provider
const connInfo = this.dashboardService.connectionManagementService.connectionInfo;

View File

@@ -77,7 +77,7 @@ const defaultVal = [
{
name: 'Tasks',
widget: {
'tasks-widget': ['newQuery', 'mssqlCluster.task.newNotebook', { name: 'restore', when: '!mssql:iscloud && mssql:engineedition != 11' }, 'configureDashboard']
'tasks-widget': ['newQuery', 'mssqlCluster.task.newNotebook', { name: 'restore', when: '!mssql:iscloud && mssql:engineedition != 11' }]
},
gridItemConfig: {
sizex: 1,
@@ -85,7 +85,7 @@ const defaultVal = [
}
},
{
name: 'Search',
name: nls.localize('databasesWidgetTitle', "Search"),
gridItemConfig: {
sizex: 1,
sizey: 2

View File

@@ -5,7 +5,7 @@
*--------------------------------------------------------------------------------------------*/
-->
<div #parent style="position: absolute; height: 100%; width: 100%;">
<div [style.margin-right.px]="_clipped ? 30 : 0" [style.width]="_clipped ? 94 + '%' : '100%'" style="overflow: hidden">
<div #container [style.margin-right.px]="_clipped ? 30 : 0" [style.width]="_clipped ? 94 + '%' : '100%'" style="overflow: hidden; padding-bottom: 10px">
<span #child style="white-space : nowrap; width: fit-content">
<ng-template ngFor let-item [ngForOf]="properties">
<span style="margin-left: 10px; display: inline-block;">

View File

@@ -18,6 +18,9 @@ import * as nls from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { ILogService } from 'vs/platform/log/common/log';
import { subscriptionToDisposable } from 'sql/base/browser/lifecycle';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { DASHBOARD_BORDER } from 'vs/workbench/common/theme';
import { IColorTheme } from 'vs/platform/theme/common/themeService';
export interface PropertiesConfig {
properties: Array<Property>;
@@ -69,13 +72,15 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb
@ViewChild('child', { read: ElementRef }) private _child: ElementRef;
@ViewChild('parent', { read: ElementRef }) private _parent: ElementRef;
@ViewChild('container', { read: ElementRef }) private _container: ElementRef;
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrap: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
@Inject(WIDGET_CONFIG) protected _config: WidgetConfig,
@Inject(ILogService) private logService: ILogService
@Inject(ILogService) private logService: ILogService,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService
) {
super();
this.init();
@@ -85,6 +90,14 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb
this._hasInit = true;
this._register(addDisposableListener(window, EventType.RESIZE, () => this.handleClipping()));
this._changeRef.detectChanges();
this._register(this.themeService.onDidColorThemeChange((event: IColorTheme) => {
this.updateTheme(event);
}));
}
ngAfterViewInit(): void {
this.updateTheme(this.themeService.getColorTheme());
}
public refresh(): void {
@@ -265,4 +278,9 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb
}
return val;
}
private updateTheme(theme: IColorTheme): void {
const border = theme.getColor(DASHBOARD_BORDER);
this._container.nativeElement.style.borderBottom = '1px solid ' + border.toString();
}
}

View File

@@ -98,7 +98,7 @@ suite('Dashboard Properties Widget Tests', () => {
}
};
let testComponent = new PropertiesWidgetComponent(dashboardService.object, new TestChangeDetectorRef(), undefined, widgetConfig, testLogService);
let testComponent = new PropertiesWidgetComponent(dashboardService.object, new TestChangeDetectorRef(), undefined, widgetConfig, testLogService, undefined);
return new Promise(resolve => {
// because config parsing is done async we need to put our asserts on the thread stack

View File

@@ -47,7 +47,7 @@ export class AgentViewComponent {
public readonly panelOpt: IPanelOptions = {
showTabsWhenOne: true,
layout: NavigationBarLayout.vertical,
layout: NavigationBarLayout.horizontal,
showIcon: true
};

View File

@@ -30,9 +30,9 @@ import { registerComponentType } from 'sql/platform/dashboard/browser/modelCompo
import HyperlinkComponent from 'sql/workbench/browser/modelComponents/hyperlink.component';
import SplitViewContainer from 'sql/workbench/browser/modelComponents/splitviewContainer.component';
import RadioCardGroup from 'sql/workbench/browser/modelComponents/radioCardGroup.component';
import TabbedPanelComponent from 'sql/workbench/browser/modelComponents/tabbedPanel.component';
import SeparatorComponent from 'sql/workbench/browser/modelComponents/separator.component';
import { ModelComponentTypes } from 'sql/platform/dashboard/browser/interfaces';
export const DIV_CONTAINER = 'div-container';
registerComponentType(DIV_CONTAINER, ModelComponentTypes.DivContainer, DivContainer);
@@ -112,5 +112,8 @@ registerComponentType(HYPERLINK_COMPONENT, ModelComponentTypes.Hyperlink, Hyperl
export const RADIOCARDGROUP_COMPONENT = 'radiocardgroup-component';
registerComponentType(RADIOCARDGROUP_COMPONENT, ModelComponentTypes.RadioCardGroup, RadioCardGroup);
export const TABBEDPANEL_COMPONENT = 'tabbedpanel-component';
registerComponentType(TABBEDPANEL_COMPONENT, ModelComponentTypes.TabbedPanel, TabbedPanelComponent);
export const SEPARATOR_COMPONENT = 'separator-component';
registerComponentType(SEPARATOR_COMPONENT, ModelComponentTypes.Separator, SeparatorComponent);

View File

@@ -9,10 +9,15 @@ export interface IDashboardTab {
provider: string | string[];
publisher: string;
description?: string;
container?: {
[key: string]: any;
};
container?: { [key: string]: any };
when?: string;
alwaysShow?: boolean;
isHomeTab?: boolean;
group?: string;
iconClass?: string;
}
export interface IDashboardTabGroup {
id: string;
title: string;
}

View File

@@ -27,6 +27,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { Registry } from 'vs/platform/registry/common/platform';
import { IBootstrapParams, ISelector } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
import { startsWith } from 'vs/base/common/strings';
import { PanelModule } from 'sql/base/browser/ui/panel/panel.module';
export const DialogModule = (params, selector: string, instantiationService: IInstantiationService): any => {
@@ -50,7 +51,8 @@ export const DialogModule = (params, selector: string, instantiationService: IIn
imports: [
FormsModule,
CommonModule,
BrowserModule
BrowserModule,
PanelModule
],
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },