diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.scss
index 39293a9fb3..1ea9825751 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.scss
+++ b/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;
}
}
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts
index 48d432bcb7..d723b9b472 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts
+++ b/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
;
+
+ @ViewChild('valueCardContent', {static: false})
+ valueCardContent: ElementRef;
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})`);
+ }
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts
index aa442bd199..481c50d254 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.models.ts
@@ -64,6 +64,7 @@ export const valueCardLayoutImages = new Map(
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',
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.html
index a3d70ed08b..a8c8ccf675 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.html
@@ -15,23 +15,25 @@
limitations under the License.
-->
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
{{ label }}
+
+
{{ valueText }}
-
{{ icon }}
-
-
-
{{ label }}
-
-
{{ valueText }}
+
chevron_right
-
chevron_right
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.scss
index 8deb211897..e2d526a3c7 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.scss
+++ b/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%;
+ }
}
}
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.ts
index 1f617958e8..0f3c922e1b 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.component.ts
+++ b/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
;
+
+ @ViewChild('countPanelContent', {static: false})
+ countPanelContent: ElementRef;
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})`);
+ }
}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.models.ts
index 4a1b307f4b..f33c1f0523 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.models.ts
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/count/count-widget.models.ts
@@ -53,6 +53,7 @@ export const entityCountCardLayoutImages = new Map(
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: {
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/aggregated-value-card-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/aggregated-value-card-widget-settings.component.html
index c4af456487..2e196f0598 100644
--- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/aggregated-value-card-widget-settings.component.html
+++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/aggregated-value-card-widget-settings.component.html
@@ -18,6 +18,11 @@