Browse Source

UI: Implement 'Auto scale' for value card widgets, count widgets and value and chart widget.

pull/9383/head
Igor Kulikov 3 years ago
parent
commit
00a09cd80b
  1. 1
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-data-key-row.component.ts
  2. 5
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-value-card-basic-config.component.html
  3. 2
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-value-card-basic-config.component.ts
  4. 5
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html
  5. 2
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts
  6. 8
      ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.html
  7. 11
      ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.scss
  8. 48
      ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts
  9. 2
      ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card.models.ts
  10. 6
      ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.html
  11. 72
      ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.scss
  12. 75
      ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts
  13. 2
      ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts
  14. 32
      ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.html
  15. 88
      ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.scss
  16. 73
      ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.ts
  17. 2
      ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.models.ts
  18. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/aggregated-value-card-widget-settings.component.html
  19. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/aggregated-value-card-widget-settings.component.ts
  20. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html
  21. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts
  22. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/count-widget-settings.component.html
  23. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/count-widget-settings.component.ts
  24. 9
      ui-ngx/src/assets/locale/locale.constant-en_US.json

1
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-data-key-row.component.ts

@ -225,6 +225,7 @@ export class AggregatedDataKeyRowComponent implements ControlValueAccessor, OnIn
this.keyRowFormGroup.get('units').patchValue(this.modelValue.units, {emitEvent: false});
this.keyRowFormGroup.get('decimals').patchValue(this.modelValue.decimals, {emitEvent: false});
this.updateModel();
this.cd.markForCheck();
}
});
}

5
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-value-card-basic-config.component.html

