Update dashboard properties widget style for Reflow (#9976)

* layout properties in 2 columns

* fix height after collapsing

* update with reflow values

* set height better

* cleanup

* fix crashing when resizing while properties widget is loading

* Switch to grid layout

* Only two columns max

* Update comment

* Undo color change

* Cleanup

* Fix test

Co-authored-by: chgagnon <chgagnon@microsoft.com>
This commit is contained in:
Kim Santiago
2020-04-16 20:23:35 -07:00
committed by GitHub
parent 02ae1ecff8
commit 530ec8c19d
7 changed files with 123 additions and 53 deletions

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import 'vs/css!./dashboardHomeContainer'; import 'vs/css!./dashboardHomeContainer';
import * as DOM from 'vs/base/browser/dom';
import { Component, forwardRef, Input, ChangeDetectorRef, Inject, ViewChild, ElementRef } from '@angular/core'; import { Component, forwardRef, Input, ChangeDetectorRef, Inject, ViewChild, ElementRef } from '@angular/core';
@@ -22,6 +23,7 @@ import { DASHBOARD_BORDER } from 'vs/workbench/common/theme';
import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { IColorTheme } from 'vs/platform/theme/common/themeService';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { PropertiesWidgetComponent } from 'sql/workbench/contrib/dashboard/browser/widgets/properties/propertiesWidget.component';
@Component({ @Component({
selector: 'dashboard-home-container', selector: 'dashboard-home-container',
@@ -31,7 +33,7 @@ import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
<div scrollable [horizontalScroll]="${ScrollbarVisibility.Hidden}" [verticalScroll]="${ScrollbarVisibility.Auto}"> <div scrollable [horizontalScroll]="${ScrollbarVisibility.Hidden}" [verticalScroll]="${ScrollbarVisibility.Auto}">
<div #propertiesContainer> <div #propertiesContainer>
<dashboard-widget-wrapper #propertiesClass *ngIf="properties" [collapsable]="true" [bottomCollapse]="true" [toggleMore]="false" [_config]="properties" <dashboard-widget-wrapper #propertiesClass *ngIf="properties" [collapsable]="true" [bottomCollapse]="true" [toggleMore]="false" [_config]="properties"
class="properties" [style.height.px]="_propertiesClass?.collapsed ? '30' : '75'"> class="properties" [style.height.px]="_propertiesClass?.collapsed ? '30' : getHeight()">
</dashboard-widget-wrapper> </dashboard-widget-wrapper>
</div> </div>
<widget-content style="flex: 1" [scrollContent]="false" [widgets]="widgets" [originalConfig]="tab.originalConfig" [context]="tab.context"> <widget-content style="flex: 1" [scrollContent]="false" [widgets]="widgets" [originalConfig]="tab.originalConfig" [context]="tab.context">
@@ -46,6 +48,8 @@ export class DashboardHomeContainer extends DashboardWidgetContainer {
@ViewChild('propertiesContainer') private _propertiesContainer: ElementRef; @ViewChild('propertiesContainer') private _propertiesContainer: ElementRef;
@ViewChild(ScrollableDirective) private _scrollable: ScrollableDirective; @ViewChild(ScrollableDirective) private _scrollable: ScrollableDirective;
private height = 75; // default initial height
constructor( constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) _cd: ChangeDetectorRef, @Inject(forwardRef(() => ChangeDetectorRef)) _cd: ChangeDetectorRef,
@Inject(forwardRef(() => CommonServiceInterface)) protected dashboardService: DashboardServiceInterface, @Inject(forwardRef(() => CommonServiceInterface)) protected dashboardService: DashboardServiceInterface,
@@ -78,6 +82,18 @@ export class DashboardHomeContainer extends DashboardWidgetContainer {
this._propertiesClass.collapsed ? 'collapsed' : true, ConfigurationTarget.USER); this._propertiesClass.collapsed ? 'collapsed' : true, ConfigurationTarget.USER);
} }
})); }));
this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, e => {
this._cd.detectChanges();
}));
}
public getHeight(): number {
if (this._propertiesClass && (<PropertiesWidgetComponent>this._propertiesClass.component).height) {
this.height = (<PropertiesWidgetComponent>this._propertiesClass.component).height;
}
return this.height;
} }
public layout() { public layout() {

View File

@@ -165,6 +165,10 @@ export class DashboardWidgetWrapper extends AngularDisposable implements OnInit
this._actionbar.pull(this._actionbar.length() - 1); this._actionbar.pull(this._actionbar.length() - 1);
} }
public get component(): IDashboardWidget {
return this._component;
}
private loadWidget(): void { private loadWidget(): void {
if (Object.keys(this._config.widget).length !== 1) { if (Object.keys(this._config.widget).length !== 1) {
this.logService.error('Exactly 1 widget must be defined per space'); this.logService.error('Exactly 1 widget must be defined per space');

View File

@@ -11,7 +11,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
// tab label // tab label
const tabLabelColor = theme.getColor(TAB_LABEL); const tabLabelColor = theme.getColor(TAB_LABEL);
if (tabLabelColor) { if (tabLabelColor) {
collector.addRule(`properties-widget .propertiesValue { collector.addRule(`properties-widget .propertyValue {
color: ${tabLabelColor} color: ${tabLabelColor}
}`); }`);
} }
@@ -32,11 +32,11 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
}`); }`);
} }
// properties name // property name
const propertiesName = theme.getColor(DASHBOARD_PROPERTIES_NAME); const propertyName = theme.getColor(DASHBOARD_PROPERTIES_NAME);
if (propertiesName) { if (propertyName) {
collector.addRule(`properties-widget .propertiesName { collector.addRule(`properties-widget .propertyName {
color: ${propertiesName} color: ${propertyName}
}`); }`);
} }
}); });

