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

@@ -127,6 +127,7 @@ declare module 'azdata' {
export interface ModelBuilder {
radioCardGroup(): ComponentBuilder<RadioCardGroupComponent>;
tabbedPanel(): TabbedPanelComponentBuilder;
separator(): ComponentBuilder<SeparatorComponent>;
}
@@ -200,6 +201,78 @@ declare module 'azdata' {
export interface ImageComponentProperties extends ComponentProperties, ComponentWithIconProperties {
}
/**
* Panel component with tabs
*/
export interface TabbedPanelComponent extends Container<TabbedPanelLayout, any> {
/**
* An event triggered when the selected tab is changed.
* The event argument is the id of the selected tab.
*/
onTabChanged: vscode.Event<string>;
}
/**
* Defines the tab orientation of TabbedPanelComponent
*/
export enum TabOrientation {
Vertical = 'vertical',
Horizontal = 'horizontal'
}
/**
* Layout of TabbedPanelComponent, can be used to initialize the component when using ModelBuilder
*/
export interface TabbedPanelLayout {
orientation: TabOrientation;
}
/**
* Represents the tab of TabbedPanelComponent
*/
export interface Tab {
/**
* Title of the tab
*/
title: string;
/**
* Content component of the tab
*/
content: Component;
/**
* Id of the tab
*/
id: string;
}
/**
* Represents the tab group of TabbedPanelComponent
*/
export interface TabGroup {
/**
* Title of the tab group
*/
title: string;
/**
* children of the tab group
*/
tabs: Tab[];
}
/**
* Builder for TabbedPannelComponent
*/
export interface TabbedPanelComponentBuilder extends ContainerBuilder<TabbedPanelComponent, any, any> {
/**
* Add the tabs to the component
* @param tabs tabs/tab groups to be added
*/
withTabs(tabs: (Tab | TabGroup)[]): ContainerBuilder<TabbedPanelComponent, any, any>;
}
export interface InputBoxProperties extends ComponentProperties {
validationErrorMessage?: string;
}

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 26 26" height="26px" id="Layer_1" version="1.1" viewBox="0 0 26 26" width="26px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><polygon fill="#231F20" points="23.885,0.58 25.969,2.664 15.133,13.5 25.969,24.336 23.885,26.42 10.965,13.5 "/><polygon fill="#231F20" points="12.885,0.58 14.969,2.664 4.133,13.5 14.969,24.336 12.885,26.42 -0.035,13.5 "/></g></svg>

After

Width:  |  Height:  |  Size: 570 B

View File

@@ -0,0 +1,12 @@
<svg width="26" height="26" 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>
<polygon id="svg_2" points="23.885,0.58 25.969,2.664 15.133,13.5 25.969,24.336 23.885,26.42 10.965,13.5 " fill="#ffffff"/>
<polygon id="svg_3" points="12.885,0.58 14.969,2.664 4.133,13.5 14.969,24.336 12.885,26.42 -0.035,13.5 " fill="#ffffff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 481 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 26 26" height="26px" id="Layer_1" version="1.1" viewBox="0 0 26 26" width="26px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><polygon fill="#231F20" points="2.049,0.58 -0.035,2.664 10.801,13.5 -0.035,24.336 2.049,26.42 14.969,13.5 "/><polygon fill="#231F20" points="13.049,0.58 10.965,2.664 21.801,13.5 10.965,24.336 13.049,26.42 25.969,13.5 "/></g></svg>

After

Width:  |  Height:  |  Size: 569 B

View File

@@ -0,0 +1,12 @@
<svg width="26" height="26" 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>
<polygon id="svg_2" points="2.1818366795778275,0.048653364181518555 0.09783665463328362,2.1326534152030945 10.933836296200752,12.968653380870819 0.09783665463328362,23.804653823375702 2.1818366795778275,25.888653457164764 15.101836517453194,12.968653380870819 " fill="#ffffff"/>
<polygon id="svg_3" points="13.049,0.58 10.965,2.664 21.801,13.5 10.965,24.336 13.049,26.42 25.969,13.5 " fill="#ffffff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 637 B

View File

@@ -59,7 +59,7 @@ panel {
display: flex;
padding-left: 5px;
padding-right: 5px;
min-width: 65px;
cursor: pointer;
}
.tabbedPanel.vertical .tabList .tab-header {
@@ -67,17 +67,16 @@ panel {
text-transform: none;
text-overflow: ellipsis;
overflow: hidden;
width: 65px;
width: auto;
height: 50px;
line-height: 45px;
}
.tabbedPanel .tabList .tab .tabLabel.codicon {
.tabbedPanel .tabList .tab .tabIcon.codicon {
background-repeat: no-repeat;
background-position: center center;
background-size: 20px;
padding: 20px 25px;
line-height: 50px;
background-position: 2px center;
background-size: 16px;
padding: 2px 2px 2px 22px;
}
.tabbedPanel .tabList .actions-container {
@@ -117,24 +116,20 @@ panel {
height: 100%;
}
.tabbedPanel.vertical .tabList {
flex-direction: column;
}
.tabbedPanel > .tab-content {
flex: 1;
position: relative;
}
.tabbedPanel.vertical > .title > .tabList {
.tabbedPanel.vertical > .title > .tabContainer > .monaco-scrollable-element > .tabList {
flex-flow: column;
}
.tabbedPanel.horizontal > .title > .tabList {
.tabbedPanel.horizontal > .title > .tabContainer > .monaco-scrollable-element > .tabList {
flex-flow: row;
}
.tabbedPanel > .title > .monaco-scrollable-element {
.tabbedPanel > .title > .tabContainer > .monaco-scrollable-element {
flex: 0 1 auto;
width: inherit;
}
@@ -147,3 +142,47 @@ panel {
width: 100%;
height: 100%;
}
.tabbedPanel .tab-group-header {
font-weight: bold;
margin: 15px 5px 3px 5px;
line-height: 35px;
height: 35px;
border-style: solid;
border-width: 0 0 1px 0;
border-color: rgb(214, 214, 214);
}
.tabbedPanel .action-container {
display: flex;
flex-flow: row-reverse;
}
.tabbedPanel .tab-action {
width: 15px;
height: 15px;
padding: 0px;
border: 0px;
background-color: transparent;
background-position: 2px center;
background-repeat: no-repeat;
background-size: 11px 11px;
}
.vs .tabbedPanel .tab-action.collapse{
background-image: url("collapse.svg");
}
.vs-dark .tabbedPanel .tab-action.collapse,
.hc-black .tabbedPanel .tab-action.collapse {
background-image: url("collapse_inverse.svg");
}
.vs .tabbedPanel .tab-action.expand {
background-image: url("expand.svg");
}
.vs-dark .tabbedPanel .tab-action.expand,
.hc-black .tabbedPanel .tab-action.expand {
background-image: url("expand_inverse.svg");
}

View File

@@ -17,4 +17,4 @@ tab-header .tab-header.active .action-label, /* always show it for active tab
tab-header .tab-header:hover .action-label, /* always show it on hover */
tab-header .tab-header:focus .action-label { /* always show it on focus */
opacity: 1;
}
}

View File

@@ -5,7 +5,7 @@
import {
Component, ContentChildren, QueryList, Inject, forwardRef, NgZone,
Input, EventEmitter, Output, ViewChild, ElementRef
Input, EventEmitter, Output, ViewChild, ElementRef, ChangeDetectorRef, ViewChildren
} from '@angular/core';
import { TabComponent } from 'sql/base/browser/ui/panel/tab.component';
@@ -19,6 +19,10 @@ import { mixin } from 'vs/base/common/objects';
import { Disposable } from 'vs/base/common/lifecycle';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { firstIndex } from 'vs/base/common/arrays';
import * as nls from 'vs/nls';
import { TabHeaderComponent } from 'sql/base/browser/ui/panel/tabHeader.component';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
export interface IPanelOptions {
/**
@@ -48,9 +52,19 @@ let idPool = 0;
<div class="tabbedPanel fullsize" [ngClass]="options.layout === NavigationBarLayout.vertical ? 'vertical' : 'horizontal'">
<div *ngIf="!options.showTabsWhenOne ? _tabs.length !== 1 : true" class="composite title">
<div class="tabContainer">
<div class="tabList" role="tablist" scrollable [horizontalScroll]="AutoScrollbarVisibility" [verticalScroll]="HiddenScrollbarVisibility" [scrollYToX]="true">
<div *ngIf="options.layout === NavigationBarLayout.vertical" class="action-container">
<button [attr.aria-expanded]="_tabExpanded" [title]="toggleTabPanelButtonAriaLabel" [attr.aria-label]="toggleTabPanelButtonAriaLabel" [ngClass]="toggleTabPanelButtonCssClass" tabindex="0" (click)="toggleTabPanel()"></button>
</div>
<div *ngIf="_tabExpanded" class="tabList" role="tablist" scrollable [horizontalScroll]="AutoScrollbarVisibility" [verticalScroll]="HiddenScrollbarVisibility" [scrollYToX]="true" (keydown)="onKey($event)">
<div role="presentation" *ngFor="let tab of _tabs">
<tab-header role="presentation" [active]="_activeTab === tab" [tab]="tab" [showIcon]="options.showIcon" (onSelectTab)='selectTab($event)' (onCloseTab)='closeTab($event)'></tab-header>
<ng-container *ngIf="tab.type!=='group-header'">
<tab-header role="presentation" [active]="_activeTab === tab" [tab]="tab" [showIcon]="options.showIcon" (onSelectTab)='selectTab($event)' (onCloseTab)='closeTab($event)'></tab-header>
</ng-container>
<ng-container *ngIf="tab.type==='group-header' && options.layout === NavigationBarLayout.vertical">
<div class="tab-group-header" *ngIf="_tabExpanded">
<span>{{tab.title}}</span>
</div>
</ng-container >
</div>
</div>
</div>
@@ -71,6 +85,7 @@ export class PanelComponent extends Disposable {
@Input() public options?: IPanelOptions;
@Input() public actions?: Array<Action>;
@ContentChildren(TabComponent) private readonly _tabs!: QueryList<TabComponent>;
@ViewChildren(TabHeaderComponent) private readonly _tabHeaders!: QueryList<TabHeaderComponent>;
@ViewChild(ScrollableDirective) private scrollable?: ScrollableDirective;
@Output() public onTabChange = new EventEmitter<TabComponent>();
@@ -79,16 +94,32 @@ export class PanelComponent extends Disposable {
private _activeTab?: TabComponent;
private _actionbar?: ActionBar;
private _mru: TabComponent[] = [];
private _tabExpanded: boolean = true;
protected AutoScrollbarVisibility = ScrollbarVisibility.Auto; // used by angular template
protected HiddenScrollbarVisibility = ScrollbarVisibility.Hidden; // used by angular template
protected NavigationBarLayout = NavigationBarLayout; // used by angular template
@ViewChild('panelActionbar', { read: ElementRef }) private _actionbarRef!: ElementRef;
constructor(@Inject(forwardRef(() => NgZone)) private _zone: NgZone) {
constructor(
@Inject(forwardRef(() => NgZone)) private _zone: NgZone,
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef) {
super();
}
public get toggleTabPanelButtonCssClass(): string {
return this._tabExpanded ? 'tab-action collapse' : 'tab-action expand';
}
public get toggleTabPanelButtonAriaLabel(): string {
return this._tabExpanded ? nls.localize('hideTextLabel', "Hide text labels") : nls.localize('showTextLabel', "Show text labels");
}
toggleTabPanel(): void {
this._tabExpanded = !this._tabExpanded;
this._cd.detectChanges();
}
ngOnInit(): void {
this.options = mixin(this.options || {}, defaultOptions, false);
}
@@ -245,4 +276,49 @@ export class PanelComponent extends Disposable {
public layout() {
this._activeTab?.layout();
}
onKey(e: KeyboardEvent): void {
const event = new StandardKeyboardEvent(e);
let eventHandled: boolean = false;
if (event.equals(KeyCode.DownArrow) || event.equals(KeyCode.RightArrow)) {
this.focusNextTab();
eventHandled = true;
} else if (event.equals(KeyCode.UpArrow) || event.equals(KeyCode.LeftArrow)) {
this.focusPreviousTab();
eventHandled = true;
}
if (eventHandled) {
event.preventDefault();
event.stopPropagation();
}
}
private focusPreviousTab(): void {
const currentIndex = this.focusedTabHeaderIndex;
if (currentIndex !== -1) {
// Move to the previous tab, if we are at the first tab then move to the last tab.
this.focusOnTabHeader(currentIndex === 0 ? this._tabHeaders.length - 1 : currentIndex - 1);
}
}
private focusNextTab(): void {
const currentIndex = this.focusedTabHeaderIndex;
if (currentIndex !== -1) {
// Move to the next tab, if we are at the last tab then move to the first tab.
this.focusOnTabHeader(currentIndex === this._tabHeaders.length - 1 ? 0 : currentIndex + 1);
}
}
private focusOnTabHeader(index: number): void {
if (index >= 0 && index <= this._tabHeaders.length - 1) {
this._tabHeaders.toArray()[index].focusOnTabHeader();
}
}
private get focusedTabHeaderIndex(): number {
return this._tabHeaders.toArray().findIndex((header) => {
return header.nativeElement === document.activeElement;
});
}
}

View File

@@ -12,6 +12,8 @@ export abstract class TabChild extends AngularDisposable {
public abstract layout(): void;
}
export type TabType = 'tab' | 'group-header';
@Component({
selector: 'tab',
template: `
@@ -29,6 +31,7 @@ export class TabComponent implements OnDestroy {
@Input() public iconClass?: string;
public _active = false;
@Input() public identifier!: string;
@Input() public type: TabType = 'tab';
@Input() private visibilityType: 'if' | 'visibility' = 'if';
private rendered = false;
private destroyed: boolean = false;

View File

@@ -19,10 +19,13 @@ import { CloseTabAction } from 'sql/base/browser/ui/panel/tabActions';
@Component({
selector: 'tab-header',
template: `
<div #actionHeader role="presentation" class="tab-header" style="flex: 0 0; flex-direction: row; height: 100%" [class.active]="tab.active" tabindex="0" (keyup)="onKey($event)">
<span class="tab" (click)="selectTab(tab)" role="tab" [attr.aria-selected]="tab.active" [attr.aria-controls]="tab.title">
<a class="tabLabel" [class.active]="tab.active" #tabLabel>
</a>
<div #actionHeader role="tab" [attr.aria-selected]="tab.active" [attr.aria-label]="tab.title" class="tab-header" style="flex: 0 0; flex-direction: row; height: 100%" [class.active]="tab.active" tabindex="0" (click)="selectTab(tab)" (keyup)="onKey($event)">
<span class="tab" role="presentation">
<div role="presentation">
<a #tabIcon></a>
<a class="tabLabel" [class.active]="tab.active" #tabLabel>
</a>
</div>
</span>
<span #actionbar style="flex: 0 0 auto; align-self: end; margin-top: auto; margin-bottom: auto;" ></span>
</div>
@@ -34,16 +37,23 @@ export class TabHeaderComponent extends Disposable implements AfterContentInit,
@Input() public active?: boolean;
@Output() public onSelectTab: EventEmitter<TabComponent> = new EventEmitter<TabComponent>();
@Output() public onCloseTab: EventEmitter<TabComponent> = new EventEmitter<TabComponent>();
@Output() public onFocusTab: EventEmitter<TabComponent> = new EventEmitter<TabComponent>();
private _actionbar!: ActionBar;
@ViewChild('actionHeader', { read: ElementRef }) private _actionHeaderRef!: ElementRef;
@ViewChild('actionbar', { read: ElementRef }) private _actionbarRef!: ElementRef;
@ViewChild('tabLabel', { read: ElementRef }) private _tabLabelRef!: ElementRef;
@ViewChild('tabIcon', { read: ElementRef }) private _tabIconRef!: ElementRef;
constructor() {
super();
}
public get nativeElement(): HTMLElement {
return this._actionHeaderRef.nativeElement;
}
ngAfterContentInit(): void {
if (this.tab.canClose || this.tab.actions) {
this._actionbar = new ActionBar(this._actionbarRef.nativeElement);
@@ -56,15 +66,14 @@ export class TabHeaderComponent extends Disposable implements AfterContentInit,
}
}
let tabLabelcontainer = this._tabLabelRef.nativeElement as HTMLElement;
const tabLabelContainer = this._tabLabelRef.nativeElement as HTMLElement;
if (this.showIcon && this.tab.iconClass) {
tabLabelcontainer.className = 'tabLabel codicon';
tabLabelcontainer.classList.add(this.tab.iconClass);
} else {
tabLabelcontainer.className = 'tabLabel';
tabLabelcontainer.textContent = this.tab.title;
const tabIconContainer = this._tabIconRef.nativeElement as HTMLElement;
tabIconContainer.className = 'tabIcon codicon';
tabIconContainer.classList.add(this.tab.iconClass);
}
tabLabelcontainer.title = this.tab.title;
tabLabelContainer.textContent = this.tab.title;
}
ngOnDestroy() {
@@ -90,7 +99,7 @@ export class TabHeaderComponent extends Disposable implements AfterContentInit,
onKey(e: Event) {
if (DOM.isAncestor(<HTMLElement>e.target, this._actionHeaderRef.nativeElement) && e instanceof KeyboardEvent) {
let event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Enter)) {
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
this.onSelectTab.emit(this.tab);
e.stopPropagation();
}

View File

@@ -1 +1,4 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#231f20;}</style></defs><title>backup_16x16</title><path class="cls-1" d="M12.11,13.69h1c0,1.51-3.36,2.2-6.48,2.2S.15,15.2.15,13.69V2.18C.25.55,4.26.11,6.63.11s6.48.45,6.48,2.13h-1C12,1.86,10,1.11,6.63,1.11s-5.37.75-5.48,1.14V13.69c0,.36,2,1.2,5.48,1.2S12.07,14,12.11,13.69Z"/><path class="cls-1" d="M13.45,12.07H11.82V6.37L9.38,8.81v-2l3.26-3.25,3.26,3.25v2L13.45,6.37Z"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.1104 13.69H13.1104C13.1104 15.2 9.75039 15.89 6.63039 15.89C3.51039 15.89 0.150391 15.2 0.150391 13.69V2.17999C0.250391 0.549986 4.26039 0.109985 6.63039 0.109985C9.00039 0.109985 13.1104 0.559986 13.1104 2.23999H12.1104C12.0004 1.85999 10.0004 1.10999 6.63039 1.10999C3.26039 1.10999 1.26039 1.85999 1.15039 2.24999V13.69C1.15039 14.05 3.15039 14.89 6.63039 14.89C10.1104 14.89 12.0704 14 12.1104 13.69Z" fill="#0078D4"/>
<path d="M13.4499 12.0701H11.8199V6.37006L9.37988 8.81006V6.81006L12.6399 3.56006L15.8999 6.81006V8.81006L13.4499 6.37006V12.0701Z" fill="#0078D4"/>
</svg>

Before

Width:  |  Height:  |  Size: 497 B

After

Width:  |  Height:  |  Size: 688 B

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>backup_inverse_16x16</title><path class="cls-1" d="M12.11,13.69h1c0,1.51-3.36,2.2-6.48,2.2S.15,15.2.15,13.69V2.18C.25.55,4.26.11,6.63.11s6.48.45,6.48,2.13h-1C12,1.86,10,1.11,6.63,1.11s-5.37.75-5.48,1.14V13.69c0,.36,2,1.2,5.48,1.2S12.07,14,12.11,13.69Z"/><path class="cls-1" d="M13.45,12.07H11.82V6.37L9.38,8.81v-2l3.26-3.25,3.26,3.25v2L13.45,6.37Z"/></svg>

Before

Width:  |  Height:  |  Size: 502 B

View File

@@ -12,22 +12,16 @@
background-image: url('settings_inverse.svg');
}
.vs .codicon.backup {
.vs .codicon.backup,
.vs-dark .codicon.backup,
.hc-black .codicon.backup {
background: url("backup.svg") center center no-repeat;
}
.vs-dark .codicon.backup,
.hc-black .codicon.backup {
background: url("backup_inverse.svg") center center no-repeat;
}
.vs .codicon.restore {
background: url("restore.svg") center center no-repeat;
}
.vs .codicon.restore,
.vs-dark .codicon.restore,
.hc-black .codicon.restore {
background: url("restore_inverse.svg") center center no-repeat;
background: url("restore.svg") center center no-repeat;
}
.vs .codicon.database {
@@ -179,13 +173,10 @@
background: url("status_warning.svg") center center no-repeat;
}
.vs .codicon.refresh {
background-image: url('refresh.svg');
}
.vs .codicon.refresh,
.vs-dark .codicon.refresh,
.hc-black .codicon.refresh {
background-image: url('refresh_inverse.svg');
.hc-black .codicon.refresh {
background: url("refresh.svg") no-repeat;
}
.hc-black .codicon.toggle-more,
@@ -206,31 +197,22 @@
background: url('new.svg') center center no-repeat;
}
.hc-black .codicon.new-query,
.vs-dark .codicon.new-query {
background: url('newquery_inverse.svg') center center no-repeat;
}
.vs .codicon.new-query {
background: url('newquery.svg') center center no-repeat;
.vs .codicon.new-query,
.vs-dark .codicon.new-query,
.hc-black .codicon.new-query {
background: url("newquery.svg") center center no-repeat;
}
.vs .codicon.configure-dashboard,
.hc-black .codicon.configure-dashboard,
.vs-dark .codicon.configure-dashboard {
background: url('configdashboard_inverse.svg') center center no-repeat;
}
.vs .codicon.configure-dashboard {
background: url('configdashboard.svg') center center no-repeat;
background: url('configuredashboard.svg') center center no-repeat;
}
.vs .codicon.edit,
.hc-black .codicon.edit,
.vs-dark .codicon.edit {
background: url('edit_inverse.svg') center center no-repeat;
}
.vs .codicon.edit {
background: url('edit.svg') center center no-repeat;
background: url("edit.svg") center center no-repeat;
}
.hc-black .codicon.pin,

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>configure_dashboard</title><path d="M16,0V4H15V1.71L10.5,6.2l-3-3L0,10.71V9.29L7.5,1.8l3,3L14.29,1H12V0Zm-.12,10.93-1,.4a2.75,2.75,0,0,1,.06.33,3,3,0,0,1,0,.68,2.71,2.71,0,0,1-.06.33l1,.4L15.5,14l-1-.4a3,3,0,0,1-.95.95l.4,1-.92.38-.4-1-.33.06L12,15l-.34,0-.33-.06-.4,1L10,15.5l.4-1a2.93,2.93,0,0,1-.95-.95l-1,.4-.38-.92,1-.4a3.25,3.25,0,0,1,0-1.34l-1-.4L8.49,10l1,.4a2.83,2.83,0,0,1,.95-.95l-.4-1,.92-.38.4,1a3.25,3.25,0,0,1,1.34,0l.4-1,.92.38-.4,1a3,3,0,0,1,.53.42,3,3,0,0,1,.42.53l1-.4ZM12,14a1.94,1.94,0,0,0,.78-.16,2,2,0,0,0,1.07-1.07,2,2,0,0,0,0-1.55,2,2,0,0,0-1.07-1.07,2,2,0,0,0-1.55,0,2,2,0,0,0-1.07,1.07,2,2,0,0,0,0,1.55,2,2,0,0,0,1.07,1.07A1.94,1.94,0,0,0,12,14Z"/></svg>

Before

Width:  |  Height:  |  Size: 781 B

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{font-size:12px;font-family:FullMDL2Assets, Full MDL2 Assets;}.cls-1,.cls-2{fill:#fff;}</style></defs><title>manage_inverse_16x16 </title><text class="cls-1" transform="translate(0.01 11.59)"> </text><path class="cls-2" d="M16,0V4H15V1.71L10.5,6.2l-3-3L0,10.71V9.29L7.5,1.8l3,3L14.29,1H12V0Zm-.12,10.93-1,.4a2.75,2.75,0,0,1,.06.33,3,3,0,0,1,0,.68,2.71,2.71,0,0,1-.06.33l1,.4L15.5,14l-1-.4a3,3,0,0,1-.95.95l.4,1-.92.38-.4-1-.33.06L12,15l-.34,0-.33-.06-.4,1L10,15.5l.4-1a2.93,2.93,0,0,1-.95-.95l-1,.4-.38-.92,1-.4a3.25,3.25,0,0,1,0-1.34l-1-.4L8.49,10l1,.4a2.83,2.83,0,0,1,.95-.95l-.4-1,.92-.38.4,1a3.25,3.25,0,0,1,1.34,0l.4-1,.92.38-.4,1a3,3,0,0,1,.53.42,3,3,0,0,1,.42.53l1-.4ZM12,14a1.94,1.94,0,0,0,.78-.16,2,2,0,0,0,1.07-1.07,2,2,0,0,0,0-1.55,2,2,0,0,0-1.07-1.07,2,2,0,0,0-1.55,0,2,2,0,0,0-1.07,1.07,2,2,0,0,0,0,1.55,2,2,0,0,0,1.07,1.07A1.94,1.94,0,0,0,12,14Z"/></svg>

Before

Width:  |  Height:  |  Size: 980 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 8H8V15H0V0H15V8ZM7 8H1V14H7V8ZM7 1H1V7H7V1ZM14 1H8V7H14V1ZM14.9531 12.0234C14.9844 12.1797 15 12.3385 15 12.5C15 12.6615 14.9844 12.8203 14.9531 12.9766L15.9219 13.375L15.5391 14.3047L14.5703 13.8984C14.4818 14.0339 14.3802 14.1589 14.2656 14.2734C14.1562 14.3828 14.0339 14.4818 13.8984 14.5703L14.3047 15.5391L13.375 15.9219L12.9766 14.9531C12.8203 14.9844 12.6615 15 12.5 15C12.3385 15 12.1797 14.9844 12.0234 14.9531L11.625 15.9219L10.6953 15.5391L11.1016 14.5703C10.8307 14.3932 10.6068 14.1693 10.4297 13.8984L9.46094 14.3047L9.07812 13.375L10.0469 12.9766C10.0156 12.8203 10 12.6615 10 12.5C10 12.3385 10.0156 12.1797 10.0469 12.0234L9.07812 11.625L9.46094 10.6953L10.4297 11.1016C10.5182 10.9661 10.6172 10.8438 10.7266 10.7344C10.8411 10.6198 10.9661 10.5182 11.1016 10.4297L10.6953 9.46094L11.625 9.07812L12.0234 10.0469C12.0964 10.0365 12.1667 10.0286 12.2344 10.0234C12.3073 10.013 12.3802 10.0078 12.4531 10.0078C12.5417 10.0078 12.6276 10.013 12.7109 10.0234C12.7943 10.0339 12.8802 10.0469 12.9688 10.0625L13.375 9.07812L14.3047 9.46094L13.8906 10.4453C14.026 10.5339 14.151 10.6328 14.2656 10.7422C14.3802 10.8464 14.4818 10.9661 14.5703 11.1016L15.5391 10.6953L15.9219 11.625L14.9531 12.0234ZM12.5078 14C12.7109 14 12.9036 13.9609 13.0859 13.8828C13.2682 13.7995 13.4271 13.6901 13.5625 13.5547C13.6979 13.4193 13.8047 13.2604 13.8828 13.0781C13.9609 12.8958 14 12.7031 14 12.5C14 12.2917 13.9609 12.0964 13.8828 11.9141C13.8047 11.7318 13.6979 11.5729 13.5625 11.4375C13.4271 11.3021 13.2682 11.1953 13.0859 11.1172C12.9036 11.0391 12.7083 11 12.5 11C12.2969 11 12.1042 11.0391 11.9219 11.1172C11.7396 11.1953 11.5781 11.3047 11.4375 11.4453C11.3021 11.5807 11.1953 11.7396 11.1172 11.9219C11.0391 12.1042 11 12.2969 11 12.5C11 12.7083 11.0391 12.9036 11.1172 13.0859C11.2005 13.2682 11.3099 13.4271 11.4453 13.5625C11.5807 13.6979 11.7396 13.8047 11.9219 13.8828C12.1094 13.9609 12.3047 14 12.5078 14Z" fill="#015CDA"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-bg{fill:#424242;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 4.28l-11.673 11.72h-4.327v-4.406l11.477-11.594h.308l4.215 4.237v.043z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M14.598 4.25l-1.688 1.75-3-3 1.688-1.75 3 3zm-5.688-.25l-7 7 3 3 7-7-3-3zm-7.91 8.09v2.91h2.91l-2.91-2.91z" id="iconBg"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 2.61719C16 2.96094 15.9349 3.29427 15.8047 3.61719C15.6745 3.9401 15.4844 4.22656 15.2344 4.47656L4.94531 14.7656L0 16L1.23438 11.0547L11.5234 0.765625C11.7734 0.515625 12.0599 0.325521 12.3828 0.195312C12.7057 0.0651042 13.0391 0 13.3828 0C13.7422 0 14.0807 0.0703125 14.3984 0.210938C14.7161 0.346354 14.9922 0.533854 15.2266 0.773438C15.4661 1.00781 15.6536 1.28385 15.7891 1.60156C15.9297 1.91927 16 2.25781 16 2.61719ZM2.54688 11.1562C3.09896 11.3385 3.57292 11.6302 3.96875 12.0312C4.36979 12.4271 4.66146 12.901 4.84375 13.4531L13.2891 5L11 2.71094L2.54688 11.1562ZM1.375 14.625L3.94531 13.9844C3.89323 13.7448 3.80729 13.5182 3.6875 13.3047C3.57292 13.0911 3.43229 12.901 3.26562 12.7344C3.09896 12.5677 2.90885 12.4271 2.69531 12.3125C2.48177 12.1927 2.25521 12.1068 2.01562 12.0547L1.375 14.625ZM14 4.28906C14.1302 4.15885 14.2552 4.03646 14.375 3.92188C14.4948 3.80729 14.6016 3.6875 14.6953 3.5625C14.7891 3.43229 14.862 3.29427 14.9141 3.14844C14.9714 2.9974 15 2.82292 15 2.625C15 2.40104 14.9557 2.19271 14.8672 2C14.7839 1.80208 14.6667 1.63021 14.5156 1.48438C14.3698 1.33333 14.1979 1.21615 14 1.13281C13.8073 1.04427 13.599 1 13.375 1C13.1771 1 13.0026 1.02865 12.8516 1.08594C12.7057 1.13802 12.5677 1.21094 12.4375 1.30469C12.3125 1.39844 12.1927 1.50521 12.0781 1.625C11.9635 1.74479 11.8411 1.86979 11.7109 2L14 4.28906Z" fill="#0078D4"/>
</svg>

Before

Width:  |  Height:  |  Size: 571 B

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#2d2d30;} .icon-vs-out{fill:#2d2d30;} .icon-vs-bg{fill:#c5c5c5;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 4.28l-11.673 11.72h-4.327v-4.406l11.477-11.594h.308l4.215 4.237v.043z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M14.598 4.25l-1.688 1.75-3-3 1.688-1.75 3 3zm-5.688-.25l-7 7 3 3 7-7-3-3zm-7.91 8.09v2.91h2.91l-2.91-2.91z" id="iconBg"/></svg>

Before

Width:  |  Height:  |  Size: 571 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 0V13H13V16H0V3H3V0H16ZM15 12V1H4V3H6V4H1V15H12V10H13V12H15ZM7.35156 9.35156L6.64844 8.64844L11.2891 4H8V3H13V8H12V4.71094L7.35156 9.35156Z" fill="#0078D4"/>
</svg>

After

Width:  |  Height:  |  Size: 273 B

View File

@@ -1 +1,3 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#212121;}</style></defs><title>newquery_16x16</title><path class="cls-1" d="M1.9,4H.38V2.45H1.9Zm0,9.14H.38V11.59H1.9ZM15.63,4H4.19V2.45H15.63Zm0,9.14H4.19V11.59H15.63Zm0-6.09H7.23V5.5h8.39Zm0,3H10.29V8.54h5.34Z"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 4V3H1V4H0ZM3 3H16V4H3V3ZM0 13V12H1V13H0ZM3 13V12H16V13H3ZM7 7V6H16V7H7ZM11 10V9H16V10H11Z" fill="#0078D4"/>
</svg>

Before

Width:  |  Height:  |  Size: 338 B

After

Width:  |  Height:  |  Size: 223 B

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1,.cls-2{fill:#fff;}.cls-2{font-size:12px;font-family:FullMDL2Assets, Full MDL2 Assets;}</style></defs><title>newquery_inverse_16x16</title><path class="cls-1" d="M1.79,3.85H.26V2.33H1.79Zm0,9.14H.26V11.47H1.79ZM15.51,3.85H4.08V2.33H15.51Zm0,9.14H4.08V11.47H15.51Zm0-6.09H7.12V5.38h8.39Zm0,3H10.18V8.42h5.34Z"/><text class="cls-2" transform="translate(0.01 11.59)"> </text></svg>

Before

Width:  |  Height:  |  Size: 490 B

View File

@@ -1 +1,3 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#212121;}</style></defs><title>refresh</title><path class="cls-1" d="M12.51,1.59a8.06,8.06,0,0,1,3.06,4A7.83,7.83,0,0,1,16,8.2a7.91,7.91,0,0,1-.29,2.12,8.13,8.13,0,0,1-.8,1.91A8,8,0,0,1,12,15.11a8.1,8.1,0,0,1-1.91.8,8.06,8.06,0,0,1-4.25,0A8.08,8.08,0,0,1,4,15.11a8,8,0,0,1-2.87-2.87,8.07,8.07,0,0,1-.8-1.91,8,8,0,0,1,0-4.25,8.11,8.11,0,0,1,.82-1.94,7.86,7.86,0,0,1,1.3-1.66A8,8,0,0,1,4.14,1.2H2V.2H6v4H5V1.88A7,7,0,0,0,1.28,6.24a7,7,0,0,0,0,3.82,7,7,0,0,0,1.8,3.09A7,7,0,0,0,6.14,15a7,7,0,0,0,3.71,0,7,7,0,0,0,1.67-.71,7,7,0,0,0,3.22-4.18,7,7,0,0,0-.13-4.12,7.07,7.07,0,0,0-2.68-3.52,6.78,6.78,0,0,0-2.07-1l.27-1A7.67,7.67,0,0,1,12.51,1.59Z"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.1328 0.296875C10.9974 0.53125 11.7891 0.898438 12.5078 1.39844C13.2266 1.89323 13.8438 2.48177 14.3594 3.16406C14.8802 3.84115 15.2839 4.59375 15.5703 5.42188C15.8568 6.24479 16 7.10417 16 8C16 8.73438 15.9036 9.44271 15.7109 10.125C15.5234 10.8073 15.2552 11.4453 14.9062 12.0391C14.5625 12.6328 14.1458 13.1745 13.6562 13.6641C13.1719 14.1484 12.6328 14.5651 12.0391 14.9141C11.4453 15.2578 10.8073 15.526 10.125 15.7188C9.44271 15.9062 8.73438 16 8 16C7.26562 16 6.55729 15.9062 5.875 15.7188C5.19271 15.526 4.55469 15.2578 3.96094 14.9141C3.36719 14.5651 2.82552 14.1484 2.33594 13.6641C1.85156 13.1745 1.4349 12.6328 1.08594 12.0391C0.742188 11.4453 0.473958 10.8099 0.28125 10.1328C0.09375 9.45052 0 8.73958 0 8C0 7.27083 0.0963542 6.5625 0.289062 5.875C0.481771 5.1875 0.755208 4.54167 1.10938 3.9375C1.46875 3.32812 1.90365 2.77604 2.41406 2.28125C2.92448 1.78125 3.5 1.35417 4.14062 1H2V0H6V4H5V1.67969C4.39062 1.97135 3.83854 2.33854 3.34375 2.78125C2.85417 3.21875 2.4349 3.71354 2.08594 4.26562C1.73698 4.8125 1.46875 5.40365 1.28125 6.03906C1.09375 6.67448 1 7.32812 1 8C1 8.64062 1.08333 9.26042 1.25 9.85938C1.41667 10.4531 1.65104 11.0104 1.95312 11.5312C2.26042 12.0469 2.6276 12.5182 3.05469 12.9453C3.48177 13.3724 3.95312 13.7396 4.46875 14.0469C4.98958 14.349 5.54688 14.5833 6.14062 14.75C6.73438 14.9167 7.35417 15 8 15C8.64062 15 9.25781 14.9167 9.85156 14.75C10.4505 14.5833 11.0078 14.349 11.5234 14.0469C12.0443 13.7396 12.5182 13.3724 12.9453 12.9453C13.3724 12.5182 13.737 12.0469 14.0391 11.5312C14.3464 11.0104 14.5833 10.4531 14.75 9.85938C14.9167 9.26562 15 8.64583 15 8C15 7.21875 14.8724 6.46615 14.6172 5.74219C14.3672 5.01823 14.0156 4.35938 13.5625 3.76562C13.1094 3.17188 12.5677 2.65885 11.9375 2.22656C11.3125 1.78906 10.6224 1.46615 9.86719 1.25781L10.1328 0.296875Z" fill="#0078D4"/>
</svg>

Before

Width:  |  Height:  |  Size: 767 B

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>refresh_inverse</title><path class="cls-1" d="M12.51,1.59a8.06,8.06,0,0,1,3.06,4A7.83,7.83,0,0,1,16,8.2a7.91,7.91,0,0,1-.29,2.12,8.13,8.13,0,0,1-.8,1.91A8,8,0,0,1,12,15.11a8.1,8.1,0,0,1-1.91.8,8.06,8.06,0,0,1-4.25,0A8.08,8.08,0,0,1,4,15.11a8,8,0,0,1-2.87-2.87,8.07,8.07,0,0,1-.8-1.91,8,8,0,0,1,0-4.25,8.11,8.11,0,0,1,.82-1.94,7.86,7.86,0,0,1,1.3-1.66A8,8,0,0,1,4.14,1.2H2V.2H6v4H5V1.88A7,7,0,0,0,1.28,6.24a7,7,0,0,0,0,3.82,7,7,0,0,0,1.8,3.09A7,7,0,0,0,6.14,15a7,7,0,0,0,3.71,0,7,7,0,0,0,1.67-.71,7,7,0,0,0,3.22-4.18,7,7,0,0,0-.13-4.12,7.07,7.07,0,0,0-2.68-3.52,6.78,6.78,0,0,0-2.07-1l.27-1A7.67,7.67,0,0,1,12.51,1.59Z"/></svg>

Before

Width:  |  Height:  |  Size: 772 B

View File

@@ -1 +1,4 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#212121;}</style></defs><title>restore_16x16</title><path class="cls-1" d="M13.21,2.19V4.66h-1V2.25c-.11-.38-2.15-1.13-5.48-1.13s-5.37.75-5.48,1.14V13.7c0,.36,2,1.2,5.48,1.2s5.43-.84,5.48-1.2V10.93h1V13.7c0,1.51-3.36,2.2-6.48,2.2S.25,15.21.25,13.7V2.19C.35.56,4.35.12,6.73.12S13.11.56,13.21,2.19Z"/><path class="cls-1" d="M15.51,11.34A3.86,3.86,0,0,1,14.28,13a.5.5,0,0,1-.31.1.5.5,0,0,1-.3-.9,2.75,2.75,0,0,0,.9-1.22,2.8,2.8,0,0,0-1.63-3.6,2.74,2.74,0,0,0-2.14.07A2.78,2.78,0,0,0,9.55,8.58l1.61-.44.26,1L8.2,10,7.33,6.77l1-.26L8.71,8a3.8,3.8,0,0,1,4.58-1.59A3.81,3.81,0,0,1,15.51,11.34Z"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.21 2.19109V4.66109H12.21V2.25109C12.1 1.87109 10.06 1.12109 6.73 1.12109C3.4 1.12109 1.36 1.87109 1.25 2.26109V13.7011C1.25 14.0611 3.25 14.9011 6.73 14.9011C10.21 14.9011 12.16 14.0611 12.21 13.7011V10.9311H13.21V13.7011C13.21 15.2111 9.85 15.9011 6.73 15.9011C3.61 15.9011 0.25 15.2111 0.25 13.7011V2.19109C0.35 0.561094 4.35 0.121094 6.73 0.121094C9.11 0.121094 13.11 0.561094 13.21 2.19109Z" fill="#0078D4"/>
<path d="M15.5101 11.3384C15.2585 11.9932 14.8333 12.5671 14.2801 12.9984C14.1908 13.0654 14.0817 13.1006 13.9701 13.0984C13.8651 13.0984 13.7629 13.0654 13.6777 13.004C13.5926 12.9427 13.5289 12.8561 13.4957 12.7565C13.4626 12.657 13.4615 12.5495 13.4928 12.4493C13.5241 12.3492 13.5861 12.2614 13.6701 12.1984C14.0785 11.8841 14.3903 11.4614 14.5701 10.9784C14.8298 10.2848 14.804 9.51651 14.4985 8.84179C14.193 8.16708 13.6327 7.64089 12.9401 7.37841C12.5969 7.24583 12.2306 7.1837 11.863 7.19573C11.4953 7.20775 11.1338 7.29369 10.8001 7.44841C10.2765 7.68669 9.83981 8.0815 9.55008 8.57841L11.1601 8.13841L11.4201 9.13841L8.20008 9.99841L7.33008 6.76841L8.33008 6.50841L8.71008 7.99841C9.16883 7.24148 9.87742 6.66842 10.7136 6.37814C11.5497 6.08786 12.461 6.09857 13.2901 6.40841C13.7606 6.5849 14.1916 6.85274 14.5582 7.1965C14.9248 7.54025 15.2198 7.95314 15.4261 8.41138C15.6325 8.86962 15.7461 9.36416 15.7605 9.86651C15.7749 10.3689 15.6898 10.8691 15.5101 11.3384Z" fill="#0078D4"/>
</svg>

Before

Width:  |  Height:  |  Size: 713 B

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>restore_inverse_16x16</title><path class="cls-1" d="M13.21,2.19V4.66h-1V2.25c-.11-.38-2.15-1.13-5.48-1.13s-5.37.75-5.48,1.14V13.7c0,.36,2,1.2,5.48,1.2s5.43-.84,5.48-1.2V10.93h1V13.7c0,1.51-3.36,2.2-6.48,2.2S.25,15.21.25,13.7V2.19C.35.56,4.35.12,6.73.12S13.11.56,13.21,2.19Z"/><path class="cls-1" d="M15.51,11.34A3.86,3.86,0,0,1,14.28,13a.5.5,0,0,1-.31.1.5.5,0,0,1-.3-.9,2.75,2.75,0,0,0,.9-1.22,2.8,2.8,0,0,0-1.63-3.6,2.74,2.74,0,0,0-2.14.07A2.78,2.78,0,0,0,9.55,8.58l1.61-.44.26,1L8.2,10,7.33,6.77l1-.26L8.71,8a3.8,3.8,0,0,1,4.58-1.59A3.81,3.81,0,0,1,15.51,11.34Z"/></svg>

Before

Width:  |  Height:  |  Size: 718 B

View File

@@ -126,5 +126,6 @@ export enum ModelComponentTypes {
Hyperlink,
Image,
RadioCardGroup,
TabbedPanel,
Separator
}

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: '/' },