diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/liquid-level-card-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/liquid-level-card-basic-config.component.html index 82dcb21c81..37463350c8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/liquid-level-card-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/liquid-level-card-basic-config.component.html @@ -84,7 +84,7 @@ label="{{ 'widgets.liquid-level-card.shape-type' | translate }}" formControlName="selectedShape"> + [image]="createShape(shapesImageMap.get(shape), this.levelCardLayouts.simple)"> {{ shapesTranslationMap.get(shape) | translate }} @@ -94,8 +94,7 @@ @@ -118,7 +117,7 @@ + [image]="createShape(shapesImageMap.get(levelCardWidgetConfigForm.get('selectedShape').value), layout)"> {{ levelCardLayoutTranslationMap.get(layout) | translate }} @@ -144,7 +143,7 @@ @@ -153,8 +152,7 @@ [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" + style="flex: 1" formControlName="widgetUnitsAttributeName"> @@ -182,17 +180,14 @@ warning - - diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/liquid-level-card-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/liquid-level-card-basic-config.component.ts index abbc0d5102..807c3cb60a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/liquid-level-card-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/cards/liquid-level-card-basic-config.component.ts @@ -43,23 +43,22 @@ import { } from '@shared/models/widget-settings.models'; import { CapacityUnits, - createAbsoluteLayout, - createPercentLayout, + createShapeLayout, levelCardDefaultSettings, LevelCardLayout, levelCardLayoutTranslations, LevelCardWidgetSettings, LevelSelectOptions, + loadSvgShapesMapping, optionsFilter, Shapes, - shapesTranslations, - svgMapping + shapesTranslations } 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 { map, share, tap } from 'rxjs/operators'; +import { Observable, of, ReplaySubject } from 'rxjs'; import { ResourcesService } from '@core/services/resources.service'; import { UnitInputComponent } from '@shared/components/unit-input.component'; import { UtilsService } from '@core/services/utils.service'; @@ -242,7 +241,7 @@ export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigCompon actions: [configData.config.actions || {}, []] }); - this.levelCardWidgetConfigForm.get('selectedShape').valueChanges.subscribe((shape) => { + this.levelCardWidgetConfigForm.get('selectedShape').valueChanges.subscribe(() => { this.cd.detectChanges(); this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges(); }); @@ -562,21 +561,8 @@ export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigCompon } private createSvgShapesMapping(): void { - const obsArray: Array> = []; - for (const shape of this.shapes) { - const svgUrl = svgMapping.get(shape).svg; - - const obs = this.resourcesService.loadJsonResource(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); - } + loadSvgShapesMapping(this.resourcesService).subscribe(shapeMap => { + this.shapesImageMap = shapeMap; this.cd.detectChanges(); this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges(); @@ -584,25 +570,8 @@ export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigCompon }); } - 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}`); - } + createShape(svg: string, layout: LevelCardLayout): SafeUrl { + return createShapeLayout(svg, layout, this.sanitizer); } public isRequired(formControlName: string): boolean { @@ -649,8 +618,12 @@ export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigCompon fetchObservable = of([]); } return fetchObservable.pipe( - publishReplay(1), - refCount() + share({ + connector: () => new ReplaySubject(1), + resetOnError: false, + resetOnComplete: false, + resetOnRefCountZero: false + }) ); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/widget-config.component.models.ts b/ui-ngx/src/app/modules/home/components/widget/config/widget-config.component.models.ts index 160331de3a..cfa0cedbee 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/widget-config.component.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/widget-config.component.models.ts @@ -25,6 +25,7 @@ import { AppState } from '@core/core.state'; import { AbstractControl, UntypedFormGroup } from '@angular/forms'; import { DataKey, DatasourceType, KeyInfo, WidgetConfigMode } from '@shared/models/widget.models'; import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { isDefinedAndNotNull } from '@core/utils'; export type WidgetConfigCallbacks = DatasourceCallbacks & WidgetActionCallbacks; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts similarity index 97% rename from ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts index de106810f0..4e54e0c125 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts @@ -20,7 +20,9 @@ import { isDefined, isDefinedAndNotNull, isNumber, isString } from '@core/utils' import { CapacityUnits, ConversionType, - convertLiters, createAbsoluteLayout, createPercentLayout, + convertLiters, + createAbsoluteLayout, + createPercentLayout, extractValue, levelCardDefaultSettings, LevelCardLayout, @@ -40,11 +42,12 @@ import { 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'; +import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; + @Component({ selector: 'tb-liquid-level-widget', template: '' @@ -128,7 +131,7 @@ export class LiquidLevelWidgetComponent implements OnInit { }); } - private declareStyles():void { + private declareStyles(): void { this.tankColor = ColorProcessor.fromSettings(this.settings.tankColor); this.volumeColor = ColorProcessor.fromSettings(this.settings.volumeColor); this.valueColor = ColorProcessor.fromSettings(this.settings.valueColor); @@ -328,7 +331,12 @@ export class LiquidLevelWidgetComponent implements OnInit { 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); } @@ -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 shapeFill = this.ctx.$container.find('.tb-shape-fill'); 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); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.models.ts index 7602a97dfe..5b51b2f277 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.models.ts @@ -24,12 +24,14 @@ import { } 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 { forkJoin, 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 { catchError, map, mergeMap } from 'rxjs/operators'; import { EntityService } from '@core/http/entity.service'; 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 { svg: string; @@ -123,7 +125,7 @@ export enum ConversionType { from = 'from', } -export const svgMapping = new Map( +export const svgMapping = new Map( [ [ Shapes.vOval, @@ -421,8 +423,49 @@ export const fetchEntityKeys = (entityAliasId: string, dataKeyTypes: Array of([])) )), catchError(() => of([] as Array)) ); + + +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> => { + const obsArray: Array> = []; + const shapesImageMap: Map = new Map(); + svgMapping.forEach((value, shape) => { + const obs = resourcesService.loadJsonResource(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; + }) + ); +}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/liquid-level-card-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/liquid-level-card-widget-settings.component.html index 0f3f943cc4..30fd2abaea 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/liquid-level-card-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/liquid-level-card-widget-settings.component.html @@ -16,7 +16,7 @@ --> -
+
@@ -39,7 +39,7 @@ label="{{ 'widgets.liquid-level-card.shape-type' | translate }}" formControlName="selectedShape"> + [image]="createShape(shapesImageMap.get(shape), this.levelCardLayouts.simple)"> {{ shapesTranslationMap.get(shape) | translate }} @@ -49,8 +49,7 @@
@@ -77,7 +76,7 @@ + [image]="createShape(shapesImageMap.get(levelCardWidgetSettingsForm.get('selectedShape').value), layout)"> {{ levelCardLayoutTranslationMap.get(layout) | translate }} @@ -103,7 +102,7 @@ @@ -112,8 +111,7 @@ [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" + style="flex: 1" formControlName="widgetUnitsAttributeName">
@@ -141,17 +139,14 @@ warning - - diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/liquid-level-card-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/liquid-level-card-widget-settings.component.ts index 50a1bec81f..f90aac8205 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/liquid-level-card-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/liquid-level-card-widget-settings.component.ts @@ -27,30 +27,26 @@ 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 { - DateFormatProcessor, - DateFormatSettings -} from '@shared/models/widget-settings.models'; -import { + CapacityUnits, + createShapeLayout, + fetchEntityKeys, + fetchEntityKeysForDevice, levelCardDefaultSettings, LevelCardLayout, levelCardLayoutTranslations, - Shapes, - shapesTranslations, - svgMapping, - CapacityUnits, LevelSelectOptions, - createPercentLayout, - createAbsoluteLayout, + loadSvgShapesMapping, optionsFilter, - fetchEntityKeysForDevice, - fetchEntityKeys + Shapes, + shapesTranslations } 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 { Observable, of, ReplaySubject } from 'rxjs'; +import { map, share, 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'; @@ -201,7 +197,7 @@ export class LiquidLevelCardWidgetSettingsComponent extends WidgetSettingsCompon tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []], }); - this.levelCardWidgetSettingsForm.get('selectedShape').valueChanges.subscribe((shape) => { + this.levelCardWidgetSettingsForm.get('selectedShape').valueChanges.subscribe(() => { this.cd.detectChanges(); this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges(); }); @@ -390,21 +386,8 @@ export class LiquidLevelCardWidgetSettingsComponent extends WidgetSettingsCompon } private createSvgShapesMapping(): void { - const obsArray: Array> = []; - for (const shape of this.shapes) { - const svgUrl = svgMapping.get(shape).svg; - - const obs = this.resourcesService.loadJsonResource(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); - } + loadSvgShapesMapping(this.resourcesService).subscribe(shapeMap => { + this.shapesImageMap = shapeMap; this.cd.detectChanges(); this.layoutsImageCardsSelect?.imageCardsSelectOptions.notifyOnChanges(); @@ -412,25 +395,8 @@ export class LiquidLevelCardWidgetSettingsComponent extends WidgetSettingsCompon }); } - 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}`); - } + createShape(svg: string, layout: LevelCardLayout): SafeUrl { + return createShapeLayout(svg, layout, this.sanitizer); } public isRequired(formControlName: string): boolean { @@ -509,8 +475,12 @@ export class LiquidLevelCardWidgetSettingsComponent extends WidgetSettingsCompon fetchObservable = of([]); } return fetchObservable.pipe( - publishReplay(1), - refCount() + share({ + connector: () => new ReplaySubject(1), + resetOnError: false, + resetOnComplete: false, + resetOnRefCountZero: false + }) ); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts index d75c4a5fc9..4f11a51223 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/image-cards-select.component.ts @@ -32,11 +32,12 @@ import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; import { map, share, startWith, takeUntil } from 'rxjs/operators'; import { BreakpointObserver } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; +import { SafeUrl } from '@angular/platform-browser'; export interface ImageCardsSelectOption { name: string; value: any; - image: string; + image: string | SafeUrl; } @Directive( @@ -49,7 +50,7 @@ export class ImageCardsSelectOptionDirective { @Input() value: any; - @Input() image: string; + @Input() image: string | SafeUrl; get viewValue(): string { return (this._element?.nativeElement.textContent || '').trim(); diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index 1cd7383c19..abaec04df8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -64,7 +64,7 @@ import { 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 { 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({ declarations: diff --git a/ui-ngx/src/app/shared/components/string-autocomplete.component.html b/ui-ngx/src/app/shared/components/string-autocomplete.component.html index b2380ba239..4750f71d63 100644 --- a/ui-ngx/src/app/shared/components/string-autocomplete.component.html +++ b/ui-ngx/src/app/shared/components/string-autocomplete.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - {{label}} Observable>; - - @ViewChild('nameInput', {static: true}) nameInput: ElementRef; + @Input() + fetchOptionsFn: (searchText?: string) => Observable>; @Input() placeholderText: string = this.translate.instant('widget-config.set'); @Input() - subscriptSizing: string = 'dynamic'; + subscriptSizing: SubscriptSizing = 'dynamic'; @Input() - ngClass: string | string[] | Set | { [klass: string]: any; } = 'tb-inline-field tb-suffix-show-on-hover'; + additionalClass: string | string[] | Record = 'tb-inline-field tb-suffix-show-on-hover'; @Input() - appearance: string = 'outline'; + appearance: MatFormFieldAppearance = 'outline'; @Input() label: string; @Input() - tooltipClass: string = 'tb-error-tooltip'; + tooltipClass = 'tb-error-tooltip'; @Input() errorText: string; - @coerceBoolean() @Input() - showInlineError: boolean = false; + @coerceBoolean() + showInlineError = false; selectionFormControl: FormControl; diff --git a/ui-ngx/src/app/shared/components/unit-input.component.ts b/ui-ngx/src/app/shared/components/unit-input.component.ts index a0779b490f..ce84aede7f 100644 --- a/ui-ngx/src/app/shared/components/unit-input.component.ts +++ b/ui-ngx/src/app/shared/components/unit-input.component.ts @@ -62,9 +62,9 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit { @Input() disabled: boolean; - @coerceBoolean() @Input() - required: boolean = false; + @coerceBoolean() + required = false; @Input() tagFilter: UnitsType;