View File

@@ -6,18 +6,12 @@
--> -->
<loading-spinner [loading]="_loading" [loadingMessage]="_loadingMessage" <loading-spinner [loading]="_loading" [loadingMessage]="_loadingMessage"
[loadingCompletedMessage]="_loadingCompletedMessage"></loading-spinner> [loadingCompletedMessage]="_loadingCompletedMessage"></loading-spinner>
<div #parent style="position: absolute; height: 100%; width: 100%;" [style.display]="_loading ? 'none':'block'"> <div class="grid-container {{gridDisplayLayout}}" style="position: absolute; height: 100%; width: 100%;" [style.display]="_loading ? 'none':'grid'">
<div #container [style.margin-right.px]="_clipped ? 30 : 0" [style.width]="_clipped ? 94 + '%' : '100%'" style="overflow: hidden; padding-bottom: 10px"> <ng-template ngFor let-item [ngForOf]="_properties">
<span #child style="white-space : nowrap; width: fit-content"> <div class="property {{propertyLayout}}" style="display:flex">
<ng-template ngFor let-item [ngForOf]="properties"> <div class="propertyName">{{item.displayName}}</div>
<span style="margin-left: 10px; display: inline-block;"> <div class="splitter">:</div>
<div class="propertiesName" style="font-size: 11px;">{{item.displayName}}</div> <div class="propertyValue">{{item.value}}</div>
<div class="propertiesValue">{{item.value}}</div> </div>
</span>
</ng-template> </ng-template>
</span>
</div>
<span *ngIf="_clipped" style="position: absolute; right: 0; top: 0; padding-top: 5px; padding-right: 14px; z-index: 2">
...
</span>
</div> </div>

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import 'vs/css!./propertiesWidget'; import 'vs/css!./propertiesWidget';
import { Component, Inject, forwardRef, ChangeDetectorRef, OnInit, ElementRef, ViewChild } from '@angular/core'; import { Component, Inject, forwardRef, ChangeDetectorRef, OnInit, ElementRef } from '@angular/core';
import { DashboardWidget, IDashboardWidget, WidgetConfig, WIDGET_CONFIG } from 'sql/workbench/contrib/dashboard/browser/core/dashboardWidget'; import { DashboardWidget, IDashboardWidget, WidgetConfig, WIDGET_CONFIG } from 'sql/workbench/contrib/dashboard/browser/core/dashboardWidget';
import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service'; import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service';
@@ -57,6 +57,21 @@ export interface DisplayProperty {
value: string; value: string;
} }
enum GridDisplayLayout {
twoColumns = 'twoColumns',
oneColumn = 'oneColumn'
}
enum PropertyLayoutDirection {
row = 'rowLayout',
column = 'columnLayout'
}
const collapseHeight = 25;
const horizontalPropertyHeight = 28;
const verticalPropertyHeight = 46;
@Component({ @Component({
selector: 'properties-widget', selector: 'properties-widget',
templateUrl: decodeURI(require.toUrl('./propertiesWidget.component.html')) templateUrl: decodeURI(require.toUrl('./propertiesWidget.component.html'))
@@ -64,11 +79,10 @@ export interface DisplayProperty {
export class PropertiesWidgetComponent extends DashboardWidget implements IDashboardWidget, OnInit { export class PropertiesWidgetComponent extends DashboardWidget implements IDashboardWidget, OnInit {
private _connection: ConnectionManagementInfo; private _connection: ConnectionManagementInfo;
private _databaseInfo: DatabaseInfo; private _databaseInfo: DatabaseInfo;
public _clipped: boolean; private _properties: Array<DisplayProperty>;
private properties: Array<DisplayProperty>; public gridDisplayLayout: GridDisplayLayout;
public propertyLayout: PropertyLayoutDirection;
@ViewChild('child', { read: ElementRef }) private _child: ElementRef; public height: number;
@ViewChild('parent', { read: ElementRef }) private _parent: ElementRef;
constructor( constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrap: CommonServiceInterface, @Inject(forwardRef(() => CommonServiceInterface)) private _bootstrap: CommonServiceInterface,
@@ -85,7 +99,7 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb
ngOnInit() { ngOnInit() {
this._inited = true; this._inited = true;
this._register(addDisposableListener(window, EventType.RESIZE, () => this.handleClipping())); this._register(addDisposableListener(window, EventType.RESIZE, () => this.layoutProperties()));
this._changeRef.detectChanges(); this._changeRef.detectChanges();
} }
@@ -100,24 +114,37 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb
this._databaseInfo = data; this._databaseInfo = data;
this._changeRef.detectChanges(); this._changeRef.detectChanges();
this.parseProperties(); this.parseProperties();
if (this._inited) {
this.handleClipping();
}
this.setLoadingStatus(false); this.setLoadingStatus(false);
this.layoutProperties();
}, error => { }, error => {
this.setLoadingStatus(false); this.setLoadingStatus(false);
(<HTMLElement>this._el.nativeElement).innerText = nls.localize('dashboard.properties.error', "Unable to load dashboard properties"); (<HTMLElement>this._el.nativeElement).innerText = nls.localize('dashboard.properties.error', "Unable to load dashboard properties");
}))); })));
} }
private handleClipping(): void { private layoutProperties(): void {
if (this._child.nativeElement.offsetWidth > this._parent.nativeElement.offsetWidth) { // Reflow:
this._clipped = true; // 2 columns w/ horizontal alignment : 1366px and above
} else { // 2 columns w/ vertical alignment : 1024 - 1365px
this._clipped = false; // 1 column w/ vertical alignment : 1024px or less
if (!this._loading) {
if (window.innerWidth >= 1366) {
this.gridDisplayLayout = GridDisplayLayout.twoColumns;
this.propertyLayout = PropertyLayoutDirection.row;
this.height = Math.ceil(this._properties.length / 2) * horizontalPropertyHeight + collapseHeight;
} else if (window.innerWidth < 1366 && window.innerWidth >= 1024) {
this.gridDisplayLayout = GridDisplayLayout.twoColumns;
this.propertyLayout = PropertyLayoutDirection.column;
this.height = Math.ceil(this._properties.length / 2) * verticalPropertyHeight + collapseHeight;
} else if (window.innerWidth < 1024) {
this.gridDisplayLayout = GridDisplayLayout.oneColumn;
this.propertyLayout = PropertyLayoutDirection.column;
this.height = this._properties.length * verticalPropertyHeight + collapseHeight;
} }
this._changeRef.detectChanges(); this._changeRef.detectChanges();
} }
}
private parseProperties() { private parseProperties() {
const provider = this._config.provider; const provider = this._config.provider;
@@ -208,11 +235,7 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb
infoObject = this._connection.serverInfo; infoObject = this._connection.serverInfo;
} }
// iterate over properties and display them this._properties = propertyArray.map(property => {
this.properties = [];
for (let i = 0; i < propertyArray.length; i++) {
const property = propertyArray[i];
const assignProperty = {};
let propertyObject = this.getValueOrDefault<string>(infoObject, property.value, property.default || '--'); let propertyObject = this.getValueOrDefault<string>(infoObject, property.value, property.default || '--');
// make sure the value we got shouldn't be ignored // make sure the value we got shouldn't be ignored
@@ -225,10 +248,11 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb
} }
} }
} }
assignProperty['displayName'] = property.displayName; return {
assignProperty['value'] = propertyObject; displayName: property.displayName,
this.properties.push(<DisplayProperty>assignProperty); value: propertyObject
} };
});
if (this._inited) { if (this._inited) {
this._changeRef.detectChanges(); this._changeRef.detectChanges();

View File

@@ -3,11 +3,43 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
.propertiesName { properties-widget .twoColumns.grid-container {
opacity: 0.6; grid-template-columns: 50% 50%;
} }
.vs-dark .propertiesName, properties-widget .oneColumn.grid-container {
.hc-black .propertiesName { grid-template-columns: 100%;
}
properties-widget .columnLayout.property {
flex-direction: column;
}
properties-widget .propertyName {
opacity: 0.6;
font-size: 12px;
flex: 0 0 auto
}
.vs-dark properties-widget .propertyName,
.hc-black properties-widget .propertyName {
opacity: 1; opacity: 1;
} }
properties-widget .propertyValue {
font-size: 12px;
flex: 1 1 auto;
margin-right: 5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
properties-widget .splitter {
flex: 0 0 15px;
text-align: center;
}
properties-widget .columnLayout .splitter {
display: none;
}

View File

@@ -104,9 +104,9 @@ suite('Dashboard Properties Widget Tests', () => {
// because config parsing is done async we need to put our asserts on the thread stack // because config parsing is done async we need to put our asserts on the thread stack
setImmediate(() => { setImmediate(() => {
// because properties is private we need to do some work arounds to access it. // because properties is private we need to do some work arounds to access it.
assert.equal((<any>testComponent).properties.length, 1); assert.equal((<any>testComponent)._properties.length, 1);
assert.equal((<any>testComponent).properties[0].displayName, 'Test'); assert.equal((<any>testComponent)._properties[0].displayName, 'Test');
assert.equal((<any>testComponent).properties[0].value, 'Test Property'); assert.equal((<any>testComponent)._properties[0].value, 'Test Property');
resolve(); resolve();
}); });
}); });