@ -29,6 +29,11 @@
</tb-datasources>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.appearance</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="autoScale">
{{ 'widgets.aggregated-value-card.auto-scale' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTitle">
{{ 'widget-config.title' | translate }}

2
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-value-card-basic-config.component.ts

@ -119,6 +119,7 @@ export class AggregatedValueCardBasicConfigComponent extends BasicWidgetConfigCo
timewindowConfig: [getTimewindowConfig(configData.config), []],
datasources: [configData.config.datasources, []],
autoScale: [settings.autoScale, []],
showTitle: [configData.config.showTitle, []],
title: [configData.config.title, []],
titleFont: [configData.config.titleFont, []],
@ -172,6 +173,7 @@ export class AggregatedValueCardBasicConfigComponent extends BasicWidgetConfigCo
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.autoScale = config.autoScale;
this.widgetConfig.config.settings.showSubtitle = config.showSubtitle;
this.widgetConfig.config.settings.subtitle = config.subtitle;
this.widgetConfig.config.settings.subtitleFont = config.subtitleFont;

5
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html

@ -40,6 +40,11 @@
{{ valueCardLayoutTranslationMap.get(layout) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="autoScale">
{{ 'widgets.value-card.auto-scale' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showLabel">
{{ 'widgets.value-card.label' | translate }}

2
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts

@ -116,6 +116,7 @@ export class ValueCardBasicConfigComponent extends BasicWidgetConfigComponent {
timewindowConfig: [getTimewindowConfig(configData.config), []],
datasources: [configData.config.datasources, []],
layout: [settings.layout, []],
autoScale: [settings.autoScale, []],
showLabel: [settings.showLabel, []],
label: [getLabel(configData.config.datasources), []],
@ -154,6 +155,7 @@ export class ValueCardBasicConfigComponent extends BasicWidgetConfigComponent {
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.layout = config.layout;
this.widgetConfig.config.settings.autoScale = config.autoScale;
this.widgetConfig.config.settings.showLabel = config.showLabel;
setLabel(config.label, this.widgetConfig.config.datasources);

8
ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.html

@ -21,7 +21,9 @@
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
<ng-container *ngTemplateOutlet="subtitleTpl"></ng-container>
</div>
<ng-container *ngTemplateOutlet="valuesTpl"></ng-container>
<ng-container *ngIf="showValues">
<ng-container *ngTemplateOutlet="valuesTpl"></ng-container>
</ng-container>
<ng-container *ngTemplateOutlet="chartTpl"></ng-container>
<ng-container *ngTemplateOutlet="dateTpl"></ng-container>
</div>
@ -31,8 +33,8 @@
[style]="subtitleStyle" [style.color]="subtitleColor">{{ subtitle$ | async }}</div>
</ng-template>
<ng-template #valuesTpl>
<div *ngIf="showValues" class="tb-aggregated-value-card-values">
<div class="tb-aggregated-value-card-values-container">
<div #valueCardValues class="tb-aggregated-value-card-values">
<div #valueCardValueContainer class="tb-aggregated-value-card-values-container">
<div class="tb-aggregated-value-card-values-section left">
<ng-container *ngIf="values[aggregatedValueCardKeyPosition.leftTop]">
<ng-container *ngTemplateOutlet="valueTpl; context:{value: values[aggregatedValueCardKeyPosition.leftTop]}"></ng-container>

11
ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.scss

@ -21,7 +21,7 @@
display: flex;
flex-direction: column;
align-items: stretch;
gap: 8px;
gap: 2px;
padding: 20px 24px 24px;
> div:not(.tb-value-card-overlay) {
z-index: 1;
@ -45,10 +45,17 @@
min-height: 0;
overflow: hidden;
}
.tb-aggregated-value-card-values {
padding: 8px 0;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.tb-aggregated-value-card-values-container {
width: 100%;
height: 100%;
padding: 8px 0;
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr) fit-content(100%) minmax(0, 1fr);
.tb-aggregated-value-card-values-section {

48
ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts

@ -20,7 +20,9 @@ import {
Component,
ElementRef,
Input,
OnDestroy,
OnInit,
Renderer2,
TemplateRef,
ViewChild
} from '@angular/core';
@ -43,22 +45,31 @@ import {
overlayStyle,
textStyle
} from '@shared/models/widget-settings.models';
import { DatePipe } from '@angular/common';
import { TbFlot } from '@home/components/widget/lib/flot-widget';
import { TbFlotKeySettings, TbFlotSettings } from '@home/components/widget/lib/flot-widget.models';
import { DataKey } from '@shared/models/widget.models';
import { formatNumberValue, formatValue, isDefined } from '@core/utils';
import { map } from 'rxjs/operators';
import { ResizeObserver } from '@juggle/resize-observer';
const valuesLayoutHeight = 66;
const valuesLayoutVerticalPadding = 16;
@Component({
selector: 'tb-aggregated-value-card-widget',
templateUrl: './aggregated-value-card-widget.component.html',
styleUrls: ['./aggregated-value-card-widget.component.scss']
})
export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit {
export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('chartElement', {static: false}) chartElement: ElementRef;
@ViewChild('valueCardValues', {static: false})
valueCardValues: ElementRef<HTMLElement>;
@ViewChild('valueCardValueContainer', {static: false})
valueCardValueContainer: ElementRef<HTMLElement>;
aggregatedValueCardKeyPosition = AggregatedValueCardKeyPosition;
settings: AggregatedValueCardWidgetSettings;
@ -96,7 +107,9 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit
tickMin$: Observable<string>;
tickMax$: Observable<string>;
constructor(private date: DatePipe,
private panelResize$: ResizeObserver;
constructor(private renderer: Renderer2,
private cd: ChangeDetectorRef) {
}
@ -174,6 +187,22 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit
(this.flotDataKey?.units || this.ctx.units))
));
}
if (this.settings.autoScale && this.showValues) {
this.renderer.setStyle(this.valueCardValueContainer.nativeElement, 'height', valuesLayoutHeight + 'px');
this.renderer.setStyle(this.valueCardValueContainer.nativeElement, 'overflow', 'visible');
this.renderer.setStyle(this.valueCardValueContainer.nativeElement, 'position', 'absolute');
this.panelResize$ = new ResizeObserver(() => {
this.onValueCardValuesResize();
});
this.panelResize$.observe(this.valueCardValues.nativeElement);
this.onValueCardValuesResize();
}
}
ngOnDestroy() {
if (this.panelResize$) {
this.panelResize$.disconnect();
}
}
public onInit() {
@ -249,4 +278,17 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit
}
}
private onValueCardValuesResize() {
const panelWidth = this.valueCardValues.nativeElement.getBoundingClientRect().width;
const panelHeight = this.valueCardValues.nativeElement.getBoundingClientRect().height - valuesLayoutVerticalPadding;
const targetWidth = panelWidth;
const minAspect = 0.25;
const aspect = Math.min(panelHeight / targetWidth, minAspect);
const targetHeight = targetWidth * aspect;
const scale = targetHeight / valuesLayoutHeight;
const width = targetWidth / scale;
this.renderer.setStyle(this.valueCardValueContainer.nativeElement, 'width', width + 'px');
this.renderer.setStyle(this.valueCardValueContainer.nativeElement, 'transform', `scale(${scale})`);
}
}

2
ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card.models.ts

@ -32,6 +32,7 @@ import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { AggregationType } from '@shared/models/time/time.models';
export interface AggregatedValueCardWidgetSettings {
autoScale: boolean;
showSubtitle: boolean;
subtitle: string;
subtitleFont: Font;
@ -112,6 +113,7 @@ export const getTsValueByLatestDataKey = (latestData: Array<DatasourceData>, dat
};
export const aggregatedValueCardDefaultSettings: AggregatedValueCardWidgetSettings = {
autoScale: true,
showSubtitle: true,
subtitle: '${entityName}',
subtitleFont: {

6
ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.html

@ -15,12 +15,13 @@
limitations under the License.
-->
<div class="tb-value-card-panel" [class]="this.layout" [style]="backgroundStyle">
<div #valueCardPanel class="tb-value-card-panel" [class]="this.layout" [style]="backgroundStyle">
<div class="tb-value-card-overlay" [style]="overlayStyle"></div>
<div class="tb-value-card-title-panel">
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
</div>
<ng-container [ngSwitch]="layout">
<div #valueCardContent class="tb-value-card-content" [class]="this.layout">
<ng-container [ngSwitch]="layout">
<ng-template [ngSwitchCase]="valueCardLayout.square">
<ng-container *ngTemplateOutlet="iconWithLabelTpl"></ng-container>
<ng-container *ngTemplateOutlet="valueTpl"></ng-container>
@ -49,6 +50,7 @@
<ng-container *ngTemplateOutlet="valueTpl"></ng-container>
</ng-template>
</ng-container>
</div>
</div>
<ng-template #iconWithLabelTpl>
<div class="tb-value-card-icon-row">

72
ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.scss

@ -19,24 +19,15 @@
height: 100%;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
padding: 24px;
&.horizontal, &.horizontal_reversed {
padding: 0 24px;
}
> div:not(.tb-value-card-overlay), > tb-icon {
z-index: 1;
}
&.square {
justify-content: space-evenly;
gap: 0;
}
&.horizontal {
flex-direction: row;
}
&.horizontal_reversed {
flex-direction: row-reverse;
}
.tb-value-card-overlay {
position: absolute;
top: 12px;
@ -51,27 +42,54 @@
right: 12px;
z-index: 2;
}
.tb-value-card-icon-row {
.tb-value-card-content {
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: row;
flex-direction: column;
align-items: center;
gap: 8px;
}
&.horizontal_reversed {
.tb-value-card-icon-row {
justify-content: center;
gap: 16px;
&.square {
justify-content: space-evenly;
gap: 0;
}
&.horizontal {
flex-direction: row;
}
&.horizontal_reversed {
flex-direction: row-reverse;
}
.tb-value-card-icon-row {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
&.horizontal_reversed {
.tb-value-card-icon-row {
flex-direction: row-reverse;
}
.tb-value-card-label-row {
align-items: flex-end;
}
}
.tb-value-card-label-row {
align-items: flex-end;
display: flex;
flex-direction: column;
justify-content: center;
}
.tb-value-card-value {
white-space: nowrap;
}
}
.tb-value-card-label-row {
display: flex;
flex-direction: column;
justify-content: center;
}
.tb-value-card-value {
white-space: nowrap;
}
}
}

75
ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts

@ -14,14 +14,25 @@
/// limitations under the License.
///
import { ChangeDetectorRef, Component, Input, OnInit, TemplateRef } from '@angular/core';
import {
AfterViewInit,
ChangeDetectorRef,
Component,
ElementRef,
Input,
OnDestroy,
OnInit,
Renderer2,
TemplateRef,
ViewChild
} from '@angular/core';
import { WidgetContext } from '@home/models/widget-component.models';
import { formatValue, isDefinedAndNotNull } from '@core/utils';
import { DatePipe } from '@angular/common';
import {
backgroundStyle,
ColorProcessor,
ComponentStyle, DateFormatProcessor,
ComponentStyle,
DateFormatProcessor,
getDataKey,
getLabel,
getSingleTsValue,
@ -32,13 +43,24 @@ import {
import { valueCardDefaultSettings, ValueCardLayout, ValueCardWidgetSettings } from './value-card-widget.models';
import { WidgetComponent } from '@home/components/widget/widget.component';
import { Observable } from 'rxjs';
import { ResizeObserver } from '@juggle/resize-observer';
const squareLayoutSize = 160;
const squareLayoutPadding = 48;
const horizontalLayoutHeight = 80;
@Component({
selector: 'tb-value-card-widget',
templateUrl: './value-card-widget.component.html',
styleUrls: ['./value-card-widget.component.scss']
})
export class ValueCardWidgetComponent implements OnInit {
export class ValueCardWidgetComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('valueCardPanel', {static: false})
valueCardPanel: ElementRef<HTMLElement>;
@ViewChild('valueCardContent', {static: false})
valueCardContent: ElementRef<HTMLElement>;
settings: ValueCardWidgetSettings;
@ -73,11 +95,13 @@ export class ValueCardWidgetComponent implements OnInit {
backgroundStyle: ComponentStyle = {};
overlayStyle: ComponentStyle = {};
private panelResize$: ResizeObserver;
private horizontal = false;
private decimals = 0;
private units = '';
constructor(private date: DatePipe,
constructor(private renderer: Renderer2,
private widgetComponent: WidgetComponent,
private cd: ChangeDetectorRef) {
}
@ -122,6 +146,29 @@ export class ValueCardWidgetComponent implements OnInit {
this.overlayStyle = overlayStyle(this.settings.background.overlay);
}
public ngAfterViewInit() {
if (this.settings.autoScale) {
if (!this.horizontal) {
this.renderer.setStyle(this.valueCardContent.nativeElement, 'width', squareLayoutSize + 'px');
}
const height = this.horizontal ? horizontalLayoutHeight : squareLayoutSize;
this.renderer.setStyle(this.valueCardContent.nativeElement, 'height', height + 'px');
this.renderer.setStyle(this.valueCardContent.nativeElement, 'overflow', 'visible');
this.renderer.setStyle(this.valueCardContent.nativeElement, 'position', 'absolute');
this.panelResize$ = new ResizeObserver(() => {
this.onResize();
});
this.panelResize$.observe(this.valueCardPanel.nativeElement);
this.onResize();
}
}
ngOnDestroy() {
if (this.panelResize$) {
this.panelResize$.disconnect();
}
}
public onInit() {
const borderRadius = this.ctx.$widgetElement.css('borderRadius');
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}};
@ -146,4 +193,22 @@ export class ValueCardWidgetComponent implements OnInit {
this.dateColor.update(value);
this.cd.detectChanges();
}
private onResize() {
const panelWidth = this.valueCardPanel.nativeElement.getBoundingClientRect().width - squareLayoutPadding;
const panelHeight = this.valueCardPanel.nativeElement.getBoundingClientRect().height - (this.horizontal ? 0 : squareLayoutPadding);
let scale: number;
if (!this.horizontal) {
const size = Math.min(panelWidth, panelHeight);
scale = size / squareLayoutSize;
} else {
const targetWidth = panelWidth;
const aspect = Math.min(panelHeight / targetWidth, 0.25);
const targetHeight = targetWidth * aspect;
scale = targetHeight / horizontalLayoutHeight;
const width = targetWidth / scale;
this.renderer.setStyle(this.valueCardContent.nativeElement, 'width', width + 'px');
}
this.renderer.setStyle(this.valueCardContent.nativeElement, 'transform', `scale(${scale})`);
}
}

2
ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts

@ -64,6 +64,7 @@ export const valueCardLayoutImages = new Map<ValueCardLayout, string>(
export interface ValueCardWidgetSettings {
layout: ValueCardLayout;
autoScale: boolean;
showLabel: boolean;
labelFont: Font;
labelColor: ColorSettings;
@ -83,6 +84,7 @@ export interface ValueCardWidgetSettings {
export const valueCardDefaultSettings = (horizontal: boolean): ValueCardWidgetSettings => ({
layout: horizontal ? ValueCardLayout.horizontal : ValueCardLayout.square,
autoScale: true,
showLabel: true,
labelFont: {
family: 'Roboto',

32
ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.html

@ -15,23 +15,25 @@
limitations under the License.
-->
<div class="tb-count-panel" [class.tb-count-pointer]="hasCardClickAction" (click)="cardClick($event)">
<div class="tb-count-panel-column">
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
<div class="tb-count-panel-row">
<div class="tb-count-icon-panel" *ngIf="showIconBackground || showIcon"
[style]="{minWidth: iconBackgroundSize, minHeight: iconBackgroundSize}">
<div *ngIf="showIconBackground" class="tb-count-icon-background-panel">
<div [style]="iconBackgroundStyle" [style.background-color]="iconBackgroundColor.color"></div>
<div #countPanel class="tb-count-panel" [class.tb-count-pointer]="hasCardClickAction" (click)="cardClick($event)">
<div #countPanelContent class="tb-count-panel-content">
<div class="tb-count-panel-column">
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
<div class="tb-count-panel-row">
<div class="tb-count-icon-panel" *ngIf="showIconBackground || showIcon"
[style]="{minWidth: iconBackgroundSize, minHeight: iconBackgroundSize}">
<div *ngIf="showIconBackground" class="tb-count-icon-background-panel">
<div [style]="iconBackgroundStyle" [style.background-color]="iconBackgroundColor.color"></div>
</div>
<tb-icon *ngIf="showIcon" [style]="iconStyle" [style.color]="iconColor.color">{{ icon }}</tb-icon>
</div>
<div class="tb-count-label-value-panel" [class.tb-count-layout-row]="layout === countCardLayout.row">
<div *ngIf="showLabel" class="tb-count-label" [style]="labelStyle" [style.color]="labelColor.color">{{ label }}</div>
<div *ngIf="layout === countCardLayout.row" style="flex: 1; min-width: 12px;"></div>
<div [style]="valueStyle" [style.color]="valueColor.color">{{ valueText }}</div>
</div>
<tb-icon *ngIf="showIcon" [style]="iconStyle" [style.color]="iconColor.color">{{ icon }}</tb-icon>
</div>
<div class="tb-count-label-value-panel" [class.tb-count-layout-row]="layout === countCardLayout.row">
<div *ngIf="showLabel" [style]="labelStyle" [style.color]="labelColor.color">{{ label }}</div>
<div *ngIf="layout === countCardLayout.row" style="flex: 1;"></div>
<div [style]="valueStyle" [style.color]="valueColor.color">{{ valueText }}</div>
</div>
</div>
<tb-icon *ngIf="showChevron" [style]="chevronStyle">chevron_right</tb-icon>
</div>
<tb-icon *ngIf="showChevron" [style]="chevronStyle">chevron_right</tb-icon>
</div>

88
ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.scss

@ -17,55 +17,71 @@
.tb-count-panel {
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: row;
align-items: center;
gap: 12px;
justify-content: center;
padding: 12px;
&.tb-count-pointer {
cursor: pointer;
}
.tb-count-panel-column {
flex: 1;
display: flex;
flex-direction: column;
gap: 12px;
}
.tb-count-panel-row {
flex: 1;
.tb-count-panel-content {
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: row;
align-items: center;
gap: 12px;
}
.tb-count-icon-panel {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.mat-icon {
z-index: 1;
.tb-count-panel-column {
flex: 1;
display: flex;
flex-direction: column;
gap: 12px;
overflow: hidden;
}
}
.tb-count-icon-background-panel {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.tb-count-label-value-panel {
flex: 1;
display: flex;
flex-direction: column;
place-content: flex-start space-around;
align-items: flex-start;
gap: 0;
&.tb-count-layout-row {
.tb-count-panel-row {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
gap: 12px;
}
.tb-count-icon-panel {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.mat-icon {
z-index: 1;
}
}
.tb-count-icon-background-panel {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.tb-count-label-value-panel {
flex: 1;
display: flex;
flex-direction: column;
place-content: flex-start space-around;
align-items: flex-start;
gap: 0;
overflow: hidden;
&.tb-count-layout-row {
flex-direction: row;
align-items: center;
}
.tb-count-label {
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
}
}
}

73
ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.ts

@ -14,7 +14,18 @@
/// limitations under the License.
///
import { ChangeDetectorRef, Component, Input, OnInit, TemplateRef } from '@angular/core';
import {
AfterViewInit,
ChangeDetectorRef,
Component,
ElementRef,
Input,
OnDestroy,
OnInit,
Renderer2,
TemplateRef,
ViewChild
} from '@angular/core';
import { WidgetContext } from '@home/models/widget-component.models';
import { formatValue } from '@core/utils';
import {
@ -24,20 +35,30 @@ import {
iconStyle,
textStyle
} from '@shared/models/widget-settings.models';
import { WidgetComponent } from '@home/components/widget/widget.component';
import {
CountCardLayout,
countDefaultSettings,
CountWidgetSettings
} from '@home/components/widget/lib/count/count-widget.models';
import { coerceBoolean } from '@shared/decorators/coercion';
import { ResizeObserver } from '@juggle/resize-observer';
const layoutHeight = 36;
const layoutHeightWithTitle = 60;
const layoutPadding = 24;
@Component({
selector: 'tb-count-widget',
templateUrl: './count-widget.component.html',
styleUrls: ['./count-widget.component.scss']
})
export class CountWidgetComponent implements OnInit {
export class CountWidgetComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('countPanel', {static: false})
countPanel: ElementRef<HTMLElement>;
@ViewChild('countPanelContent', {static: false})
countPanelContent: ElementRef<HTMLElement>;
settings: CountWidgetSettings;
@ -79,7 +100,10 @@ export class CountWidgetComponent implements OnInit {
hasCardClickAction = false;
constructor(private widgetComponent: WidgetComponent,
private panelResize$: ResizeObserver;
private hasTitle = false;
constructor(private renderer: Renderer2,
private cd: ChangeDetectorRef) {
}
@ -120,6 +144,27 @@ export class CountWidgetComponent implements OnInit {
this.chevronStyle.color = this.settings.chevronColor;
this.hasCardClickAction = this.ctx.actionsApi.getActionDescriptors('cardClick').length > 0;
this.hasTitle = this.ctx.widgetConfig.showTitle;
}
public ngAfterViewInit() {
if (this.settings.autoScale) {
const height = this.hasTitle ? layoutHeightWithTitle : layoutHeight;
this.renderer.setStyle(this.countPanelContent.nativeElement, 'height', height + 'px');
this.renderer.setStyle(this.countPanelContent.nativeElement, 'overflow', 'visible');
this.renderer.setStyle(this.countPanelContent.nativeElement, 'position', 'absolute');
this.panelResize$ = new ResizeObserver(() => {
this.onResize();
});
this.panelResize$.observe(this.countPanel.nativeElement);
this.onResize();
}
}
ngOnDestroy() {
if (this.panelResize$) {
this.panelResize$.disconnect();
}
}
public onInit() {
@ -144,4 +189,24 @@ export class CountWidgetComponent implements OnInit {
public cardClick($event: Event) {
this.ctx.actionsApi.cardClick($event);
}
private onResize() {
const panelWidth = this.countPanel.nativeElement.getBoundingClientRect().width - layoutPadding;
const panelHeight = this.countPanel.nativeElement.getBoundingClientRect().height - layoutPadding;
const targetWidth = panelWidth;
let minAspect = 0.25;
if (this.settings.showChevron) {
minAspect -= 0.05;
}
if (this.hasTitle) {
minAspect += 0.15;
}
const aspect = Math.min(panelHeight / targetWidth, minAspect);
const targetHeight = targetWidth * aspect;
const height = this.hasTitle ? layoutHeightWithTitle : layoutHeight;
const scale = targetHeight / height;
const width = targetWidth / scale;
this.renderer.setStyle(this.countPanelContent.nativeElement, 'width', width + 'px');
this.renderer.setStyle(this.countPanelContent.nativeElement, 'transform', `scale(${scale})`);
}
}

2
ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.models.ts

@ -53,6 +53,7 @@ export const entityCountCardLayoutImages = new Map<CountCardLayout, string>(
export interface CountWidgetSettings {
layout: CountCardLayout;
autoScale: boolean;
showLabel: boolean;
label: string;
labelFont: Font;
@ -76,6 +77,7 @@ export interface CountWidgetSettings {
export const countDefaultSettings = (alarmElseEntity: boolean): CountWidgetSettings => ({
layout: CountCardLayout.column,
autoScale: true,
showLabel: true,
label: alarmElseEntity ? 'Total' : 'Devices',
labelFont: {

5
ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/aggregated-value-card-widget-settings.component.html

@ -18,6 +18,11 @@
<ng-container [formGroup]="aggregatedValueCardWidgetSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.aggregated-value-card.aggregated-value-card-style</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="autoScale">
{{ 'widgets.aggregated-value-card.auto-scale' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showSubtitle">
{{ 'widgets.aggregated-value-card.subtitle' | translate }}

1
ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/aggregated-value-card-widget-settings.component.ts

@ -50,6 +50,7 @@ export class AggregatedValueCardWidgetSettingsComponent extends WidgetSettingsCo
protected onSettingsSet(settings: WidgetSettings) {
this.aggregatedValueCardWidgetSettingsForm = this.fb.group({
autoScale: [settings.autoScale, []],
showSubtitle: [settings.showSubtitle, []],
subtitle: [settings.subtitle, []],
subtitleFont: [settings.subtitleFont, []],

5
ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html

@ -28,6 +28,11 @@
{{ valueCardLayoutTranslationMap.get(layout) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="autoScale">
{{ 'widgets.value-card.auto-scale' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide" formControlName="showLabel">
{{ 'widgets.value-card.label' | translate }}

1
ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts

@ -90,6 +90,7 @@ export class ValueCardWidgetSettingsComponent extends WidgetSettingsComponent {
protected onSettingsSet(settings: WidgetSettings) {
this.valueCardWidgetSettingsForm = this.fb.group({
layout: [settings.layout, []],
autoScale: [settings.autoScale, []],
showLabel: [settings.showLabel, []],
labelFont: [settings.labelFont, []],

5
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/count-widget-settings.component.html

@ -26,6 +26,11 @@
{{ countCardLayoutTranslationMap.get(layout) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="autoScale">
{{ 'widgets.count.auto-scale' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showLabel">
{{ 'widgets.count.label' | translate }}

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/count-widget-settings.component.ts

@ -78,6 +78,8 @@ export class CountWidgetSettingsComponent extends PageComponent implements OnIni
this.countCardLayoutImageMap = this.alarmElseEntity ? alarmCountCardLayoutImages : entityCountCardLayoutImages;
this.countWidgetConfigForm = this.fb.group({
layout: [null, []],
autoScale: [null, []],
showLabel: [null, []],
label: [null, []],
labelFont: [null, []],

9
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -6039,7 +6039,8 @@
"icon": "Icon",
"value": "Value",
"date": "Date",
"value-card-style": "Value card style"
"value-card-style": "Value card style",
"auto-scale": "Auto scale"
},
"aggregated-value-card": {
"subtitle": "Subtitle",
@ -6060,7 +6061,8 @@
"remove-value": "Remove value",
"no-values": "No values configured",
"aggregation": "Aggregation",
"aggregated-value-card-style": "Aggregated value card style"
"aggregated-value-card-style": "Aggregated value card style",
"auto-scale": "Auto scale"
},
"alarm-count": {
"alarm-count-card-style": "Alarm count card style"
@ -6076,7 +6078,8 @@
"icon": "Icon",
"icon-background": "Icon background",
"value": "Value",
"chevron": "Chevron"
"chevron": "Chevron",
"auto-scale": "Auto scale"
},
"table": {
"common-table-settings": "Common Table Settings",

Loading…
Cancel
Save