Browse Source

UI: Implement Value card widget settings. Improve widget container layout.

pull/9014/head
Igor Kulikov 3 years ago
parent
commit
df8585c1b6
  1. 16
      application/src/main/data/json/system/widget_bundles/cards.json
  2. 6
      ui-ngx/src/app/core/services/dashboard-utils.service.ts
  3. 4
      ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.scss
  4. 1
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  5. 2
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss
  6. 46
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.html
  7. 10
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-card-basic-config.component.ts
  8. 6
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html
  9. 13
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss
  10. 4
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html
  11. 12
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss
  12. 2
      ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html
  13. 8
      ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss
  14. 10
      ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts
  15. 20
      ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts
  16. 89
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.html
  17. 200
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-card-widget-settings.component.ts
  18. 87
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html
  19. 73
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss
  20. 120
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts
  21. 30
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.html
  22. 41
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.scss
  23. 120
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.ts
  24. 27
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts
  25. 20
      ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
  26. 3
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  27. 1
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  28. 17
      ui-ngx/src/app/modules/home/components/widget/widget-container.component.html
  29. 47
      ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss
  30. 1
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  31. 2
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  32. 2
      ui-ngx/src/app/shared/components/unit-input.component.html
  33. 19
      ui-ngx/src/app/shared/components/unit-input.component.ts
  34. 6
      ui-ngx/src/app/shared/models/unit.models.ts
  35. 18
      ui-ngx/src/app/shared/models/widget.models.ts
  36. 16
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  37. 0
      ui-ngx/src/assets/metadata/units.json
  38. 25
      ui-ngx/src/styles.scss

16
application/src/main/data/json/system/widget_bundles/cards.json

File diff suppressed because one or more lines are too long

6
ui-ngx/src/app/core/services/dashboard-utils.service.ts

@ -348,10 +348,8 @@ export class DashboardUtilsService {
private convertDatasourcesFromWidgetType(widgetTypeDescriptor: WidgetTypeDescriptor,
config: WidgetConfig, datasources?: Datasource[]): Datasource[] {
const newDatasources: Datasource[] = [];
if (datasources) {
datasources.forEach(datasource => {
newDatasources.push(this.convertDatasourceFromWidgetType(widgetTypeDescriptor, config, datasource));
});
if (datasources?.length) {
newDatasources.push(this.convertDatasourceFromWidgetType(widgetTypeDescriptor, config, datasources[0]));
}
return newDatasources;
}

4
ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.scss

@ -21,9 +21,9 @@
position: relative;
}
@media #{$mat-gt-xs} {
width: 1200px;
width: 900px;
.mat-mdc-dialog-content {
height: 600px;
height: 900px;
}
}
}

1
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -1177,6 +1177,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
Widget>(AddWidgetDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
maxWidth: '95vw',
data: {
dashboard: this.dashboard,
aliasController: this.dashboardCtx.aliasController,

2
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.scss

@ -45,6 +45,8 @@
}
.preview {
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
object-fit: contain;

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

@ -55,7 +55,7 @@
</tb-color-settings>
</div>
</div>
<div class="tb-form-row">
<div [fxShow]="iconEnabled" class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon">
{{ 'widgets.value-card.icon' | translate }}
</mat-slide-toggle>
@ -87,18 +87,38 @@
</tb-color-settings>
</div>
</div>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showDate">
{{ 'widgets.value-card.date' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex formControlName="dateFormat"></tb-date-format-select>
<tb-font-settings formControlName="dateFont"
[previewText]="datePreviewFn">
</tb-font-settings>
<tb-color-settings formControlName="dateColor">
</tb-color-settings>
<div [fxShow]="dateEnabled" class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showDate">
{{ 'widgets.value-card.date' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex formControlName="dateFormat"></tb-date-format-select>
<tb-font-settings formControlName="dateFont"
[previewText]="datePreviewFn">
</tb-font-settings>
<tb-color-settings formControlName="dateColor">
</tb-color-settings>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">
</tb-background-settings>
</div>
<div class="tb-form-row space-between">
<div translate>widget-config.show-card-buttons</div>
<mat-chip-listbox multiple formControlName="cardButtons">
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widget-config.card-border-radius' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<tb-widget-actions-panel
formControlName="actions">
</tb-widget-actions-panel>
</ng-container>

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

@ -74,6 +74,16 @@ export class ValueCardBasicConfigComponent extends BasicWidgetConfigComponent {
datePreviewFn = this._datePreviewFn.bind(this);
get dateEnabled(): boolean {
const layout: ValueCardLayout = this.valueCardWidgetConfigForm.get('layout').value;
return ![ValueCardLayout.vertical, ValueCardLayout.simplified].includes(layout);
}
get iconEnabled(): boolean {
const layout: ValueCardLayout = this.valueCardWidgetConfigForm.get('layout').value;
return layout !== ValueCardLayout.simplified;
}
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
private cd: ChangeDetectorRef,

6
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html

@ -22,7 +22,7 @@
<mat-option [value]="true">{{ 'datakey.latest' | translate }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex class="tb-inline-field tb-key-field" subscriptSizing="dynamic">
<mat-form-field class="tb-inline-field tb-key-field" subscriptSizing="dynamic">
<mat-chip-grid #chipList>
<mat-chip-row class="tb-datakey-chip" *ngIf="modelValue.type"
(removed)="removeKey()">
@ -44,7 +44,7 @@
matTooltipPosition="above">timeline</mat-icon>
</ng-container>
</div>
<div class="tb-chip-label">
<div class="tb-chip-label tb-chip-text">
<strong>
<ng-container *ngTemplateOutlet="keyName"></ng-container>
</strong>
@ -139,7 +139,7 @@
</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-form-field fxFlex class="tb-inline-field" appearance="outline" subscriptSizing="dynamic">
<mat-form-field class="tb-inline-field tb-label-field" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="label" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<div *ngIf="!hideDataKeyColor" class="tb-color-field">

13
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss

@ -38,7 +38,16 @@
}
.tb-source-field {
width: 140px;
width: 120px;
min-width: 120px;
}
.tb-key-field {
flex: 1 1 60%;
}
.tb-label-field {
flex: 1 1 40%;
}
.tb-color-field, .tb-units-field, .tb-decimals-field {
@ -50,9 +59,11 @@
.tb-units-field {
width: 80px;
min-width: 80px;
}
.tb-color-field, .tb-decimals-field {
width: 60px;
min-width: 60px;
}
}

4
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html

@ -20,8 +20,8 @@
<div class="tb-form-table">
<div class="tb-form-table-header">
<div *ngIf="hasAdditionalLatestDataKeys" class="tb-form-table-header-cell tb-source-header" translate>datakey.source</div>
<div class="tb-form-table-header-cell" fxFlex translate>datakey.key</div>
<div class="tb-form-table-header-cell" fxFlex translate>datakey.label</div>
<div class="tb-form-table-header-cell tb-key-header" translate>datakey.key</div>
<div class="tb-form-table-header-cell tb-label-header" translate>datakey.label</div>
<div *ngIf="!hideDataKeyColor" class="tb-form-table-header-cell tb-color-header" translate>datakey.color</div>
<div *ngIf="!hideUnits" class="tb-form-table-header-cell tb-units-header" translate>widget-config.units-short</div>
<div *ngIf="!hideDecimals" class="tb-form-table-header-cell tb-decimals-header" translate>widget-config.decimals-short</div>

12
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss

@ -15,15 +15,25 @@
*/
.tb-form-table-header-cell {
&.tb-source-header {
width: 140px;
width: 120px;
min-width: 120px;
}
&.tb-key-header {
flex: 1 1 60%;
}
&.tb-label-header {
flex: 1 1 40%;
}
&.tb-units-header {
width: 80px;
min-width: 80px;
}
&.tb-color-header, &.tb-decimals-header {
width: 60px;
min-width: 60px;
}
&.tb-actions-header {
width: 114px;
min-width: 114px;
}
}

2
ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.html

@ -65,7 +65,7 @@
<span *ngIf="!hideDataKeyLabel && key.label !== key.name">{{key.label}}</span>
</div>
<div *ngIf="!hideDataKeyLabel && key.label !== key.name" class="tb-chip-separator">: </div>
<div class="tb-chip-label">
<div class="tb-chip-label tb-chip-text">
<strong>
<ng-container *ngTemplateOutlet="keyName; context:{key: key}"></ng-container>
</strong>

8
ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.scss

@ -65,9 +65,11 @@
font-weight: normal;
font-size: 14px;
line-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&.tb-chip-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.mat-icon.tb-datakey-icon {
margin-right: 4px;
margin-left: 4px;

10
ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts

@ -44,6 +44,7 @@ import { widgetSettingsComponentsMap } from '@home/components/widget/lib/setting
import { Dashboard } from '@shared/models/dashboard.models';
import { WidgetService } from '@core/http/widget.service';
import { IAliasController } from '@core/api/widget-api.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
@Component({
selector: 'tb-widget-settings',
@ -73,6 +74,9 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On
@Input()
widget: Widget;
@Input()
widgetConfig: WidgetConfigComponentData;
private settingsDirective: string;
definedDirectiveError: string;
@ -126,6 +130,11 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On
this.definedSettingsComponent.aliasController = this.aliasController;
}
}
if (propName === 'widgetConfig') {
if (this.definedSettingsComponent) {
this.definedSettingsComponent.widgetConfig = this.widgetConfig;
}
}
}
}
}
@ -214,6 +223,7 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnInit, On
this.definedSettingsComponent.aliasController = this.aliasController;
this.definedSettingsComponent.dashboard = this.dashboard;
this.definedSettingsComponent.widget = this.widget;
this.definedSettingsComponent.widgetConfig = this.widgetConfig;
this.definedSettingsComponent.functionScopeVariables = this.widgetService.getWidgetScopeVariables();
this.changeSubscription = this.definedSettingsComponent.settingsChanged.subscribe((settings) => {
this.updateModel(settings);

20
ui-ngx/src/app/modules/home/components/widget/config/widget-settings.models.ts

@ -276,6 +276,14 @@ export enum BackgroundType {
color = 'color'
}
export const backgroundTypeTranslations = new Map<BackgroundType, string>(
[
[BackgroundType.image, 'widgets.background.background-type-image'],
[BackgroundType.imageUrl, 'widgets.background.background-type-image-url'],
[BackgroundType.color, 'widgets.background.background-type-color']
]
);
export interface OverlaySettings {
enabled: boolean;
color: string;
@ -313,11 +321,13 @@ export const backgroundStyle = (background: BackgroundSettings): ComponentStyle
};
} else {
const imageUrl = background.type === BackgroundType.image ? background.imageBase64 : background.imageUrl;
return {
background: `url(${imageUrl}) no-repeat`,
backgroundSize: 'cover',
backgroundPosition: '50% 50%'
};
if (imageUrl) {
return {
background: `url(${imageUrl}) no-repeat 50% 50% / cover`
};
} else {
return {};
}
}
};

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

@ -0,0 +1,89 @@
<!--
Copyright © 2016-2023 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<ng-container [formGroup]="valueCardWidgetSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.value-card.value-card-style</div>
<tb-image-cards-select rowHeight="{{ horizontal ? '3:1' : '7:5' }}"
[cols]="horizontal ? 2 : 4"
[colsLtMd]="horizontal ? 1 : 2"
label="{{ 'widgets.value-card.layout' | translate }}" formControlName="layout">
<tb-image-cards-select-option *ngFor="let layout of valueCardLayouts"
[value]="layout"
[image]="valueCardLayoutImageMap.get(layout)">
{{ valueCardLayoutTranslationMap.get(layout) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide" formControlName="showLabel">
{{ 'widgets.value-card.label' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="labelFont"
[previewText]="label">
</tb-font-settings>
<tb-color-settings formControlName="labelColor">
</tb-color-settings>
</div>
</div>
<div [fxShow]="iconEnabled" class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon">
{{ 'widgets.value-card.icon' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field fxFlex appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-css-unit-select fxFlex formControlName="iconSizeUnit"></tb-css-unit-select>
<tb-material-icon-select asBoxInput
[color]="valueCardWidgetSettingsForm.get('iconColor').value?.color"
formControlName="icon">
</tb-material-icon-select>
<tb-color-settings formControlName="iconColor">
</tb-color-settings>
</div>
</div>
<div class="tb-form-row space-between">
<div translate>widgets.value-card.value</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="valueFont"
[previewText]="valuePreviewFn">
</tb-font-settings>
<tb-color-settings formControlName="valueColor">
</tb-color-settings>
</div>
</div>
<div [fxShow]="dateEnabled" class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showDate">
{{ 'widgets.value-card.date' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex formControlName="dateFormat"></tb-date-format-select>
<tb-font-settings formControlName="dateFont"
[previewText]="datePreviewFn">
</tb-font-settings>
<tb-color-settings formControlName="dateColor">
</tb-color-settings>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">
</tb-background-settings>
</div>
</div>
</ng-container>

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

@ -0,0 +1,200 @@
///
/// Copyright © 2016-2023 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, Injector } from '@angular/core';
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import {
valueCardDefaultSettings,
ValueCardLayout, valueCardLayoutImages,
valueCardLayouts, valueCardLayoutTranslations
} from '@home/components/widget/lib/cards/value-card-widget.models';
import { formatValue, isDefinedAndNotNull } from '@core/utils';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import {
DateFormatProcessor,
DateFormatSettings,
getLabel
} from '@home/components/widget/config/widget-settings.models';
@Component({
selector: 'tb-value-card-widget-settings',
templateUrl: './value-card-widget-settings.component.html',
styleUrls: []
})
export class ValueCardWidgetSettingsComponent extends WidgetSettingsComponent {
valueCardLayouts: ValueCardLayout[] = [];
valueCardLayoutTranslationMap = valueCardLayoutTranslations;
valueCardLayoutImageMap = valueCardLayoutImages;
horizontal = false;
valueCardWidgetSettingsForm: UntypedFormGroup;
valuePreviewFn = this._valuePreviewFn.bind(this);
datePreviewFn = this._datePreviewFn.bind(this);
get label(): string {
return getLabel(this.widgetConfig.config.datasources);
}
get dateEnabled(): boolean {
const layout: ValueCardLayout = this.valueCardWidgetSettingsForm.get('layout').value;
return ![ValueCardLayout.vertical, ValueCardLayout.simplified].includes(layout);
}
get iconEnabled(): boolean {
const layout: ValueCardLayout = this.valueCardWidgetSettingsForm.get('layout').value;
return layout !== ValueCardLayout.simplified;
}
constructor(protected store: Store<AppState>,
private $injector: Injector,
private fb: UntypedFormBuilder) {
super(store);
}
protected settingsForm(): UntypedFormGroup {
return this.valueCardWidgetSettingsForm;
}
protected onWidgetConfigSet(widgetConfig: WidgetConfigComponentData) {
const params = widgetConfig.typeParameters as any;
this.horizontal = isDefinedAndNotNull(params.horizontal) ? params.horizontal : false;
this.valueCardLayouts = valueCardLayouts(this.horizontal);
}
protected defaultSettings(): WidgetSettings {
return valueCardDefaultSettings(this.horizontal);
}
protected onSettingsSet(settings: WidgetSettings) {
this.valueCardWidgetSettingsForm = this.fb.group({
layout: [settings.layout, []],
showLabel: [settings.showLabel, []],
labelFont: [settings.labelFont, []],
labelColor: [settings.labelColor, []],
showIcon: [settings.showIcon, []],
iconSize: [settings.iconSize, [Validators.min(0)]],
iconSizeUnit: [settings.iconSizeUnit, []],
icon: [settings.icon, []],
iconColor: [settings.iconColor, []],
valueFont: [settings.valueFont, []],
valueColor: [settings.valueColor, []],
showDate: [settings.showDate, []],
dateFormat: [settings.dateFormat, []],
dateFont: [settings.dateFont, []],
dateColor: [settings.dateColor, []],
background: [settings.background, []]
});
}
protected validatorTriggers(): string[] {
return ['layout', 'showLabel', 'showIcon', 'showDate'];
}
protected updateValidators(emitEvent: boolean) {
const layout: ValueCardLayout = this.valueCardWidgetSettingsForm.get('layout').value;
const showLabel: boolean = this.valueCardWidgetSettingsForm.get('showLabel').value;
const showIcon: boolean = this.valueCardWidgetSettingsForm.get('showIcon').value;
const showDate: boolean = this.valueCardWidgetSettingsForm.get('showDate').value;
const dateEnabled = ![ValueCardLayout.vertical, ValueCardLayout.simplified].includes(layout);
const iconEnabled = layout !== ValueCardLayout.simplified;
if (showLabel) {
this.valueCardWidgetSettingsForm.get('labelFont').enable();
this.valueCardWidgetSettingsForm.get('labelColor').enable();
} else {
this.valueCardWidgetSettingsForm.get('labelFont').disable();
this.valueCardWidgetSettingsForm.get('labelColor').disable();
}
if (iconEnabled) {
this.valueCardWidgetSettingsForm.get('showIcon').enable({emitEvent: false});
if (showIcon) {
this.valueCardWidgetSettingsForm.get('iconSize').enable();
this.valueCardWidgetSettingsForm.get('iconSizeUnit').enable();
this.valueCardWidgetSettingsForm.get('icon').enable();
this.valueCardWidgetSettingsForm.get('iconColor').enable();
} else {
this.valueCardWidgetSettingsForm.get('iconSize').disable();
this.valueCardWidgetSettingsForm.get('iconSizeUnit').disable();
this.valueCardWidgetSettingsForm.get('icon').disable();
this.valueCardWidgetSettingsForm.get('iconColor').disable();
}
} else {
this.valueCardWidgetSettingsForm.get('showIcon').disable({emitEvent: false});
this.valueCardWidgetSettingsForm.get('iconSize').disable();
this.valueCardWidgetSettingsForm.get('iconSizeUnit').disable();
this.valueCardWidgetSettingsForm.get('icon').disable();
this.valueCardWidgetSettingsForm.get('iconColor').disable();
}
if (dateEnabled) {
this.valueCardWidgetSettingsForm.get('showDate').enable({emitEvent: false});
if (showDate) {
this.valueCardWidgetSettingsForm.get('dateFormat').enable();
this.valueCardWidgetSettingsForm.get('dateFont').enable();
this.valueCardWidgetSettingsForm.get('dateColor').enable();
} else {
this.valueCardWidgetSettingsForm.get('dateFormat').disable();
this.valueCardWidgetSettingsForm.get('dateFont').disable();
this.valueCardWidgetSettingsForm.get('dateColor').disable();
}
} else {
this.valueCardWidgetSettingsForm.get('showDate').disable({emitEvent: false});
this.valueCardWidgetSettingsForm.get('dateFormat').disable();
this.valueCardWidgetSettingsForm.get('dateFont').disable();
this.valueCardWidgetSettingsForm.get('dateColor').disable();
}
this.valueCardWidgetSettingsForm.get('showIcon').updateValueAndValidity({emitEvent: false});
this.valueCardWidgetSettingsForm.get('showDate').updateValueAndValidity({emitEvent: false});
this.valueCardWidgetSettingsForm.get('labelFont').updateValueAndValidity({emitEvent});
this.valueCardWidgetSettingsForm.get('labelColor').updateValueAndValidity({emitEvent});
this.valueCardWidgetSettingsForm.get('iconSize').updateValueAndValidity({emitEvent});
this.valueCardWidgetSettingsForm.get('iconSizeUnit').updateValueAndValidity({emitEvent});
this.valueCardWidgetSettingsForm.get('icon').updateValueAndValidity({emitEvent});
this.valueCardWidgetSettingsForm.get('iconColor').updateValueAndValidity({emitEvent});
this.valueCardWidgetSettingsForm.get('dateFormat').updateValueAndValidity({emitEvent});
this.valueCardWidgetSettingsForm.get('dateFont').updateValueAndValidity({emitEvent});
this.valueCardWidgetSettingsForm.get('dateColor').updateValueAndValidity({emitEvent});
}
private _valuePreviewFn(): string {
const units: string = this.widgetConfig.config.units;
const decimals: number = this.widgetConfig.config.decimals;
return formatValue(22, decimals, units, true);
}
private _datePreviewFn(): string {
const dateFormat: DateFormatSettings = this.valueCardWidgetSettingsForm.get('dateFormat').value;
const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat);
processor.update(Date.now());
return processor.formatted;
}
}

87
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.html

@ -0,0 +1,87 @@
<!--
Copyright © 2016-2023 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-background-settings-panel" [formGroup]="backgroundSettingsFormGroup">
<div class="tb-background-settings-title" translate>widgets.background.background-settings</div>
<div class="tb-form-panel">
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<div class="tb-form-panel-title" translate>widgets.background.background</div>
<tb-toggle-select formControlName="type" fxFlex>
<tb-toggle-option *ngFor="let type of backgroundTypes"
[value]="type">
{{ backgroundTypeTranslationsMap.get(type) | translate }}
</tb-toggle-option>
</tb-toggle-select>
</div>
<tb-image-input [fxShow]="backgroundSettingsFormGroup.get('type').value === backgroundType.image" formControlName="imageBase64"></tb-image-input>
<div [fxShow]="backgroundSettingsFormGroup.get('type').value === backgroundType.imageUrl" class="tb-form-row space-between">
<div translate>widgets.background.image-url</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="imageUrl" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div [fxShow]="backgroundSettingsFormGroup.get('type').value === backgroundType.color" class="tb-form-row space-between">
<div translate>widgets.color.color</div>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
<div class="tb-form-panel" formGroupName="overlay">
<div class="tb-form-panel-title" translate>widgets.background.overlay</div>
<mat-slide-toggle class="mat-slide" formControlName="enabled">
{{ 'widgets.background.enable-overlay' | translate }}
</mat-slide-toggle>
<div class="tb-form-row space-between">
<div translate>widgets.color.color</div>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
<div class="tb-form-row space-between">
<div translate>widgets.background.blur</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="blur" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div>
</mat-form-field>
</div>
</div>
<div class="tb-background-settings-preview">
<div class="tb-background-settings-preview-title" translate>
widgets.background.preview
</div>
<div class="tb-background-settings-preview-box mat-elevation-z4" [style]="backgroundStyle">
<div class="tb-background-settings-preview-overlay" [style]="overlayStyle">
</div>
</div>
</div>
<div class="tb-background-settings-panel-buttons">
<button mat-button
color="primary"
type="button"
(click)="cancel()">
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button
color="primary"
type="button"
(click)="applyColorSettings()"
[disabled]="backgroundSettingsFormGroup.invalid || !backgroundSettingsFormGroup.dirty">
{{ 'action.apply' | translate }}
</button>
</div>
</div>

73
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.scss

@ -0,0 +1,73 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../../../../../../scss/constants';
.tb-background-settings-panel {
width: 620px;
display: flex;
flex-direction: column;
gap: 16px;
@media #{$mat-lt-md} {
width: 90vw;
}
.tb-background-settings-title {
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: 0.25px;
color: rgba(0, 0, 0, 0.87);
}
.tb-background-settings-preview {
flex: 1;
background: rgba(0, 0, 0, 0.04);
display: flex;
flex-direction: column;
padding: 12px 16px 24px 16px;
align-items: center;
gap: 12px;
}
.tb-background-settings-preview-title {
align-self: stretch;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 24px;
color: rgba(0, 0, 0, 0.38);
}
.tb-background-settings-preview-box {
position: relative;
width: 136px;
height: 118px;
border-radius: 2.666px;
}
.tb-background-settings-preview-overlay {
position: absolute;
border-radius: 2.666px;
top: 7.998px;
bottom: 7.998px;
left: 7.998px;
right: 7.998px;
}
.tb-background-settings-panel-buttons {
height: 40px;
display: flex;
flex-direction: row;
gap: 16px;
justify-content: flex-end;
align-items: flex-end;
}
}

120
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings-panel.component.ts

@ -0,0 +1,120 @@
///
/// Copyright © 2016-2023 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import {
backgroundStyle,
overlayStyle,
BackgroundSettings,
BackgroundType,
backgroundTypeTranslations, ComponentStyle
} from '@home/components/widget/config/widget-settings.models';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@Component({
selector: 'tb-background-settings-panel',
templateUrl: './background-settings-panel.component.html',
providers: [],
styleUrls: ['./background-settings-panel.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class BackgroundSettingsPanelComponent extends PageComponent implements OnInit {
@Input()
backgroundSettings: BackgroundSettings;
@Input()
popover: TbPopoverComponent<BackgroundSettingsPanelComponent>;
@Output()
backgroundSettingsApplied = new EventEmitter<BackgroundSettings>();
backgroundType = BackgroundType;
backgroundTypes = Object.keys(BackgroundType) as BackgroundType[];
backgroundTypeTranslationsMap = backgroundTypeTranslations;
backgroundSettingsFormGroup: UntypedFormGroup;
backgroundStyle: ComponentStyle = {};
overlayStyle: ComponentStyle = {};
constructor(private fb: UntypedFormBuilder,
protected store: Store<AppState>) {
super(store);
}
ngOnInit(): void {
this.backgroundSettingsFormGroup = this.fb.group(
{
type: [this.backgroundSettings?.type, []],
imageBase64: [this.backgroundSettings?.imageBase64, []],
imageUrl: [this.backgroundSettings?.imageUrl, []],
color: [this.backgroundSettings?.color, []],
overlay: this.fb.group({
enabled: [this.backgroundSettings?.overlay?.enabled, []],
color: [this.backgroundSettings?.overlay?.color, []],
blur: [this.backgroundSettings?.overlay?.blur, []]
})
}
);
this.backgroundSettingsFormGroup.get('type').valueChanges.subscribe(() => {
setTimeout(() => {this.popover?.updatePosition();}, 0);
});
this.backgroundSettingsFormGroup.get('overlay').get('enabled').valueChanges.subscribe(() => {
this.updateValidators();
});
this.backgroundSettingsFormGroup.valueChanges.subscribe(() => {
this.updateBackgroundStyle();
});
this.updateValidators();
this.updateBackgroundStyle();
}
cancel() {
this.popover?.hide();
}
applyColorSettings() {
const backgroundSettings = this.backgroundSettingsFormGroup.value;
this.backgroundSettingsApplied.emit(backgroundSettings);
}
private updateValidators() {
const overlayEnabled: boolean = this.backgroundSettingsFormGroup.get('overlay').get('enabled').value;
if (overlayEnabled) {
this.backgroundSettingsFormGroup.get('overlay').get('color').enable();
this.backgroundSettingsFormGroup.get('overlay').get('blur').enable();
} else {
this.backgroundSettingsFormGroup.get('overlay').get('color').disable();
this.backgroundSettingsFormGroup.get('overlay').get('blur').disable();
}
this.backgroundSettingsFormGroup.get('overlay').get('color').updateValueAndValidity({emitEvent: false});
this.backgroundSettingsFormGroup.get('overlay').get('blur').updateValueAndValidity({emitEvent: false});
}
private updateBackgroundStyle() {
const background: BackgroundSettings = this.backgroundSettingsFormGroup.value;
this.backgroundStyle = backgroundStyle(background);
this.overlayStyle = overlayStyle(background.overlay);
}
}

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

@ -0,0 +1,30 @@
<!--
Copyright © 2016-2023 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<button type="button"
mat-stroked-button
class="tb-box-button tb-background-settings"
[disabled]="disabled"
#matButton
(click)="openBackgroundSettingsPopup($event, matButton)">
<div class="tb-color-preview box" [ngClass]="{'disabled': disabled}">
<div class="tb-color-result" [style]="backgroundStyle">
<div class="tb-color-overlay" [style]="overlayStyle">
</div>
</div>
</div>
</button>

41
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/background-settings.component.scss

@ -0,0 +1,41 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
button.mat-mdc-button-base.tb-box-button.tb-background-settings {
padding: 0;
.mat-mdc-button-persistent-ripple {
z-index: 2;
}
.tb-color-preview {
width: 38px;
min-width: 38px;
height: 38px;
&.box {
.tb-color-result {
&:after {
border: none;
}
}
.tb-color-overlay {
position: absolute;
border-radius: 3px;
top: 4px;
bottom: 4px;
left: 4px;
right: 4px;
}
}
}
}

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

@ -0,0 +1,120 @@
///
/// Copyright © 2016-2023 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef, ViewEncapsulation } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import {
BackgroundSettings,
backgroundStyle,
BackgroundType,
ComponentStyle,
overlayStyle
} from '@home/components/widget/config/widget-settings.models';
import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
import {
BackgroundSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/background-settings-panel.component';
@Component({
selector: 'tb-background-settings',
templateUrl: './background-settings.component.html',
styleUrls: ['./background-settings.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => BackgroundSettingsComponent),
multi: true
}
],
encapsulation: ViewEncapsulation.None
})
export class BackgroundSettingsComponent implements OnInit, ControlValueAccessor {
@Input()
disabled: boolean;
backgroundType = BackgroundType;
modelValue: BackgroundSettings;
backgroundStyle: ComponentStyle = {};
overlayStyle: ComponentStyle = {};
private propagateChange = null;
constructor(private popoverService: TbPopoverService,
private renderer: Renderer2,
private viewContainerRef: ViewContainerRef) {}
ngOnInit(): void {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
this.updateBackgroundStyle();
}
writeValue(value: BackgroundSettings): void {
this.modelValue = value;
this.updateBackgroundStyle();
}
openBackgroundSettingsPopup($event: Event, matButton: MatButton) {
if ($event) {
$event.stopPropagation();
}
const trigger = matButton._elementRef.nativeElement;
if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger);
} else {
const ctx: any = {
backgroundSettings: this.modelValue
};
const backgroundSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, BackgroundSettingsPanelComponent, 'left', true, null,
ctx,
{},
{}, {}, true);
backgroundSettingsPanelPopover.tbComponentRef.instance.popover = backgroundSettingsPanelPopover;
backgroundSettingsPanelPopover.tbComponentRef.instance.backgroundSettingsApplied.subscribe((backgroundSettings) => {
backgroundSettingsPanelPopover.hide();
this.modelValue = backgroundSettings;
this.updateBackgroundStyle();
this.propagateChange(this.modelValue);
});
}
}
private updateBackgroundStyle() {
if (!this.disabled) {
this.backgroundStyle = backgroundStyle(this.modelValue);
this.overlayStyle = overlayStyle(this.modelValue.overlay);
} else {
this.backgroundStyle = {};
this.overlayStyle = {};
}
}
}

27
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts

@ -21,14 +21,14 @@ import {
Directive,
ElementRef,
forwardRef,
Input,
Input, OnChanges,
OnDestroy, OnInit,
QueryList,
QueryList, SimpleChanges,
ViewEncapsulation
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms';
import { coerceBoolean } from '@shared/decorators/coercion';
import { Observable, Subject } from 'rxjs';
import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
import { map, share, startWith, takeUntil } from 'rxjs/operators';
import { BreakpointObserver } from '@angular/cdk/layout';
import { MediaBreakpoints } from '@shared/models/constants';
@ -73,7 +73,7 @@ export class ImageCardsSelectOptionDirective {
],
encapsulation: ViewEncapsulation.None
})
export class ImageCardsSelectComponent implements ControlValueAccessor, OnInit, AfterContentInit, OnDestroy {
export class ImageCardsSelectComponent implements ControlValueAccessor, OnInit, OnChanges, AfterContentInit, OnDestroy {
@ContentChildren(ImageCardsSelectOptionDirective) imageCardsSelectOptions: QueryList<ImageCardsSelectOptionDirective>;
@ -107,20 +107,33 @@ export class ImageCardsSelectComponent implements ControlValueAccessor, OnInit,
private _destroyed = new Subject<void>();
private _colsChanged = new BehaviorSubject<void>(null);
constructor(private breakpointObserver: BreakpointObserver) {
this.valueFormControl = new UntypedFormControl('');
}
ngOnInit(): void {
const gridColumns = this.breakpointObserver.isMatched(MediaBreakpoints['lt-md']) ? this.colsLtMd : this.cols;
this.cols$ = this.breakpointObserver
.observe(MediaBreakpoints['lt-md']).pipe(
map((state) => state.matches ? this.colsLtMd : this.cols),
this.cols$ = combineLatest({state: this.breakpointObserver
.observe(MediaBreakpoints['lt-md']), colsChanged: this._colsChanged.asObservable()}).pipe(
map((data) => data.state.matches ? this.colsLtMd : this.cols),
startWith(gridColumns),
share()
);
}
ngOnChanges(changes: SimpleChanges): void {
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
if (['cols', 'colsLtMd'].includes(propName)) {
this._colsChanged.next(null);
}
}
}
}
ngAfterContentInit(): void {
this.imageCardsSelectOptions.changes.pipe(startWith(null), takeUntil(this._destroyed)).subscribe(() => {
this.syncImageCardsSelectOptions();

20
ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts

@ -281,6 +281,13 @@ import { DateFormatSelectComponent } from '@home/components/widget/lib/settings/
import {
DateFormatSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/date-format-settings-panel.component';
import { BackgroundSettingsComponent } from '@home/components/widget/lib/settings/common/background-settings.component';
import {
BackgroundSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/background-settings-panel.component';
import {
ValueCardWidgetSettingsComponent
} from '@home/components/widget/lib/settings/cards/value-card-widget-settings.component';
@NgModule({
declarations: [
@ -391,7 +398,10 @@ import {
ColorSettingsPanelComponent,
CssUnitSelectComponent,
DateFormatSelectComponent,
DateFormatSettingsPanelComponent
DateFormatSettingsPanelComponent,
BackgroundSettingsComponent,
BackgroundSettingsPanelComponent,
ValueCardWidgetSettingsComponent
],
imports: [
CommonModule,
@ -506,7 +516,10 @@ import {
ColorSettingsPanelComponent,
CssUnitSelectComponent,
DateFormatSelectComponent,
DateFormatSettingsPanelComponent
DateFormatSettingsPanelComponent,
BackgroundSettingsComponent,
BackgroundSettingsPanelComponent,
ValueCardWidgetSettingsComponent
]
})
export class WidgetSettingsModule {
@ -575,5 +588,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsCo
'tb-route-map-widget-settings': RouteMapWidgetSettingsComponent,
'tb-trip-animation-widget-settings': TripAnimationWidgetSettingsComponent,
'tb-doc-links-widget-settings': DocLinksWidgetSettingsComponent,
'tb-quick-links-widget-settings': QuickLinksWidgetSettingsComponent
'tb-quick-links-widget-settings': QuickLinksWidgetSettingsComponent,
'tb-value-card-widget-settings': ValueCardWidgetSettingsComponent
};

3
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -546,6 +546,9 @@ export class WidgetComponentService {
if (isUndefined(result.typeParameters.previewHeight)) {
result.typeParameters.previewHeight = '70%';
}
if (isUndefined(result.typeParameters.absoluteHeader)) {
result.typeParameters.absoluteHeader = false;
}
if (isFunction(widgetTypeInstance.actionSources)) {
result.actionSources = widgetTypeInstance.actionSources();
} else {

1
ui-ngx/src/app/modules/home/components/widget/widget-config.component.html

@ -282,6 +282,7 @@
[aliasController]="aliasController"
[dashboard]="dashboard"
[widget]="widget"
[widgetConfig]="modelValue"
formControlName="settings">
</tb-widget-settings>
</div>

17
ui-ngx/src/app/modules/home/components/widget/widget-container.component.html

@ -19,7 +19,6 @@
[fullscreenBackgroundStyle]="dashboardStyle"
[fullscreenBackgroundImage]="backgroundImage"
(fullscreenChanged)="onFullscreenChanged($event)"
fxLayout="column"
class="tb-widget"
[ngClass]="{
'tb-highlighted': isHighlighted(widget),
@ -32,8 +31,11 @@
(mousedown)="onMouseDown($event)"
(click)="onClicked($event)"
(contextmenu)="onContextMenu($event)">
<div *ngIf="!!widgetComponent.widgetContext?.inited" fxLayout="row" fxLayoutAlign="space-between start">
<div class="tb-widget-title" fxLayout="column" fxLayoutAlign="center start" *ngIf="widget.showWidgetTitlePanel">
<div *ngIf="!!widgetComponent.widgetContext?.inited"
class="tb-widget-header"
[class]="{'tb-widget-header-absolute': widgetComponent.widgetContext?.absoluteHeader}">
<div *ngIf="widget.showWidgetTitlePanel"
class="tb-widget-title">
<span *ngIf="widget.showTitle"
[ngClass]="{'single-row': widget.hasTimewindow}"
[ngStyle]="widget.titleStyle"
@ -57,9 +59,7 @@
</div>
<div *ngIf="widget.showWidgetActions"
class="tb-widget-actions"
[ngClass]="{'tb-widget-actions-absolute': !(widget.showWidgetTitlePanel&&(widget.showTitle||widget.hasAggregation))}"
fxLayout="row"
fxLayoutAlign="start center"
[class]="{'tb-widget-actions-absolute': !(widget.showWidgetTitlePanel&&(widget.showTitle||widget.hasAggregation))}"
(mousedown)="$event.stopPropagation()">
<button mat-icon-button *ngFor="let action of widget.customHeaderActions"
[fxShow]="!isEdit"
@ -105,9 +105,8 @@
</button>
</div>
</div>
<div fxFlex fxLayout="column" class="tb-widget-content" [ngClass]="{'tb-no-interaction': disableWidgetInteraction}">
<tb-widget fxFlex
#widgetComponent
<div class="tb-widget-content" [ngClass]="{'tb-no-interaction': disableWidgetInteraction}">
<tb-widget #widgetComponent
[dashboardWidget]="widget"
[isEdit]="isEdit"
[isMobile]="isMobile">

47
ui-ngx/src/app/modules/home/components/widget/widget-container.component.scss

@ -14,9 +14,14 @@
* limitations under the License.
*/
tb-widget.tb-widget {
position: relative;
height: 100%;
.tb-widget-container {
position: absolute;
inset: 0;
}
.tb-widget {
position: absolute;
inset: 0;
margin: 0;
overflow: hidden;
outline: none;
@ -25,15 +30,27 @@ tb-widget.tb-widget {
}
div.tb-widget {
position: relative;
height: 100%;
margin: 0;
overflow: hidden;
outline: none;
transition: all .2s ease-in-out;
display: flex;
flex-direction: column;
.tb-widget-header {
display: flex;
flex-direction: row;
place-content: flex-start space-between;
align-items: flex-start;
&-absolute {
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: 1;
}
}
.tb-widget-title {
display: flex;
flex-direction: column;
place-content: flex-start center;
align-items: flex-start;
max-height: 65px;
padding-top: 5px;
padding-left: 5px;
@ -63,6 +80,10 @@ div.tb-widget {
}
.tb-widget-actions {
display: flex;
flex-direction: row;
place-content: center flex-start;
align-items: center;
z-index: 19;
margin: 5px 0 0;
@ -104,13 +125,11 @@ div.tb-widget {
}
.tb-widget-content {
flex: 1;
position: relative;
&.tb-no-interaction {
pointer-events: none;
}
tb-widget {
position: relative;
width: 100%;
}
}
&.tb-highlighted {

1
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -409,6 +409,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
elem.classList.add(this.widgetContext.widgetNamespace);
this.widgetType = this.widgetInfo.widgetTypeFunction;
this.typeParameters = this.widgetInfo.typeParameters;
this.widgetContext.absoluteHeader = this.typeParameters.absoluteHeader;
if (!this.widgetType) {
this.widgetTypeInstance = {};

2
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -265,6 +265,8 @@ export class WidgetContext {
hiddenData?: Array<{data: DataSet}>;
timeWindow?: WidgetTimewindow;
absoluteHeader?: boolean;
hideTitlePanel = false;
widgetTitle?: string;

2
ui-ngx/src/app/shared/components/unit-input.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<mat-form-field appearance="outline" class="tb-inline-field tb-suffix-show-on-hover" subscriptSizing="dynamic" style="width: 100%;">
<mat-form-field fxFlex appearance="outline" class="tb-inline-field tb-suffix-show-on-hover" subscriptSizing="dynamic">
<input matInput #unitInput [formControl]="unitsFormControl"
placeholder="{{ 'widget-config.set' | translate }}"
(focusin)="onFocus()"

19
ui-ngx/src/app/shared/components/unit-input.component.ts

@ -14,16 +14,23 @@
/// limitations under the License.
///
import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import {
Component,
ElementRef,
forwardRef,
HostBinding,
Input,
OnInit,
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Observable, of, shareReplay, switchMap } from 'rxjs';
import { searchUnits, Unit, unitBySymbol } from '@shared/models/unit.models';
import { getUnits, searchUnits, Unit, unitBySymbol } from '@shared/models/unit.models';
import { map, mergeMap, tap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { ResourcesService } from '@core/services/resources.service';
const unitsModels = '/assets/model/units.json';
@Component({
selector: 'tb-unit-input',
templateUrl: './unit-input.component.html',
@ -39,6 +46,8 @@ const unitsModels = '/assets/model/units.json';
})
export class UnitInputComponent implements ControlValueAccessor, OnInit {
@HostBinding('style.display') get hostDisplay() {return 'flex';};
unitsFormControl: FormControl;
modelValue: string | null;
@ -147,7 +156,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit {
private unitsConstant(): Observable<Array<Unit>> {
if (this.fetchUnits$ === null) {
this.fetchUnits$ = this.resourcesService.loadJsonResource<Array<Unit>>(unitsModels).pipe(
this.fetchUnits$ = getUnits(this.resourcesService).pipe(
map(units => units.map(u => ({
symbol: u.symbol,
name: this.translate.instant(u.name),

6
ui-ngx/src/app/shared/models/unit.models.ts

@ -14,6 +14,9 @@
/// limitations under the License.
///
import { ResourcesService } from '@core/services/resources.service';
import { Observable } from 'rxjs';
export interface Unit {
name: string;
symbol: string;
@ -30,3 +33,6 @@ export const searchUnits = (_units: Array<Unit>, searchText: string): Array<Unit
u.name.toUpperCase().includes(searchText.toUpperCase()) ||
searchUnitTags(u, searchText)
);
export const getUnits = (resourcesService: ResourcesService): Observable<Array<Unit>> =>
resourcesService.loadJsonResource('/assets/metadata/units.json');

18
ui-ngx/src/app/shared/models/widget.models.ts

@ -19,7 +19,6 @@ import { TenantId } from '@shared/models/id/tenant-id';
import { WidgetTypeId } from '@shared/models/id/widget-type-id';
import { AggregationType, ComparisonDuration, Timewindow } from '@shared/models/time/time.models';
import { EntityType } from '@shared/models/entity-type.models';
import { AlarmSearchStatus, AlarmSeverity } from '@shared/models/alarm.models';
import { DataKeyType } from './telemetry/telemetry.models';
import { EntityId } from '@shared/models/id/entity-id';
import * as moment_ from 'moment';
@ -40,6 +39,7 @@ import { Observable } from 'rxjs';
import { Dashboard } from '@shared/models/dashboard.models';
import { IAliasController } from '@core/api/widget-api.models';
import { isEmptyStr } from '@core/utils';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
export enum widgetType {
timeseries = 'timeseries',
@ -182,6 +182,7 @@ export interface WidgetTypeParameters {
processNoDataByWidget?: boolean;
previewWidth?: string;
previewHeight?: string;
absoluteHeader?: boolean;
}
export interface WidgetControllerDescriptor {
@ -706,6 +707,7 @@ export interface IWidgetSettingsComponent {
aliasController: IAliasController;
dashboard: Dashboard;
widget: Widget;
widgetConfig: WidgetConfigComponentData;
functionScopeVariables: string[];
settings: WidgetSettings;
settingsChanged: Observable<WidgetSettings>;
@ -737,6 +739,17 @@ export abstract class WidgetSettingsComponent extends PageComponent implements
widget: Widget;
widgetConfigValue: WidgetConfigComponentData;
set widgetConfig(value: WidgetConfigComponentData) {
this.widgetConfigValue = value;
this.onWidgetConfigSet(value);
}
get widgetConfig(): WidgetConfigComponentData {
return this.widgetConfigValue;
}
functionScopeVariables: string[];
settingsValue: WidgetSettings;
@ -848,4 +861,7 @@ export abstract class WidgetSettingsComponent extends PageComponent implements
return {};
}
protected onWidgetConfigSet(widgetConfig: WidgetConfigComponentData) {
}
}

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

@ -4690,6 +4690,7 @@
"advanced-widget-style": "Advanced widget style",
"card-buttons": "Card buttons",
"show-card-buttons": "Show card buttons",
"card-border-radius": "Card border radius",
"card-appearance": "Card appearance",
"color": "Color"
},
@ -4702,6 +4703,18 @@
"invalid-widget-type-file-error": "Unable to import widget type: Invalid widget type data structure."
},
"widgets": {
"background": {
"background": "Background",
"background-settings": "Background settings",
"background-type-image": "Upload image",
"background-type-image-url": "Image URL",
"background-type-color": "Solid color",
"image-url": "Image URL",
"overlay": "Overlay",
"enable-overlay": "Enable overlay",
"blur": "Blur",
"preview": "Preview"
},
"chart": {
"common-settings": "Common settings",
"enable-stacking-mode": "Enable stacking mode",
@ -5665,7 +5678,8 @@
"label": "Label",
"icon": "Icon",
"value": "Value",
"date": "Date"
"date": "Date",
"value-card-style": "Value card style"
},
"table": {
"common-table-settings": "Common Table Settings",

0
ui-ngx/src/assets/model/units.json → ui-ngx/src/assets/metadata/units.json

25
ui-ngx/src/styles.scss

@ -306,10 +306,7 @@ pre.tb-highlight {
.tb-fullscreen {
position: fixed !important;
top: 0;
left: 0;
width: 100% !important;
height: 100% !important;
inset: 0 !important;
}
.tb-fullscreen-parent {
@ -983,10 +980,7 @@ mat-label {
min-width: 100%;
max-width: none !important;
position: absolute !important;
top: 0;
bottom: 0;
left: 0;
right: 0;
inset: 0;
.mat-mdc-dialog-container {
> *:first-child, form {
min-width: 100% !important;
@ -1004,10 +998,7 @@ mat-label {
min-width: 100%;
max-width: none !important;
position: absolute !important;
top: 0;
bottom: 0;
left: 0;
right: 0;
inset: 0;
.mat-mdc-dialog-container {
> *:first-child, form {
min-width: 100% !important;
@ -1022,10 +1013,7 @@ mat-label {
.tb-absolute-fill {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
inset: 0;
}
.tb-layout-fill {
@ -1037,10 +1025,7 @@ mat-label {
.tb-progress-cover {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
inset: 0;
z-index: 6;
background-color: #eee;
opacity: 1;

Loading…
Cancel
Save