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

@@ -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();
}