UI: Refactoring liquid level widgets after review
This commit is contained in:
parent
73566db398
commit
0bab29c8ec
@ -84,7 +84,7 @@
|
|||||||
label="{{ 'widgets.liquid-level-card.shape-type' | translate }}" formControlName="selectedShape">
|
label="{{ 'widgets.liquid-level-card.shape-type' | translate }}" formControlName="selectedShape">
|
||||||
<tb-image-cards-select-option *ngFor="let shape of shapes"
|
<tb-image-cards-select-option *ngFor="let shape of shapes"
|
||||||
[value]="shape"
|
[value]="shape"
|
||||||
[image]="createShapeLayout(shapesImageMap.get(shape), this.levelCardLayouts.simple)">
|
[image]="createShape(shapesImageMap.get(shape), this.levelCardLayouts.simple)">
|
||||||
{{ shapesTranslationMap.get(shape) | translate }}
|
{{ shapesTranslationMap.get(shape) | translate }}
|
||||||
</tb-image-cards-select-option>
|
</tb-image-cards-select-option>
|
||||||
</tb-image-cards-select>
|
</tb-image-cards-select>
|
||||||
@ -94,8 +94,7 @@
|
|||||||
<tb-string-autocomplete [fetchOptionsFn]="fetchOptions.bind(this)"
|
<tb-string-autocomplete [fetchOptionsFn]="fetchOptions.bind(this)"
|
||||||
[required]="isRequired('shapeAttributeName')"
|
[required]="isRequired('shapeAttributeName')"
|
||||||
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
|
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
|
||||||
style="flex: 1; width: auto;"
|
style="flex: 1"
|
||||||
asBoxInput colorClearButton class="flex"
|
|
||||||
formControlName="shapeAttributeName">
|
formControlName="shapeAttributeName">
|
||||||
</tb-string-autocomplete>
|
</tb-string-autocomplete>
|
||||||
</div>
|
</div>
|
||||||
@ -118,7 +117,7 @@
|
|||||||
<tb-image-cards-select-option
|
<tb-image-cards-select-option
|
||||||
*ngFor="let layout of [levelCardLayouts.simple, levelCardLayouts.percentage, levelCardLayouts.absolute]"
|
*ngFor="let layout of [levelCardLayouts.simple, levelCardLayouts.percentage, levelCardLayouts.absolute]"
|
||||||
[value]="layout"
|
[value]="layout"
|
||||||
[image]="createShapeLayout(shapesImageMap.get(levelCardWidgetConfigForm.get('selectedShape').value), layout)">
|
[image]="createShape(shapesImageMap.get(levelCardWidgetConfigForm.get('selectedShape').value), layout)">
|
||||||
{{ levelCardLayoutTranslationMap.get(layout) | translate }}
|
{{ levelCardLayoutTranslationMap.get(layout) | translate }}
|
||||||
</tb-image-cards-select-option>
|
</tb-image-cards-select-option>
|
||||||
</tb-image-cards-select>
|
</tb-image-cards-select>
|
||||||
@ -144,7 +143,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<tb-unit-input [fxShow]="levelCardWidgetConfigForm.get('widgetUnitsSource')?.value !== levelOptions.attribute"
|
<tb-unit-input [fxShow]="levelCardWidgetConfigForm.get('widgetUnitsSource')?.value !== levelOptions.attribute"
|
||||||
asBoxInput colorClearButton class="flex"
|
class="flex"
|
||||||
[tagFilter]="unitsType.capacity"
|
[tagFilter]="unitsType.capacity"
|
||||||
[required]="isRequired('units')"
|
[required]="isRequired('units')"
|
||||||
formControlName="units">
|
formControlName="units">
|
||||||
@ -153,8 +152,7 @@
|
|||||||
[fetchOptionsFn]="fetchOptions.bind(this)"
|
[fetchOptionsFn]="fetchOptions.bind(this)"
|
||||||
[required]="isRequired('widgetUnitsAttributeName')"
|
[required]="isRequired('widgetUnitsAttributeName')"
|
||||||
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
|
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
|
||||||
style="flex: 1; width: auto;"
|
style="flex: 1"
|
||||||
asBoxInput colorClearButton class="flex"
|
|
||||||
formControlName="widgetUnitsAttributeName">
|
formControlName="widgetUnitsAttributeName">
|
||||||
</tb-string-autocomplete>
|
</tb-string-autocomplete>
|
||||||
</div>
|
</div>
|
||||||
@ -182,17 +180,14 @@
|
|||||||
warning
|
warning
|
||||||
</mat-icon>
|
</mat-icon>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<tb-string-autocomplete style="max-width: 25%"
|
<tb-string-autocomplete [fxShow]="levelCardWidgetConfigForm.get('volumeSource')?.value === levelOptions.attribute"
|
||||||
[fxShow]="levelCardWidgetConfigForm.get('volumeSource')?.value === levelOptions.attribute"
|
|
||||||
[fetchOptionsFn]="fetchOptions.bind(this)"
|
[fetchOptionsFn]="fetchOptions.bind(this)"
|
||||||
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
|
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
|
||||||
[required]="isRequired('volumeAttributeName')"
|
[required]="isRequired('volumeAttributeName')"
|
||||||
style="flex: 1; width: auto;"
|
style="flex: 1"
|
||||||
asBoxInput colorClearButton class="flex"
|
|
||||||
formControlName="volumeAttributeName">
|
formControlName="volumeAttributeName">
|
||||||
</tb-string-autocomplete>
|
</tb-string-autocomplete>
|
||||||
<tb-unit-input asBoxInput colorClearButton
|
<tb-unit-input [tagFilter]="unitsType.capacity"
|
||||||
[tagFilter]="unitsType.capacity"
|
|
||||||
[required]="isRequired('volumeUnits')"
|
[required]="isRequired('volumeUnits')"
|
||||||
style="max-width: 25%" class="flex"
|
style="max-width: 25%" class="flex"
|
||||||
formControlName="volumeUnits">
|
formControlName="volumeUnits">
|
||||||
|
|||||||
@ -43,23 +43,22 @@ import {
|
|||||||
} from '@shared/models/widget-settings.models';
|
} from '@shared/models/widget-settings.models';
|
||||||
import {
|
import {
|
||||||
CapacityUnits,
|
CapacityUnits,
|
||||||
createAbsoluteLayout,
|
createShapeLayout,
|
||||||
createPercentLayout,
|
|
||||||
levelCardDefaultSettings,
|
levelCardDefaultSettings,
|
||||||
LevelCardLayout,
|
LevelCardLayout,
|
||||||
levelCardLayoutTranslations,
|
levelCardLayoutTranslations,
|
||||||
LevelCardWidgetSettings,
|
LevelCardWidgetSettings,
|
||||||
LevelSelectOptions,
|
LevelSelectOptions,
|
||||||
|
loadSvgShapesMapping,
|
||||||
optionsFilter,
|
optionsFilter,
|
||||||
Shapes,
|
Shapes,
|
||||||
shapesTranslations,
|
shapesTranslations
|
||||||
svgMapping
|
|
||||||
} from '@home/components/widget/lib/indicator/liquid-level-widget.models';
|
} from '@home/components/widget/lib/indicator/liquid-level-widget.models';
|
||||||
import { UnitsType } from '@shared/models/unit.models';
|
import { UnitsType } from '@shared/models/unit.models';
|
||||||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
||||||
import { ImageCardsSelectComponent } from '@home/components/widget/lib/settings/common/image-cards-select.component';
|
import { ImageCardsSelectComponent } from '@home/components/widget/lib/settings/common/image-cards-select.component';
|
||||||
import { map, publishReplay, refCount, tap } from 'rxjs/operators';
|
import { map, share, tap } from 'rxjs/operators';
|
||||||
import { forkJoin, Observable, of } from 'rxjs';
|
import { Observable, of, ReplaySubject } from 'rxjs';
|
||||||
import { ResourcesService } from '@core/services/resources.service';
|
import { ResourcesService } from '@core/services/resources.service';
|
||||||
import { UnitInputComponent } from '@shared/components/unit-input.component';
|
import { UnitInputComponent } from '@shared/components/unit-input.component';
|
||||||
import { UtilsService } from '@core/services/utils.service';
|
import { UtilsService } from '@core/services/utils.service';
|
||||||
@ -242,7 +241,7 @@ export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigCompon
|
|||||||
actions: [configData.config.actions || {}, []]
|
actions: [configData.config.actions || {}, []]
|
||||||
});
|
});
|
||||||
|
|
||||||
this.levelCardWidgetConfigForm.get('selectedShape').valueChanges.subscribe((shape) => {
|
this.levelCardWidgetConfigForm.get('selectedShape').valueChanges.subscribe(() => {
|
||||||
this.cd.detectChanges();
|
this.cd.detectChanges();
|
||||||
this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges();
|
this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges();
|
||||||
});
|
});
|
||||||
@ -562,21 +561,8 @@ export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigCompon
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createSvgShapesMapping(): void {
|
private createSvgShapesMapping(): void {
|
||||||
const obsArray: Array<Observable<{svg: string; shape: Shapes}>> = [];
|
loadSvgShapesMapping(this.resourcesService).subscribe(shapeMap => {
|
||||||
for (const shape of this.shapes) {
|
this.shapesImageMap = shapeMap;
|
||||||
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.cd.detectChanges();
|
||||||
this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges();
|
this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges();
|
||||||
@ -584,25 +570,8 @@ export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigCompon
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public createShapeLayout(svg: string, layout: LevelCardLayout): SafeUrl {
|
createShape(svg: string, layout: LevelCardLayout): SafeUrl {
|
||||||
if (svg && layout) {
|
return createShapeLayout(svg, layout, this.sanitizer);
|
||||||
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 {
|
public isRequired(formControlName: string): boolean {
|
||||||
@ -649,8 +618,12 @@ export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigCompon
|
|||||||
fetchObservable = of([]);
|
fetchObservable = of([]);
|
||||||
}
|
}
|
||||||
return fetchObservable.pipe(
|
return fetchObservable.pipe(
|
||||||
publishReplay(1),
|
share({
|
||||||
refCount()
|
connector: () => new ReplaySubject(1),
|
||||||
|
resetOnError: false,
|
||||||
|
resetOnComplete: false,
|
||||||
|
resetOnRefCountZero: false
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import { AppState } from '@core/core.state';
|
|||||||
import { AbstractControl, UntypedFormGroup } from '@angular/forms';
|
import { AbstractControl, UntypedFormGroup } from '@angular/forms';
|
||||||
import { DataKey, DatasourceType, KeyInfo, WidgetConfigMode } from '@shared/models/widget.models';
|
import { DataKey, DatasourceType, KeyInfo, WidgetConfigMode } from '@shared/models/widget.models';
|
||||||
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
|
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
|
||||||
|
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
|
||||||
import { isDefinedAndNotNull } from '@core/utils';
|
import { isDefinedAndNotNull } from '@core/utils';
|
||||||
|
|
||||||
export type WidgetConfigCallbacks = DatasourceCallbacks & WidgetActionCallbacks;
|
export type WidgetConfigCallbacks = DatasourceCallbacks & WidgetActionCallbacks;
|
||||||
|
|||||||
@ -20,7 +20,9 @@ import { isDefined, isDefinedAndNotNull, isNumber, isString } from '@core/utils'
|
|||||||
import {
|
import {
|
||||||
CapacityUnits,
|
CapacityUnits,
|
||||||
ConversionType,
|
ConversionType,
|
||||||
convertLiters, createAbsoluteLayout, createPercentLayout,
|
convertLiters,
|
||||||
|
createAbsoluteLayout,
|
||||||
|
createPercentLayout,
|
||||||
extractValue,
|
extractValue,
|
||||||
levelCardDefaultSettings,
|
levelCardDefaultSettings,
|
||||||
LevelCardLayout,
|
LevelCardLayout,
|
||||||
@ -40,11 +42,12 @@ import {
|
|||||||
DateFormatProcessor,
|
DateFormatProcessor,
|
||||||
inlineTextStyle
|
inlineTextStyle
|
||||||
} from '@shared/models/widget-settings.models';
|
} from '@shared/models/widget-settings.models';
|
||||||
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
|
|
||||||
import { ResourcesService } from '@core/services/resources.service';
|
import { ResourcesService } from '@core/services/resources.service';
|
||||||
import { NULL_UUID } from '@shared/models/id/has-uuid';
|
import { NULL_UUID } from '@shared/models/id/has-uuid';
|
||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tb-liquid-level-widget',
|
selector: 'tb-liquid-level-widget',
|
||||||
template: ''
|
template: ''
|
||||||
@ -128,7 +131,7 @@ export class LiquidLevelWidgetComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private declareStyles():void {
|
private declareStyles(): void {
|
||||||
this.tankColor = ColorProcessor.fromSettings(this.settings.tankColor);
|
this.tankColor = ColorProcessor.fromSettings(this.settings.tankColor);
|
||||||
this.volumeColor = ColorProcessor.fromSettings(this.settings.volumeColor);
|
this.volumeColor = ColorProcessor.fromSettings(this.settings.volumeColor);
|
||||||
this.valueColor = ColorProcessor.fromSettings(this.settings.valueColor);
|
this.valueColor = ColorProcessor.fromSettings(this.settings.valueColor);
|
||||||
@ -328,7 +331,12 @@ export class LiquidLevelWidgetComponent implements OnInit {
|
|||||||
this.updateLevel(newYPos, percentage);
|
this.updateLevel(newYPos, percentage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculatePosition(percentage, limits): number {
|
private calculatePosition(percentage: number, limits: SvgLimits): number {
|
||||||
|
if (percentage > 100) {
|
||||||
|
return limits.max;
|
||||||
|
} if (percentage <= 0) {
|
||||||
|
return limits.min;
|
||||||
|
}
|
||||||
return limits.min + (percentage / 100) * (limits.max - limits.min);
|
return limits.min + (percentage / 100) * (limits.max - limits.min);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,7 +376,7 @@ export class LiquidLevelWidgetComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateShapeColor(value): void {
|
private updateShapeColor(value: number): void {
|
||||||
const shapeStrokes = this.ctx.$container.find('.tb-shape-stroke');
|
const shapeStrokes = this.ctx.$container.find('.tb-shape-stroke');
|
||||||
const shapeFill = this.ctx.$container.find('.tb-shape-fill');
|
const shapeFill = this.ctx.$container.find('.tb-shape-fill');
|
||||||
this.tankColor.update(value);
|
this.tankColor.update(value);
|
||||||
@ -504,7 +512,7 @@ export class LiquidLevelWidgetComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public cardClick($event) {
|
public cardClick($event: Event) {
|
||||||
this.ctx.actionsApi.cardClick($event);
|
this.ctx.actionsApi.cardClick($event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -24,12 +24,14 @@ import {
|
|||||||
} from '@shared/models/widget-settings.models';
|
} from '@shared/models/widget-settings.models';
|
||||||
import { DataKey, WidgetConfig } from '@shared/models/widget.models';
|
import { DataKey, WidgetConfig } from '@shared/models/widget.models';
|
||||||
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
|
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
|
||||||
import { Observable, of } from 'rxjs';
|
import { forkJoin, Observable, of } from 'rxjs';
|
||||||
import { singleEntityFilterFromDeviceId } from '@shared/models/query/query.models';
|
import { singleEntityFilterFromDeviceId } from '@shared/models/query/query.models';
|
||||||
import { EntityType } from '@shared/models/entity-type.models';
|
import { EntityType } from '@shared/models/entity-type.models';
|
||||||
import { catchError, mergeMap } from 'rxjs/operators';
|
import { catchError, map, mergeMap } from 'rxjs/operators';
|
||||||
import { EntityService } from '@core/http/entity.service';
|
import { EntityService } from '@core/http/entity.service';
|
||||||
import { IAliasController } from '@core/api/widget-api.models';
|
import { IAliasController } from '@core/api/widget-api.models';
|
||||||
|
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
||||||
|
import { ResourcesService } from '@core/services/resources.service';
|
||||||
|
|
||||||
export interface SvgInfo {
|
export interface SvgInfo {
|
||||||
svg: string;
|
svg: string;
|
||||||
@ -123,7 +125,7 @@ export enum ConversionType {
|
|||||||
from = 'from',
|
from = 'from',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const svgMapping = new Map<string, SvgInfo>(
|
export const svgMapping = new Map<Shapes, SvgInfo>(
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
Shapes.vOval,
|
Shapes.vOval,
|
||||||
@ -421,8 +423,49 @@ export const fetchEntityKeys = (entityAliasId: string, dataKeyTypes: Array<DataK
|
|||||||
aliasInfo.entityFilter,
|
aliasInfo.entityFilter,
|
||||||
dataKeyTypes, [],
|
dataKeyTypes, [],
|
||||||
{ignoreLoading: true, ignoreErrors: true}
|
{ignoreLoading: true, ignoreErrors: true}
|
||||||
).pipe(
|
|
||||||
catchError(() => of([]))
|
|
||||||
)),
|
)),
|
||||||
catchError(() => of([] as Array<DataKey>))
|
catchError(() => of([] as Array<DataKey>))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
export const createShapeLayout = (svg: string, layout: LevelCardLayout, sanitizer: DomSanitizer): SafeUrl => {
|
||||||
|
if (svg && layout) {
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const svgImage = parser.parseFromString(svg, 'image/svg+xml');
|
||||||
|
|
||||||
|
if (layout === LevelCardLayout.simple) {
|
||||||
|
svgImage.querySelector('.container-overlay').remove();
|
||||||
|
} else if (layout === LevelCardLayout.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 sanitizer.bypassSecurityTrustResourceUrl(`data:image/svg+xml,${encodedSvg}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadSvgShapesMapping = (resourcesService: ResourcesService): Observable<Map<Shapes, string>> => {
|
||||||
|
const obsArray: Array<Observable<{svg: string; shape: Shapes}>> = [];
|
||||||
|
const shapesImageMap: Map<Shapes, string> = new Map();
|
||||||
|
svgMapping.forEach((value, shape) => {
|
||||||
|
const obs = resourcesService.loadJsonResource<string>(value.svg).pipe(
|
||||||
|
map((svg) => ({svg, shape}))
|
||||||
|
);
|
||||||
|
|
||||||
|
obsArray.push(obs);
|
||||||
|
});
|
||||||
|
|
||||||
|
return forkJoin(obsArray).pipe(
|
||||||
|
map(svgData => {
|
||||||
|
for (const data of svgData) {
|
||||||
|
shapesImageMap.set(data.shape, data.svg);
|
||||||
|
}
|
||||||
|
return shapesImageMap;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
<ng-container *ngIf="levelCardWidgetSettingsForm" [formGroup]="levelCardWidgetSettingsForm">
|
<ng-container *ngIf="levelCardWidgetSettingsForm" [formGroup]="levelCardWidgetSettingsForm">
|
||||||
<div style="display: flex; flex-direction: column; gap: 16px;">
|
<div class="tb-form-panel no-padding no-border">
|
||||||
<div class="tb-form-panel">
|
<div class="tb-form-panel">
|
||||||
<div fxFlex fxLayout="column" class="tb-form-row space-between">
|
<div fxFlex fxLayout="column" class="tb-form-row space-between">
|
||||||
<div fxFlex fxLayout="row" style="width: 100%;" fxLayoutAlign="space-between center">
|
<div fxFlex fxLayout="row" style="width: 100%;" fxLayoutAlign="space-between center">
|
||||||
@ -39,7 +39,7 @@
|
|||||||
label="{{ 'widgets.liquid-level-card.shape-type' | translate }}" formControlName="selectedShape">
|
label="{{ 'widgets.liquid-level-card.shape-type' | translate }}" formControlName="selectedShape">
|
||||||
<tb-image-cards-select-option *ngFor="let shape of shapes"
|
<tb-image-cards-select-option *ngFor="let shape of shapes"
|
||||||
[value]="shape"
|
[value]="shape"
|
||||||
[image]="createShapeLayout(shapesImageMap.get(shape), this.levelCardLayouts.simple)">
|
[image]="createShape(shapesImageMap.get(shape), this.levelCardLayouts.simple)">
|
||||||
{{ shapesTranslationMap.get(shape) | translate }}
|
{{ shapesTranslationMap.get(shape) | translate }}
|
||||||
</tb-image-cards-select-option>
|
</tb-image-cards-select-option>
|
||||||
</tb-image-cards-select>
|
</tb-image-cards-select>
|
||||||
@ -49,8 +49,7 @@
|
|||||||
<tb-string-autocomplete [fetchOptionsFn]="fetchOptions.bind(this)"
|
<tb-string-autocomplete [fetchOptionsFn]="fetchOptions.bind(this)"
|
||||||
[required]="isRequired('shapeAttributeName')"
|
[required]="isRequired('shapeAttributeName')"
|
||||||
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
|
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
|
||||||
style="flex: 1; width: auto;"
|
style="flex: 1"
|
||||||
asBoxInput colorClearButton class="flex"
|
|
||||||
formControlName="shapeAttributeName">
|
formControlName="shapeAttributeName">
|
||||||
</tb-string-autocomplete>
|
</tb-string-autocomplete>
|
||||||
</div>
|
</div>
|
||||||
@ -77,7 +76,7 @@
|
|||||||
<tb-image-cards-select-option
|
<tb-image-cards-select-option
|
||||||
*ngFor="let layout of [levelCardLayouts.simple, levelCardLayouts.percentage, levelCardLayouts.absolute]"
|
*ngFor="let layout of [levelCardLayouts.simple, levelCardLayouts.percentage, levelCardLayouts.absolute]"
|
||||||
[value]="layout"
|
[value]="layout"
|
||||||
[image]="createShapeLayout(shapesImageMap.get(levelCardWidgetSettingsForm.get('selectedShape').value), layout)">
|
[image]="createShape(shapesImageMap.get(levelCardWidgetSettingsForm.get('selectedShape').value), layout)">
|
||||||
{{ levelCardLayoutTranslationMap.get(layout) | translate }}
|
{{ levelCardLayoutTranslationMap.get(layout) | translate }}
|
||||||
</tb-image-cards-select-option>
|
</tb-image-cards-select-option>
|
||||||
</tb-image-cards-select>
|
</tb-image-cards-select>
|
||||||
@ -103,7 +102,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<tb-unit-input [fxShow]="levelCardWidgetSettingsForm.get('widgetUnitsSource')?.value !== volumeOptions.attribute"
|
<tb-unit-input [fxShow]="levelCardWidgetSettingsForm.get('widgetUnitsSource')?.value !== volumeOptions.attribute"
|
||||||
asBoxInput colorClearButton class="flex"
|
class="flex"
|
||||||
[tagFilter]="unitsType.capacity"
|
[tagFilter]="unitsType.capacity"
|
||||||
[required]="isRequired('units')"
|
[required]="isRequired('units')"
|
||||||
formControlName="units">
|
formControlName="units">
|
||||||
@ -112,8 +111,7 @@
|
|||||||
[fetchOptionsFn]="fetchOptions.bind(this)"
|
[fetchOptionsFn]="fetchOptions.bind(this)"
|
||||||
[required]="isRequired('widgetUnitsAttributeName')"
|
[required]="isRequired('widgetUnitsAttributeName')"
|
||||||
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
|
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
|
||||||
style="flex: 1; width: auto;"
|
style="flex: 1"
|
||||||
asBoxInput colorClearButton class="flex"
|
|
||||||
formControlName="widgetUnitsAttributeName">
|
formControlName="widgetUnitsAttributeName">
|
||||||
</tb-string-autocomplete>
|
</tb-string-autocomplete>
|
||||||
</div>
|
</div>
|
||||||
@ -141,17 +139,14 @@
|
|||||||
warning
|
warning
|
||||||
</mat-icon>
|
</mat-icon>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<tb-string-autocomplete style="max-width: 25%"
|
<tb-string-autocomplete [fxShow]="levelCardWidgetSettingsForm.get('volumeSource')?.value === volumeOptions.attribute"
|
||||||
[fxShow]="levelCardWidgetSettingsForm.get('volumeSource')?.value === volumeOptions.attribute"
|
|
||||||
[fetchOptionsFn]="fetchOptions.bind(this)"
|
[fetchOptionsFn]="fetchOptions.bind(this)"
|
||||||
[required]="isRequired('volumeAttributeName')"
|
[required]="isRequired('volumeAttributeName')"
|
||||||
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
|
[errorText]="'widgets.liquid-level-card.attribute-name-required' | translate"
|
||||||
style="flex: 1; width: auto;"
|
style="flex: 1"
|
||||||
asBoxInput colorClearButton class="flex"
|
|
||||||
formControlName="volumeAttributeName">
|
formControlName="volumeAttributeName">
|
||||||
</tb-string-autocomplete>
|
</tb-string-autocomplete>
|
||||||
<tb-unit-input asBoxInput colorClearButton
|
<tb-unit-input [tagFilter]="unitsType.capacity"
|
||||||
[tagFilter]="unitsType.capacity"
|
|
||||||
[required]="isRequired('volumeUnits')"
|
[required]="isRequired('volumeUnits')"
|
||||||
style="max-width: 25%" class="flex"
|
style="max-width: 25%" class="flex"
|
||||||
formControlName="volumeUnits">
|
formControlName="volumeUnits">
|
||||||
|
|||||||
@ -27,30 +27,26 @@ import { Store } from '@ngrx/store';
|
|||||||
import { AppState } from '@core/core.state';
|
import { AppState } from '@core/core.state';
|
||||||
import { formatValue, isDefined } from '@core/utils';
|
import { formatValue, isDefined } from '@core/utils';
|
||||||
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
|
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
|
||||||
|
import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models';
|
||||||
import {
|
import {
|
||||||
DateFormatProcessor,
|
CapacityUnits,
|
||||||
DateFormatSettings
|
createShapeLayout,
|
||||||
} from '@shared/models/widget-settings.models';
|
fetchEntityKeys,
|
||||||
import {
|
fetchEntityKeysForDevice,
|
||||||
levelCardDefaultSettings,
|
levelCardDefaultSettings,
|
||||||
LevelCardLayout,
|
LevelCardLayout,
|
||||||
levelCardLayoutTranslations,
|
levelCardLayoutTranslations,
|
||||||
Shapes,
|
|
||||||
shapesTranslations,
|
|
||||||
svgMapping,
|
|
||||||
CapacityUnits,
|
|
||||||
LevelSelectOptions,
|
LevelSelectOptions,
|
||||||
createPercentLayout,
|
loadSvgShapesMapping,
|
||||||
createAbsoluteLayout,
|
|
||||||
optionsFilter,
|
optionsFilter,
|
||||||
fetchEntityKeysForDevice,
|
Shapes,
|
||||||
fetchEntityKeys
|
shapesTranslations
|
||||||
} from '@home/components/widget/lib/indicator/liquid-level-widget.models';
|
} from '@home/components/widget/lib/indicator/liquid-level-widget.models';
|
||||||
import { UnitsType } from '@shared/models/unit.models';
|
import { UnitsType } from '@shared/models/unit.models';
|
||||||
import { ImageCardsSelectComponent } from '@home/components/widget/lib/settings/common/image-cards-select.component';
|
import { ImageCardsSelectComponent } from '@home/components/widget/lib/settings/common/image-cards-select.component';
|
||||||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
||||||
import { forkJoin, Observable, of } from 'rxjs';
|
import { Observable, of, ReplaySubject } from 'rxjs';
|
||||||
import { map, publishReplay, refCount, tap } from 'rxjs/operators';
|
import { map, share, tap } from 'rxjs/operators';
|
||||||
import { ResourcesService } from '@core/services/resources.service';
|
import { ResourcesService } from '@core/services/resources.service';
|
||||||
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
|
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
|
||||||
import { UtilsService } from '@core/services/utils.service';
|
import { UtilsService } from '@core/services/utils.service';
|
||||||
@ -201,7 +197,7 @@ export class LiquidLevelCardWidgetSettingsComponent extends WidgetSettingsCompon
|
|||||||
tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []],
|
tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.levelCardWidgetSettingsForm.get('selectedShape').valueChanges.subscribe((shape) => {
|
this.levelCardWidgetSettingsForm.get('selectedShape').valueChanges.subscribe(() => {
|
||||||
this.cd.detectChanges();
|
this.cd.detectChanges();
|
||||||
this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges();
|
this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges();
|
||||||
});
|
});
|
||||||
@ -390,21 +386,8 @@ export class LiquidLevelCardWidgetSettingsComponent extends WidgetSettingsCompon
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createSvgShapesMapping(): void {
|
private createSvgShapesMapping(): void {
|
||||||
const obsArray: Array<Observable<{svg: string; shape: Shapes}>> = [];
|
loadSvgShapesMapping(this.resourcesService).subscribe(shapeMap => {
|
||||||
for (const shape of this.shapes) {
|
this.shapesImageMap = shapeMap;
|
||||||
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.cd.detectChanges();
|
||||||
this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges();
|
this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges();
|
||||||
@ -412,25 +395,8 @@ export class LiquidLevelCardWidgetSettingsComponent extends WidgetSettingsCompon
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public createShapeLayout(svg: string, layout: LevelCardLayout): SafeUrl {
|
createShape(svg: string, layout: LevelCardLayout): SafeUrl {
|
||||||
if (svg && layout) {
|
return createShapeLayout(svg, layout, this.sanitizer);
|
||||||
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 {
|
public isRequired(formControlName: string): boolean {
|
||||||
@ -509,8 +475,12 @@ export class LiquidLevelCardWidgetSettingsComponent extends WidgetSettingsCompon
|
|||||||
fetchObservable = of([]);
|
fetchObservable = of([]);
|
||||||
}
|
}
|
||||||
return fetchObservable.pipe(
|
return fetchObservable.pipe(
|
||||||
publishReplay(1),
|
share({
|
||||||
refCount()
|
connector: () => new ReplaySubject(1),
|
||||||
|
resetOnError: false,
|
||||||
|
resetOnComplete: false,
|
||||||
|
resetOnRefCountZero: false
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,11 +32,12 @@ import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs';
|
|||||||
import { map, share, startWith, takeUntil } from 'rxjs/operators';
|
import { map, share, startWith, takeUntil } from 'rxjs/operators';
|
||||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||||
import { MediaBreakpoints } from '@shared/models/constants';
|
import { MediaBreakpoints } from '@shared/models/constants';
|
||||||
|
import { SafeUrl } from '@angular/platform-browser';
|
||||||
|
|
||||||
export interface ImageCardsSelectOption {
|
export interface ImageCardsSelectOption {
|
||||||
name: string;
|
name: string;
|
||||||
value: any;
|
value: any;
|
||||||
image: string;
|
image: string | SafeUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Directive(
|
@Directive(
|
||||||
@ -49,7 +50,7 @@ export class ImageCardsSelectOptionDirective {
|
|||||||
|
|
||||||
@Input() value: any;
|
@Input() value: any;
|
||||||
|
|
||||||
@Input() image: string;
|
@Input() image: string | SafeUrl;
|
||||||
|
|
||||||
get viewValue(): string {
|
get viewValue(): string {
|
||||||
return (this._element?.nativeElement.textContent || '').trim();
|
return (this._element?.nativeElement.textContent || '').trim();
|
||||||
|
|||||||
@ -64,7 +64,7 @@ import {
|
|||||||
import { SignalStrengthWidgetComponent } from '@home/components/widget/lib/indicator/signal-strength-widget.component';
|
import { SignalStrengthWidgetComponent } from '@home/components/widget/lib/indicator/signal-strength-widget.component';
|
||||||
import { ValueChartCardWidgetComponent } from '@home/components/widget/lib/cards/value-chart-card-widget.component';
|
import { ValueChartCardWidgetComponent } from '@home/components/widget/lib/cards/value-chart-card-widget.component';
|
||||||
import { ProgressBarWidgetComponent } from '@home/components/widget/lib/cards/progress-bar-widget.component';
|
import { ProgressBarWidgetComponent } from '@home/components/widget/lib/cards/progress-bar-widget.component';
|
||||||
import { LiquidLevelWidgetComponent } from '@home/components/widget/lib/indicator/liquid-level-widget';
|
import { LiquidLevelWidgetComponent } from '@home/components/widget/lib/indicator/liquid-level-widget.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations:
|
declarations:
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
-->
|
-->
|
||||||
<mat-form-field [appearance]="appearance" [ngClass]="ngClass"
|
<mat-form-field [appearance]="appearance" [class]="additionalClass"
|
||||||
[subscriptSizing]="subscriptSizing" style="width: 100%">
|
[subscriptSizing]="subscriptSizing" style="width: 100%">
|
||||||
<mat-label *ngIf="label">{{label}}</mat-label>
|
<mat-label *ngIf="label">{{label}}</mat-label>
|
||||||
<input matInput #nameInput [formControl]="selectionFormControl"
|
<input matInput #nameInput [formControl]="selectionFormControl"
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
:host {
|
:host {
|
||||||
mat-form-field {
|
mat-form-field {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 0%;
|
flex: 1;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import { Observable, of } from 'rxjs';
|
|||||||
import { tap, map, switchMap, take } from 'rxjs/operators';
|
import { tap, map, switchMap, take } from 'rxjs/operators';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { coerceBoolean } from '@shared/decorators/coercion';
|
import { coerceBoolean } from '@shared/decorators/coercion';
|
||||||
|
import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tb-string-autocomplete',
|
selector: 'tb-string-autocomplete',
|
||||||
@ -48,41 +49,42 @@ import { coerceBoolean } from '@shared/decorators/coercion';
|
|||||||
})
|
})
|
||||||
export class StringAutocompleteComponent implements ControlValueAccessor, OnInit {
|
export class StringAutocompleteComponent implements ControlValueAccessor, OnInit {
|
||||||
|
|
||||||
|
@ViewChild('nameInput', {static: true}) nameInput: ElementRef;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
|
||||||
@coerceBoolean()
|
|
||||||
@Input()
|
@Input()
|
||||||
required: boolean = false;
|
@coerceBoolean()
|
||||||
|
required = false;
|
||||||
|
|
||||||
@Input() fetchOptionsFn: (searchText?: string) => Observable<Array<string>>;
|
@Input()
|
||||||
|
fetchOptionsFn: (searchText?: string) => Observable<Array<string>>;
|
||||||
@ViewChild('nameInput', {static: true}) nameInput: ElementRef;
|
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
placeholderText: string = this.translate.instant('widget-config.set');
|
placeholderText: string = this.translate.instant('widget-config.set');
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
subscriptSizing: string = 'dynamic';
|
subscriptSizing: SubscriptSizing = 'dynamic';
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
ngClass: string | string[] | Set<string> | { [klass: string]: any; } = 'tb-inline-field tb-suffix-show-on-hover';
|
additionalClass: string | string[] | Record<string, boolean | undefined | null> = 'tb-inline-field tb-suffix-show-on-hover';
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
appearance: string = 'outline';
|
appearance: MatFormFieldAppearance = 'outline';
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
label: string;
|
label: string;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
tooltipClass: string = 'tb-error-tooltip';
|
tooltipClass = 'tb-error-tooltip';
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
errorText: string;
|
errorText: string;
|
||||||
|
|
||||||
@coerceBoolean()
|
|
||||||
@Input()
|
@Input()
|
||||||
showInlineError: boolean = false;
|
@coerceBoolean()
|
||||||
|
showInlineError = false;
|
||||||
|
|
||||||
selectionFormControl: FormControl;
|
selectionFormControl: FormControl;
|
||||||
|
|
||||||
|
|||||||
@ -62,9 +62,9 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit {
|
|||||||
@Input()
|
@Input()
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
|
||||||
@coerceBoolean()
|
|
||||||
@Input()
|
@Input()
|
||||||
required: boolean = false;
|
@coerceBoolean()
|
||||||
|
required = false;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
tagFilter: UnitsType;
|
tagFilter: UnitsType;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user