@ -0,0 +1,334 @@ |
|||
<!-- |
|||
|
|||
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 *ngIf="levelCardWidgetConfigForm" [formGroup]="levelCardWidgetConfigForm"> |
|||
<tb-timewindow-config-panel *ngIf="displayTimewindowConfig" |
|||
[onlyHistoryTimewindow]="onlyHistoryTimewindow()" |
|||
formControlName="timewindowConfig"> |
|||
</tb-timewindow-config-panel> |
|||
<tb-datasources |
|||
[configMode]="basicMode" |
|||
hideDataKeyLabel |
|||
hideDataKeyColor |
|||
hideDataKeyUnits |
|||
hideDataKeyDecimals |
|||
formControlName="datasources"> |
|||
</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 fixed-title-width" formControlName="showTitle"> |
|||
{{ 'widgets.liquid-level-card.title' | translate }} |
|||
</mat-slide-toggle> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic"> |
|||
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
</mat-form-field> |
|||
<tb-font-settings formControlName="titleFont" |
|||
[previewText]="levelCardWidgetConfigForm.get('title').value"> |
|||
</tb-font-settings> |
|||
<tb-color-input asBoxInput colorClearButton formControlName="titleColor"> |
|||
</tb-color-input> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row"> |
|||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTitleIcon"> |
|||
{{ 'widgets.liquid-level-card.icon' | translate }} |
|||
</mat-slide-toggle> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px"> |
|||
<mat-form-field appearance="outline" class="flex 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 |
|||
iconClearButton |
|||
[color]="levelCardWidgetConfigForm.get('iconColor').value" |
|||
formControlName="titleIcon"> |
|||
</tb-material-icon-select> |
|||
<tb-color-input asBoxInput colorClearButton formControlName="iconColor"> |
|||
</tb-color-input> |
|||
</div> |
|||
</div> |
|||
<div fxFlex fxLayout="column" class="tb-form-row space-between"> |
|||
<div fxFlex fxLayout="row" style="width: 100%;" fxLayoutAlign="space-between center"> |
|||
<div class="fixed-title-width" translate>widgets.liquid-level-card.shape</div> |
|||
<tb-toggle-select formControlName="tankSelectionType"> |
|||
<tb-toggle-option [value]="shapeSelectOptions.static"> |
|||
{{ 'widgets.liquid-level-card.static' | translate }} |
|||
</tb-toggle-option> |
|||
<tb-toggle-option [value]="shapeSelectOptions.attribute"> |
|||
{{ 'widgets.liquid-level-card.attribute' | translate }} |
|||
</tb-toggle-option> |
|||
</tb-toggle-select> |
|||
</div> |
|||
<tb-image-cards-select #shapesImageCardsSelect |
|||
[fxShow]="levelCardWidgetConfigForm.get('tankSelectionType')?.value === shapeSelectOptions.static" |
|||
rowHeight="{{ '1:1' }}" |
|||
[cols]="5" |
|||
[colsLtMd]="5" |
|||
style="width: 100%;" |
|||
label="{{ 'widgets.liquid-level-card.shape-type' | translate }}" formControlName="selectedShape"> |
|||
<tb-image-cards-select-option *ngFor="let shape of shapes" |
|||
[value]="shape" |
|||
[image]="createShapeLayout(shapesImageMap.get(shape), this.levelCardLayouts.simple)"> |
|||
{{ shapesTranslationMap.get(shape) | translate }} |
|||
</tb-image-cards-select-option> |
|||
</tb-image-cards-select> |
|||
<div class="tb-form-row space-between" style="width: 100%;" |
|||
[fxShow]="levelCardWidgetConfigForm.get('tankSelectionType')?.value === shapeSelectOptions.attribute"> |
|||
<div class="fixed-title-width tb-required" translate>widgets.liquid-level-card.shape-attribute-name</div> |
|||
<tb-string-autocomplete [fetchOptionsFn]="fetchOptions.bind(this)" |
|||
[required]="isRequired('shapeAttributeName')" |
|||
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate" |
|||
style="flex: 1; width: auto;" |
|||
asBoxInput colorClearButton class="flex" |
|||
formControlName="shapeAttributeName"> |
|||
</tb-string-autocomplete> |
|||
</div> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="space-between center" style="width: 100%;" |
|||
[fxShow]="levelCardWidgetConfigForm.get('tankSelectionType')?.value === shapeSelectOptions.attribute"> |
|||
<div class="tb-hint" style="padding: 0;" translate>widgets.liquid-level-card.shape-by-attribute</div> |
|||
<div class="see-example" |
|||
[tb-help-popup]="'widget/lib/indicator/shape_attribute_fn'" |
|||
tb-help-popup-placement="left" |
|||
trigger-style="font-size: 12px" |
|||
[tb-help-popup-style]="{maxWidth: '820px'}" |
|||
trigger-text="{{ 'widgets.liquid-level-card.see-examples' | translate }}"> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<tb-image-cards-select #layoutsImageCardsSelect rowHeight="{{ '1:1' }}" |
|||
[cols]="3" |
|||
[colsLtMd]="3" |
|||
label="{{ 'widgets.liquid-level-card.layout' | translate }}" formControlName="layout"> |
|||
<tb-image-cards-select-option |
|||
*ngFor="let layout of [levelCardLayouts.simple, levelCardLayouts.percentage, levelCardLayouts.absolute]" |
|||
[value]="layout" |
|||
[image]="createShapeLayout(shapesImageMap.get(levelCardWidgetConfigForm.get('selectedShape').value), layout)"> |
|||
{{ levelCardLayoutTranslationMap.get(layout) | translate }} |
|||
</tb-image-cards-select-option> |
|||
</tb-image-cards-select> |
|||
</div> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widgets.liquid-level-card.units</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div class="fixed-title-width tb-required" translate>widgets.liquid-level-card.datasource-units</div> |
|||
<tb-unit-input style="max-width: 30%;" class="flex" |
|||
[tagFilter]="unitsType.capacity" |
|||
[required]="isRequired('datasourceUnits')" |
|||
formControlName="datasourceUnits"> |
|||
</tb-unit-input> |
|||
</div> |
|||
<div class="tb-form-row space-between" [fxShow]="widgetUnitsInput"> |
|||
<div class="fixed-title-width tb-required" translate>widgets.liquid-level-card.widget-units</div> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic" style="max-width: 25%"> |
|||
<mat-select formControlName="widgetUnitsSource" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
<mat-option *ngFor="let option of [levelOptions.static, levelOptions.attribute]" [value]="option"> |
|||
{{ option }} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<tb-unit-input [fxShow]="levelCardWidgetConfigForm.get('widgetUnitsSource')?.value !== levelOptions.attribute" |
|||
asBoxInput colorClearButton class="flex" |
|||
[tagFilter]="unitsType.capacity" |
|||
[required]="isRequired('units')" |
|||
formControlName="units"> |
|||
</tb-unit-input> |
|||
<tb-string-autocomplete [fxShow]="levelCardWidgetConfigForm.get('widgetUnitsSource')?.value === levelOptions.attribute" |
|||
[fetchOptionsFn]="fetchOptions.bind(this)" |
|||
[required]="isRequired('widgetUnitsAttributeName')" |
|||
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate" |
|||
style="flex: 1; width: auto;" |
|||
asBoxInput colorClearButton class="flex" |
|||
formControlName="widgetUnitsAttributeName"> |
|||
</tb-string-autocomplete> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row" [fxShow]="volumeInput"> |
|||
<div class="fixed-title-width tb-required" translate>widgets.liquid-level-card.total-volume</div> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic" style="max-width: 25%"> |
|||
<mat-select formControlName="volumeSource" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
<mat-option *ngFor="let option of [levelOptions.static, levelOptions.attribute]" [value]="option"> |
|||
{{ option }} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<mat-form-field [fxShow]="levelCardWidgetConfigForm.get('volumeSource')?.value === levelOptions.static" |
|||
appearance="outline" class="flex number" subscriptSizing="dynamic"> |
|||
<input matInput formControlName="volumeConstant" type="number" |
|||
min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
<mat-icon matSuffix |
|||
matTooltipPosition="above" |
|||
matTooltipClass="tb-error-tooltip" |
|||
[matTooltip]="'widgets.liquid-level-card.total-volume-required' | translate" |
|||
*ngIf="levelCardWidgetConfigForm.get('volumeConstant').hasError('required')" |
|||
class="material-icons tb-suffix-show-always tb-error"> |
|||
warning |
|||
</mat-icon> |
|||
</mat-form-field> |
|||
<tb-string-autocomplete style="max-width: 25%" |
|||
[fxShow]="levelCardWidgetConfigForm.get('volumeSource')?.value === levelOptions.attribute" |
|||
[fetchOptionsFn]="fetchOptions.bind(this)" |
|||
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate" |
|||
[required]="isRequired('volumeAttributeName')" |
|||
style="flex: 1; width: auto;" |
|||
asBoxInput colorClearButton class="flex" |
|||
formControlName="volumeAttributeName"> |
|||
</tb-string-autocomplete> |
|||
<tb-unit-input asBoxInput colorClearButton |
|||
[tagFilter]="unitsType.capacity" |
|||
[required]="isRequired('volumeUnits')" |
|||
style="max-width: 25%" class="flex" |
|||
formControlName="volumeUnits"> |
|||
</tb-unit-input> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widgets.liquid-level-card.color-and-font</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div class="fixed-title-width" translate>widgets.liquid-level-card.tank-color</div> |
|||
<tb-color-settings formControlName="tankColor"> |
|||
</tb-color-settings> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div class="fixed-title-width" translate>widgets.liquid-level-card.liquid-color</div> |
|||
<tb-color-settings formControlName="liquidColor"> |
|||
</tb-color-settings> |
|||
</div> |
|||
<div class="tb-form-row space-between" |
|||
*ngIf="levelCardWidgetConfigForm.get('layout')?.value !== this.levelCardLayouts.simple"> |
|||
<div class="fixed-title-width" translate>widgets.liquid-level-card.value</div> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px"> |
|||
<mat-form-field style="max-width: 12%;" appearance="outline" class="flex number" subscriptSizing="dynamic"> |
|||
<input matInput formControlName="decimals" type="number" |
|||
min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
</mat-form-field> |
|||
<tb-font-settings formControlName="valueFont" |
|||
[previewText]="valuePreviewFn"> |
|||
</tb-font-settings> |
|||
<tb-color-settings formControlName="valueColor"> |
|||
</tb-color-settings> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row" [fxShow]="volumeInput"> |
|||
<div class="fixed-title-width" translate>widgets.liquid-level-card.total-volume</div> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px"> |
|||
<tb-font-settings formControlName="volumeFont" |
|||
[previewText]="totalVolumeValuePreviewFn"> |
|||
</tb-font-settings> |
|||
<tb-color-settings formControlName="volumeColor"> |
|||
</tb-color-settings> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBackgroundOverlay"> |
|||
{{ 'widgets.liquid-level-card.background-overlay' | translate }} |
|||
</mat-slide-toggle> |
|||
<tb-color-settings formControlName="backgroundOverlayColor"> |
|||
</tb-color-settings> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-panel tb-slide-toggle"> |
|||
<mat-expansion-panel class="tb-settings" |
|||
[expanded]="levelCardWidgetConfigForm.get('showTooltip').value" |
|||
[disabled]="!levelCardWidgetConfigForm.get('showTooltip').value"> |
|||
<mat-expansion-panel-header fxLayout="row wrap"> |
|||
<mat-panel-title> |
|||
<mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()" |
|||
fxLayoutAlign="center"> |
|||
{{ 'widget-config.tooltip' | translate }} |
|||
</mat-slide-toggle> |
|||
</mat-panel-title> |
|||
</mat-expansion-panel-header> |
|||
<ng-template matExpansionPanelContent> |
|||
<div class="tb-form-row"> |
|||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTooltipLevel"> |
|||
{{ 'widgets.liquid-level-card.level' | translate }} |
|||
</mat-slide-toggle> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<tb-unit-input [required]="isRequired('tooltipUnits')" |
|||
[tagFilter]="unitsType.capacity" class="flex" |
|||
formControlName="tooltipUnits"> |
|||
</tb-unit-input> |
|||
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic"> |
|||
<input matInput formControlName="tooltipLevelDecimals" type="number" min="0" |
|||
max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
<div matSuffix fxHide.lt-md translate>widget-config.decimals-suffix</div> |
|||
</mat-form-field> |
|||
<tb-font-settings formControlName="tooltipLevelFont" |
|||
[previewText]="tooltipValuePreviewFn"> |
|||
</tb-font-settings> |
|||
<tb-color-settings formControlName="tooltipLevelColor"> |
|||
</tb-color-settings> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row column-xs"> |
|||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTooltipDate"> |
|||
{{ 'widgets.value-card.date' | translate }} |
|||
</mat-slide-toggle> |
|||
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<tb-date-format-select fxFlex formControlName="tooltipDateFormat"></tb-date-format-select> |
|||
<tb-font-settings formControlName="tooltipDateFont" |
|||
[previewText]="datePreviewFn"> |
|||
</tb-font-settings> |
|||
<tb-color-settings formControlName="tooltipDateColor"> |
|||
</tb-color-settings> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div class="fixed-title-width" translate>widgets.liquid-level-card.tooltip-background</div> |
|||
<tb-color-settings formControlName="tooltipBackgroundColor"> |
|||
</tb-color-settings> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div class="fixed-title-width" translate>widgets.liquid-level-card.background-blur</div> |
|||
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> |
|||
<input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1" |
|||
placeholder="{{ 'widget-config.set' | translate }}"> |
|||
<div matSuffix>px</div> |
|||
</mat-form-field> |
|||
</div> |
|||
</ng-template> |
|||
</mat-expansion-panel> |
|||
</div> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widgets.background.background' | translate }}</div> |
|||
<tb-color-input asBoxInput colorClearButton formControlName="backgroundColor"> |
|||
</tb-color-input> |
|||
</div> |
|||
<div class="tb-form-row space-between column-lt-md"> |
|||
<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> |
|||
@ -0,0 +1,656 @@ |
|||
///
|
|||
/// 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 { ChangeDetectorRef, Component, Injector, ViewChild } from '@angular/core'; |
|||
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models'; |
|||
import { WidgetConfigComponentData } from '@home/models/widget-component.models'; |
|||
import { |
|||
DataKey, |
|||
Datasource, |
|||
datasourcesHasAggregation, |
|||
datasourcesHasOnlyComparisonAggregation, |
|||
DatasourceType, |
|||
WidgetConfig, |
|||
} from '@shared/models/widget.models'; |
|||
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; |
|||
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; |
|||
import { |
|||
getTimewindowConfig, |
|||
setTimewindowConfig |
|||
} from '@home/components/widget/config/timewindow-config-panel.component'; |
|||
import { formatValue, isDefined, isUndefined } from '@core/utils'; |
|||
import { |
|||
cssSizeToStrSize, |
|||
DateFormatProcessor, |
|||
DateFormatSettings, |
|||
resolveCssSize |
|||
} from '@shared/models/widget-settings.models'; |
|||
import { |
|||
CapacityUnits, |
|||
createAbsoluteLayout, |
|||
createPercentLayout, |
|||
levelCardDefaultSettings, |
|||
LevelCardLayout, |
|||
levelCardLayoutTranslations, |
|||
LevelCardWidgetSettings, |
|||
LevelSelectOptions, |
|||
optionsFilter, |
|||
Shapes, |
|||
shapesTranslations, |
|||
svgMapping |
|||
} from '@home/components/widget/lib/indicator/liquid-level-widget.models'; |
|||
import { UnitsType } from '@shared/models/unit.models'; |
|||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; |
|||
import { ImageCardsSelectComponent } from '@home/components/widget/lib/settings/common/image-cards-select.component'; |
|||
import { map, publishReplay, refCount, tap } from 'rxjs/operators'; |
|||
import { forkJoin, Observable, of } from 'rxjs'; |
|||
import { ResourcesService } from '@core/services/resources.service'; |
|||
import { UnitInputComponent } from '@shared/components/unit-input.component'; |
|||
import { UtilsService } from '@core/services/utils.service'; |
|||
|
|||
@Component({ |
|||
selector: 'liquid-level-card-basic-config', |
|||
templateUrl: './liquid-level-card-basic-config.component.html', |
|||
styleUrls: ['../basic-config.scss'] |
|||
}) |
|||
export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigComponent { |
|||
|
|||
@ViewChild('layoutsImageCardsSelect', { static: false }) layoutsImageCardsSelect: ImageCardsSelectComponent; |
|||
|
|||
@ViewChild('shapesImageCardsSelect', { static: false }) shapesImageCardsSelect: ImageCardsSelectComponent; |
|||
|
|||
@ViewChild('widgetUnits', { static: false }) widgetUnits: UnitInputComponent; |
|||
|
|||
public get datasource(): Datasource { |
|||
if (this.widgetConfig.config.datasources && this.widgetConfig.config.datasources) { |
|||
return this.widgetConfig.config.datasources[0]; |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public get volumeInput(): boolean { |
|||
const datasourceUnits = this.levelCardWidgetConfigForm.get('datasourceUnits').value; |
|||
const layout: LevelCardLayout = this.levelCardWidgetConfigForm.get('layout').value; |
|||
const units = this.levelCardWidgetConfigForm.get('units').value; |
|||
return !(datasourceUnits === CapacityUnits.percent && layout !== LevelCardLayout.absolute |
|||
|| (datasourceUnits === CapacityUnits.percent && units === CapacityUnits.percent)); |
|||
} |
|||
|
|||
public get widgetUnitsInput(): boolean { |
|||
const layout: LevelCardLayout = this.levelCardWidgetConfigForm.get('layout').value; |
|||
|
|||
if (layout === LevelCardLayout.absolute) { |
|||
const datasourceUnits = this.levelCardWidgetConfigForm.get('datasourceUnits').value; |
|||
return !(datasourceUnits === CapacityUnits.percent); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
public get displayTimewindowConfig(): boolean { |
|||
const datasources = this.levelCardWidgetConfigForm.get('datasources').value; |
|||
return datasourcesHasAggregation(datasources); |
|||
} |
|||
|
|||
public onlyHistoryTimewindow(): boolean { |
|||
const datasources = this.levelCardWidgetConfigForm.get('datasources').value; |
|||
return datasourcesHasOnlyComparisonAggregation(datasources); |
|||
} |
|||
|
|||
levelCardLayouts = LevelCardLayout; |
|||
|
|||
shapes: Shapes[] = []; |
|||
|
|||
levelOptions = LevelSelectOptions; |
|||
|
|||
shapeSelectOptions = LevelSelectOptions; |
|||
|
|||
levelCardLayoutTranslationMap = levelCardLayoutTranslations; |
|||
|
|||
shapesTranslationMap = shapesTranslations; |
|||
shapesImageMap: Map<Shapes, string> = new Map(); |
|||
|
|||
unitsType = UnitsType; |
|||
|
|||
levelCardWidgetConfigForm: UntypedFormGroup; |
|||
|
|||
valuePreviewFn = this._valuePreviewFn.bind(this); |
|||
|
|||
tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this); |
|||
|
|||
totalVolumeValuePreviewFn = this._totalVolumeValuePreviewFn.bind(this); |
|||
|
|||
datePreviewFn = this._datePreviewFn.bind(this); |
|||
|
|||
keySearchText: string; |
|||
|
|||
latestKeySearchTextResult: Array<string>; |
|||
|
|||
datasources: Array<Datasource>; |
|||
|
|||
functionTypeKeys: Array<DataKey> = []; |
|||
|
|||
lastKeysId: string; |
|||
|
|||
lastFetchedKeys: Array<DataKey>; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
protected widgetConfigComponent: WidgetConfigComponent, |
|||
private cd: ChangeDetectorRef, |
|||
private $injector: Injector, |
|||
private fb: UntypedFormBuilder, |
|||
private resourcesService: ResourcesService, |
|||
private sanitizer: DomSanitizer, |
|||
private utils: UtilsService) { |
|||
super(store, widgetConfigComponent); |
|||
} |
|||
|
|||
protected configForm(): UntypedFormGroup { |
|||
return this.levelCardWidgetConfigForm; |
|||
} |
|||
|
|||
protected setupConfig(widgetConfig: WidgetConfigComponentData) { |
|||
this.shapes = Object.values(Shapes); |
|||
this.createSvgShapesMapping(); |
|||
|
|||
for (const type of this.utils.getPredefinedFunctionsList()) { |
|||
this.functionTypeKeys.push({ |
|||
name: type, |
|||
type: DataKeyType.function |
|||
}); |
|||
} |
|||
super.setupConfig(widgetConfig); |
|||
} |
|||
|
|||
protected setupDefaults(configData: WidgetConfigComponentData) { |
|||
this.setupDefaultDatasource(configData, [{ name: 'fuelLevel', label: 'Fuel Level', type: DataKeyType.timeseries }]); |
|||
} |
|||
|
|||
protected onConfigSet(configData: WidgetConfigComponentData) { |
|||
const settings: LevelCardWidgetSettings = {...configData.config, ...levelCardDefaultSettings(), ...(configData.config.settings || {})}; |
|||
const iconSize = resolveCssSize(configData.config.iconSize); |
|||
|
|||
this.levelCardWidgetConfigForm = this.fb.group({ |
|||
timewindowConfig: [getTimewindowConfig(configData.config), []], |
|||
datasources: [configData.config.datasources, []], |
|||
|
|||
tankSelectionType: [settings.tankSelectionType, []], |
|||
selectedShape: [settings.selectedShape, [Validators.required]], |
|||
shapeAttributeName: [settings.shapeAttributeName, [Validators.required]], |
|||
tankColor: [settings.tankColor, []], |
|||
datasourceUnits: [settings.datasourceUnits, [Validators.required]], |
|||
|
|||
layout: [settings.layout, []], |
|||
showTitle: [configData.config.showTitle, []], |
|||
title: [settings.title, []], |
|||
titleFont: [settings.titleFont, []], |
|||
titleColor: [settings.titleColor, []], |
|||
showTitleIcon: [configData.config.showTitleIcon, []], |
|||
iconSize: [iconSize[0], [Validators.min(0)]], |
|||
iconSizeUnit: [iconSize[1], []], |
|||
titleIcon: [settings.titleIcon, []], |
|||
iconColor: [settings.iconColor, []], |
|||
volumeSource: [settings.volumeSource, []], |
|||
volumeConstant: [settings.volumeConstant, [Validators.required]], |
|||
volumeAttributeName: [settings.volumeAttributeName, [Validators.required]], |
|||
volumeUnits: [settings.volumeUnits, [Validators.required]], |
|||
volumeFont: [settings.volumeFont, []], |
|||
volumeColor: [settings.volumeColor, []], |
|||
units: [settings.units, [Validators.required]], |
|||
widgetUnitsSource: [settings.widgetUnitsSource, [Validators.required]], |
|||
widgetUnitsAttributeName: [settings.widgetUnitsAttributeName, [Validators.required]], |
|||
decimals: [settings.decimals, []], |
|||
liquidColor: [settings.liquidColor, []], |
|||
valueFont: [settings.valueFont, []], |
|||
valueColor: [settings.valueColor, []], |
|||
showBackgroundOverlay: [settings.showBackgroundOverlay, []], |
|||
backgroundOverlayColor: [settings.backgroundOverlayColor, []], |
|||
|
|||
showTooltip: [settings.showTooltip, []], |
|||
showTooltipLevel: [settings.showTooltipLevel, []], |
|||
tooltipUnits: [settings.tooltipUnits, []], |
|||
tooltipLevelDecimals: [settings.tooltipLevelDecimals, []], |
|||
tooltipLevelFont: [settings.tooltipLevelFont, []], |
|||
tooltipLevelColor: [settings.tooltipLevelColor, []], |
|||
showTooltipDate: [settings.showTooltipDate, []], |
|||
tooltipDateFormat: [settings.tooltipDateFormat, []], |
|||
tooltipDateFont: [settings.tooltipDateFont, []], |
|||
tooltipDateColor: [settings.tooltipDateColor, []], |
|||
tooltipBackgroundColor: [settings.tooltipBackgroundColor, []], |
|||
tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []], |
|||
|
|||
backgroundColor: [settings.backgroundColor, []], |
|||
cardButtons: [this.getCardButtons(configData.config), []], |
|||
borderRadius: [configData.config.borderRadius, []], |
|||
|
|||
actions: [configData.config.actions || {}, []] |
|||
}); |
|||
|
|||
this.levelCardWidgetConfigForm.get('selectedShape').valueChanges.subscribe((shape) => { |
|||
this.cd.detectChanges(); |
|||
this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges(); |
|||
}); |
|||
} |
|||
|
|||
protected prepareOutputConfig(config: any): WidgetConfigComponentData { |
|||
setTimewindowConfig(this.widgetConfig.config, config.timewindowConfig); |
|||
this.widgetConfig.config.datasources = config.datasources; |
|||
|
|||
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; |
|||
|
|||
this.widgetConfig.config.settings.tankSelectionType = config.tankSelectionType; |
|||
this.widgetConfig.config.settings.selectedShape = config.selectedShape; |
|||
this.widgetConfig.config.settings.shapeAttributeName = config.shapeAttributeName; |
|||
this.widgetConfig.config.settings.tankColor = config.tankColor; |
|||
this.widgetConfig.config.settings.datasourceUnits = config.datasourceUnits; |
|||
|
|||
this.widgetConfig.config.settings.layout = config.layout; |
|||
this.widgetConfig.config.showTitle = config.showTitle; |
|||
this.widgetConfig.config.title = config.title; |
|||
this.widgetConfig.config.titleFont = config.titleFont; |
|||
this.widgetConfig.config.titleColor = config.titleColor; |
|||
this.widgetConfig.config.showTitleIcon = config.showTitleIcon; |
|||
this.widgetConfig.config.iconSize = cssSizeToStrSize(config.iconSize, config.iconSizeUnit); |
|||
this.widgetConfig.config.titleIcon = config.titleIcon; |
|||
this.widgetConfig.config.iconColor = config.iconColor; |
|||
this.widgetConfig.config.settings.volumeSource = config.volumeSource; |
|||
this.widgetConfig.config.settings.volumeConstant = config.volumeConstant; |
|||
this.widgetConfig.config.settings.volumeAttributeName = config.volumeAttributeName; |
|||
this.widgetConfig.config.settings.volumeUnits = config.volumeUnits; |
|||
this.widgetConfig.config.settings.volumeFont = config.volumeFont; |
|||
this.widgetConfig.config.settings.volumeColor = config.volumeColor; |
|||
this.widgetConfig.config.settings.units = config.units; |
|||
this.widgetConfig.config.settings.widgetUnitsSource = config.widgetUnitsSource; |
|||
this.widgetConfig.config.settings.widgetUnitsAttributeName = config.widgetUnitsAttributeName; |
|||
this.widgetConfig.config.decimals = config.decimals; |
|||
this.widgetConfig.config.settings.liquidColor = config.liquidColor; |
|||
this.widgetConfig.config.settings.valueFont = config.valueFont; |
|||
this.widgetConfig.config.settings.valueColor = config.valueColor; |
|||
this.widgetConfig.config.settings.showBackgroundOverlay = config.showBackgroundOverlay; |
|||
this.widgetConfig.config.settings.backgroundOverlayColor = config.backgroundOverlayColor; |
|||
|
|||
this.widgetConfig.config.settings.showTooltip = config.showTooltip; |
|||
this.widgetConfig.config.settings.showTooltipLevel = config.showTooltipLevel; |
|||
this.widgetConfig.config.settings.tooltipUnits = config.tooltipUnits; |
|||
this.widgetConfig.config.settings.tooltipLevelDecimals = config.tooltipLevelDecimals; |
|||
this.widgetConfig.config.settings.tooltipLevelFont = config.tooltipLevelFont; |
|||
this.widgetConfig.config.settings.tooltipLevelColor = config.tooltipLevelColor; |
|||
this.widgetConfig.config.settings.showTooltipDate = config.showTooltipDate; |
|||
this.widgetConfig.config.settings.tooltipDateFormat = config.tooltipDateFormat; |
|||
this.widgetConfig.config.settings.tooltipDateFont = config.tooltipDateFont; |
|||
this.widgetConfig.config.settings.tooltipDateColor = config.tooltipDateColor; |
|||
this.widgetConfig.config.settings.tooltipBackgroundColor = config.tooltipBackgroundColor; |
|||
this.widgetConfig.config.settings.tooltipBackgroundBlur = config.tooltipBackgroundBlur; |
|||
|
|||
this.widgetConfig.config.backgroundColor = config.backgroundColor; |
|||
this.setCardButtons(config.cardButtons, this.widgetConfig.config); |
|||
this.widgetConfig.config.borderRadius = config.borderRadius; |
|||
|
|||
this.widgetConfig.config.actions = config.actions; |
|||
return this.widgetConfig; |
|||
} |
|||
|
|||
protected validatorTriggers(): string[] { |
|||
return [ |
|||
'showBackgroundOverlay', 'showTooltip', 'showTooltipLevel', |
|||
'tankSelectionType', 'datasourceUnits', |
|||
'showTitleIcon', 'volumeSource', |
|||
'showTooltipDate', 'units', |
|||
'layout', 'showTitle', |
|||
'widgetUnitsSource' |
|||
]; |
|||
} |
|||
|
|||
protected updateValidators(emitEvent: boolean, trigger?: string) { |
|||
const emitEventFields = []; |
|||
|
|||
const datasourceUnits: string = this.levelCardWidgetConfigForm.get('datasourceUnits').value; |
|||
const layout: LevelCardLayout = this.levelCardWidgetConfigForm.get('layout').value; |
|||
const showTitleIcon: AbstractControl<boolean> = this.levelCardWidgetConfigForm.get('showTitleIcon'); |
|||
const volumeSource: AbstractControl<LevelSelectOptions> = this.levelCardWidgetConfigForm.get('volumeSource'); |
|||
const units: AbstractControl<string> = this.levelCardWidgetConfigForm.get('units'); |
|||
const tooltipUnits: AbstractControl<string> = this.levelCardWidgetConfigForm.get('tooltipUnits'); |
|||
const widgetUnitsSource: AbstractControl<LevelSelectOptions> = this.levelCardWidgetConfigForm.get('widgetUnitsSource'); |
|||
const showTooltipLevel: AbstractControl<boolean> = this.levelCardWidgetConfigForm.get('showTooltipLevel'); |
|||
const showTooltipDate: AbstractControl<boolean> = this.levelCardWidgetConfigForm.get('showTooltipDate'); |
|||
const showTooltip: boolean = this.levelCardWidgetConfigForm.get('showTooltip').value; |
|||
|
|||
if (trigger === 'tankSelectionType') { |
|||
const tankSelectionType: LevelSelectOptions = this.levelCardWidgetConfigForm.get('tankSelectionType').value; |
|||
if (tankSelectionType === LevelSelectOptions.static) { |
|||
this.levelCardWidgetConfigForm.get('selectedShape').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('shapeAttributeName').disable({emitEvent: false}); |
|||
} else { |
|||
this.levelCardWidgetConfigForm.get('selectedShape').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('shapeAttributeName').enable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('selectedShape', 'shapeAttributeName'); |
|||
} |
|||
|
|||
if (trigger === 'datasourceUnits' || trigger === 'layout' || trigger === 'units') { |
|||
if (datasourceUnits === CapacityUnits.percent && (layout !== LevelCardLayout.absolute) |
|||
|| (datasourceUnits === CapacityUnits.percent && units?.value === CapacityUnits.percent) |
|||
) { |
|||
volumeSource.disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('volumeConstant').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('volumeAttributeName').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('volumeUnits').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('volumeFont').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('volumeColor').disable({emitEvent: false}); |
|||
} else { |
|||
volumeSource.enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('volumeConstant').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('volumeAttributeName').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('volumeUnits').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('volumeFont').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('volumeColor').enable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('volumeSource', 'volumeConstant', 'volumeAttributeName', 'volumeFont', 'volumeColor', 'volumeUnits'); |
|||
} else if (trigger === 'volumeSource') { |
|||
if((datasourceUnits !== CapacityUnits.percent) || |
|||
(layout === LevelCardLayout.absolute && units?.value !== CapacityUnits.percent)) { |
|||
if (volumeSource.value === LevelSelectOptions.static) { |
|||
this.levelCardWidgetConfigForm.get('volumeConstant').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('volumeAttributeName').disable({emitEvent: false}); |
|||
} else { |
|||
this.levelCardWidgetConfigForm.get('volumeConstant').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('volumeAttributeName').enable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('volumeConstant', 'volumeAttributeName'); |
|||
} |
|||
} |
|||
|
|||
if (trigger === 'showTitle') { |
|||
const showTitle: boolean = this.levelCardWidgetConfigForm.get('showTitle').value; |
|||
if (showTitle) { |
|||
this.levelCardWidgetConfigForm.get('title').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('titleFont').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('titleColor').enable({emitEvent: false}); |
|||
showTitleIcon.enable({emitEvent: false}); |
|||
} else { |
|||
this.levelCardWidgetConfigForm.get('title').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('titleFont').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('titleColor').disable({emitEvent: false}); |
|||
showTitleIcon.disable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('title', 'titleFont', 'titleColor', 'showTitleIcon'); |
|||
} |
|||
|
|||
if (showTitleIcon?.value) { |
|||
this.levelCardWidgetConfigForm.get('titleIcon').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('iconColor').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('iconSize').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('iconSizeUnit').enable({emitEvent: false}); |
|||
} else { |
|||
this.levelCardWidgetConfigForm.get('titleIcon').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('iconColor').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('iconSize').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('iconSizeUnit').disable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('titleIcon', 'iconColor', 'iconSize', 'iconSizeUnit'); |
|||
|
|||
if (trigger === 'showBackgroundOverlay') { |
|||
const showBackgroundOverlay: boolean = this.levelCardWidgetConfigForm.get('showBackgroundOverlay').value; |
|||
if (showBackgroundOverlay) { |
|||
this.levelCardWidgetConfigForm.get('backgroundOverlayColor').enable({emitEvent: false}); |
|||
} else { |
|||
this.levelCardWidgetConfigForm.get('backgroundOverlayColor').disable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('backgroundOverlayColor'); |
|||
} |
|||
|
|||
if (trigger === 'showTooltip') { |
|||
if (showTooltip) { |
|||
showTooltipLevel.enable({emitEvent: false}); |
|||
showTooltipDate.enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('tooltipBackgroundColor').enable(); |
|||
this.levelCardWidgetConfigForm.get('tooltipBackgroundBlur').enable(); |
|||
} else { |
|||
showTooltipLevel.disable({emitEvent: false}); |
|||
showTooltipDate.disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('tooltipBackgroundColor').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('tooltipBackgroundBlur').disable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('showTooltipLevel', 'showTooltipDate', 'tooltipBackgroundColor', 'tooltipBackgroundBlur'); |
|||
} |
|||
|
|||
if (trigger === 'showTooltipLevel') { |
|||
if (showTooltipLevel?.value && !showTooltipLevel.disabled) { |
|||
tooltipUnits.enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('tooltipLevelDecimals').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('tooltipLevelFont').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('tooltipLevelColor').enable({emitEvent: false}); |
|||
} else { |
|||
tooltipUnits.disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('tooltipLevelDecimals').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('tooltipLevelFont').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('tooltipLevelColor').disable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('tooltipUnits', 'tooltipLevelDecimals', 'tooltipLevelFont', 'tooltipLevelColor'); |
|||
} |
|||
|
|||
if (trigger === 'showTooltipDate') { |
|||
if (showTooltipDate?.value && !showTooltipDate.disabled) { |
|||
this.levelCardWidgetConfigForm.get('tooltipDateFormat').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('tooltipDateFont').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('tooltipDateColor').enable({emitEvent: false}); |
|||
} else { |
|||
this.levelCardWidgetConfigForm.get('tooltipDateFormat').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('tooltipDateFont').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('tooltipDateColor').disable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('tooltipDateFormat', 'tooltipDateFont', 'tooltipDateColor'); |
|||
} |
|||
|
|||
if (trigger === 'layout' || trigger === 'datasourceUnits') { |
|||
if (layout === LevelCardLayout.simple) { |
|||
units.disable({emitEvent: false}); |
|||
tooltipUnits.disable({emitEvent: false}); |
|||
widgetUnitsSource.setValue(LevelSelectOptions.static, {emitEvent: false}); |
|||
widgetUnitsSource.disable({emitEvent: false}); |
|||
|
|||
this.levelCardWidgetConfigForm.get('decimals').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('valueFont').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('valueColor').disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('widgetUnitsAttributeName').disable({emitEvent: false}); |
|||
} else if (layout === LevelCardLayout.percentage) { |
|||
if (units.value !== CapacityUnits.percent) { |
|||
units.setValue(CapacityUnits.percent, {emitEvent: false}); |
|||
} |
|||
widgetUnitsSource.setValue(LevelSelectOptions.static, {emitEvent: false}); |
|||
units.disable({emitEvent: false}); |
|||
widgetUnitsSource.disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('widgetUnitsAttributeName').disable({emitEvent: false}); |
|||
|
|||
if (tooltipUnits.value !== CapacityUnits.percent) { |
|||
tooltipUnits.setValue(CapacityUnits.percent, {emitEvent: false}); |
|||
} |
|||
tooltipUnits.disable({emitEvent: false}); |
|||
} else { |
|||
units.enable({emitEvent: false}); |
|||
tooltipUnits.enable({emitEvent: false}); |
|||
widgetUnitsSource.enable({emitEvent: false}); |
|||
|
|||
this.levelCardWidgetConfigForm.get('decimals').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('valueFont').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('valueColor').enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('widgetUnitsAttributeName').enable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('units', 'tooltipUnits', 'decimals', 'valueFont', 'valueColor', 'widgetUnitsSource', 'widgetUnitsAttributeName'); |
|||
} else if (trigger === 'widgetUnitsSource') { |
|||
if (layout !== LevelCardLayout.percentage) { |
|||
if (widgetUnitsSource.value === LevelSelectOptions.static) { |
|||
units.enable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('widgetUnitsAttributeName').disable({emitEvent: false}); |
|||
} else { |
|||
units.disable({emitEvent: false}); |
|||
this.levelCardWidgetConfigForm.get('widgetUnitsAttributeName').enable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('units', 'widgetUnitsAttributeName'); |
|||
} |
|||
} |
|||
|
|||
for (const controlKey in this.levelCardWidgetConfigForm.controls) { |
|||
if (emitEventFields.includes(controlKey)) { |
|||
this.levelCardWidgetConfigForm.controls[controlKey].updateValueAndValidity({emitEvent}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private getCardButtons(config: WidgetConfig): string[] { |
|||
const buttons: string[] = []; |
|||
if (isUndefined(config.enableDataExport) || config.enableDataExport) { |
|||
buttons.push('dataExport'); |
|||
} |
|||
if (isUndefined(config.enableFullscreen) || config.enableFullscreen) { |
|||
buttons.push('fullscreen'); |
|||
} |
|||
return buttons; |
|||
} |
|||
|
|||
private setCardButtons(buttons: string[], config: WidgetConfig) { |
|||
config.enableDataExport = buttons.includes('dataExport'); |
|||
config.enableFullscreen = buttons.includes('fullscreen'); |
|||
} |
|||
|
|||
private _valuePreviewFn(): string { |
|||
const units: string = this.levelCardWidgetConfigForm.get('units').value; |
|||
const decimals: number = this.levelCardWidgetConfigForm.get('decimals').value; |
|||
return formatValue(22, decimals, units, true); |
|||
} |
|||
|
|||
private _tooltipValuePreviewFn() { |
|||
const units: string = this.levelCardWidgetConfigForm.get('tooltipUnits').value; |
|||
const decimals: number = this.levelCardWidgetConfigForm.get('tooltipLevelDecimals').value; |
|||
return formatValue(32, decimals, units, true); |
|||
} |
|||
|
|||
private _totalVolumeValuePreviewFn() { |
|||
const value = this.levelCardWidgetConfigForm.get('volumeConstant').value; |
|||
const datasourceUnits = this.levelCardWidgetConfigForm.get('datasourceUnits').value; |
|||
const decimals: number = this.widgetConfig.config.decimals; |
|||
let units: string = this.widgetConfig.config.units; |
|||
|
|||
if (datasourceUnits !== CapacityUnits.percent) { |
|||
units = datasourceUnits; |
|||
} |
|||
|
|||
return formatValue((isDefined(value) ? value : 500), decimals, units, true); |
|||
} |
|||
|
|||
private _datePreviewFn(): string { |
|||
const dateFormat: DateFormatSettings = this.levelCardWidgetConfigForm.get('tooltipDateFormat').value; |
|||
const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat); |
|||
processor.update(Date.now()); |
|||
return processor.formatted; |
|||
} |
|||
|
|||
private createSvgShapesMapping(): void { |
|||
const obsArray: Array<Observable<{svg: string; shape: Shapes}>> = []; |
|||
for (const shape of this.shapes) { |
|||
const svgUrl = svgMapping.get(shape).svg; |
|||
|
|||
const obs = this.resourcesService.loadJsonResource<string>(svgUrl).pipe( |
|||
map((svg) => ({svg, shape})) |
|||
); |
|||
|
|||
obsArray.push(obs); |
|||
} |
|||
|
|||
forkJoin(obsArray).subscribe((svgData) => { |
|||
for (const data of svgData) { |
|||
this.shapesImageMap.set(data.shape, data.svg); |
|||
} |
|||
|
|||
this.cd.detectChanges(); |
|||
this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges(); |
|||
this.shapesImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges(); |
|||
}); |
|||
} |
|||
|
|||
public createShapeLayout(svg: string, layout: LevelCardLayout): SafeUrl { |
|||
if (svg && layout) { |
|||
const parser = new DOMParser(); |
|||
const svgImage = parser.parseFromString(svg, 'image/svg+xml'); |
|||
|
|||
if (layout === this.levelCardLayouts.simple) { |
|||
svgImage.querySelector('.container-overlay').remove(); |
|||
} else if (layout === this.levelCardLayouts.percentage) { |
|||
svgImage.querySelector('.absolute-overlay').remove(); |
|||
svgImage.querySelector('.percentage-value-container').innerHTML = createPercentLayout(); |
|||
} else { |
|||
svgImage.querySelector('.absolute-value-container').innerHTML = createAbsoluteLayout(); |
|||
svgImage.querySelector('.percentage-overlay').remove(); |
|||
} |
|||
|
|||
const encodedSvg = encodeURIComponent(svgImage.documentElement.outerHTML); |
|||
|
|||
return this.sanitizer.bypassSecurityTrustResourceUrl(`data:image/svg+xml,${encodedSvg}`); |
|||
} |
|||
} |
|||
|
|||
public isRequired(formControlName: string): boolean { |
|||
return this.levelCardWidgetConfigForm.get(formControlName)?.hasValidator(Validators.required); |
|||
} |
|||
|
|||
public fetchOptions(searchText: string): Observable<Array<string>> { |
|||
if (this.keySearchText !== searchText || !this.lastFetchedKeys) { |
|||
this.keySearchText = searchText; |
|||
const dataKeyFilter = optionsFilter(this.keySearchText); |
|||
return this.getKeys().pipe( |
|||
tap(res => this.lastFetchedKeys !== res ? this.lastFetchedKeys = res : []), |
|||
map(name => name?.filter(dataKeyFilter).map(key => key.name)), |
|||
tap(res => this.latestKeySearchTextResult = res) |
|||
); |
|||
} |
|||
return of(this.latestKeySearchTextResult); |
|||
} |
|||
|
|||
private getKeys(): Observable<Array<DataKey>> { |
|||
let fetchObservable: Observable<Array<DataKey>>; |
|||
const callbacks = this.widgetConfigComponent.widgetConfigCallbacks; |
|||
if (this.datasource?.type === DatasourceType.function) { |
|||
fetchObservable = of(this.functionTypeKeys); |
|||
} else if (this.datasource?.type === DatasourceType.entity && this.datasource?.entityAliasId || |
|||
this.datasource?.type === DatasourceType.device && this.datasource?.deviceId) { |
|||
if (this.datasource?.type === DatasourceType.device) { |
|||
if (this.lastKeysId !== this.datasource?.deviceId || !this.lastFetchedKeys) { |
|||
this.lastKeysId = this.datasource.deviceId; |
|||
fetchObservable = callbacks.fetchEntityKeysForDevice(this.datasource?.deviceId, [DataKeyType.attribute]); |
|||
} else { |
|||
fetchObservable = of(this.lastFetchedKeys); |
|||
} |
|||
} else { |
|||
if (this.lastKeysId !== this.datasource?.entityAliasId || !this.lastFetchedKeys) { |
|||
this.lastKeysId = this.datasource.entityAliasId; |
|||
fetchObservable = callbacks.fetchEntityKeys(this.datasource?.entityAliasId, [DataKeyType.attribute]); |
|||
} else { |
|||
fetchObservable = of(this.lastFetchedKeys); |
|||
} |
|||
} |
|||
} else { |
|||
this.lastKeysId = null; |
|||
fetchObservable = of([]); |
|||
} |
|||
return fetchObservable.pipe( |
|||
publishReplay(1), |
|||
refCount() |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,428 @@ |
|||
///
|
|||
/// 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 { |
|||
ColorSettings, |
|||
constantColor, |
|||
cssTextFromInlineStyle, |
|||
DateFormatSettings, |
|||
Font, |
|||
lastUpdateAgoDateFormat |
|||
} from '@shared/models/widget-settings.models'; |
|||
import { DataKey, WidgetConfig } from '@shared/models/widget.models'; |
|||
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; |
|||
import { Observable, of } from 'rxjs'; |
|||
import { singleEntityFilterFromDeviceId } from '@shared/models/query/query.models'; |
|||
import { EntityType } from '@shared/models/entity-type.models'; |
|||
import { catchError, mergeMap } from 'rxjs/operators'; |
|||
import { EntityService } from '@core/http/entity.service'; |
|||
import { IAliasController } from '@core/api/widget-api.models'; |
|||
|
|||
export interface SvgInfo { |
|||
svg: string; |
|||
limits: SvgLimits; |
|||
} |
|||
|
|||
export interface SvgLimits { |
|||
min: number; |
|||
max: number; |
|||
} |
|||
|
|||
export interface LevelCardWidgetSettings extends WidgetConfig { |
|||
tankSelectionType: LevelSelectOptions; |
|||
selectedShape: Shapes; |
|||
shapeAttributeName: string; |
|||
tankColor: ColorSettings; |
|||
datasourceUnits: CapacityUnits; |
|||
layout: LevelCardLayout; |
|||
volumeSource: LevelSelectOptions; |
|||
volumeConstant: number; |
|||
volumeAttributeName: string; |
|||
volumeUnits: CapacityUnits; |
|||
volumeFont: Font; |
|||
volumeColor: ColorSettings; |
|||
liquidColor: ColorSettings; |
|||
valueFont: Font; |
|||
widgetUnitsSource: LevelSelectOptions; |
|||
widgetUnitsAttributeName: string; |
|||
valueColor: ColorSettings; |
|||
showBackgroundOverlay: boolean; |
|||
backgroundOverlayColor: ColorSettings; |
|||
showTooltip: boolean; |
|||
showTooltipLevel: boolean; |
|||
tooltipUnits: CapacityUnits; |
|||
tooltipLevelDecimals: number; |
|||
tooltipLevelFont: Font; |
|||
tooltipLevelColor: ColorSettings; |
|||
showTooltipDate: boolean; |
|||
tooltipDateFormat: DateFormatSettings; |
|||
tooltipDateFont: Font; |
|||
tooltipDateColor: ColorSettings; |
|||
tooltipBackgroundColor: ColorSettings; |
|||
tooltipBackgroundBlur: number; |
|||
} |
|||
|
|||
export enum Shapes { |
|||
vOval = 'Vertical Oval', |
|||
vCylinder = 'Vertical Cylinder', |
|||
vCapsule = 'Vertical Capsule', |
|||
rectangle = 'Rectangle', |
|||
hOval = 'Horizontal Oval', |
|||
hEllipse = 'Horizontal Ellipse', |
|||
hDishEnds = 'Horizontal Dish Ends', |
|||
hCylinder = 'Horizontal Cylinder', |
|||
hCapsule = 'Horizontal Capsule', |
|||
hElliptical_2_1 = 'Horizontal 2:1 Elliptical' |
|||
} |
|||
|
|||
export enum CapacityUnits { |
|||
percent = '%', |
|||
liters = 'L', |
|||
cubicMillimeter = 'mm³', |
|||
cubicCentimeter = 'cm³', |
|||
cubicMeter = 'm³', |
|||
cubicKilometer = 'km³', |
|||
milliliter = 'mL', |
|||
hectoliter = 'hl', |
|||
cubicInch = 'in³', |
|||
cubicFoot = 'ft³', |
|||
cubicYard = 'yd³', |
|||
fluidOunce = 'fl-oz', |
|||
pint = 'pt', |
|||
quart = 'qt', |
|||
gallon = 'gal', |
|||
oilBarrels = 'bbl' |
|||
} |
|||
|
|||
export enum LevelCardLayout { |
|||
simple = 'simple', |
|||
percentage = 'percentage', |
|||
absolute = 'absolute' |
|||
} |
|||
|
|||
export enum LevelSelectOptions { |
|||
attribute = 'Attribute', |
|||
static = 'Static' |
|||
} |
|||
|
|||
export enum ConversionType { |
|||
to = 'to', |
|||
from = 'from', |
|||
} |
|||
|
|||
export const svgMapping = new Map<string, SvgInfo>( |
|||
[ |
|||
[ |
|||
Shapes.vOval, |
|||
{ |
|||
svg: 'assets/widget/liquid-level/shapes/vertical-oval.svg', |
|||
limits: { min: 170, max: 50 } |
|||
} |
|||
], |
|||
[ |
|||
Shapes.vCylinder, |
|||
{ |
|||
svg: 'assets/widget/liquid-level/shapes/vertical-cylinder.svg', |
|||
limits: { min: 161, max: 55 } |
|||
} |
|||
], |
|||
[ |
|||
Shapes.vCapsule, |
|||
{ |
|||
svg: 'assets/widget/liquid-level/shapes/vertical-capsule.svg', |
|||
limits: { min: 195, max: 25 } |
|||
} |
|||
], |
|||
[ |
|||
Shapes.rectangle, |
|||
{ |
|||
svg: 'assets/widget/liquid-level/shapes/rectangle.svg', |
|||
limits: { min: 168, max: 52 } |
|||
} |
|||
], |
|||
[ |
|||
Shapes.hOval, |
|||
{ |
|||
svg: 'assets/widget/liquid-level/shapes/horizontal-oval.svg', |
|||
limits: { min: 160, max: 63 } |
|||
} |
|||
], |
|||
[ |
|||
Shapes.hEllipse, |
|||
{ |
|||
svg: 'assets/widget/liquid-level/shapes/horizontal-ellipse.svg', |
|||
limits: { min: 160, max: 65 } |
|||
} |
|||
], |
|||
[ |
|||
Shapes.hDishEnds, |
|||
{ |
|||
svg: 'assets/widget/liquid-level/shapes/horizontal-dish-ends.svg', |
|||
limits: { min: 172, max: 50 } |
|||
} |
|||
], |
|||
[ |
|||
Shapes.hCylinder, |
|||
{ |
|||
svg: 'assets/widget/liquid-level/shapes/horizontal-cylinder.svg', |
|||
limits: { min: 165, max: 55 } |
|||
} |
|||
], |
|||
[ |
|||
Shapes.hCapsule, |
|||
{ |
|||
svg: 'assets/widget/liquid-level/shapes/horizontal-capsule.svg', |
|||
limits: { min: 158, max: 52 } |
|||
} |
|||
], |
|||
[ |
|||
Shapes.hElliptical_2_1, |
|||
{ |
|||
svg: 'assets/widget/liquid-level/shapes/horizontal-2_1-elliptical.svg', |
|||
limits: { min: 172, max: 50 } |
|||
} |
|||
] |
|||
] |
|||
); |
|||
|
|||
export const levelCardLayoutTranslations = new Map<LevelCardLayout, string>( |
|||
[ |
|||
[LevelCardLayout.simple, 'widgets.liquid-level-card.layout-simple'], |
|||
[LevelCardLayout.percentage, 'widgets.liquid-level-card.layout-percentage'], |
|||
[LevelCardLayout.absolute, 'widgets.liquid-level-card.layout-absolute'] |
|||
] |
|||
); |
|||
|
|||
export const shapesTranslations = new Map<Shapes, string>( |
|||
[ |
|||
[Shapes.vOval, 'widgets.liquid-level-card.v-oval'], |
|||
[Shapes.vCylinder, 'widgets.liquid-level-card.v-cylinder'], |
|||
[Shapes.vCapsule, 'widgets.liquid-level-card.v-capsule'], |
|||
[Shapes.rectangle, 'widgets.liquid-level-card.rectangle'], |
|||
[Shapes.hOval, 'widgets.liquid-level-card.h-oval'], |
|||
[Shapes.hEllipse, 'widgets.liquid-level-card.h-ellipse'], |
|||
[Shapes.hDishEnds, 'widgets.liquid-level-card.h-dish-ends'], |
|||
[Shapes.hCylinder, 'widgets.liquid-level-card.h-cylinder'], |
|||
[Shapes.hCapsule, 'widgets.liquid-level-card.h-capsule'], |
|||
[Shapes.hElliptical_2_1, 'widgets.liquid-level-card.h-elliptical_2_1'] |
|||
] |
|||
); |
|||
|
|||
export const levelCardDefaultSettings = (): LevelCardWidgetSettings => ({ |
|||
tankSelectionType: LevelSelectOptions.static, |
|||
selectedShape: Shapes.vCylinder, |
|||
shapeAttributeName: 'tankShape', |
|||
tankColor: constantColor('#242770'), |
|||
datasourceUnits: CapacityUnits.percent, |
|||
layout: LevelCardLayout.percentage, |
|||
showTitle: false, |
|||
title: 'Liquid level', |
|||
titleFont: { |
|||
family: 'Roboto', |
|||
size: 16, |
|||
sizeUnit: 'px', |
|||
style: 'normal', |
|||
weight: '500', |
|||
lineHeight: '1.5' |
|||
}, |
|||
titleColor: 'rgba(0, 0, 0, 0.87)', |
|||
showTitleIcon: false, |
|||
titleIcon: 'water_drop', |
|||
iconColor: '#5469FF', |
|||
volumeSource: LevelSelectOptions.static, |
|||
volumeConstant: 500, |
|||
volumeUnits: CapacityUnits.liters, |
|||
volumeAttributeName: 'volume', |
|||
volumeFont: { |
|||
family: 'Roboto', |
|||
size: 14, |
|||
sizeUnit: 'px', |
|||
style: 'normal', |
|||
weight: '500', |
|||
lineHeight: '100%' |
|||
}, |
|||
volumeColor: constantColor('rgba(0, 0, 0, 0.18)'), |
|||
units: CapacityUnits.percent as string, |
|||
widgetUnitsSource: LevelSelectOptions.static, |
|||
widgetUnitsAttributeName: 'units', |
|||
decimals: 0, |
|||
liquidColor: constantColor('#7A8BFF'), |
|||
valueFont: { |
|||
family: 'Roboto', |
|||
size: 24, |
|||
sizeUnit: 'px', |
|||
style: 'normal', |
|||
weight: '500', |
|||
lineHeight: '100%' |
|||
}, |
|||
valueColor: constantColor('rgba(0, 0, 0, 0.87)'), |
|||
showBackgroundOverlay: true, |
|||
backgroundOverlayColor: constantColor('rgba(255, 255, 255, 0.76)'), |
|||
showTooltip: true, |
|||
showTooltipLevel: true, |
|||
tooltipUnits: CapacityUnits.percent, |
|||
tooltipLevelDecimals: 0, |
|||
tooltipLevelFont: { |
|||
family: 'Roboto', |
|||
size: 13, |
|||
sizeUnit: 'px', |
|||
style: 'normal', |
|||
weight: '500', |
|||
lineHeight: '100%' |
|||
}, |
|||
tooltipLevelColor: constantColor('rgba(0, 0, 0, 0.76)'), |
|||
showTooltipDate: true, |
|||
tooltipDateFormat: lastUpdateAgoDateFormat(), |
|||
tooltipDateFont: { |
|||
family: 'Roboto', |
|||
size: 13, |
|||
sizeUnit: 'px', |
|||
style: 'normal', |
|||
weight: '500', |
|||
lineHeight: '100%' |
|||
}, |
|||
tooltipDateColor: constantColor('rgba(0, 0, 0, 0.76)'), |
|||
tooltipBackgroundColor: constantColor('rgba(255, 255, 255, 0.76)'), |
|||
tooltipBackgroundBlur: 3 |
|||
}); |
|||
|
|||
export const convertLiters = (value: number, units: CapacityUnits, conversionType: ConversionType): number => { |
|||
let factor: number; |
|||
|
|||
switch (units) { |
|||
case CapacityUnits.liters: |
|||
return value; |
|||
case CapacityUnits.cubicMillimeter: |
|||
factor = 1e6; |
|||
break; |
|||
case CapacityUnits.cubicCentimeter: |
|||
case CapacityUnits.milliliter: |
|||
factor = 1e3; |
|||
break; |
|||
case CapacityUnits.cubicMeter: |
|||
factor = 1e-3; |
|||
break; |
|||
case CapacityUnits.cubicKilometer: |
|||
factor = 1e-15; |
|||
break; |
|||
case CapacityUnits.hectoliter: |
|||
factor = 0.01; |
|||
break; |
|||
case CapacityUnits.cubicInch: |
|||
factor = 61.0237; |
|||
break; |
|||
case CapacityUnits.cubicFoot: |
|||
factor = 1 / 28.3168; |
|||
break; |
|||
case CapacityUnits.cubicYard: |
|||
factor = 1 / 764.555; |
|||
break; |
|||
case CapacityUnits.fluidOunce: |
|||
factor = 33.814; |
|||
break; |
|||
case CapacityUnits.pint: |
|||
factor = 1 / 0.473176; |
|||
break; |
|||
case CapacityUnits.quart: |
|||
factor = 1 / 0.946353; |
|||
break; |
|||
case CapacityUnits.gallon: |
|||
factor = 1 / 3.78541; |
|||
break; |
|||
case CapacityUnits.oilBarrels: |
|||
factor = 1 / 158.987; |
|||
break; |
|||
default: |
|||
return value; |
|||
// throw new Error(`Unknown unit: ${units}`);
|
|||
} |
|||
|
|||
return conversionType === ConversionType.to ? value / factor : value * factor; |
|||
}; |
|||
|
|||
export const extractValue = (attributes: any[], attributeName: string, defaultValue: any): any => { |
|||
const index = attributes.findIndex(attr => attr.key === attributeName); |
|||
return index !== -1 ? attributes[index].value : defaultValue; |
|||
}; |
|||
|
|||
export const valueContainerStyleDefaults = cssTextFromInlineStyle({ |
|||
width: '100%', |
|||
height: '100%', |
|||
display: 'flex', |
|||
gap: '8px', |
|||
'text-align': 'center', |
|||
'align-items': 'center', |
|||
'align-content': 'center', |
|||
'justify-content': 'center', |
|||
}); |
|||
|
|||
export const valueTextStyleDefaults = 'letterSpacing: normal; font-style: normal; font-weight: 500; line-height: 100%;' + |
|||
' font-size: 24px; font-family: Roboto; color: #000000DE'; |
|||
|
|||
export const volumeTextStyleDefaults = 'letterSpacing: normal; font-style: normal; font-weight: 500;' + |
|||
' line-height: 100%; font-size: 14px; font-family: Roboto; color: rgba(0, 0, 0, 0.18)'; |
|||
|
|||
export const createAbsoluteLayout = (values?: {inputValue: number | string; volume: number | string}, |
|||
styles?: {valueStyle: string; volumeStyle: string}, |
|||
units?: string): string => { |
|||
const inputValue = values?.inputValue ? values?.inputValue : 500; |
|||
const valueTextStyle = styles?.valueStyle ? styles?.valueStyle : valueTextStyleDefaults; |
|||
const volume = values?.volume ? values?.volume : 1000; |
|||
const volumeTextStyle = styles?.volumeStyle ? styles?.volumeStyle : volumeTextStyleDefaults; |
|||
const displayUnits = units ? units : CapacityUnits.liters; |
|||
|
|||
return `<div xmlns="http://www.w3.org/1999/xhtml" style="${valueContainerStyleDefaults}">
|
|||
<div style="display: flex; flex-direction: column; justify-content: center; align-content: center;"> |
|||
<label style="${valueTextStyle}">${inputValue}</label> |
|||
<div style="border-top: 1px solid rgba(0, 0, 0, 0.38); height: 1px; width: 100%; margin: 3px 0"></div> |
|||
<label style="${volumeTextStyle}">${volume}</label> |
|||
</div> |
|||
<label><b>${displayUnits}</b></label> |
|||
</div>`;
|
|||
}; |
|||
|
|||
export const createPercentLayout = (value: number | string = 50, valueTextStyle: string = valueTextStyleDefaults): string => |
|||
`<div xmlns="http://www.w3.org/1999/xhtml" style="${valueContainerStyleDefaults}">
|
|||
<label><span style="${valueTextStyle}">${value}</span> ${CapacityUnits.percent}</label> |
|||
</div>`;
|
|||
|
|||
export const optionsFilter = (searchText: string): ((key: DataKey) => boolean) => |
|||
key => key?.name.toUpperCase().includes(searchText?.toUpperCase()); |
|||
|
|||
export const fetchEntityKeysForDevice = (deviceId: string, dataKeyTypes: Array<DataKeyType>, |
|||
entityService: EntityService): Observable<Array<DataKey>> => { |
|||
const entityFilter = singleEntityFilterFromDeviceId(deviceId); |
|||
return entityService.getEntityKeysByEntityFilter( |
|||
entityFilter, |
|||
dataKeyTypes, [EntityType.DEVICE], |
|||
{ignoreLoading: true, ignoreErrors: true} |
|||
).pipe( |
|||
catchError(() => of([])) |
|||
); |
|||
}; |
|||
|
|||
export const fetchEntityKeys = (entityAliasId: string, dataKeyTypes: Array<DataKeyType>, |
|||
entityService: EntityService, aliasController: IAliasController): Observable<Array<DataKey>> => |
|||
aliasController.getAliasInfo(entityAliasId).pipe( |
|||
mergeMap((aliasInfo) => entityService.getEntityKeysByEntityFilter( |
|||
aliasInfo.entityFilter, |
|||
dataKeyTypes, [], |
|||
{ignoreLoading: true, ignoreErrors: true} |
|||
).pipe( |
|||
catchError(() => of([])) |
|||
)), |
|||
catchError(() => of([] as Array<DataKey>)) |
|||
); |
|||
@ -0,0 +1,510 @@ |
|||
///
|
|||
/// 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 { WidgetContext } from '@home/models/widget-component.models'; |
|||
import { isDefined, isDefinedAndNotNull, isNumber, isString } from '@core/utils'; |
|||
import { |
|||
CapacityUnits, |
|||
ConversionType, |
|||
convertLiters, createAbsoluteLayout, createPercentLayout, |
|||
extractValue, |
|||
levelCardDefaultSettings, |
|||
LevelCardLayout, |
|||
LevelCardWidgetSettings, |
|||
LevelSelectOptions, |
|||
Shapes, |
|||
SvgInfo, |
|||
SvgLimits, |
|||
svgMapping |
|||
} from '@home/components/widget/lib/indicator/liquid-level-widget.models'; |
|||
import { Observable, of } from 'rxjs'; |
|||
import { map, switchMap } from 'rxjs/operators'; |
|||
import { EntityId } from '@shared/models/id/entity-id'; |
|||
import { |
|||
ColorProcessor, |
|||
cssTextFromInlineStyle, |
|||
DateFormatProcessor, |
|||
inlineTextStyle |
|||
} from '@shared/models/widget-settings.models'; |
|||
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; |
|||
import { ResourcesService } from '@core/services/resources.service'; |
|||
import { NULL_UUID } from '@shared/models/id/has-uuid'; |
|||
import { Component, Input, OnInit } from '@angular/core'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-liquid-level-widget', |
|||
template: '' |
|||
}) |
|||
export class LiquidLevelWidgetComponent implements OnInit { |
|||
|
|||
@Input() |
|||
ctx: WidgetContext; |
|||
|
|||
svgParams: SvgInfo; |
|||
|
|||
svg: JQuery<SVGElement>; |
|||
|
|||
tooltip: ITooltipsterInstance; |
|||
|
|||
overlayContainer: JQuery<HTMLElement>; |
|||
|
|||
shape: Shapes; |
|||
|
|||
settings: LevelCardWidgetSettings; |
|||
|
|||
tankColor: ColorProcessor; |
|||
|
|||
volumeColor: ColorProcessor; |
|||
|
|||
valueColor: ColorProcessor; |
|||
|
|||
liquidColor: ColorProcessor; |
|||
|
|||
backgroundOverlayColor: ColorProcessor; |
|||
|
|||
tooltipLevelColor: ColorProcessor; |
|||
|
|||
tooltipDateFormat: DateFormatProcessor; |
|||
|
|||
tooltipDateColor: ColorProcessor; |
|||
|
|||
tooltipBackgroundColor: ColorProcessor; |
|||
|
|||
volume: number; |
|||
|
|||
tooltipContent: string; |
|||
|
|||
widgetUnits: string; |
|||
|
|||
constructor() { |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.ctx.$scope.liquidLevelWidget = this; |
|||
this.settings = {...levelCardDefaultSettings(), ...this.ctx.widgetConfig, ...this.ctx.settings}; |
|||
this.declareStyles(); |
|||
} |
|||
|
|||
public onInit() { |
|||
this.getData().subscribe(data => { |
|||
if (data) { |
|||
const { svg, volume, units } = data; |
|||
if (svg && isString(svg) && this.ctx.$container) { |
|||
this.ctx.$container.html(svg); |
|||
this.svg = this.ctx.$container.find('svg'); |
|||
this.svg.on('click', this.cardClick.bind(this)); |
|||
this.createSVG(); |
|||
this.createValueElement(); |
|||
|
|||
if (this.settings.showTooltip) { |
|||
this.createTooltip(); |
|||
} |
|||
} |
|||
|
|||
if (isDefined(volume) && isNumber(volume)) { |
|||
this.volume = volume; |
|||
} |
|||
|
|||
if (units) { |
|||
this.widgetUnits = units; |
|||
} |
|||
|
|||
this.update(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private declareStyles():void { |
|||
this.tankColor = ColorProcessor.fromSettings(this.settings.tankColor); |
|||
this.volumeColor = ColorProcessor.fromSettings(this.settings.volumeColor); |
|||
this.valueColor = ColorProcessor.fromSettings(this.settings.valueColor); |
|||
this.liquidColor = ColorProcessor.fromSettings(this.settings.liquidColor); |
|||
this.backgroundOverlayColor = ColorProcessor.fromSettings(this.settings.backgroundOverlayColor); |
|||
this.tooltipLevelColor = ColorProcessor.fromSettings(this.settings.tooltipLevelColor); |
|||
this.tooltipDateColor = ColorProcessor.fromSettings(this.settings.tooltipDateColor); |
|||
this.tooltipBackgroundColor = ColorProcessor.fromSettings(this.settings.tooltipBackgroundColor); |
|||
|
|||
this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat); |
|||
} |
|||
|
|||
private getData(): Observable<{ svg: string; volume: number; units: string }> { |
|||
const entityId: EntityId = { |
|||
entityType: this.ctx.datasources[0].entityType, |
|||
id: this.ctx.datasources[0].entityId |
|||
}; |
|||
|
|||
return this.getShape(entityId).pipe( |
|||
switchMap(shape => { |
|||
this.shape = shape; |
|||
this.svgParams = svgMapping.get(shape); |
|||
if (this.svgParams) { |
|||
return this.loadSVG(this.svgParams.svg).pipe( |
|||
switchMap( svg => |
|||
this.getSecondaryResources(entityId).pipe( |
|||
map(({ volume, units }) => |
|||
({ svg, volume, units }) |
|||
) |
|||
) |
|||
) |
|||
); |
|||
} |
|||
|
|||
return of(null); |
|||
}) |
|||
); |
|||
} |
|||
|
|||
public update() { |
|||
if (this.svg) { |
|||
this.updateData(); |
|||
} |
|||
} |
|||
|
|||
public destroy() { |
|||
|
|||
} |
|||
|
|||
private createSVG() { |
|||
if (this.svg) { |
|||
this.svg.css('display', 'block') |
|||
.css('overflow', 'hidden') |
|||
.css('width', '100%') |
|||
.css('height', '100%'); |
|||
} |
|||
} |
|||
|
|||
private updateData() { |
|||
const data = this.ctx.data[0]?.data[0]?.map(value => Number(value)); |
|||
if (isDefinedAndNotNull(data) && data.length && typeof Number(data[1]) === 'number') { |
|||
const percentage = this.convertInputData(Number(data[1])); |
|||
this.updateSvg(percentage); |
|||
this.updateValueElement(this.convertOutputData(percentage), percentage); |
|||
|
|||
if (this.settings.showTooltip) { |
|||
this.updateTooltip(data); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private createTooltip(): void { |
|||
this.tooltipContent = this.getTooltipContent(); |
|||
|
|||
import('tooltipster').then(() => { |
|||
if ($.tooltipster && this.tooltip) { |
|||
this.tooltip.destroy(); |
|||
this.tooltip = null; |
|||
} |
|||
this.tooltip = this.svg.tooltipster({ |
|||
contentAsHTML: true, |
|||
theme: 'tooltipster-shadow', |
|||
side: 'top', |
|||
delay: 10, |
|||
distance: -33, |
|||
triggerClose: { |
|||
click: true, |
|||
tap: true, |
|||
scroll: true, |
|||
mouseleave: true |
|||
}, |
|||
animation: 'grow', |
|||
updateAnimation: null, |
|||
trackOrigin: true, |
|||
functionBefore: (instance, helper) => { |
|||
instance.content(this.tooltipContent); |
|||
}, |
|||
functionReady: (instance, helper) => { |
|||
const tooltipsterBoxStyles: JQuery.PlainObject = { |
|||
backgroundColor: this.getTooltipBackground(), |
|||
backdropFilter: `blur(${this.settings.tooltipBackgroundBlur}px)`, |
|||
width: '100%' |
|||
}; |
|||
|
|||
$(helper.tooltip).css('max-width', this.svg.width() + 'px'); |
|||
$(helper.tooltip).find('.tooltipster-box').css(tooltipsterBoxStyles); |
|||
$(helper.tooltip).find('.tooltipster-arrow').empty(); |
|||
|
|||
instance.reposition(); |
|||
} |
|||
}).tooltipster('instance'); |
|||
}); |
|||
} |
|||
|
|||
private createValueElement(): void { |
|||
const containerOverlay = this.ctx.$container.find('.container-overlay'); |
|||
const percentageOverlay = this.ctx.$container.find('.percentage-overlay'); |
|||
const absoluteOverlay = this.ctx.$container.find('.absolute-overlay'); |
|||
|
|||
if (this.settings.layout === LevelCardLayout.absolute) { |
|||
this.overlayContainer = absoluteOverlay; |
|||
percentageOverlay.css('visibility', 'hidden'); |
|||
if (!this.settings.showBackgroundOverlay) { |
|||
absoluteOverlay.css('visibility', 'hidden'); |
|||
} |
|||
} else if (this.settings.layout === LevelCardLayout.percentage) { |
|||
this.overlayContainer = percentageOverlay; |
|||
absoluteOverlay.css('visibility', 'hidden'); |
|||
if (!this.settings.showBackgroundOverlay) { |
|||
percentageOverlay.css('visibility', 'hidden'); |
|||
} |
|||
} else { |
|||
containerOverlay.css('visibility', 'hidden'); |
|||
} |
|||
|
|||
if (this.overlayContainer) { |
|||
this.overlayContainer.attr('fill', this.backgroundOverlayColor.color); |
|||
this.overlayContainer.removeAttr('fill-opacity'); |
|||
} |
|||
} |
|||
|
|||
private loadSVG(url: string): Observable<string> { |
|||
const resourcesService = this.ctx.$injector.get(ResourcesService); |
|||
return resourcesService.loadJsonResource(url); |
|||
} |
|||
|
|||
private getShape(entityId: EntityId): Observable<Shapes> { |
|||
if (this.settings.tankSelectionType === LevelSelectOptions.attribute && entityId.id !== NULL_UUID) { |
|||
return this.ctx.attributeService.getEntityAttributes(entityId, null, |
|||
[this.settings.shapeAttributeName]).pipe( |
|||
map(attributes => |
|||
extractValue(attributes, this.settings.shapeAttributeName, this.settings.selectedShape) |
|||
) |
|||
); |
|||
} |
|||
|
|||
return of(this.settings.selectedShape); |
|||
} |
|||
|
|||
private getSecondaryResources(entityId: EntityId): Observable<{ volume: number; units: string }> { |
|||
const attributeNames = this.prepareAttributeNames(); |
|||
|
|||
if (!attributeNames.length || entityId.id === NULL_UUID) { |
|||
return of({ |
|||
volume: this.settings.volumeConstant, |
|||
units: this.settings.units |
|||
}); |
|||
} |
|||
|
|||
return this.ctx.attributeService.getEntityAttributes(entityId, null, attributeNames).pipe( |
|||
map(attributes => ({ |
|||
volume: extractValue(attributes, this.settings.volumeAttributeName, this.settings.volumeConstant), |
|||
units: extractValue(attributes, this.settings.widgetUnitsAttributeName, this.settings.units) |
|||
})) |
|||
); |
|||
} |
|||
|
|||
private prepareAttributeNames(): string[] { |
|||
const names = []; |
|||
if (this.settings.volumeSource !== LevelSelectOptions.static) { |
|||
names.push(this.settings.volumeAttributeName); |
|||
} |
|||
if (this.settings.widgetUnitsSource !== LevelSelectOptions.static) { |
|||
names.push(this.settings.widgetUnitsAttributeName); |
|||
} |
|||
return names; |
|||
} |
|||
|
|||
private updateSvg(percentage: number) { |
|||
const yLimits: SvgLimits = { |
|||
min: this.svgParams.limits.min, |
|||
max: this.svgParams.limits.max |
|||
}; |
|||
|
|||
const newYPos = this.calculatePosition(percentage, yLimits); |
|||
this.updateShapeColor(percentage); |
|||
this.updateLevel(newYPos, percentage); |
|||
} |
|||
|
|||
private calculatePosition(percentage, limits): number { |
|||
return limits.min + (percentage / 100) * (limits.max - limits.min); |
|||
} |
|||
|
|||
private updateTooltip(value: number[]): void { |
|||
this.tooltipContent = this.getTooltipContent(value); |
|||
|
|||
if (this.tooltip) { |
|||
this.tooltip.content(this.tooltipContent); |
|||
} |
|||
} |
|||
|
|||
private updateLevel(newY: number, percentage: number): void { |
|||
this.liquidColor.update(percentage); |
|||
const fill = this.ctx.$container.find('.tb-liquid-fill'); |
|||
const surfaces = this.ctx.$container.find('.tb-liquid-surface'); |
|||
const surfacePositionAttr = this.shape !== Shapes.vCylinder ? 'y' : 'cy'; |
|||
const animationSpeed = 500; |
|||
|
|||
if (percentage > 1) { |
|||
fill.css('visibility', 'visible'); |
|||
fill.animate({y : newY}, animationSpeed); |
|||
fill.attr('fill', this.liquidColor.color); |
|||
|
|||
surfaces.each((index, element) => { |
|||
const $element = $(element); |
|||
$element.css('visibility', 'visible'); |
|||
$element.animate({[surfacePositionAttr]: newY}, animationSpeed); |
|||
if ($element.hasClass('tb-liquid')) { |
|||
$element.attr('fill', this.liquidColor.color); |
|||
} |
|||
}); |
|||
} else { |
|||
fill.css('visibility', 'hidden').stop().clearQueue(); |
|||
surfaces.each((index, element) => { |
|||
$(element).css('visibility', 'hidden').stop().clearQueue(); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
private updateShapeColor(value): void { |
|||
const shapeStrokes = this.ctx.$container.find('.tb-shape-stroke'); |
|||
const shapeFill = this.ctx.$container.find('.tb-shape-fill'); |
|||
this.tankColor.update(value); |
|||
|
|||
shapeStrokes.each((index, element) => { |
|||
$(element).attr('stroke', this.tankColor.color); |
|||
}); |
|||
|
|||
shapeFill.each((index, element) => { |
|||
$(element).attr('fill', this.tankColor.color); |
|||
}); |
|||
} |
|||
|
|||
private updateValueElement(data: number, percentage: number): void { |
|||
let content: string; |
|||
let container: JQuery<HTMLElement>; |
|||
|
|||
const value = convertLiters(data, this.widgetUnits as CapacityUnits, ConversionType.from) |
|||
.toFixed(this.settings.decimals || 0); |
|||
const valueTextStyle = cssTextFromInlineStyle({...inlineTextStyle(this.settings.valueFont), |
|||
color: this.valueColor.color}); |
|||
this.backgroundOverlayColor.update(percentage); |
|||
if (this.overlayContainer) { |
|||
this.overlayContainer.attr('fill', this.backgroundOverlayColor.color); |
|||
} |
|||
|
|||
if (this.settings.layout === LevelCardLayout.absolute) { |
|||
this.volumeColor.update(percentage); |
|||
|
|||
const volumeInLiters: number = convertLiters(this.volume, this.settings.volumeUnits as CapacityUnits, ConversionType.to); |
|||
const volume = convertLiters(volumeInLiters, this.widgetUnits as CapacityUnits, ConversionType.from) |
|||
.toFixed(this.settings.decimals || 0); |
|||
const volumeTextStyle = cssTextFromInlineStyle({...inlineTextStyle(this.settings.volumeFont), |
|||
color: this.volumeColor.color}); |
|||
|
|||
container = this.ctx.$container.find('.absolute-value-container'); |
|||
content = createAbsoluteLayout({inputValue: value, volume}, |
|||
{valueStyle: valueTextStyle, volumeStyle: volumeTextStyle}, this.widgetUnits); |
|||
|
|||
} else if (this.settings.layout === LevelCardLayout.percentage) { |
|||
container = this.ctx.$container.find('.percentage-value-container'); |
|||
content = createPercentLayout(value, valueTextStyle); |
|||
} |
|||
|
|||
if (content && container) { |
|||
container.html(content); |
|||
} |
|||
} |
|||
|
|||
private getTooltipContent(value?: number[]): string { |
|||
const contentValue = value || [0, 0]; |
|||
|
|||
if (contentValue[1]) { |
|||
contentValue[1] = this.convertTooltipData(contentValue[1]); |
|||
} |
|||
|
|||
this.tooltipLevelColor.update(contentValue[1]); |
|||
this.tooltipDateColor.update(contentValue[0]); |
|||
this.tooltipDateFormat.update(contentValue[0]); |
|||
this.tooltipBackgroundColor.update(contentValue); |
|||
|
|||
const levelTextStyle = cssTextFromInlineStyle({...inlineTextStyle(this.settings.tooltipLevelFont), |
|||
color: this.tooltipLevelColor.color}); |
|||
|
|||
const dateTextStyle = cssTextFromInlineStyle({...inlineTextStyle(this.settings.tooltipDateFont), |
|||
color: this.tooltipDateColor.color, overflow: 'hidden', 'text-overflow': 'ellipsis', 'white-space': 'nowrap'}); |
|||
|
|||
let content = `<div style="display: flex; flex-direction: column;
|
|||
gap: 8px; background-color: transparent">`;
|
|||
|
|||
if (this.settings.showTooltipLevel) { |
|||
const levelValue = contentValue[1]?.toFixed(this.settings.tooltipLevelDecimals) + this.settings.tooltipUnits; |
|||
content += this.createTooltipContent( |
|||
this.ctx.translate.instant('widgets.liquid-level-card.level'), |
|||
levelValue, |
|||
levelTextStyle |
|||
); |
|||
} |
|||
|
|||
if (this.settings.showTooltipDate) { |
|||
content += this.createTooltipContent( |
|||
this.ctx.translate.instant('widgets.liquid-level-card.last-update'), |
|||
this.tooltipDateFormat.formatted, |
|||
dateTextStyle |
|||
); |
|||
} |
|||
|
|||
content += '</div>'; |
|||
|
|||
return content; |
|||
} |
|||
|
|||
private createTooltipContent(labelText: string, contentValue: string, textStyle: string): string { |
|||
return `<div style="display: flex; justify-content: space-between; gap: 8px">
|
|||
<label style="color: rgba(0, 0, 0, 0.38); font-size: 12px; font-weight: 400; white-space: nowrap;"> |
|||
${labelText} |
|||
</label> |
|||
<label style="${textStyle}"> |
|||
${contentValue} |
|||
</label> |
|||
</div>`;
|
|||
} |
|||
|
|||
private getTooltipBackground(): string { |
|||
return this.tooltipBackgroundColor.color; |
|||
} |
|||
|
|||
private convertInputData(value: number): number { |
|||
if (this.settings.datasourceUnits !== CapacityUnits.percent) { |
|||
return (convertLiters(value, this.settings.datasourceUnits, ConversionType.to) / |
|||
convertLiters(this.volume, this.settings.volumeUnits, ConversionType.to)) * 100; |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
private convertOutputData(value: number): number { |
|||
if (this.widgetUnits !== CapacityUnits.percent) { |
|||
return convertLiters(this.volume * (value / 100), this.settings.volumeUnits, ConversionType.to); |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
private convertTooltipData(value: number): number { |
|||
const percentage = this.convertInputData(value); |
|||
if (this.settings.tooltipUnits !== CapacityUnits.percent) { |
|||
const liters = this.convertOutputData(percentage); |
|||
|
|||
return convertLiters(liters, this.settings.tooltipUnits, ConversionType.from); |
|||
} else { |
|||
return percentage; |
|||
} |
|||
} |
|||
|
|||
public cardClick($event) { |
|||
this.ctx.actionsApi.cardClick($event); |
|||
} |
|||
} |
|||
@ -0,0 +1,267 @@ |
|||
<!-- |
|||
|
|||
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 *ngIf="levelCardWidgetSettingsForm" [formGroup]="levelCardWidgetSettingsForm"> |
|||
<div style="display: flex; flex-direction: column; gap: 16px;"> |
|||
<div class="tb-form-panel"> |
|||
<div fxFlex fxLayout="column" class="tb-form-row space-between"> |
|||
<div fxFlex fxLayout="row" style="width: 100%;" fxLayoutAlign="space-between center"> |
|||
<div class="fixed-title-width" translate>widgets.liquid-level-card.shape</div> |
|||
<tb-toggle-select formControlName="tankSelectionType"> |
|||
<tb-toggle-option [value]="shapeSelectOptions.static"> |
|||
{{ 'widgets.liquid-level-card.static' | translate }} |
|||
</tb-toggle-option> |
|||
<tb-toggle-option [value]="shapeSelectOptions.attribute"> |
|||
{{ 'widgets.liquid-level-card.attribute' | translate }} |
|||
</tb-toggle-option> |
|||
</tb-toggle-select> |
|||
</div> |
|||
<tb-image-cards-select #shapesImageCardsSelect |
|||
[fxShow]="levelCardWidgetSettingsForm.get('tankSelectionType')?.value === shapeSelectOptions.static" |
|||
rowHeight="{{ '1:1' }}" |
|||
[cols]="5" |
|||
[colsLtMd]="5" |
|||
style="width: 100%;" |
|||
label="{{ 'widgets.liquid-level-card.shape-type' | translate }}" formControlName="selectedShape"> |
|||
<tb-image-cards-select-option *ngFor="let shape of shapes" |
|||
[value]="shape" |
|||
[image]="createShapeLayout(shapesImageMap.get(shape), this.levelCardLayouts.simple)"> |
|||
{{ shapesTranslationMap.get(shape) | translate }} |
|||
</tb-image-cards-select-option> |
|||
</tb-image-cards-select> |
|||
<div class="tb-form-row space-between" style="width: 100%;" |
|||
[fxShow]="levelCardWidgetSettingsForm.get('tankSelectionType')?.value === shapeSelectOptions.attribute"> |
|||
<div class="fixed-title-width" translate>widgets.liquid-level-card.shape-attribute-name</div> |
|||
<tb-string-autocomplete [fetchOptionsFn]="fetchOptions.bind(this)" |
|||
[required]="isRequired('shapeAttributeName')" |
|||
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate" |
|||
style="flex: 1; width: auto;" |
|||
asBoxInput colorClearButton class="flex" |
|||
formControlName="shapeAttributeName"> |
|||
</tb-string-autocomplete> |
|||
</div> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="space-between center" style="width: 100%;" |
|||
[fxShow]="levelCardWidgetSettingsForm.get('tankSelectionType')?.value === shapeSelectOptions.attribute"> |
|||
<div class="tb-hint" style="padding: 0;" translate>widgets.liquid-level-card.shape-by-attribute</div> |
|||
<div class="see-example" |
|||
[tb-help-popup]="'widget/lib/indicator/shape_attribute_fn'" |
|||
tb-help-popup-placement="left" |
|||
trigger-style="font-size: 12px" |
|||
[tb-help-popup-style]="{maxWidth: '820px'}" |
|||
trigger-text="{{ 'widgets.liquid-level-card.see-examples' | translate }}"> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widgets.liquid-level-card.layout</div> |
|||
<tb-image-cards-select #layoutsImageCardsSelect |
|||
rowHeight="{{ '1:1' }}" |
|||
[cols]="3" |
|||
[colsLtMd]="3" |
|||
label="{{ 'widgets.liquid-level-card.layout' | translate }}" formControlName="layout"> |
|||
<tb-image-cards-select-option |
|||
*ngFor="let layout of [levelCardLayouts.simple, levelCardLayouts.percentage, levelCardLayouts.absolute]" |
|||
[value]="layout" |
|||
[image]="createShapeLayout(shapesImageMap.get(levelCardWidgetSettingsForm.get('selectedShape').value), layout)"> |
|||
{{ levelCardLayoutTranslationMap.get(layout) | translate }} |
|||
</tb-image-cards-select-option> |
|||
</tb-image-cards-select> |
|||
</div> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widgets.liquid-level-card.units</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div class="fixed-title-width tb-required" translate>widgets.liquid-level-card.datasource-units</div> |
|||
<tb-unit-input style="max-width: 30%;" class="flex" |
|||
[tagFilter]="unitsType.capacity" |
|||
[required]="isRequired('datasourceUnits')" |
|||
formControlName="datasourceUnits"> |
|||
</tb-unit-input> |
|||
</div> |
|||
<div class="tb-form-row space-between" [fxShow]="widgetUnitsInput"> |
|||
<div class="fixed-title-width tb-required" translate>widgets.liquid-level-card.widget-units</div> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic" style="max-width: 25%"> |
|||
<mat-select formControlName="widgetUnitsSource" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
<mat-option *ngFor="let option of [volumeOptions.static, volumeOptions.attribute]" [value]="option"> |
|||
{{ option }} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<tb-unit-input [fxShow]="levelCardWidgetSettingsForm.get('widgetUnitsSource')?.value !== volumeOptions.attribute" |
|||
asBoxInput colorClearButton class="flex" |
|||
[tagFilter]="unitsType.capacity" |
|||
[required]="isRequired('units')" |
|||
formControlName="units"> |
|||
</tb-unit-input> |
|||
<tb-string-autocomplete [fxShow]="levelCardWidgetSettingsForm.get('widgetUnitsSource')?.value === volumeOptions.attribute" |
|||
[fetchOptionsFn]="fetchOptions.bind(this)" |
|||
[required]="isRequired('widgetUnitsAttributeName')" |
|||
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate" |
|||
style="flex: 1; width: auto;" |
|||
asBoxInput colorClearButton class="flex" |
|||
formControlName="widgetUnitsAttributeName"> |
|||
</tb-string-autocomplete> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row" [fxShow]="volumeInput"> |
|||
<div class="fixed-title-width tb-required" translate>widgets.liquid-level-card.total-volume</div> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic" style="max-width: 25%"> |
|||
<mat-select formControlName="volumeSource" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
<mat-option *ngFor="let option of [volumeOptions.static, volumeOptions.attribute]" [value]="option"> |
|||
{{ option }} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<mat-form-field [fxShow]="levelCardWidgetSettingsForm.get('volumeSource')?.value === volumeOptions.static" |
|||
appearance="outline" class="flex number" subscriptSizing="dynamic"> |
|||
<input matInput formControlName="volumeConstant" type="number" |
|||
min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
<mat-icon matSuffix |
|||
matTooltipPosition="above" |
|||
matTooltipClass="tb-error-tooltip" |
|||
[matTooltip]="'widgets.liquid-level-card.total-volume-required' | translate" |
|||
*ngIf="levelCardWidgetSettingsForm.get('volumeConstant').hasError('required')" |
|||
class="material-icons tb-suffix-show-always tb-error"> |
|||
warning |
|||
</mat-icon> |
|||
</mat-form-field> |
|||
<tb-string-autocomplete style="max-width: 25%" |
|||
[fxShow]="levelCardWidgetSettingsForm.get('volumeSource')?.value === volumeOptions.attribute" |
|||
[fetchOptionsFn]="fetchOptions.bind(this)" |
|||
[required]="isRequired('volumeAttributeName')" |
|||
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate" |
|||
style="flex: 1; width: auto;" |
|||
asBoxInput colorClearButton class="flex" |
|||
formControlName="volumeAttributeName"> |
|||
</tb-string-autocomplete> |
|||
<tb-unit-input asBoxInput colorClearButton |
|||
[tagFilter]="unitsType.capacity" |
|||
[required]="isRequired('volumeUnits')" |
|||
style="max-width: 25%" class="flex" |
|||
formControlName="volumeUnits"> |
|||
</tb-unit-input> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widgets.liquid-level-card.color-and-font</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div class="fixed-title-width" translate>widgets.liquid-level-card.tank-color</div> |
|||
<tb-color-settings formControlName="tankColor"> |
|||
</tb-color-settings> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div class="fixed-title-width" translate>widgets.liquid-level-card.liquid-color</div> |
|||
<tb-color-settings formControlName="liquidColor"> |
|||
</tb-color-settings> |
|||
</div> |
|||
<div class="tb-form-row space-between" |
|||
*ngIf="levelCardWidgetSettingsForm.get('layout')?.value !== this.levelCardLayouts.simple"> |
|||
<div class="fixed-title-width" translate>widgets.liquid-level-card.value</div> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px"> |
|||
<tb-font-settings formControlName="valueFont" |
|||
[previewText]="valuePreviewFn"> |
|||
</tb-font-settings> |
|||
<tb-color-settings formControlName="valueColor"> |
|||
</tb-color-settings> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row" [fxShow]="volumeInput"> |
|||
<div class="fixed-title-width" translate>widgets.liquid-level-card.total-volume</div> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="8px"> |
|||
<tb-font-settings formControlName="volumeFont" |
|||
[previewText]="totalVolumeValuePreviewFn"> |
|||
</tb-font-settings> |
|||
<tb-color-settings formControlName="volumeColor"> |
|||
</tb-color-settings> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBackgroundOverlay"> |
|||
{{ 'widgets.liquid-level-card.background-overlay' | translate }} |
|||
</mat-slide-toggle> |
|||
<tb-color-settings formControlName="backgroundOverlayColor"> |
|||
</tb-color-settings> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-panel tb-slide-toggle"> |
|||
<mat-expansion-panel class="tb-settings" |
|||
[expanded]="levelCardWidgetSettingsForm.get('showTooltip').value" |
|||
[disabled]="!levelCardWidgetSettingsForm.get('showTooltip').value"> |
|||
<mat-expansion-panel-header fxLayout="row wrap"> |
|||
<mat-panel-title> |
|||
<mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()" |
|||
fxLayoutAlign="center"> |
|||
{{ 'widget-config.tooltip' | translate }} |
|||
</mat-slide-toggle> |
|||
</mat-panel-title> |
|||
</mat-expansion-panel-header> |
|||
<ng-template matExpansionPanelContent> |
|||
<div class="tb-form-row"> |
|||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTooltipLevel"> |
|||
{{ 'widgets.liquid-level-card.level' | translate }} |
|||
</mat-slide-toggle> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<tb-unit-input [tagFilter]="unitsType.capacity" |
|||
[required]="isRequired('tooltipUnits')" |
|||
class="flex" formControlName="tooltipUnits"> |
|||
</tb-unit-input> |
|||
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic"> |
|||
<input matInput formControlName="tooltipLevelDecimals" type="number" min="0" |
|||
max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
<div matSuffix fxHide.lt-md translate>widget-config.decimals-suffix</div> |
|||
</mat-form-field> |
|||
<tb-font-settings formControlName="tooltipLevelFont" |
|||
[previewText]="tooltipValuePreviewFn"> |
|||
</tb-font-settings> |
|||
<tb-color-settings formControlName="tooltipLevelColor"> |
|||
</tb-color-settings> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row column-xs"> |
|||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTooltipDate"> |
|||
{{ 'widgets.value-card.date' | translate }} |
|||
</mat-slide-toggle> |
|||
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<tb-date-format-select fxFlex formControlName="tooltipDateFormat"></tb-date-format-select> |
|||
<tb-font-settings formControlName="tooltipDateFont" |
|||
[previewText]="datePreviewFn"> |
|||
</tb-font-settings> |
|||
<tb-color-settings formControlName="tooltipDateColor"> |
|||
</tb-color-settings> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div class="fixed-title-width" translate>widgets.liquid-level-card.tooltip-background</div> |
|||
<tb-color-settings formControlName="tooltipBackgroundColor"> |
|||
</tb-color-settings> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div class="fixed-title-width" translate>widgets.liquid-level-card.background-blur</div> |
|||
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> |
|||
<input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1" |
|||
placeholder="{{ 'widget-config.set' | translate }}"> |
|||
<div matSuffix>px</div> |
|||
</mat-form-field> |
|||
</div> |
|||
</ng-template> |
|||
</mat-expansion-panel> |
|||
</div> |
|||
</div> |
|||
</ng-container> |
|||
@ -0,0 +1,516 @@ |
|||
///
|
|||
/// 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 { ChangeDetectorRef, Component, Injector, ViewChild } from '@angular/core'; |
|||
import { |
|||
DataKey, |
|||
Datasource, |
|||
DatasourceType, |
|||
WidgetSettings, |
|||
WidgetSettingsComponent |
|||
} from '@shared/models/widget.models'; |
|||
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { formatValue, isDefined } from '@core/utils'; |
|||
import { WidgetConfigComponentData } from '@home/models/widget-component.models'; |
|||
import { |
|||
DateFormatProcessor, |
|||
DateFormatSettings |
|||
} from '@shared/models/widget-settings.models'; |
|||
import { |
|||
levelCardDefaultSettings, |
|||
LevelCardLayout, |
|||
levelCardLayoutTranslations, |
|||
Shapes, |
|||
shapesTranslations, |
|||
svgMapping, |
|||
CapacityUnits, |
|||
LevelSelectOptions, |
|||
createPercentLayout, |
|||
createAbsoluteLayout, |
|||
optionsFilter, |
|||
fetchEntityKeysForDevice, |
|||
fetchEntityKeys |
|||
} from '@home/components/widget/lib/indicator/liquid-level-widget.models'; |
|||
import { UnitsType } from '@shared/models/unit.models'; |
|||
import { ImageCardsSelectComponent } from '@home/components/widget/lib/settings/common/image-cards-select.component'; |
|||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; |
|||
import { forkJoin, Observable, of } from 'rxjs'; |
|||
import { map, publishReplay, refCount, tap } from 'rxjs/operators'; |
|||
import { ResourcesService } from '@core/services/resources.service'; |
|||
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; |
|||
import { UtilsService } from '@core/services/utils.service'; |
|||
import { EntityService } from '@core/http/entity.service'; |
|||
|
|||
@Component({ |
|||
selector: 'liquid-level-card-widget-settings', |
|||
templateUrl: './liquid-level-card-widget-settings.component.html', |
|||
styleUrls: [] |
|||
}) |
|||
export class LiquidLevelCardWidgetSettingsComponent extends WidgetSettingsComponent { |
|||
|
|||
@ViewChild('layoutsImageCardsSelect', { static: false }) layoutsImageCardsSelect: ImageCardsSelectComponent; |
|||
|
|||
@ViewChild('shapesImageCardsSelect', { static: false }) shapesImageCardsSelect: ImageCardsSelectComponent; |
|||
|
|||
public get volumeInput(): boolean { |
|||
const datasourceUnits = this.levelCardWidgetSettingsForm.get('datasourceUnits').value; |
|||
const layout: LevelCardLayout = this.levelCardWidgetSettingsForm.get('layout').value; |
|||
const widgetUnits = this.levelCardWidgetSettingsForm.get('units').value; |
|||
return !(datasourceUnits === CapacityUnits.percent && layout !== LevelCardLayout.absolute |
|||
|| (datasourceUnits === CapacityUnits.percent && widgetUnits === CapacityUnits.percent)); |
|||
} |
|||
|
|||
public get widgetUnitsInput(): boolean { |
|||
const layout: LevelCardLayout = this.levelCardWidgetSettingsForm.get('layout').value; |
|||
|
|||
if (layout === LevelCardLayout.absolute) { |
|||
const datasourceUnits = this.levelCardWidgetSettingsForm.get('datasourceUnits').value; |
|||
return !(datasourceUnits === CapacityUnits.percent); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
public get datasource(): Datasource { |
|||
if (this.widgetConfig.config.datasources && this.widgetConfig.config.datasources) { |
|||
return this.widgetConfig.config.datasources[0]; |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
shapeSelectOptions = LevelSelectOptions; |
|||
|
|||
shapes: Shapes[] = []; |
|||
|
|||
shapesTranslationMap = shapesTranslations; |
|||
|
|||
unitsType = UnitsType; |
|||
|
|||
levelCardLayouts = LevelCardLayout; |
|||
|
|||
levelCardLayoutTranslationMap = levelCardLayoutTranslations; |
|||
shapesImageMap: Map<Shapes, string> = new Map(); |
|||
|
|||
volumeOptions = LevelSelectOptions; |
|||
|
|||
levelCardWidgetSettingsForm: UntypedFormGroup; |
|||
|
|||
valuePreviewFn = this._valuePreviewFn.bind(this); |
|||
|
|||
tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this); |
|||
|
|||
totalVolumeValuePreviewFn = this._totalVolumeValuePreviewFn.bind(this); |
|||
|
|||
datePreviewFn = this._datePreviewFn.bind(this); |
|||
|
|||
keySearchText: string; |
|||
|
|||
latestKeySearchTextResult: Array<string>; |
|||
|
|||
datasources: Array<Datasource>; |
|||
|
|||
functionTypeKeys: Array<DataKey> = []; |
|||
|
|||
lastKeysId: string; |
|||
|
|||
lastFetchedKeys: Array<DataKey>; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
private $injector: Injector, |
|||
private fb: UntypedFormBuilder, |
|||
private resourcesService: ResourcesService, |
|||
private sanitizer: DomSanitizer, |
|||
private cd: ChangeDetectorRef, |
|||
private utils: UtilsService, |
|||
private entityService: EntityService) { |
|||
super(store); |
|||
} |
|||
|
|||
protected settingsForm(): UntypedFormGroup { |
|||
return this.levelCardWidgetSettingsForm; |
|||
} |
|||
|
|||
protected onWidgetConfigSet(widgetConfig: WidgetConfigComponentData) { |
|||
this.shapes = Object.values(Shapes); |
|||
this.createSvgShapesMapping(); |
|||
|
|||
for (const type of this.utils.getPredefinedFunctionsList()) { |
|||
this.functionTypeKeys.push({ |
|||
name: type, |
|||
type: DataKeyType.function |
|||
}); |
|||
} |
|||
} |
|||
|
|||
protected defaultSettings(): WidgetSettings { |
|||
return levelCardDefaultSettings(); |
|||
} |
|||
|
|||
protected onSettingsSet(settings: WidgetSettings) { |
|||
this.levelCardWidgetSettingsForm = this.fb.group({ |
|||
tankSelectionType: [settings.tankSelectionType, []], |
|||
selectedShape: [settings.selectedShape, [Validators.required]], |
|||
shapeAttributeName: [settings.shapeAttributeName, [Validators.required]], |
|||
tankColor: [settings.tankColor, []], |
|||
datasourceUnits: [settings.datasourceUnits, [Validators.required]], |
|||
|
|||
layout: [settings.layout, []], |
|||
|
|||
volumeSource: [settings.volumeSource, []], |
|||
volumeConstant: [settings.volumeConstant, [Validators.required]], |
|||
volumeAttributeName: [settings.volumeAttributeName, [Validators.required]], |
|||
volumeUnits: [settings.volumeUnits, [Validators.required]], |
|||
volumeFont: [settings.volumeFont, []], |
|||
volumeColor: [settings.volumeColor, []], |
|||
valueFont: [settings.valueFont, []], |
|||
valueColor: [settings.valueColor, []], |
|||
units: [settings.units, [Validators.required]], |
|||
widgetUnitsSource: [settings.widgetUnitsSource, [Validators.required]], |
|||
widgetUnitsAttributeName: [settings.widgetUnitsAttributeName, [Validators.required]], |
|||
showBackgroundOverlay: [settings.showBackgroundOverlay, []], |
|||
backgroundOverlayColor: [settings.backgroundOverlayColor, []], |
|||
|
|||
liquidColor: [settings.liquidColor, []], |
|||
|
|||
showTooltip: [settings.showTooltip, []], |
|||
showTooltipLevel: [settings.showTooltipLevel, []], |
|||
tooltipUnits: [settings.tooltipUnits, []], |
|||
tooltipLevelDecimals: [settings.tooltipLevelDecimals, []], |
|||
tooltipLevelFont: [settings.tooltipLevelFont, []], |
|||
tooltipLevelColor: [settings.tooltipLevelColor, []], |
|||
showTooltipDate: [settings.showTooltipDate, []], |
|||
tooltipDateFormat: [settings.tooltipDateFormat, []], |
|||
tooltipDateFont: [settings.tooltipDateFont, []], |
|||
tooltipDateColor: [settings.tooltipDateColor, []], |
|||
tooltipBackgroundColor: [settings.tooltipBackgroundColor, []], |
|||
tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []], |
|||
}); |
|||
|
|||
this.levelCardWidgetSettingsForm.get('selectedShape').valueChanges.subscribe((shape) => { |
|||
this.cd.detectChanges(); |
|||
this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges(); |
|||
}); |
|||
} |
|||
|
|||
protected validatorTriggers(): string[] { |
|||
return [ |
|||
'showBackgroundOverlay', 'showTooltip', 'showTooltipLevel', |
|||
'tankSelectionType', 'datasourceUnits', |
|||
'showTooltipDate', 'units', |
|||
'layout', 'volumeSource', |
|||
'widgetUnitsSource' |
|||
]; |
|||
} |
|||
|
|||
protected updateValidators(emitEvent: boolean, trigger?: string) { |
|||
const emitEventFields = []; |
|||
|
|||
const datasourceUnits: string = this.levelCardWidgetSettingsForm.get('datasourceUnits').value; |
|||
const layout: LevelCardLayout = this.levelCardWidgetSettingsForm.get('layout').value; |
|||
const volumeSource: AbstractControl<LevelSelectOptions> = this.levelCardWidgetSettingsForm.get('volumeSource'); |
|||
const widgetUnits: AbstractControl<string> = this.levelCardWidgetSettingsForm.get('units'); |
|||
const tooltipUnits: AbstractControl<string> = this.levelCardWidgetSettingsForm.get('tooltipUnits'); |
|||
const widgetUnitsSource: AbstractControl<LevelSelectOptions> = this.levelCardWidgetSettingsForm.get('widgetUnitsSource'); |
|||
const showTooltipLevel: AbstractControl<boolean> = this.levelCardWidgetSettingsForm.get('showTooltipLevel'); |
|||
const showTooltipDate: AbstractControl<boolean> = this.levelCardWidgetSettingsForm.get('showTooltipDate'); |
|||
const showTooltip: boolean = this.levelCardWidgetSettingsForm.get('showTooltip').value; |
|||
|
|||
if (trigger === 'tankSelectionType') { |
|||
const tankSelectionType: LevelSelectOptions = this.levelCardWidgetSettingsForm.get('tankSelectionType').value; |
|||
if (tankSelectionType === LevelSelectOptions.static) { |
|||
this.levelCardWidgetSettingsForm.get('selectedShape').enable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('shapeAttributeName').disable({emitEvent: false}); |
|||
} else { |
|||
this.levelCardWidgetSettingsForm.get('selectedShape').disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('shapeAttributeName').enable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('selectedShape', 'shapeAttributeName'); |
|||
} |
|||
|
|||
if (trigger === 'datasourceUnits' || trigger === 'layout' || trigger === 'units') { |
|||
if (datasourceUnits === CapacityUnits.percent && (layout !== LevelCardLayout.absolute) |
|||
|| (datasourceUnits === CapacityUnits.percent && widgetUnits?.value === CapacityUnits.percent) |
|||
) { |
|||
volumeSource.disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('volumeConstant').disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('volumeAttributeName').disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('volumeUnits').disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('volumeFont').disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('volumeColor').disable({emitEvent: false}); |
|||
} else { |
|||
volumeSource.enable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('volumeConstant').enable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('volumeAttributeName').enable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('volumeUnits').enable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('volumeFont').enable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('volumeColor').enable({emitEvent: false}); |
|||
} |
|||
|
|||
if (layout === LevelCardLayout.simple && datasourceUnits === CapacityUnits.percent) { |
|||
this.levelCardWidgetSettingsForm.get('valueFont').disable(); |
|||
this.levelCardWidgetSettingsForm.get('valueColor').disable(); |
|||
} else { |
|||
this.levelCardWidgetSettingsForm.get('valueFont').enable(); |
|||
this.levelCardWidgetSettingsForm.get('valueColor').enable(); |
|||
} |
|||
emitEventFields.push('volumeSource', 'volumeConstant', 'volumeAttributeName', 'volumeFont', 'volumeColor', 'volumeUnits'); |
|||
} else if (trigger === 'volumeSource') { |
|||
if((datasourceUnits !== CapacityUnits.percent) || |
|||
(layout === LevelCardLayout.absolute && widgetUnits?.value !== CapacityUnits.percent)) { |
|||
if (volumeSource.value === LevelSelectOptions.static) { |
|||
this.levelCardWidgetSettingsForm.get('volumeConstant').enable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('volumeAttributeName').disable({emitEvent: false}); |
|||
} else { |
|||
this.levelCardWidgetSettingsForm.get('volumeConstant').disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('volumeAttributeName').enable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('volumeConstant', 'volumeAttributeName'); |
|||
} |
|||
} |
|||
|
|||
if (trigger === 'showBackgroundOverlay') { |
|||
const showBackgroundOverlay: boolean = this.levelCardWidgetSettingsForm.get('showBackgroundOverlay').value; |
|||
if (showBackgroundOverlay) { |
|||
this.levelCardWidgetSettingsForm.get('backgroundOverlayColor').enable({emitEvent: false}); |
|||
} else { |
|||
this.levelCardWidgetSettingsForm.get('backgroundOverlayColor').disable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('backgroundOverlayColor'); |
|||
} |
|||
|
|||
if (trigger === 'showTooltip') { |
|||
if (showTooltip) { |
|||
showTooltipLevel.enable({emitEvent: false}); |
|||
showTooltipDate.enable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('tooltipBackgroundColor').enable(); |
|||
this.levelCardWidgetSettingsForm.get('tooltipBackgroundBlur').enable(); |
|||
} else { |
|||
showTooltipLevel.disable({emitEvent: false}); |
|||
showTooltipDate.disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('tooltipBackgroundColor').disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('tooltipBackgroundBlur').disable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('showTooltipLevel', 'showTooltipDate', 'tooltipBackgroundColor', 'tooltipBackgroundBlur'); |
|||
} |
|||
|
|||
if (trigger === 'showTooltipLevel') { |
|||
if (showTooltipLevel?.value && !showTooltipLevel.disabled) { |
|||
this.levelCardWidgetSettingsForm.get('tooltipUnits').enable(); |
|||
this.levelCardWidgetSettingsForm.get('tooltipLevelDecimals').enable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('tooltipLevelFont').enable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('tooltipLevelColor').enable({emitEvent: false}); |
|||
} else { |
|||
this.levelCardWidgetSettingsForm.get('tooltipUnits').disable(); |
|||
this.levelCardWidgetSettingsForm.get('tooltipLevelDecimals').disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('tooltipLevelFont').disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('tooltipLevelColor').disable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('tooltipUnits', 'tooltipLevelDecimals', 'tooltipLevelFont', 'tooltipLevelColor'); |
|||
} |
|||
|
|||
if (trigger === 'showTooltipDate') { |
|||
if (showTooltipDate?.value && !showTooltipDate.disabled) { |
|||
this.levelCardWidgetSettingsForm.get('tooltipDateFormat').enable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('tooltipDateFont').enable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('tooltipDateColor').enable({emitEvent: false}); |
|||
} else { |
|||
this.levelCardWidgetSettingsForm.get('tooltipDateFormat').disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('tooltipDateFont').disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('tooltipDateColor').disable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('tooltipDateFormat', 'tooltipDateFont', 'tooltipDateColor'); |
|||
} |
|||
|
|||
if (trigger === 'layout' || trigger === 'datasourceUnits') { |
|||
if (layout === LevelCardLayout.simple) { |
|||
widgetUnits.disable({emitEvent: false}); |
|||
tooltipUnits.disable({emitEvent: false}); |
|||
widgetUnitsSource.setValue(LevelSelectOptions.static, {emitEvent: false}); |
|||
widgetUnitsSource.disable({emitEvent: false}); |
|||
|
|||
this.levelCardWidgetSettingsForm.get('valueFont').disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('valueColor').disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('widgetUnitsAttributeName').disable({emitEvent: false}); |
|||
} else if (layout === LevelCardLayout.percentage) { |
|||
if (widgetUnits.value !== CapacityUnits.percent) { |
|||
widgetUnits.setValue(CapacityUnits.percent, {emitEvent: false}); |
|||
widgetUnitsSource.setValue(LevelSelectOptions.static, {emitEvent: false}); |
|||
} |
|||
widgetUnits.disable({emitEvent: false}); |
|||
widgetUnitsSource.disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('widgetUnitsAttributeName').disable({emitEvent: false}); |
|||
|
|||
if (tooltipUnits.value !== CapacityUnits.percent) { |
|||
tooltipUnits.setValue(CapacityUnits.percent, {emitEvent: false}); |
|||
} |
|||
tooltipUnits.disable({emitEvent: false}); |
|||
} else { |
|||
widgetUnits.enable({emitEvent: false}); |
|||
tooltipUnits.enable({emitEvent: false}); |
|||
widgetUnitsSource.enable({emitEvent: false}); |
|||
|
|||
this.levelCardWidgetSettingsForm.get('valueFont').enable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('valueColor').enable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('widgetUnitsAttributeName').enable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('units', 'tooltipUnits', 'valueFont', 'valueColor', 'widgetUnitsSource', 'widgetUnitsAttributeName'); |
|||
} else if (trigger === 'widgetUnitsSource') { |
|||
if (layout !== LevelCardLayout.percentage) { |
|||
if (widgetUnitsSource.value === LevelSelectOptions.static) { |
|||
widgetUnits.enable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('widgetUnitsAttributeName').disable({emitEvent: false}); |
|||
} else { |
|||
widgetUnits.disable({emitEvent: false}); |
|||
this.levelCardWidgetSettingsForm.get('widgetUnitsAttributeName').enable({emitEvent: false}); |
|||
} |
|||
emitEventFields.push('units', 'widgetUnitsAttributeName'); |
|||
} |
|||
} |
|||
|
|||
for (const controlKey in this.levelCardWidgetSettingsForm.controls) { |
|||
if (emitEventFields.includes(controlKey)) { |
|||
this.levelCardWidgetSettingsForm.controls[controlKey].updateValueAndValidity({emitEvent}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private createSvgShapesMapping(): void { |
|||
const obsArray: Array<Observable<{svg: string; shape: Shapes}>> = []; |
|||
for (const shape of this.shapes) { |
|||
const svgUrl = svgMapping.get(shape).svg; |
|||
|
|||
const obs = this.resourcesService.loadJsonResource<string>(svgUrl).pipe( |
|||
map((svg) => ({svg, shape})) |
|||
); |
|||
|
|||
obsArray.push(obs); |
|||
} |
|||
|
|||
forkJoin(obsArray).subscribe((svgData) => { |
|||
for (const data of svgData) { |
|||
this.shapesImageMap.set(data.shape, data.svg); |
|||
} |
|||
|
|||
this.cd.detectChanges(); |
|||
this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges(); |
|||
this.shapesImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges(); |
|||
}); |
|||
} |
|||
|
|||
public createShapeLayout(svg: string, layout: LevelCardLayout): SafeUrl { |
|||
if (svg && layout) { |
|||
const parser = new DOMParser(); |
|||
const svgImage = parser.parseFromString(svg, 'image/svg+xml'); |
|||
|
|||
if (layout === this.levelCardLayouts.simple) { |
|||
svgImage.querySelector('.container-overlay').remove(); |
|||
} else if (layout === this.levelCardLayouts.percentage) { |
|||
svgImage.querySelector('.absolute-overlay').remove(); |
|||
svgImage.querySelector('.percentage-value-container').innerHTML = createPercentLayout(); |
|||
} else { |
|||
svgImage.querySelector('.absolute-value-container').innerHTML = createAbsoluteLayout(); |
|||
svgImage.querySelector('.percentage-overlay').remove(); |
|||
} |
|||
|
|||
const encodedSvg = encodeURIComponent(svgImage.documentElement.outerHTML); |
|||
|
|||
return this.sanitizer.bypassSecurityTrustResourceUrl(`data:image/svg+xml,${encodedSvg}`); |
|||
} |
|||
} |
|||
|
|||
public isRequired(formControlName: string): boolean { |
|||
return this.levelCardWidgetSettingsForm.get(formControlName)?.hasValidator(Validators.required); |
|||
} |
|||
|
|||
private _valuePreviewFn(): string { |
|||
const units: string = this.widgetConfig.config.units; |
|||
const decimals: number = this.widgetConfig.config.decimals; |
|||
return formatValue(32, decimals, units, true); |
|||
} |
|||
|
|||
private _tooltipValuePreviewFn() { |
|||
const units: string = this.levelCardWidgetSettingsForm.get('tooltipUnits').value; |
|||
const decimals: number = this.levelCardWidgetSettingsForm.get('tooltipLevelDecimals').value; |
|||
return formatValue(32, decimals, units, true); |
|||
} |
|||
|
|||
private _totalVolumeValuePreviewFn() { |
|||
const value = this.levelCardWidgetSettingsForm.get('volumeConstant').value; |
|||
const datasourceUnits = this.levelCardWidgetSettingsForm.get('datasourceUnits').value; |
|||
const decimals: number = this.widgetConfig.config.decimals; |
|||
let units: string = this.widgetConfig.config.units; |
|||
|
|||
if (datasourceUnits !== CapacityUnits.percent) { |
|||
units = datasourceUnits; |
|||
} |
|||
|
|||
return formatValue((isDefined(value) ? value : 500), decimals, units, true); |
|||
} |
|||
|
|||
private _datePreviewFn(): string { |
|||
const dateFormat: DateFormatSettings = this.levelCardWidgetSettingsForm.get('tooltipDateFormat').value; |
|||
const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat); |
|||
processor.update(Date.now()); |
|||
return processor.formatted; |
|||
} |
|||
|
|||
public fetchOptions(searchText: string): Observable<Array<string>> { |
|||
if (this.keySearchText !== searchText) { |
|||
this.keySearchText = searchText; |
|||
const dataKeyFilter = optionsFilter(this.keySearchText); |
|||
return this.getKeys().pipe( |
|||
tap(res => this.lastFetchedKeys !== res ? this.lastFetchedKeys = res : []), |
|||
map(name => name?.filter(dataKeyFilter).map(key => key.name)), |
|||
tap(res => this.latestKeySearchTextResult = res) |
|||
); |
|||
} |
|||
return of(this.latestKeySearchTextResult); |
|||
} |
|||
|
|||
private getKeys(): Observable<Array<DataKey>> { |
|||
let fetchObservable: Observable<Array<DataKey>>; |
|||
if (this.datasource?.type === DatasourceType.function) { |
|||
fetchObservable = of(this.functionTypeKeys); |
|||
} else if (this.datasource?.type === DatasourceType.entity && this.datasource?.entityAliasId || |
|||
this.datasource?.type === DatasourceType.device && this.datasource?.deviceId) { |
|||
if (this.datasource?.type === DatasourceType.device) { |
|||
if (this.lastKeysId !== this.datasource?.deviceId || !this.lastFetchedKeys) { |
|||
this.lastKeysId = this.datasource.deviceId; |
|||
fetchObservable = fetchEntityKeysForDevice(this.datasource?.deviceId, [DataKeyType.attribute], |
|||
this.entityService); |
|||
} else { |
|||
fetchObservable = of(this.lastFetchedKeys); |
|||
} |
|||
} else { |
|||
if (this.lastKeysId !== this.datasource?.entityAliasId || !this.lastFetchedKeys) { |
|||
this.lastKeysId = this.datasource.entityAliasId; |
|||
fetchObservable = fetchEntityKeys(this.datasource?.entityAliasId, [DataKeyType.attribute], |
|||
this.entityService, this.aliasController); |
|||
} else { |
|||
fetchObservable = of(this.lastFetchedKeys); |
|||
} |
|||
} |
|||
} else { |
|||
fetchObservable = of([]); |
|||
} |
|||
return fetchObservable.pipe( |
|||
publishReplay(1), |
|||
refCount() |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
<!-- |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<mat-form-field [appearance]="appearance" [ngClass]="ngClass" |
|||
[subscriptSizing]="subscriptSizing" style="width: 100%"> |
|||
<mat-label *ngIf="label">{{label}}</mat-label> |
|||
<input matInput #nameInput [formControl]="selectionFormControl" |
|||
[placeholder]="placeholderText" |
|||
(focusin)="onFocus()" |
|||
[matAutocomplete]="optionsAutocomplete"> |
|||
<button *ngIf="selectionFormControl.value && !disabled" |
|||
type="button" |
|||
class="tb-icon-24" |
|||
matSuffix mat-icon-button aria-label="Clear" |
|||
(click)="clear()"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
<mat-icon *ngIf="selectionFormControl.hasError('required') && !showInlineError" |
|||
matIconSuffix |
|||
[matTooltip]="errorText" |
|||
matTooltipPosition="above" |
|||
[matTooltipClass]="tooltipClass" |
|||
class="material-icons tb-suffix-show-always tb-error"> |
|||
warning |
|||
</mat-icon> |
|||
<mat-error *ngIf="selectionFormControl.hasError('required') && showInlineError"> |
|||
{{errorText}} |
|||
</mat-error> |
|||
<mat-autocomplete |
|||
#optionsAutocomplete="matAutocomplete" |
|||
class="tb-autocomplete tb-options-input-autocomplete" |
|||
panelWidth="fit-content"> |
|||
<mat-option *ngFor="let option of filteredOptions$ | async" [value]="option"> |
|||
<span class="tb-option-name" fxFlex [innerHTML]="option | highlight:searchText:true:'ig'"></span> |
|||
</mat-option> |
|||
</mat-autocomplete> |
|||
</mat-form-field> |
|||
@ -0,0 +1,43 @@ |
|||
/** |
|||
* 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. |
|||
*/ |
|||
:host { |
|||
mat-form-field { |
|||
display: flex; |
|||
flex: 1 1 0%; |
|||
max-width: 100%; |
|||
box-sizing: border-box; |
|||
|
|||
.tb-autocomplete.tb-option-input-autocomplete { |
|||
.mat-mdc-option { |
|||
border-bottom: none; |
|||
|
|||
.mdc-list-item__primary-text { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: row; |
|||
gap: 8px; |
|||
|
|||
.tb-option { |
|||
font-size: 14px; |
|||
font-weight: 400; |
|||
line-height: 20px; |
|||
letter-spacing: 0.2px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,178 @@ |
|||
///
|
|||
/// 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, |
|||
Input, |
|||
forwardRef, |
|||
OnInit, |
|||
ViewChild, |
|||
ElementRef |
|||
} from '@angular/core'; |
|||
import { |
|||
ControlValueAccessor, |
|||
NG_VALUE_ACCESSOR, |
|||
FormControl, |
|||
Validators, |
|||
FormBuilder |
|||
} from '@angular/forms'; |
|||
import { Observable, of } from 'rxjs'; |
|||
import { tap, map, switchMap, take } from 'rxjs/operators'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { coerceBoolean } from '@shared/decorators/coercion'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-string-autocomplete', |
|||
templateUrl: './string-autocomplete.component.html', |
|||
styleUrls: ['./string-autocomplete.component.scss'], |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => StringAutocompleteComponent), |
|||
multi: true |
|||
} |
|||
] |
|||
}) |
|||
export class StringAutocompleteComponent implements ControlValueAccessor, OnInit { |
|||
|
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
@coerceBoolean() |
|||
@Input() |
|||
required: boolean = false; |
|||
|
|||
@Input() fetchOptionsFn: (searchText?: string) => Observable<Array<string>>; |
|||
|
|||
@ViewChild('nameInput', {static: true}) nameInput: ElementRef; |
|||
|
|||
@Input() |
|||
placeholderText: string = this.translate.instant('widget-config.set'); |
|||
|
|||
@Input() |
|||
subscriptSizing: string = 'dynamic'; |
|||
|
|||
@Input() |
|||
ngClass: string | string[] | Set<string> | { [klass: string]: any; } = 'tb-inline-field tb-suffix-show-on-hover'; |
|||
|
|||
@Input() |
|||
appearance: string = 'outline'; |
|||
|
|||
@Input() |
|||
label: string; |
|||
|
|||
@Input() |
|||
tooltipClass: string = 'tb-error-tooltip'; |
|||
|
|||
@Input() |
|||
errorText: string; |
|||
|
|||
@coerceBoolean() |
|||
@Input() |
|||
showInlineError: boolean = false; |
|||
|
|||
selectionFormControl: FormControl; |
|||
|
|||
modelValue: string | null; |
|||
|
|||
filteredOptions$: Observable<Array<string>>; |
|||
|
|||
searchText = ''; |
|||
|
|||
private dirty = false; |
|||
|
|||
private propagateChange = (_val: any) => {}; |
|||
|
|||
constructor(private fb: FormBuilder, |
|||
private translate: TranslateService) { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.selectionFormControl = this.fb.control('', this.required ? [Validators.required] : []); |
|||
this.filteredOptions$ = this.selectionFormControl.valueChanges |
|||
.pipe( |
|||
tap(value => this.updateView(value)), |
|||
map(value => value ? value : ''), |
|||
switchMap(value => this.fetchOptionsFn ? this.fetchOptionsFn(value) : of([])) |
|||
); |
|||
} |
|||
|
|||
writeValue(option?: string): void { |
|||
this.searchText = ''; |
|||
this.modelValue = option ? option : null; |
|||
|
|||
if (this.fetchOptionsFn) { |
|||
this.fetchOptionsFn(option) |
|||
.pipe( |
|||
map(options => { |
|||
if (options) { |
|||
const foundOption = options.find(opt => opt === option); |
|||
return foundOption ? foundOption : option; |
|||
} |
|||
|
|||
return option; |
|||
}), |
|||
take(1) |
|||
) |
|||
.subscribe(result => { |
|||
this.selectionFormControl.patchValue(result, { emitEvent: false }); |
|||
this.dirty = true; |
|||
}); |
|||
} else { |
|||
this.selectionFormControl.patchValue(null, { emitEvent: false }); |
|||
this.dirty = true; |
|||
} |
|||
} |
|||
|
|||
onFocus() { |
|||
if (this.dirty) { |
|||
this.selectionFormControl.updateValueAndValidity({onlySelf: true, emitEvent: true}); |
|||
this.dirty = false; |
|||
} |
|||
} |
|||
|
|||
updateView(value: string) { |
|||
this.searchText = value ? value : ''; |
|||
if (this.modelValue !== value) { |
|||
this.modelValue = value; |
|||
this.propagateChange(this.modelValue); |
|||
} |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
if (this.disabled) { |
|||
this.selectionFormControl.disable({emitEvent: false}); |
|||
} else { |
|||
this.selectionFormControl.enable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
clear() { |
|||
this.selectionFormControl.patchValue(null, {emitEvent: true}); |
|||
setTimeout(() => { |
|||
this.nameInput.nativeElement.blur(); |
|||
this.nameInput.nativeElement.focus(); |
|||
}, 0); |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
#### Shape attribute name |
|||
|
|||
A string expression value that allows you dynamically select shape image depending on attribute name. |
|||
|
|||
**Attribute values that could be used to add the image:** |
|||
|
|||
<img style="width: 50px; height: 50px; margin-bottom: -58px; margin-left: 300px;" src="assets/widget/liquid-level/shapes/vertical-oval.svg" alt="Alt text" /> |
|||
|
|||
``` |
|||
Vertical Oval |
|||
{:copy-code} |
|||
``` |
|||
<img style="width: 50px; height: 50px; margin-bottom: -58px; margin-left: 300px;" src="assets/widget/liquid-level/shapes/vertical-cylinder.svg" alt="Alt text" /> |
|||
|
|||
``` |
|||
Vertical Cylinder |
|||
{:copy-code} |
|||
``` |
|||
<img style="width: 50px; height: 50px; margin-bottom: -58px; margin-left: 300px;" src="assets/widget/liquid-level/shapes/vertical-capsule.svg" alt="Alt text" /> |
|||
|
|||
``` |
|||
Vertical Capsule |
|||
{:copy-code} |
|||
``` |
|||
<img style="width: 50px; height: 50px; margin-bottom: -58px; margin-left: 300px;" src="assets/widget/liquid-level/shapes/rectangle.svg" alt="Alt text" /> |
|||
|
|||
``` |
|||
Rectangle |
|||
{:copy-code} |
|||
``` |
|||
<img style="width: 50px; height: 50px; margin-bottom: -58px; margin-left: 300px;" src="assets/widget/liquid-level/shapes/horizontal-oval.svg" alt="Alt text" /> |
|||
|
|||
``` |
|||
Horizontal Oval |
|||
{:copy-code} |
|||
``` |
|||
<img style="width: 50px; height: 50px; margin-bottom: -58px; margin-left: 300px;" src="assets/widget/liquid-level/shapes/horizontal-ellipse.svg" alt="Alt text" /> |
|||
|
|||
``` |
|||
Horizontal Ellipse |
|||
{:copy-code} |
|||
``` |
|||
<img style="width: 50px; height: 50px; margin-bottom: -58px; margin-left: 300px;" src="assets/widget/liquid-level/shapes/horizontal-dish-ends.svg" alt="Alt text" /> |
|||
|
|||
``` |
|||
Horizontal Dish Ends |
|||
{:copy-code} |
|||
``` |
|||
<img style="width: 50px; height: 50px; margin-bottom: -58px; margin-left: 300px;" src="assets/widget/liquid-level/shapes/horizontal-cylinder.svg" alt="Alt text" /> |
|||
|
|||
``` |
|||
Horizontal Cylinder |
|||
{:copy-code} |
|||
``` |
|||
<img style="width: 50px; height: 50px; margin-bottom: -58px; margin-left: 300px;" src="assets/widget/liquid-level/shapes/horizontal-capsule.svg" alt="Alt text" /> |
|||
|
|||
``` |
|||
Horizontal Capsule |
|||
{:copy-code} |
|||
``` |
|||
<img style="width: 50px; height: 50px; margin-bottom: -58px; margin-left: 300px;" src="assets/widget/liquid-level/shapes/horizontal-2_1-elliptical.svg" alt="Alt text" /> |
|||
|
|||
``` |
|||
Horizontal 2:1 Elliptical |
|||
{:copy-code} |
|||
``` |
|||
|
|||
<br> |
|||
<br> |
|||
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |