From 1734a890e3ec640e465219dcd34a64dcf53df3d3 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 1 May 2025 16:05:35 +0300 Subject: [PATCH] UI: Refactoring units service --- ui-ngx/src/app/app.component.ts | 2 +- ui-ngx/src/app/core/services/public-api.ts | 2 +- .../core/services/{unit => }/unit.service.ts | 71 ++-- .../app/core/services/unit/converter-unit.ts | 272 -------------- .../app/core/services/unit/definitions/all.ts | 121 ------ .../lib/cards/value-card-widget.component.ts | 10 +- .../app/modules/home/models/services.map.ts | 2 +- .../home/pages/profile/profile.component.ts | 2 +- .../convert-unit-settings-panel.component.ts | 13 +- .../shared/components/unit-input.component.ts | 37 +- ui-ngx/src/app/shared/models/public-api.ts | 1 + ui-ngx/src/app/shared/models/unit.models.ts | 350 +++++++++++++++++- .../models/units}/acceleration.ts | 0 .../models/units}/angle.ts | 0 .../models/units}/angular-acceleration.ts | 0 .../models/units}/area.ts | 0 .../models/units}/charge.ts | 0 .../models/units}/digital.ts | 0 .../models/units}/electric-current.ts | 0 .../models/units}/energy.ts | 0 .../models/units}/force.ts | 0 .../models/units}/frequency.ts | 0 .../models/units}/illuminance.ts | 0 .../models/units}/length.ts | 0 .../models/units}/mass.ts | 0 .../models/units}/parts-per.ts | 0 .../models/units}/power.ts | 0 .../models/units}/pressure.ts | 0 .../models/units}/speed.ts | 0 .../models/units}/temperature.ts | 0 .../models/units}/time.ts | 0 .../models/units}/torque.ts | 0 .../models/units}/voltage.ts | 0 .../models/units}/volume-flow-rate.ts | 0 .../models/units}/volume.ts | 0 .../shared/models/widget-settings.models.ts | 56 ++- 36 files changed, 445 insertions(+), 494 deletions(-) rename ui-ngx/src/app/core/services/{unit => }/unit.service.ts (50%) delete mode 100644 ui-ngx/src/app/core/services/unit/converter-unit.ts delete mode 100644 ui-ngx/src/app/core/services/unit/definitions/all.ts rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/acceleration.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/angle.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/angular-acceleration.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/area.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/charge.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/digital.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/electric-current.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/energy.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/force.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/frequency.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/illuminance.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/length.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/mass.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/parts-per.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/power.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/pressure.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/speed.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/temperature.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/time.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/torque.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/voltage.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/volume-flow-rate.ts (100%) rename ui-ngx/src/app/{core/services/unit/definitions => shared/models/units}/volume.ts (100%) diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index 6e04735706..85bddd56cb 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -33,7 +33,7 @@ import { svgIcons, svgIconsUrl } from '@shared/models/icon.models'; import { ActionSettingsChangeLanguage } from '@core/settings/settings.actions'; import { SETTINGS_KEY } from '@core/settings/settings.effects'; import { initCustomJQueryEvents } from '@shared/models/jquery-event.models'; -import { UnitService } from '@core/services/unit/unit.service'; +import { UnitService } from '@core/services/unit.service'; @Component({ selector: 'tb-root', diff --git a/ui-ngx/src/app/core/services/public-api.ts b/ui-ngx/src/app/core/services/public-api.ts index f4e770bdb5..c728565c8b 100644 --- a/ui-ngx/src/app/core/services/public-api.ts +++ b/ui-ngx/src/app/core/services/public-api.ts @@ -15,7 +15,6 @@ /// export * from './script/node-script-test.service'; -export * from './unit/unit.service'; export * from './broadcast.models'; export * from './broadcast.service'; export * from './dashboard-utils.service'; @@ -29,5 +28,6 @@ export * from './raf.service'; export * from './resources.service'; export * from './time.service'; export * from './title.service'; +export * from './unit.service'; export * from './utils.service'; export * from './window.service'; diff --git a/ui-ngx/src/app/core/services/unit/unit.service.ts b/ui-ngx/src/app/core/services/unit.service.ts similarity index 50% rename from ui-ngx/src/app/core/services/unit/unit.service.ts rename to ui-ngx/src/app/core/services/unit.service.ts index 6945c89d3a..3d1fc4de0a 100644 --- a/ui-ngx/src/app/core/services/unit/unit.service.ts +++ b/ui-ngx/src/app/core/services/unit.service.ts @@ -17,18 +17,19 @@ import { Injectable } from '@angular/core'; import moment from 'moment-timezone'; import { - TbUnitConvertor, - UnitDescription, - UnitDescriptionGroupByMeasure, + AllMeasures, + AllMeasuresUnits, + Converter, + getUnitConverter, + TbUnitConverter, + TbUnitMapping, + UnitInfo, + UnitInfoGroupByMeasure, UnitSystem } from '@shared/models/unit.models'; -import { isNotEmptyStr } from '@core/utils'; -import { configureMeasurements, Converter } from '@core/services/unit/converter-unit'; -import allMeasures, { AllMeasures, AllMeasuresUnits } from '@core/services/unit/definitions/all'; +import { isNotEmptyStr, isObject } from '@core/utils'; import { TranslateService } from '@ngx-translate/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; @Injectable({ providedIn: 'root' @@ -36,18 +37,17 @@ import { AppState } from '@core/core.state'; export class UnitService { private currentUnitSystem: UnitSystem = UnitSystem.METRIC; - private converter: Converter; + private converter: Converter; - constructor(private store: Store, - private translate: TranslateService) { + constructor(private translate: TranslateService) { this.translate.onLangChange.pipe( takeUntilDestroyed() ).subscribe(() => { - this.converter = configureMeasurements(allMeasures, this.translate); - console.warn(this.converter?.list()); - console.warn(this.converter?.list('temperature')); - console.warn(this.converter?.list('temperature', UnitSystem.METRIC)); - console.warn(this.converter?.list(null, UnitSystem.IMPERIAL)); + this.converter = getUnitConverter(this.translate); + console.warn(this.converter?.listUnits()); + console.warn(this.converter?.listUnits('temperature')); + console.warn(this.converter?.listUnits('temperature', UnitSystem.METRIC)); + console.warn(this.converter?.listUnits(null, UnitSystem.IMPERIAL)); }); } @@ -64,28 +64,47 @@ export class UnitService { console.warn('[Unit system] setUnitSystem', this.currentUnitSystem); } - getUnits(measure?: AllMeasures, unitSystem?: UnitSystem): UnitDescription[] { - return this.converter?.list(measure, unitSystem); + getUnits(measure?: AllMeasures, unitSystem?: UnitSystem): UnitInfo[] { + return this.converter?.listUnits(measure, unitSystem); } - getUnitsGroupByMeasure(measure?: AllMeasures, unitSystem?: UnitSystem): UnitDescriptionGroupByMeasure { - return this.converter?.listGroupByMeasure(measure, unitSystem); + getUnitsGroupedByMeasure(measure?: AllMeasures, unitSystem?: UnitSystem): UnitInfoGroupByMeasure { + return this.converter?.unitsGroupByMeasure(measure, unitSystem); } - getUnitDescription(abbr: AllMeasuresUnits | string): UnitDescription { - return this.converter.describe(abbr); + getUnitInfo(symbol: AllMeasuresUnits | string): UnitInfo { + return this.converter.describe(symbol); } getDefaultUnit(measure: AllMeasures, unitSystem: UnitSystem): AllMeasuresUnits { return this.converter.getDefaultUnit(measure, unitSystem); } - geUnitConvertor(from: string, to: string): TbUnitConvertor { - return this.converter.convertor(from, to); + geUnitConverter(unit: TbUnitMapping): TbUnitConverter; + geUnitConverter(from: string, to: string): TbUnitConverter; + geUnitConverter(unit: TbUnitMapping | string, to?: string): TbUnitConverter { + if (unit !== null && typeof unit === 'object') { + const target = this.getTargetUnitSymbol(unit); + return this.converter.getUnitConverter(unit.from, target); + } + return this.converter.getUnitConverter(unit as string, to); } - convertValue(value: number, from: string, to: string): number { - return this.converter.convert(value, from, to); + getTargetUnitSymbol(unit: TbUnitMapping): string { + if (isObject(unit)) { + return isNotEmptyStr(unit[this.currentUnitSystem]) ? unit[this.currentUnitSystem] : unit.from; + } + return null; + } + + convertUnitValue(value: number, unit: TbUnitMapping): number; + convertUnitValue(value: number, from: string, to: string): number; + convertUnitValue(value: number, unit: string | TbUnitMapping, to?: string): number { + if (unit !== null && typeof unit === 'object') { + const target = this.getTargetUnitSymbol(unit); + return this.converter.convert(value, unit.from, target); + } + return this.converter.convert(value, unit as string, to); } private getUnitSystemByTimezone(): UnitSystem { diff --git a/ui-ngx/src/app/core/services/unit/converter-unit.ts b/ui-ngx/src/app/core/services/unit/converter-unit.ts deleted file mode 100644 index a97951db15..0000000000 --- a/ui-ngx/src/app/core/services/unit/converter-unit.ts +++ /dev/null @@ -1,272 +0,0 @@ -/// -/// Copyright © 2016-2025 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 { - Conversion, - TbMeasure, - TbUnitConvertor, - Unit, - UnitCache, - UnitDescription, - UnitDescriptionGroupByMeasure, - UnitSystem -} from '@shared/models/unit.models'; -import { AllMeasures } from '@core/services/unit/definitions/all'; -import { TranslateService } from '@ngx-translate/core'; - -type Entries = [S, T[keyof T]]; - -export class Converter< - TMeasures extends AllMeasures, - TUnits extends string, -> { - private readonly measureData: Record>; - private unitCache: Map< - string, - { - system: UnitSystem; - measure: TMeasures; - unit: Unit; - abbr: TUnits; - } - >; - - constructor( - measures: Record>, - unitCache: UnitCache - ) { - this.measureData = measures; - this.unitCache = unitCache; - } - - convertor(from: TUnits | string, to: TUnits | string): TbUnitConvertor { - return (value: number) => this.convert(value, from, to); - } - - convert(value: number, from: TUnits | string, to: TUnits | string): number { - const origin = this.getUnit(from); - const destination = this.getUnit(to); - - if (!origin) { - throw new Error(`Unsupported unit: ${from}`); - } - if (!destination) { - throw new Error(`Unsupported unit: ${to}`); - } - if (origin.abbr === destination.abbr) { - return value; - } - if (destination.measure !== origin.measure) { - throw Error(`Cannot convert incompatible measures: ${origin.measure} to ${destination.measure}`); - } - let result = value * origin.unit.to_anchor; - if (origin.unit.anchor_shift) { - result -= origin.unit.anchor_shift; - } - if (origin.system !== destination.system) { - const measureUnits = this.measureData[origin.measure][origin.system]; - const transform = measureUnits?.transform; - const ratio = measureUnits?.ratio; - if (typeof transform === 'function') { - result = transform(result); - } else if (typeof ratio === 'number') { - result *= ratio; - } else { - throw Error('System anchor requires a defined ratio or transform function'); - } - } - - if (destination.unit.anchor_shift) { - result += destination.unit.anchor_shift; - } - return result / destination.unit.to_anchor; - } - - getDefaultUnit(measureName: TMeasures | (string & {}), unitSystem: UnitSystem): TUnits { - if (!this.isMeasure(measureName)) { - return null; - } - const units = this.getUnitsForMeasure(measureName, unitSystem); - if (!units) { - return null; - } - for (const [abbr, unit] of Object.entries(units) as [TUnits, Unit][]) { - if (unit.to_anchor === 1 && (!unit.anchor_shift || unit.anchor_shift === 0)) { - return abbr; - } - } - return null; - } - - getUnit(abbr: TUnits | (string & {})): Conversion | null { - return this.unitCache.get(abbr) ?? null; - } - - describe(abbr: TUnits | (string & {})): UnitDescription { - const unit = this.getUnit(abbr); - return unit ? this.describeUnit(unit) : null; - } - - list(measureName?: TMeasures, unitSystem?: UnitSystem): UnitDescription[] { - const results: UnitDescription[] = []; - - const measures = measureName - ? { [measureName]: this.measureData[measureName] } as Record> - : this.measureData; - - for (const [name, measure] of Object.entries(measures) as [TMeasures, TbMeasure][]) { - if (!this.isMeasure(name)) { - continue; - } - - const systems = unitSystem - ? [unitSystem] - : (Object.keys(measure) as UnitSystem[]); - - for (const system of systems) { - const units = this.getUnitsForMeasure(name, system); - if (!units) { - continue; - } - - for (const [abbr, unit] of Object.entries(units) as [TUnits, Unit][]) { - results.push( - this.describeUnit({ - abbr, - measure: name as TMeasures, - system, - unit, - }) - ); - } - } - } - return results; - } - - listGroupByMeasure(measureName?: TMeasures, unitSystem?: UnitSystem): UnitDescriptionGroupByMeasure | never { - const results: UnitDescriptionGroupByMeasure = {}; - - const measures = measureName - ? { [measureName]: this.measureData[measureName]} as Record> - : this.measureData; - - for (const [name, measure] of Object.entries(measures) as [TMeasures, TbMeasure][]) { - if (!this.isMeasure(name)) { - continue; - } - - results[name] = []; - - const systems = unitSystem - ? [unitSystem] - : (Object.keys(measure) as UnitSystem[]); - - for (const system of systems) { - const units = this.getUnitsForMeasure(name, system); - if (!units) { - continue; - } - - for (const [abbr, unit] of Object.entries(units) as [TUnits, Unit][]) { - results[name].push( - this.describeUnit({ - abbr, - measure: name as TMeasures, - system, - unit, - }) - ); - } - } - } - return results; - } - - private describeUnit(unit: Conversion): UnitDescription { - return { - abbr: unit.abbr, - measure: unit.measure, - system: unit.system, - name: unit.unit.name, - tags: unit.unit.tags - }; - } - - private isMeasure(measureName: string): measureName is TMeasures { - return measureName in this.measureData; - } - - private getUnitsForMeasure( - measureName: TMeasures, - unitSystem: UnitSystem - ): Partial> | null { - const measure = this.measureData[measureName]; - let system = unitSystem; - let units = measure[system]?.units; - if (!units && unitSystem === UnitSystem.IMPERIAL) { - system = UnitSystem.METRIC; - units = measure[system]?.units; - } - return units ?? null; - } -} - -export function buildUnitCache< - TMeasures extends string, - TUnits extends string, ->(measures: Record>, - translate: TranslateService -) { - const unitCache: UnitCache = new Map(); - for (const [measureName, measure] of Object.entries(measures) as Entries< - typeof measures, - TMeasures - >[]) { - for (const [systemName, system] of Object.entries( - measure - ) as Entries, UnitSystem>[]) { - for (const [testAbbr, unit] of Object.entries(system.units) as Entries< - Record, - TUnits - >[]) { - unit.name = translate.instant(unit.name); - unitCache.set(testAbbr, { - measure: measureName, - system: systemName, - abbr: testAbbr, - unit, - }); - } - } - } - return unitCache; -} - -export function configureMeasurements< - TMeasures extends AllMeasures, - TUnits extends string, ->( - measures: Record>, - translate: TranslateService -): Converter { - if (typeof measures !== 'object') { - throw new TypeError('The measures argument needs to be an object'); - } - - const unitCache = buildUnitCache(measures, translate); - return new Converter(measures, unitCache); -} diff --git a/ui-ngx/src/app/core/services/unit/definitions/all.ts b/ui-ngx/src/app/core/services/unit/definitions/all.ts deleted file mode 100644 index 7b447de453..0000000000 --- a/ui-ngx/src/app/core/services/unit/definitions/all.ts +++ /dev/null @@ -1,121 +0,0 @@ -/// -/// Copyright © 2016-2025 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 { TbMeasure } from '@shared/models/unit.models'; -import acceleration, { AccelerationUnits } from '@core/services/unit/definitions/acceleration'; -import angle, { AngleUnits } from '@core/services/unit/definitions/angle'; -import angularAcceleration, { AngularAccelerationUnits } from '@core/services/unit/definitions/angular-acceleration'; -import area, { AreaUnits } from '@core/services/unit/definitions/area'; -import charge, { ChargeUnits } from '@core/services/unit/definitions/charge'; -import digital, { DigitalUnits } from '@core/services/unit/definitions/digital'; -import electricCurrent, { ElectricCurrentUnits } from '@core/services/unit/definitions/electric-current'; -import energy, { EnergyUnits } from '@core/services/unit/definitions/energy'; -import force, { ForceUnits } from '@core/services/unit/definitions/force'; -import frequency, { FrequencyUnits } from '@core/services/unit/definitions/frequency'; -import illuminance,{ IlluminanceUnits } from '@core/services/unit/definitions/illuminance'; -import length, { LengthUnits } from '@core/services/unit/definitions/length'; -import mass, { MassUnits } from '@core/services/unit/definitions/mass'; -import partsPer, { PartsPerUnits } from '@core/services/unit/definitions/parts-per'; -import power, { PowerUnits } from '@core/services/unit/definitions/power'; -import pressure, { PressureUnits } from '@core/services/unit/definitions/pressure'; -import speed, { SpeedUnits } from '@core/services/unit/definitions/speed'; -import temperature, { TemperatureUnits } from './temperature'; -import time, { TimeUnits } from './time'; -import torque, { TorqueUnits } from '@core/services/unit/definitions/torque'; -import voltage, { VoltageUnits } from '@core/services/unit/definitions/voltage'; -import volume, { VolumeUnits } from '@core/services/unit/definitions/volume'; -import volumeFlowRate, { VolumeFlowRateUnits } from '@core/services/unit/definitions/volume-flow-rate'; - -export type AllMeasuresUnits = - | AccelerationUnits - | AngleUnits - | AngularAccelerationUnits - | AreaUnits - | ChargeUnits - | DigitalUnits - | ElectricCurrentUnits - | EnergyUnits - | ForceUnits - | FrequencyUnits - | IlluminanceUnits - | LengthUnits - | MassUnits - | PartsPerUnits - | PowerUnits - | PressureUnits - | SpeedUnits - | TemperatureUnits - | TimeUnits - | TorqueUnits - | VoltageUnits - | VolumeUnits - | VolumeFlowRateUnits; - -export type AllMeasures = - | 'acceleration' - | 'angle' - | 'angular-acceleration' - | 'area' - | 'charge' - | 'digital' - | 'electric-current' - | 'energy' - | 'force' - | 'frequency' - | 'illuminance' - | 'length' - | 'mass' - | 'parts-per' - | 'power' - | 'pressure' - | 'speed' - | 'temperature' - | 'time' - | 'torque' - | 'voltage' - | 'volume' - | 'volume-flow-rate'; - -const allMeasures: Record< - AllMeasures, - TbMeasure -> = { - acceleration, - angle, - 'angular-acceleration': angularAcceleration, - area, - charge, - digital, - 'electric-current': electricCurrent, - energy, - force, - frequency, - illuminance, - length, - mass, - 'parts-per': partsPer, - power, - pressure, - speed, - temperature, - time, - torque, - voltage, - volume, - 'volume-flow-rate': volumeFlowRate, -}; - -export default allMeasures; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts index 55abf8dad0..ff54db2fc3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts @@ -33,14 +33,14 @@ import { ColorProcessor, ComponentStyle, DateFormatProcessor, - FormatValueProcessor, getDataKey, getLabel, getSingleTsValue, iconStyle, overlayStyle, resolveCssSize, - textStyle + textStyle, + ValueFormatProcessor } from '@shared/models/widget-settings.models'; import { valueCardDefaultSettings, ValueCardLayout, ValueCardWidgetSettings } from './value-card-widget.models'; import { WidgetComponent } from '@home/components/widget/widget.component'; @@ -101,7 +101,7 @@ export class ValueCardWidgetComponent implements OnInit, AfterViewInit, OnDestro private panelResize$: ResizeObserver; private horizontal = false; - private formatValue: FormatValueProcessor; + private valueFormat: ValueFormatProcessor; constructor(private imagePipe: ImagePipe, private sanitizer: DomSanitizer, @@ -125,7 +125,7 @@ export class ValueCardWidgetComponent implements OnInit, AfterViewInit, OnDestro if (dataKey?.units) { units = dataKey.units; } - this.formatValue = FormatValueProcessor.fromSettings(this.ctx.$injector, {units: units, dec: decimals}); + this.valueFormat = ValueFormatProcessor.fromSettings(this.ctx.$injector, {units: units, dec: decimals}); this.layout = this.settings.layout; @@ -188,7 +188,7 @@ export class ValueCardWidgetComponent implements OnInit, AfterViewInit, OnDestro if (tsValue && isDefinedAndNotNull(tsValue[1]) && tsValue[0] !== 0) { ts = tsValue[0]; value = tsValue[1]; - this.valueText = this.formatValue.format(value); // formatValue(value, this.decimals, this.units, false); + this.valueText = this.valueFormat.update(value); // formatValue(value, this.decimals, this.units, false); } else { this.valueText = 'N/A'; } diff --git a/ui-ngx/src/app/modules/home/models/services.map.ts b/ui-ngx/src/app/modules/home/models/services.map.ts index 481e070b90..94c09bb3d3 100644 --- a/ui-ngx/src/app/modules/home/models/services.map.ts +++ b/ui-ngx/src/app/modules/home/models/services.map.ts @@ -51,7 +51,7 @@ import { TenantProfileService } from '@core/http/tenant-profile.service'; import { UiSettingsService } from '@core/http/ui-settings.service'; import { UsageInfoService } from '@core/http/usage-info.service'; import { EventService } from '@core/http/event.service'; -import { UnitService } from '@core/services/unit/unit.service'; +import { UnitService } from '@core/services/unit.service'; import { AuditLogService } from '@core/http/audit-log.service'; export const ServicesMap = new Map>( diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts b/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts index ebac40571e..7d1aa2aa4e 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.component.ts @@ -32,7 +32,7 @@ import { isDefinedAndNotNull, isNotEmptyStr } from '@core/utils'; import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { AuthService } from '@core/auth/auth.service'; import { UnitSystem, UnitSystems } from '@shared/models/unit.models'; -import { UnitService } from '@core/services/unit/unit.service'; +import { UnitService } from '@core/services/unit.service'; @Component({ selector: 'tb-profile', diff --git a/ui-ngx/src/app/shared/components/convert-unit-settings-panel.component.ts b/ui-ngx/src/app/shared/components/convert-unit-settings-panel.component.ts index e8afedfffa..624bd9ec33 100644 --- a/ui-ngx/src/app/shared/components/convert-unit-settings-panel.component.ts +++ b/ui-ngx/src/app/shared/components/convert-unit-settings-panel.component.ts @@ -15,12 +15,11 @@ /// import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewEncapsulation } from '@angular/core'; -import { TbUnit, UnitDescription, UnitSystem } from '@shared/models/unit.models'; +import { AllMeasures, TbUnit, UnitInfo, UnitSystem } from '@shared/models/unit.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { FormBuilder, Validators } from '@angular/forms'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { UnitService } from '@core/services/unit/unit.service'; -import { AllMeasures } from '@core/services/unit/definitions/all'; +import { UnitService } from '@core/services/unit.service'; import { debounceTime, first } from 'rxjs/operators'; import { isEmptyStr } from '@core/utils'; import type { UnitInputComponent } from '@shared/components/unit-input.component'; @@ -69,7 +68,7 @@ export class ConvertUnitSettingsPanelComponent implements OnInit { debounceTime(200), takeUntilDestroyed() ).subscribe(unit => { - const unitDescription = this.unitService.getUnitDescription(unit); + const unitDescription = this.unitService.getUnitInfo(unit); if (unitDescription) { this.convertUnitForm.get('convertUnit').enable({emitEvent: true}); this.measure = unitDescription.measure; @@ -109,7 +108,7 @@ export class ConvertUnitSettingsPanelComponent implements OnInit { } ngOnInit() { - let unitDescription: UnitDescription; + let unitDescription: UnitInfo; if (this.required) { this.convertUnitForm.get('from').setValidators(Validators.required); this.convertUnitForm.get('from').updateValueAndValidity({emitEvent: false}); @@ -117,13 +116,13 @@ export class ConvertUnitSettingsPanelComponent implements OnInit { if (typeof this.unit === 'string') { this.convertUnitForm.get('convertUnit').setValue(false, {onlySelf: true}); this.convertUnitForm.get('from').setValue(this.unit, {emitEvent: true}); - unitDescription = this.unitService.getUnitDescription(this.unit); + unitDescription = this.unitService.getUnitInfo(this.unit); } else if (this.unit === null) { this.convertUnitForm.get('convertUnit').setValue(false, {onlySelf: true}); this.convertUnitForm.get('from').setValue(null, {emitEvent: true}); } else { this.convertUnitForm.patchValue(this.unit, {emitEvent: false}); - unitDescription = this.unitService.getUnitDescription(this.unit.from); + unitDescription = this.unitService.getUnitInfo(this.unit.from); } if (unitDescription?.measure) { 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 9cd044d3dd..9b582d1266 100644 --- a/ui-ngx/src/app/shared/components/unit-input.component.ts +++ b/ui-ngx/src/app/shared/components/unit-input.component.ts @@ -31,10 +31,9 @@ import { } from '@angular/core'; import { ControlValueAccessor, FormBuilder, FormControl, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { Observable, of, shareReplay } from 'rxjs'; -import { searchUnits, TbUnit, UnitDescription, UnitsType, UnitSystem } from '@shared/models/unit.models'; +import { AllMeasures, searchUnits, TbUnit, UnitInfo, UnitsType, UnitSystem } from '@shared/models/unit.models'; import { map, mergeMap } from 'rxjs/operators'; -import { AllMeasures } from '@core/services/unit/definitions/all'; -import { UnitService } from '@core/services/unit/unit.service'; +import { UnitService } from '@core/services/unit.service'; import { TbPopoverService } from '@shared/components/popover.service'; import { ConvertUnitSettingsPanelComponent } from '@shared/components/convert-unit-settings-panel.component'; import { isNotEmptyStr, isObject } from '@core/utils'; @@ -57,7 +56,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang @HostBinding('style.display') readonly hostDisplay = 'flex'; @ViewChild('unitInput', {static: true}) unitInput: ElementRef; - unitsFormControl: FormControl; + unitsFormControl: FormControl; @Input({transform: booleanAttribute}) disabled: boolean; @@ -77,7 +76,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang @Input({transform: booleanAttribute}) allowConverted = false; - filteredUnits: Observable]>>; + filteredUnits: Observable]>>; searchText = ''; @@ -87,7 +86,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang private modelValue: TbUnit | null; - private fetchUnits$: Observable]>> = null; + private fetchUnits$: Observable]>> = null; private propagateChange = (_val: any) => {}; @@ -100,7 +99,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang } ngOnInit() { - this.unitsFormControl = this.fb.control('', this.required ? [Validators.required] : []); + this.unitsFormControl = this.fb.control('', this.required ? [Validators.required] : []); this.filteredUnits = this.unitsFormControl.valueChanges .pipe( map(value => { @@ -127,7 +126,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang this.searchText = ''; this.modelValue = symbol; if (typeof symbol === 'string') { - this.unitsFormControl.patchValue(this.unitService.getUnitDescription(symbol) ?? symbol, {emitEvent: false}); + this.unitsFormControl.patchValue(this.unitService.getUnitInfo(symbol) ?? symbol, {emitEvent: false}); this.isUnitMapping = false; } else { this.unitsFormControl.patchValue(symbol, {emitEvent: false}); @@ -143,7 +142,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang } } - displayUnitFn(unit?: TbUnit | UnitDescription): string | undefined { + displayUnitFn(unit?: TbUnit | UnitInfo): string | undefined { if (unit) { return this.getUnitSymbol(unit); } @@ -210,7 +209,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang } } - private updateView(value: UnitDescription | TbUnit ) { + private updateView(value: UnitInfo | TbUnit ) { const res = this.getTbUnit(value); if (this.modelValue !== res) { this.modelValue = res; @@ -219,22 +218,22 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang } } - private fetchUnits(searchText?: string): Observable]>> { + private fetchUnits(searchText?: string): Observable]>> { this.searchText = searchText; return this.unitsConstant().pipe( map(unit => this.searchUnit(unit, searchText)) ); } - private unitsConstant(): Observable]>> { + private unitsConstant(): Observable]>> { if (this.fetchUnits$ === null) { - this.fetchUnits$ = of(this.unitService.getUnitsGroupByMeasure(this.measure, this.unitSystem)).pipe( + this.fetchUnits$ = of(this.unitService.getUnitsGroupedByMeasure(this.measure, this.unitSystem)).pipe( map(data => { - let objectData = Object.entries(data) as Array<[AllMeasures, UnitDescription[]]>; + let objectData = Object.entries(data) as Array<[AllMeasures, UnitInfo[]]>; if (this.tagFilter) { objectData = objectData - .map((measure) => [measure[0], measure[1].filter(u => u.tags.includes(this.tagFilter))] as [AllMeasures, UnitDescription[]]) + .map((measure) => [measure[0], measure[1].filter(u => u.tags.includes(this.tagFilter))] as [AllMeasures, UnitInfo[]]) .filter((measure) => measure[1].length > 0); } return objectData; @@ -245,17 +244,17 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang return this.fetchUnits$; } - private searchUnit(units: Array<[AllMeasures, Array]>, searchText?: string): Array<[AllMeasures, Array]> { + private searchUnit(units: Array<[AllMeasures, Array]>, searchText?: string): Array<[AllMeasures, Array]> { if (isNotEmptyStr(searchText)) { const filterValue = searchText.trim().toUpperCase() return units - .map(measure => [measure[0], searchUnits(measure[1], filterValue)] as [AllMeasures, UnitDescription[]]) + .map(measure => [measure[0], searchUnits(measure[1], filterValue)] as [AllMeasures, UnitInfo[]]) .filter((measure) => measure[1].length > 0); } return units; } - private getUnitSymbol(value: TbUnit | UnitDescription | null): string { + private getUnitSymbol(value: TbUnit | UnitInfo | null): string { if (value === null) { return ''; } @@ -268,7 +267,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang return value.from; } - private getTbUnit(value: TbUnit | UnitDescription | null): TbUnit { + private getTbUnit(value: TbUnit | UnitInfo | null): TbUnit { if (value === null) { return null; } diff --git a/ui-ngx/src/app/shared/models/public-api.ts b/ui-ngx/src/app/shared/models/public-api.ts index 53e4bb286e..fbee1ec6bb 100644 --- a/ui-ngx/src/app/shared/models/public-api.ts +++ b/ui-ngx/src/app/shared/models/public-api.ts @@ -54,6 +54,7 @@ export * from './rule-node.models'; export * from './settings.models'; export * from './tenant.model'; export * from './user.model'; +export * from './unit.models'; export * from './user-settings.models'; export * from './widget-settings.models'; export * from './widget.models'; diff --git a/ui-ngx/src/app/shared/models/unit.models.ts b/ui-ngx/src/app/shared/models/unit.models.ts index 01f88c3406..4f73fc4fbc 100644 --- a/ui-ngx/src/app/shared/models/unit.models.ts +++ b/ui-ngx/src/app/shared/models/unit.models.ts @@ -14,16 +14,118 @@ /// limitations under the License. /// -import { AllMeasures } from '@core/services/unit/definitions/all'; +import acceleration, { AccelerationUnits } from '@shared/models/units/acceleration'; +import angle, { AngleUnits } from '@shared/models/units/angle'; +import angularAcceleration, { AngularAccelerationUnits } from '@shared/models/units/angular-acceleration'; +import area, { AreaUnits } from '@shared/models/units/area'; +import charge, { ChargeUnits } from '@shared/models/units/charge'; +import digital, { DigitalUnits } from '@shared/models/units/digital'; +import electricCurrent, { ElectricCurrentUnits } from '@shared/models/units/electric-current'; +import energy, { EnergyUnits } from '@shared/models/units/energy'; +import force, { ForceUnits } from '@shared/models/units/force'; +import frequency, { FrequencyUnits } from '@shared/models/units/frequency'; +import illuminance, { IlluminanceUnits } from '@shared/models/units/illuminance'; +import length, { LengthUnits } from '@shared/models/units/length'; +import mass, { MassUnits } from '@shared/models/units/mass'; +import partsPer, { PartsPerUnits } from '@shared/models/units/parts-per'; +import power, { PowerUnits } from '@shared/models/units/power'; +import pressure, { PressureUnits } from '@shared/models/units/pressure'; +import speed, { SpeedUnits } from '@shared/models/units/speed'; +import temperature, { TemperatureUnits } from '@shared/models/units/temperature'; +import time, { TimeUnits } from '@shared/models/units/time'; +import torque, { TorqueUnits } from '@shared/models/units/torque'; +import voltage, { VoltageUnits } from '@shared/models/units/voltage'; +import volume, { VolumeUnits } from '@shared/models/units/volume'; +import volumeFlowRate, { VolumeFlowRateUnits } from '@shared/models/units/volume-flow-rate'; +import { TranslateService } from '@ngx-translate/core'; + +export type AllMeasuresUnits = + | AccelerationUnits + | AngleUnits + | AngularAccelerationUnits + | AreaUnits + | ChargeUnits + | DigitalUnits + | ElectricCurrentUnits + | EnergyUnits + | ForceUnits + | FrequencyUnits + | IlluminanceUnits + | LengthUnits + | MassUnits + | PartsPerUnits + | PowerUnits + | PressureUnits + | SpeedUnits + | TemperatureUnits + | TimeUnits + | TorqueUnits + | VoltageUnits + | VolumeUnits + | VolumeFlowRateUnits; + +export type AllMeasures = + | 'acceleration' + | 'angle' + | 'angular-acceleration' + | 'area' + | 'charge' + | 'digital' + | 'electric-current' + | 'energy' + | 'force' + | 'frequency' + | 'illuminance' + | 'length' + | 'mass' + | 'parts-per' + | 'power' + | 'pressure' + | 'speed' + | 'temperature' + | 'time' + | 'torque' + | 'voltage' + | 'volume' + | 'volume-flow-rate'; + +const allMeasures: Record< + AllMeasures, + TbMeasure +> = Object.freeze({ + acceleration, + angle, + 'angular-acceleration': angularAcceleration, + area, + charge, + digital, + 'electric-current': electricCurrent, + energy, + force, + frequency, + illuminance, + length, + mass, + 'parts-per': partsPer, + power, + pressure, + speed, + temperature, + time, + torque, + voltage, + volume, + 'volume-flow-rate': volumeFlowRate, +}); export enum UnitsType { capacity = 'capacity' } -export type TbUnitConvertor = (value: number) => number; -export type UnitDescriptionGroupByMeasure = Partial>; +export type TbUnitConverter = (value: number) => number; +export type UnitInfoGroupByMeasure = Partial>; -export interface UnitDescription { +export interface UnitInfo { abbr: string; measure: AllMeasures; system: UnitSystem; @@ -70,21 +172,247 @@ export interface Conversion { unit: Unit; } -export type UnitCache = Map< - string, - { +export type UnitCache = Map; -const searchUnitTags = (unit: UnitDescription, searchText: string): boolean => +const searchUnitTags = (unit: UnitInfo, searchText: string): boolean => !!unit.tags.find(t => t.toUpperCase().includes(searchText)); -export const searchUnits = (_units: Array, searchText: string): Array => _units.filter( +export const searchUnits = (_units: Array, searchText: string): Array => _units.filter( u => u.abbr.toUpperCase().includes(searchText) || u.name.toUpperCase().includes(searchText) || searchUnitTags(u, searchText) ); + +type Entries = [S, T[keyof T]]; + +export class Converter { + private readonly measureData: Record>; + private unitCache: Map< + string, + { + system: UnitSystem; + measure: AllMeasures; + unit: Unit; + abbr: AllMeasuresUnits; + } + >; + + constructor( + measures: Record>, + unitCache: UnitCache + ) { + this.measureData = measures; + this.unitCache = unitCache; + } + + getUnitConverter(from: AllMeasuresUnits | string, to: AllMeasuresUnits | string): TbUnitConverter { + return (value: number) => this.convert(value, from, to); + } + + convert(value: number, from: AllMeasuresUnits | string, to: AllMeasuresUnits | string): number { + const origin = this.getUnit(from); + const destination = this.getUnit(to); + + if (!origin) { + throw new Error(`Unsupported unit: ${from}`); + } + if (!destination) { + throw new Error(`Unsupported unit: ${to}`); + } + if (origin.abbr === destination.abbr) { + return value; + } + if (destination.measure !== origin.measure) { + throw Error(`Cannot convert incompatible measures: ${origin.measure} to ${destination.measure}`); + } + let result = value * origin.unit.to_anchor; + if (origin.unit.anchor_shift) { + result -= origin.unit.anchor_shift; + } + if (origin.system !== destination.system) { + const measureUnits = this.measureData[origin.measure][origin.system]; + const transform = measureUnits?.transform; + const ratio = measureUnits?.ratio; + if (typeof transform === 'function') { + result = transform(result); + } else if (typeof ratio === 'number') { + result *= ratio; + } else { + throw Error('System anchor requires a defined ratio or transform function'); + } + } + + if (destination.unit.anchor_shift) { + result += destination.unit.anchor_shift; + } + return result / destination.unit.to_anchor; + } + + getDefaultUnit(measureName: AllMeasures | (string & {}), unitSystem: UnitSystem): AllMeasuresUnits { + if (!this.isMeasure(measureName)) { + return null; + } + const units = this.getUnitsForMeasure(measureName, unitSystem); + if (!units) { + return null; + } + for (const [abbr, unit] of Object.entries(units) as [AllMeasuresUnits, Unit][]) { + if (unit.to_anchor === 1 && (!unit.anchor_shift || unit.anchor_shift === 0)) { + return abbr; + } + } + return null; + } + + getUnit(abbr: AllMeasuresUnits | string): Conversion | null { + return this.unitCache.get(abbr) ?? null; + } + + describe(abbr: AllMeasuresUnits | string): UnitInfo { + const unit = this.getUnit(abbr); + return unit ? this.describeUnit(unit) : null; + } + + listUnits(measureName?: AllMeasures, unitSystem?: UnitSystem): UnitInfo[] { + const results: UnitInfo[] = []; + + const measures = measureName + ? { [measureName]: this.measureData[measureName] } as Record> + : this.measureData; + + for (const [name, measure] of Object.entries(measures) as [AllMeasures, TbMeasure][]) { + if (!this.isMeasure(name)) { + continue; + } + + const systems = unitSystem + ? [unitSystem] + : (Object.keys(measure) as UnitSystem[]); + + for (const system of systems) { + const units = this.getUnitsForMeasure(name, system); + if (!units) { + continue; + } + + for (const [abbr, unit] of Object.entries(units) as [AllMeasuresUnits, Unit][]) { + results.push( + this.describeUnit({ + abbr, + measure: name as AllMeasures, + system, + unit, + }) + ); + } + } + } + return results; + } + + unitsGroupByMeasure(measureName?: AllMeasures, unitSystem?: UnitSystem): UnitInfoGroupByMeasure | never { + const results: UnitInfoGroupByMeasure = {}; + + const measures = measureName + ? { [measureName]: this.measureData[measureName]} as Record> + : this.measureData; + + for (const [name, measure] of Object.entries(measures) as [AllMeasures, TbMeasure][]) { + if (!this.isMeasure(name)) { + continue; + } + + results[name] = []; + + const systems = unitSystem + ? [unitSystem] + : (Object.keys(measure) as UnitSystem[]); + + for (const system of systems) { + const units = this.getUnitsForMeasure(name, system); + if (!units) { + continue; + } + + for (const [abbr, unit] of Object.entries(units) as [AllMeasuresUnits, Unit][]) { + results[name].push( + this.describeUnit({ + abbr, + measure: name as AllMeasures, + system, + unit, + }) + ); + } + } + } + return results; + } + + private describeUnit(unit: Conversion): UnitInfo { + return { + abbr: unit.abbr, + measure: unit.measure, + system: unit.system, + name: unit.unit.name, + tags: unit.unit.tags + }; + } + + private isMeasure(measureName: string): boolean { + return measureName in this.measureData; + } + + private getUnitsForMeasure( + measureName: AllMeasures | string, + unitSystem: UnitSystem + ): Partial> | null { + const measure = this.measureData[measureName]; + let system = unitSystem; + let units = measure[system]?.units; + if (!units && unitSystem === UnitSystem.IMPERIAL) { + system = UnitSystem.METRIC; + units = measure[system]?.units; + } + return units ?? null; + } +} + +function buildUnitCache(measures: Record>, + translate: TranslateService +) { + const unitCache: UnitCache = new Map(); + for (const [measureName, measure] of Object.entries(measures) as Entries< + typeof measures, + AllMeasures + >[]) { + for (const [systemName, system] of Object.entries( + measure + ) as Entries, UnitSystem>[]) { + for (const [testAbbr, unit] of Object.entries(system.units) as Entries< + Record, + AllMeasuresUnits + >[]) { + unit.name = translate.instant(unit.name); + unitCache.set(testAbbr, { + measure: measureName, + system: systemName, + abbr: testAbbr, + unit, + }); + } + } + } + return unitCache; +} + +export function getUnitConverter(translate: TranslateService): Converter { + const unitCache = buildUnitCache(allMeasures, translate); + return new Converter(allMeasures, unitCache); +} diff --git a/ui-ngx/src/app/core/services/unit/definitions/acceleration.ts b/ui-ngx/src/app/shared/models/units/acceleration.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/acceleration.ts rename to ui-ngx/src/app/shared/models/units/acceleration.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/angle.ts b/ui-ngx/src/app/shared/models/units/angle.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/angle.ts rename to ui-ngx/src/app/shared/models/units/angle.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/angular-acceleration.ts b/ui-ngx/src/app/shared/models/units/angular-acceleration.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/angular-acceleration.ts rename to ui-ngx/src/app/shared/models/units/angular-acceleration.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/area.ts b/ui-ngx/src/app/shared/models/units/area.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/area.ts rename to ui-ngx/src/app/shared/models/units/area.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/charge.ts b/ui-ngx/src/app/shared/models/units/charge.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/charge.ts rename to ui-ngx/src/app/shared/models/units/charge.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/digital.ts b/ui-ngx/src/app/shared/models/units/digital.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/digital.ts rename to ui-ngx/src/app/shared/models/units/digital.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/electric-current.ts b/ui-ngx/src/app/shared/models/units/electric-current.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/electric-current.ts rename to ui-ngx/src/app/shared/models/units/electric-current.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/energy.ts b/ui-ngx/src/app/shared/models/units/energy.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/energy.ts rename to ui-ngx/src/app/shared/models/units/energy.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/force.ts b/ui-ngx/src/app/shared/models/units/force.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/force.ts rename to ui-ngx/src/app/shared/models/units/force.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/frequency.ts b/ui-ngx/src/app/shared/models/units/frequency.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/frequency.ts rename to ui-ngx/src/app/shared/models/units/frequency.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/illuminance.ts b/ui-ngx/src/app/shared/models/units/illuminance.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/illuminance.ts rename to ui-ngx/src/app/shared/models/units/illuminance.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/length.ts b/ui-ngx/src/app/shared/models/units/length.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/length.ts rename to ui-ngx/src/app/shared/models/units/length.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/mass.ts b/ui-ngx/src/app/shared/models/units/mass.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/mass.ts rename to ui-ngx/src/app/shared/models/units/mass.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/parts-per.ts b/ui-ngx/src/app/shared/models/units/parts-per.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/parts-per.ts rename to ui-ngx/src/app/shared/models/units/parts-per.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/power.ts b/ui-ngx/src/app/shared/models/units/power.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/power.ts rename to ui-ngx/src/app/shared/models/units/power.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/pressure.ts b/ui-ngx/src/app/shared/models/units/pressure.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/pressure.ts rename to ui-ngx/src/app/shared/models/units/pressure.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/speed.ts b/ui-ngx/src/app/shared/models/units/speed.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/speed.ts rename to ui-ngx/src/app/shared/models/units/speed.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/temperature.ts b/ui-ngx/src/app/shared/models/units/temperature.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/temperature.ts rename to ui-ngx/src/app/shared/models/units/temperature.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/time.ts b/ui-ngx/src/app/shared/models/units/time.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/time.ts rename to ui-ngx/src/app/shared/models/units/time.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/torque.ts b/ui-ngx/src/app/shared/models/units/torque.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/torque.ts rename to ui-ngx/src/app/shared/models/units/torque.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/voltage.ts b/ui-ngx/src/app/shared/models/units/voltage.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/voltage.ts rename to ui-ngx/src/app/shared/models/units/voltage.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/volume-flow-rate.ts b/ui-ngx/src/app/shared/models/units/volume-flow-rate.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/volume-flow-rate.ts rename to ui-ngx/src/app/shared/models/units/volume-flow-rate.ts diff --git a/ui-ngx/src/app/core/services/unit/definitions/volume.ts b/ui-ngx/src/app/shared/models/units/volume.ts similarity index 100% rename from ui-ngx/src/app/core/services/unit/definitions/volume.ts rename to ui-ngx/src/app/shared/models/units/volume.ts diff --git a/ui-ngx/src/app/shared/models/widget-settings.models.ts b/ui-ngx/src/app/shared/models/widget-settings.models.ts index 89c2824545..d6f46b0815 100644 --- a/ui-ngx/src/app/shared/models/widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/widget-settings.models.ts @@ -53,8 +53,8 @@ import { WidgetSubscriptionCallbacks, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; -import { UnitService } from '@core/services/unit/unit.service'; -import { TbUnit, TbUnitConvertor, TbUnitMapping } from '@shared/models/unit.models'; +import { UnitService } from '@core/services/unit.service'; +import { TbUnit, TbUnitConverter, TbUnitMapping } from '@shared/models/unit.models'; export type ComponentStyle = {[klass: string]: any}; @@ -862,50 +862,50 @@ export class AutoDateFormatProcessor extends DateFormatProcessor { } } -export interface FormatValueSettingProcessor { +export interface ValueFormatSettingProcessor { dec?: number; units?: TbUnit; showZeroDecimals?: boolean; } -export abstract class FormatValueProcessor { +export abstract class ValueFormatProcessor { - static fromSettings($injector: Injector, settings: FormatValueSettingProcessor): FormatValueProcessor { - if (typeof settings.units !== 'string' && isDefinedAndNotNull(settings.units?.from)) { - return new ConvertUnitProcessor($injector, settings) + static fromSettings($injector: Injector, settings: ValueFormatSettingProcessor): ValueFormatProcessor { + if (settings.units !== null && typeof settings.units === 'object') { + return new ConverterValueFormatProcessor($injector, settings) } else { - return new SimpleUnitProcessor($injector, settings); + return new SimpleValueFormatProcessor($injector, settings); } } protected constructor(protected $injector: Injector, - protected settings: FormatValueSettingProcessor) { + protected settings: ValueFormatSettingProcessor) { } - abstract format(value: any): string; + abstract update(value: any): string; } -export class SimpleUnitProcessor extends FormatValueProcessor { +export class SimpleValueFormatProcessor extends ValueFormatProcessor { private readonly isDefinedUnit: boolean; private readonly isDefinedDec: boolean; - private readonly showZeroDecimals: boolean; + private readonly hideZeroDecimals: boolean; constructor(protected $injector: Injector, - protected settings: FormatValueSettingProcessor) { + protected settings: ValueFormatSettingProcessor) { super($injector, settings); this.isDefinedUnit = isNotEmptyStr(settings.units); this.isDefinedDec = isDefinedAndNotNull(settings.dec); - this.showZeroDecimals = !!settings.showZeroDecimals; + this.hideZeroDecimals = !settings.showZeroDecimals; } - format(value: any): string { + update(value: any): string { if (isDefinedAndNotNull(value) && isNumeric(value) && (this.isDefinedDec || this.isDefinedUnit || Number(value).toString() === value)) { let formatted = value; if (this.isDefinedDec) { formatted = Number(formatted).toFixed(this.settings.dec); } - if (!this.showZeroDecimals) { + if (this.hideZeroDecimals) { formatted = Number(formatted) } formatted = formatted.toString(); @@ -918,39 +918,37 @@ export class SimpleUnitProcessor extends FormatValueProcessor { } } -export class ConvertUnitProcessor extends FormatValueProcessor { +export class ConverterValueFormatProcessor extends ValueFormatProcessor { private readonly isDefinedDec: boolean; - private readonly showZeroDecimals: boolean; - private readonly unitConvertor: TbUnitConvertor; + private readonly hideZeroDecimals: boolean; + private readonly unitConverter: TbUnitConverter; private readonly unitAbbr: string; constructor(protected $injector: Injector, - protected settings: FormatValueSettingProcessor) { + protected settings: ValueFormatSettingProcessor) { super($injector, settings); const unitService = this.$injector.get(UnitService); - const userUnitSystem = unitService.getUnitSystem(); const unit = settings.units as TbUnitMapping; - const fromUnit = unit.from; - this.unitAbbr = isNotEmptyStr(unit[userUnitSystem]) ? unit[userUnitSystem] : fromUnit; + this.unitAbbr = unitService.getTargetUnitSymbol(unit); try { - this.unitConvertor = unitService.geUnitConvertor(fromUnit, this.unitAbbr); + this.unitConverter = unitService.geUnitConverter(unit); } catch (e) {/**/} this.isDefinedDec = isDefinedAndNotNull(settings.dec); - this.showZeroDecimals = !!settings.showZeroDecimals; + this.hideZeroDecimals = !settings.showZeroDecimals; } - format(value: any): string { + update(value: any): string { if (isDefinedAndNotNull(value) && isNumeric(value)) { let formatted: number | string = Number(value); - if (this.unitConvertor) { - formatted = this.unitConvertor(value); + if (this.unitConverter) { + formatted = this.unitConverter(value); } if (this.isDefinedDec) { formatted = Number(formatted).toFixed(this.settings.dec); } - if (!this.showZeroDecimals) { + if (this.hideZeroDecimals) { formatted = Number(formatted) } formatted = formatted.toString();