Browse Source

UI: Refactoring units service

pull/13282/head
Vladyslav_Prykhodko 1 year ago
parent
commit
1734a890e3
  1. 2
      ui-ngx/src/app/app.component.ts
  2. 2
      ui-ngx/src/app/core/services/public-api.ts
  3. 71
      ui-ngx/src/app/core/services/unit.service.ts
  4. 272
      ui-ngx/src/app/core/services/unit/converter-unit.ts
  5. 121
      ui-ngx/src/app/core/services/unit/definitions/all.ts
  6. 10
      ui-ngx/src/app/modules/home/components/widget/lib/cards/value-card-widget.component.ts
  7. 2
      ui-ngx/src/app/modules/home/models/services.map.ts
  8. 2
      ui-ngx/src/app/modules/home/pages/profile/profile.component.ts
  9. 13
      ui-ngx/src/app/shared/components/convert-unit-settings-panel.component.ts
  10. 37
      ui-ngx/src/app/shared/components/unit-input.component.ts
  11. 1
      ui-ngx/src/app/shared/models/public-api.ts
  12. 350
      ui-ngx/src/app/shared/models/unit.models.ts
  13. 0
      ui-ngx/src/app/shared/models/units/acceleration.ts
  14. 0
      ui-ngx/src/app/shared/models/units/angle.ts
  15. 0
      ui-ngx/src/app/shared/models/units/angular-acceleration.ts
  16. 0
      ui-ngx/src/app/shared/models/units/area.ts
  17. 0
      ui-ngx/src/app/shared/models/units/charge.ts
  18. 0
      ui-ngx/src/app/shared/models/units/digital.ts
  19. 0
      ui-ngx/src/app/shared/models/units/electric-current.ts
  20. 0
      ui-ngx/src/app/shared/models/units/energy.ts
  21. 0
      ui-ngx/src/app/shared/models/units/force.ts
  22. 0
      ui-ngx/src/app/shared/models/units/frequency.ts
  23. 0
      ui-ngx/src/app/shared/models/units/illuminance.ts
  24. 0
      ui-ngx/src/app/shared/models/units/length.ts
  25. 0
      ui-ngx/src/app/shared/models/units/mass.ts
  26. 0
      ui-ngx/src/app/shared/models/units/parts-per.ts
  27. 0
      ui-ngx/src/app/shared/models/units/power.ts
  28. 0
      ui-ngx/src/app/shared/models/units/pressure.ts
  29. 0
      ui-ngx/src/app/shared/models/units/speed.ts
  30. 0
      ui-ngx/src/app/shared/models/units/temperature.ts
  31. 0
      ui-ngx/src/app/shared/models/units/time.ts
  32. 0
      ui-ngx/src/app/shared/models/units/torque.ts
  33. 0
      ui-ngx/src/app/shared/models/units/voltage.ts
  34. 0
      ui-ngx/src/app/shared/models/units/volume-flow-rate.ts
  35. 0
      ui-ngx/src/app/shared/models/units/volume.ts
  36. 56
      ui-ngx/src/app/shared/models/widget-settings.models.ts

2
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 { ActionSettingsChangeLanguage } from '@core/settings/settings.actions';
import { SETTINGS_KEY } from '@core/settings/settings.effects'; import { SETTINGS_KEY } from '@core/settings/settings.effects';
import { initCustomJQueryEvents } from '@shared/models/jquery-event.models'; import { initCustomJQueryEvents } from '@shared/models/jquery-event.models';
import { UnitService } from '@core/services/unit/unit.service'; import { UnitService } from '@core/services/unit.service';
@Component({ @Component({
selector: 'tb-root', selector: 'tb-root',

2
ui-ngx/src/app/core/services/public-api.ts

@ -15,7 +15,6 @@
/// ///
export * from './script/node-script-test.service'; export * from './script/node-script-test.service';
export * from './unit/unit.service';
export * from './broadcast.models'; export * from './broadcast.models';
export * from './broadcast.service'; export * from './broadcast.service';
export * from './dashboard-utils.service'; export * from './dashboard-utils.service';
@ -29,5 +28,6 @@ export * from './raf.service';
export * from './resources.service'; export * from './resources.service';
export * from './time.service'; export * from './time.service';
export * from './title.service'; export * from './title.service';
export * from './unit.service';
export * from './utils.service'; export * from './utils.service';
export * from './window.service'; export * from './window.service';

71
ui-ngx/src/app/core/services/unit/unit.service.ts → ui-ngx/src/app/core/services/unit.service.ts

@ -17,18 +17,19 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import moment from 'moment-timezone'; import moment from 'moment-timezone';
import { import {
TbUnitConvertor, AllMeasures,
UnitDescription, AllMeasuresUnits,
UnitDescriptionGroupByMeasure, Converter,
getUnitConverter,
TbUnitConverter,
TbUnitMapping,
UnitInfo,
UnitInfoGroupByMeasure,
UnitSystem UnitSystem
} from '@shared/models/unit.models'; } from '@shared/models/unit.models';
import { isNotEmptyStr } from '@core/utils'; import { isNotEmptyStr, isObject } from '@core/utils';
import { configureMeasurements, Converter } from '@core/services/unit/converter-unit';
import allMeasures, { AllMeasures, AllMeasuresUnits } from '@core/services/unit/definitions/all';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -36,18 +37,17 @@ import { AppState } from '@core/core.state';
export class UnitService { export class UnitService {
private currentUnitSystem: UnitSystem = UnitSystem.METRIC; private currentUnitSystem: UnitSystem = UnitSystem.METRIC;
private converter: Converter<AllMeasures, AllMeasuresUnits>; private converter: Converter;
constructor(private store: Store<AppState>, constructor(private translate: TranslateService) {
private translate: TranslateService) {
this.translate.onLangChange.pipe( this.translate.onLangChange.pipe(
takeUntilDestroyed() takeUntilDestroyed()
).subscribe(() => { ).subscribe(() => {
this.converter = configureMeasurements<AllMeasures, AllMeasuresUnits>(allMeasures, this.translate); this.converter = getUnitConverter(this.translate);
console.warn(this.converter?.list()); console.warn(this.converter?.listUnits());
console.warn(this.converter?.list('temperature')); console.warn(this.converter?.listUnits('temperature'));
console.warn(this.converter?.list('temperature', UnitSystem.METRIC)); console.warn(this.converter?.listUnits('temperature', UnitSystem.METRIC));
console.warn(this.converter?.list(null, UnitSystem.IMPERIAL)); console.warn(this.converter?.listUnits(null, UnitSystem.IMPERIAL));
}); });
} }
@ -64,28 +64,47 @@ export class UnitService {
console.warn('[Unit system] setUnitSystem', this.currentUnitSystem); console.warn('[Unit system] setUnitSystem', this.currentUnitSystem);
} }
getUnits(measure?: AllMeasures, unitSystem?: UnitSystem): UnitDescription[] { getUnits(measure?: AllMeasures, unitSystem?: UnitSystem): UnitInfo[] {
return this.converter?.list(measure, unitSystem); return this.converter?.listUnits(measure, unitSystem);
} }
getUnitsGroupByMeasure(measure?: AllMeasures, unitSystem?: UnitSystem): UnitDescriptionGroupByMeasure<AllMeasures> { getUnitsGroupedByMeasure(measure?: AllMeasures, unitSystem?: UnitSystem): UnitInfoGroupByMeasure<AllMeasures> {
return this.converter?.listGroupByMeasure(measure, unitSystem); return this.converter?.unitsGroupByMeasure(measure, unitSystem);
} }
getUnitDescription(abbr: AllMeasuresUnits | string): UnitDescription { getUnitInfo(symbol: AllMeasuresUnits | string): UnitInfo {
return this.converter.describe(abbr); return this.converter.describe(symbol);
} }
getDefaultUnit(measure: AllMeasures, unitSystem: UnitSystem): AllMeasuresUnits { getDefaultUnit(measure: AllMeasures, unitSystem: UnitSystem): AllMeasuresUnits {
return this.converter.getDefaultUnit(measure, unitSystem); return this.converter.getDefaultUnit(measure, unitSystem);
} }
geUnitConvertor(from: string, to: string): TbUnitConvertor { geUnitConverter(unit: TbUnitMapping): TbUnitConverter;
return this.converter.convertor(from, to); 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 { getTargetUnitSymbol(unit: TbUnitMapping): string {
return this.converter.convert(value, from, to); 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 { private getUnitSystemByTimezone(): UnitSystem {

272
ui-ngx/src/app/core/services/unit/converter-unit.ts

@ -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<T, S extends keyof T> = [S, T[keyof T]];
export class Converter<
TMeasures extends AllMeasures,
TUnits extends string,
> {
private readonly measureData: Record<TMeasures, TbMeasure<TUnits>>;
private unitCache: Map<
string,
{
system: UnitSystem;
measure: TMeasures;
unit: Unit;
abbr: TUnits;
}
>;
constructor(
measures: Record<TMeasures, TbMeasure<TUnits>>,
unitCache: UnitCache<TMeasures, TUnits>
) {
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<TMeasures, TUnits> | 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<TMeasures, TbMeasure<TUnits>>
: this.measureData;
for (const [name, measure] of Object.entries(measures) as [TMeasures, TbMeasure<TUnits>][]) {
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<TMeasures> | never {
const results: UnitDescriptionGroupByMeasure<TMeasures> = {};
const measures = measureName
? { [measureName]: this.measureData[measureName]} as Record<TMeasures, TbMeasure<TUnits>>
: this.measureData;
for (const [name, measure] of Object.entries(measures) as [TMeasures, TbMeasure<TUnits>][]) {
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<TMeasures, TUnits>): 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<Record<TUnits, Unit>> | 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<TMeasures, TbMeasure<TUnits>>,
translate: TranslateService
) {
const unitCache: UnitCache<TMeasures, TUnits> = 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<TbMeasure<TUnits>, UnitSystem>[]) {
for (const [testAbbr, unit] of Object.entries(system.units) as Entries<
Record<TUnits, Unit>,
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<TMeasures, TbMeasure<TUnits>>,
translate: TranslateService
): Converter<TMeasures, TUnits> {
if (typeof measures !== 'object') {
throw new TypeError('The measures argument needs to be an object');
}
const unitCache = buildUnitCache(measures, translate);
return new Converter<TMeasures, TUnits>(measures, unitCache);
}

121
ui-ngx/src/app/core/services/unit/definitions/all.ts

@ -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<AllMeasuresUnits>
> = {
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;

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

@ -33,14 +33,14 @@ import {
ColorProcessor, ColorProcessor,
ComponentStyle, ComponentStyle,
DateFormatProcessor, DateFormatProcessor,
FormatValueProcessor,
getDataKey, getDataKey,
getLabel, getLabel,
getSingleTsValue, getSingleTsValue,
iconStyle, iconStyle,
overlayStyle, overlayStyle,
resolveCssSize, resolveCssSize,
textStyle textStyle,
ValueFormatProcessor
} from '@shared/models/widget-settings.models'; } from '@shared/models/widget-settings.models';
import { valueCardDefaultSettings, ValueCardLayout, ValueCardWidgetSettings } from './value-card-widget.models'; import { valueCardDefaultSettings, ValueCardLayout, ValueCardWidgetSettings } from './value-card-widget.models';
import { WidgetComponent } from '@home/components/widget/widget.component'; import { WidgetComponent } from '@home/components/widget/widget.component';
@ -101,7 +101,7 @@ export class ValueCardWidgetComponent implements OnInit, AfterViewInit, OnDestro
private panelResize$: ResizeObserver; private panelResize$: ResizeObserver;
private horizontal = false; private horizontal = false;
private formatValue: FormatValueProcessor; private valueFormat: ValueFormatProcessor;
constructor(private imagePipe: ImagePipe, constructor(private imagePipe: ImagePipe,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
@ -125,7 +125,7 @@ export class ValueCardWidgetComponent implements OnInit, AfterViewInit, OnDestro
if (dataKey?.units) { if (dataKey?.units) {
units = 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; this.layout = this.settings.layout;
@ -188,7 +188,7 @@ export class ValueCardWidgetComponent implements OnInit, AfterViewInit, OnDestro
if (tsValue && isDefinedAndNotNull(tsValue[1]) && tsValue[0] !== 0) { if (tsValue && isDefinedAndNotNull(tsValue[1]) && tsValue[0] !== 0) {
ts = tsValue[0]; ts = tsValue[0];
value = tsValue[1]; 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 { } else {
this.valueText = 'N/A'; this.valueText = 'N/A';
} }

2
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 { UiSettingsService } from '@core/http/ui-settings.service';
import { UsageInfoService } from '@core/http/usage-info.service'; import { UsageInfoService } from '@core/http/usage-info.service';
import { EventService } from '@core/http/event.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'; import { AuditLogService } from '@core/http/audit-log.service';
export const ServicesMap = new Map<string, Type<any>>( export const ServicesMap = new Map<string, Type<any>>(

2
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 { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { AuthService } from '@core/auth/auth.service'; import { AuthService } from '@core/auth/auth.service';
import { UnitSystem, UnitSystems } from '@shared/models/unit.models'; import { UnitSystem, UnitSystems } from '@shared/models/unit.models';
import { UnitService } from '@core/services/unit/unit.service'; import { UnitService } from '@core/services/unit.service';
@Component({ @Component({
selector: 'tb-profile', selector: 'tb-profile',

13
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 { 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 { TbPopoverComponent } from '@shared/components/popover.component';
import { FormBuilder, Validators } from '@angular/forms'; import { FormBuilder, Validators } from '@angular/forms';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { UnitService } from '@core/services/unit/unit.service'; import { UnitService } from '@core/services/unit.service';
import { AllMeasures } from '@core/services/unit/definitions/all';
import { debounceTime, first } from 'rxjs/operators'; import { debounceTime, first } from 'rxjs/operators';
import { isEmptyStr } from '@core/utils'; import { isEmptyStr } from '@core/utils';
import type { UnitInputComponent } from '@shared/components/unit-input.component'; import type { UnitInputComponent } from '@shared/components/unit-input.component';
@ -69,7 +68,7 @@ export class ConvertUnitSettingsPanelComponent implements OnInit {
debounceTime(200), debounceTime(200),
takeUntilDestroyed() takeUntilDestroyed()
).subscribe(unit => { ).subscribe(unit => {
const unitDescription = this.unitService.getUnitDescription(unit); const unitDescription = this.unitService.getUnitInfo(unit);
if (unitDescription) { if (unitDescription) {
this.convertUnitForm.get('convertUnit').enable({emitEvent: true}); this.convertUnitForm.get('convertUnit').enable({emitEvent: true});
this.measure = unitDescription.measure; this.measure = unitDescription.measure;
@ -109,7 +108,7 @@ export class ConvertUnitSettingsPanelComponent implements OnInit {
} }
ngOnInit() { ngOnInit() {
let unitDescription: UnitDescription; let unitDescription: UnitInfo;
if (this.required) { if (this.required) {
this.convertUnitForm.get('from').setValidators(Validators.required); this.convertUnitForm.get('from').setValidators(Validators.required);
this.convertUnitForm.get('from').updateValueAndValidity({emitEvent: false}); this.convertUnitForm.get('from').updateValueAndValidity({emitEvent: false});
@ -117,13 +116,13 @@ export class ConvertUnitSettingsPanelComponent implements OnInit {
if (typeof this.unit === 'string') { if (typeof this.unit === 'string') {
this.convertUnitForm.get('convertUnit').setValue(false, {onlySelf: true}); this.convertUnitForm.get('convertUnit').setValue(false, {onlySelf: true});
this.convertUnitForm.get('from').setValue(this.unit, {emitEvent: 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) { } else if (this.unit === null) {
this.convertUnitForm.get('convertUnit').setValue(false, {onlySelf: true}); this.convertUnitForm.get('convertUnit').setValue(false, {onlySelf: true});
this.convertUnitForm.get('from').setValue(null, {emitEvent: true}); this.convertUnitForm.get('from').setValue(null, {emitEvent: true});
} else { } else {
this.convertUnitForm.patchValue(this.unit, {emitEvent: false}); this.convertUnitForm.patchValue(this.unit, {emitEvent: false});
unitDescription = this.unitService.getUnitDescription(this.unit.from); unitDescription = this.unitService.getUnitInfo(this.unit.from);
} }
if (unitDescription?.measure) { if (unitDescription?.measure) {

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

@ -31,10 +31,9 @@ import {
} from '@angular/core'; } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormControl, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { ControlValueAccessor, FormBuilder, FormControl, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { Observable, of, shareReplay } from 'rxjs'; 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 { map, mergeMap } from 'rxjs/operators';
import { AllMeasures } from '@core/services/unit/definitions/all'; import { UnitService } from '@core/services/unit.service';
import { UnitService } from '@core/services/unit/unit.service';
import { TbPopoverService } from '@shared/components/popover.service'; import { TbPopoverService } from '@shared/components/popover.service';
import { ConvertUnitSettingsPanelComponent } from '@shared/components/convert-unit-settings-panel.component'; import { ConvertUnitSettingsPanelComponent } from '@shared/components/convert-unit-settings-panel.component';
import { isNotEmptyStr, isObject } from '@core/utils'; import { isNotEmptyStr, isObject } from '@core/utils';
@ -57,7 +56,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang
@HostBinding('style.display') readonly hostDisplay = 'flex'; @HostBinding('style.display') readonly hostDisplay = 'flex';
@ViewChild('unitInput', {static: true}) unitInput: ElementRef; @ViewChild('unitInput', {static: true}) unitInput: ElementRef;
unitsFormControl: FormControl<TbUnit | UnitDescription>; unitsFormControl: FormControl<TbUnit | UnitInfo>;
@Input({transform: booleanAttribute}) @Input({transform: booleanAttribute})
disabled: boolean; disabled: boolean;
@ -77,7 +76,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang
@Input({transform: booleanAttribute}) @Input({transform: booleanAttribute})
allowConverted = false; allowConverted = false;
filteredUnits: Observable<Array<[AllMeasures, Array<UnitDescription>]>>; filteredUnits: Observable<Array<[AllMeasures, Array<UnitInfo>]>>;
searchText = ''; searchText = '';
@ -87,7 +86,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang
private modelValue: TbUnit | null; private modelValue: TbUnit | null;
private fetchUnits$: Observable<Array<[AllMeasures, Array<UnitDescription>]>> = null; private fetchUnits$: Observable<Array<[AllMeasures, Array<UnitInfo>]>> = null;
private propagateChange = (_val: any) => {}; private propagateChange = (_val: any) => {};
@ -100,7 +99,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang
} }
ngOnInit() { ngOnInit() {
this.unitsFormControl = this.fb.control<TbUnit | UnitDescription>('', this.required ? [Validators.required] : []); this.unitsFormControl = this.fb.control<TbUnit | UnitInfo>('', this.required ? [Validators.required] : []);
this.filteredUnits = this.unitsFormControl.valueChanges this.filteredUnits = this.unitsFormControl.valueChanges
.pipe( .pipe(
map(value => { map(value => {
@ -127,7 +126,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang
this.searchText = ''; this.searchText = '';
this.modelValue = symbol; this.modelValue = symbol;
if (typeof symbol === 'string') { 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; this.isUnitMapping = false;
} else { } else {
this.unitsFormControl.patchValue(symbol, {emitEvent: false}); 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) { if (unit) {
return this.getUnitSymbol(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); const res = this.getTbUnit(value);
if (this.modelValue !== res) { if (this.modelValue !== res) {
this.modelValue = res; this.modelValue = res;
@ -219,22 +218,22 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang
} }
} }
private fetchUnits(searchText?: string): Observable<Array<[AllMeasures, Array<UnitDescription>]>> { private fetchUnits(searchText?: string): Observable<Array<[AllMeasures, Array<UnitInfo>]>> {
this.searchText = searchText; this.searchText = searchText;
return this.unitsConstant().pipe( return this.unitsConstant().pipe(
map(unit => this.searchUnit(unit, searchText)) map(unit => this.searchUnit(unit, searchText))
); );
} }
private unitsConstant(): Observable<Array<[AllMeasures, Array<UnitDescription>]>> { private unitsConstant(): Observable<Array<[AllMeasures, Array<UnitInfo>]>> {
if (this.fetchUnits$ === null) { 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 => { map(data => {
let objectData = Object.entries(data) as Array<[AllMeasures, UnitDescription[]]>; let objectData = Object.entries(data) as Array<[AllMeasures, UnitInfo[]]>;
if (this.tagFilter) { if (this.tagFilter) {
objectData = objectData 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); .filter((measure) => measure[1].length > 0);
} }
return objectData; return objectData;
@ -245,17 +244,17 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang
return this.fetchUnits$; return this.fetchUnits$;
} }
private searchUnit(units: Array<[AllMeasures, Array<UnitDescription>]>, searchText?: string): Array<[AllMeasures, Array<UnitDescription>]> { private searchUnit(units: Array<[AllMeasures, Array<UnitInfo>]>, searchText?: string): Array<[AllMeasures, Array<UnitInfo>]> {
if (isNotEmptyStr(searchText)) { if (isNotEmptyStr(searchText)) {
const filterValue = searchText.trim().toUpperCase() const filterValue = searchText.trim().toUpperCase()
return units 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); .filter((measure) => measure[1].length > 0);
} }
return units; return units;
} }
private getUnitSymbol(value: TbUnit | UnitDescription | null): string { private getUnitSymbol(value: TbUnit | UnitInfo | null): string {
if (value === null) { if (value === null) {
return ''; return '';
} }
@ -268,7 +267,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang
return value.from; return value.from;
} }
private getTbUnit(value: TbUnit | UnitDescription | null): TbUnit { private getTbUnit(value: TbUnit | UnitInfo | null): TbUnit {
if (value === null) { if (value === null) {
return null; return null;
} }

1
ui-ngx/src/app/shared/models/public-api.ts

@ -54,6 +54,7 @@ export * from './rule-node.models';
export * from './settings.models'; export * from './settings.models';
export * from './tenant.model'; export * from './tenant.model';
export * from './user.model'; export * from './user.model';
export * from './unit.models';
export * from './user-settings.models'; export * from './user-settings.models';
export * from './widget-settings.models'; export * from './widget-settings.models';
export * from './widget.models'; export * from './widget.models';

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

@ -14,16 +14,118 @@
/// limitations under the License. /// 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<AllMeasuresUnits>
> = 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 { export enum UnitsType {
capacity = 'capacity' capacity = 'capacity'
} }
export type TbUnitConvertor = (value: number) => number; export type TbUnitConverter = (value: number) => number;
export type UnitDescriptionGroupByMeasure<TMeasure extends string> = Partial<Record<TMeasure, UnitDescription[]>>; export type UnitInfoGroupByMeasure<TMeasure extends string> = Partial<Record<TMeasure, UnitInfo[]>>;
export interface UnitDescription { export interface UnitInfo {
abbr: string; abbr: string;
measure: AllMeasures; measure: AllMeasures;
system: UnitSystem; system: UnitSystem;
@ -70,21 +172,247 @@ export interface Conversion<TMeasures extends string, TUnits extends string> {
unit: Unit; unit: Unit;
} }
export type UnitCache<TMeasures, TUnits> = Map< export type UnitCache = Map<string, {
string,
{
system: UnitSystem; system: UnitSystem;
measure: TMeasures; measure: AllMeasures;
unit: Unit; unit: Unit;
abbr: TUnits; abbr: AllMeasuresUnits;
} }
>; >;
const searchUnitTags = (unit: UnitDescription, searchText: string): boolean => const searchUnitTags = (unit: UnitInfo, searchText: string): boolean =>
!!unit.tags.find(t => t.toUpperCase().includes(searchText)); !!unit.tags.find(t => t.toUpperCase().includes(searchText));
export const searchUnits = (_units: Array<UnitDescription>, searchText: string): Array<UnitDescription> => _units.filter( export const searchUnits = (_units: Array<UnitInfo>, searchText: string): Array<UnitInfo> => _units.filter(
u => u.abbr.toUpperCase().includes(searchText) || u => u.abbr.toUpperCase().includes(searchText) ||
u.name.toUpperCase().includes(searchText) || u.name.toUpperCase().includes(searchText) ||
searchUnitTags(u, searchText) searchUnitTags(u, searchText)
); );
type Entries<T, S extends keyof T> = [S, T[keyof T]];
export class Converter {
private readonly measureData: Record<AllMeasures, TbMeasure<AllMeasuresUnits>>;
private unitCache: Map<
string,
{
system: UnitSystem;
measure: AllMeasures;
unit: Unit;
abbr: AllMeasuresUnits;
}
>;
constructor(
measures: Record<AllMeasures, TbMeasure<AllMeasuresUnits>>,
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<AllMeasures, AllMeasuresUnits> | 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<AllMeasures, TbMeasure<AllMeasuresUnits>>
: this.measureData;
for (const [name, measure] of Object.entries(measures) as [AllMeasures, TbMeasure<AllMeasuresUnits>][]) {
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<AllMeasures> | never {
const results: UnitInfoGroupByMeasure<AllMeasures> = {};
const measures = measureName
? { [measureName]: this.measureData[measureName]} as Record<AllMeasures, TbMeasure<AllMeasuresUnits>>
: this.measureData;
for (const [name, measure] of Object.entries(measures) as [AllMeasures, TbMeasure<AllMeasuresUnits>][]) {
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<AllMeasures, AllMeasuresUnits>): 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<Record<AllMeasuresUnits, Unit>> | 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<AllMeasures, TbMeasure<AllMeasuresUnits>>,
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<TbMeasure<AllMeasuresUnits>, UnitSystem>[]) {
for (const [testAbbr, unit] of Object.entries(system.units) as Entries<
Record<AllMeasuresUnits, Unit>,
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);
}

0
ui-ngx/src/app/core/services/unit/definitions/acceleration.ts → ui-ngx/src/app/shared/models/units/acceleration.ts

0
ui-ngx/src/app/core/services/unit/definitions/angle.ts → ui-ngx/src/app/shared/models/units/angle.ts

0
ui-ngx/src/app/core/services/unit/definitions/angular-acceleration.ts → ui-ngx/src/app/shared/models/units/angular-acceleration.ts

0
ui-ngx/src/app/core/services/unit/definitions/area.ts → ui-ngx/src/app/shared/models/units/area.ts

0
ui-ngx/src/app/core/services/unit/definitions/charge.ts → ui-ngx/src/app/shared/models/units/charge.ts

0
ui-ngx/src/app/core/services/unit/definitions/digital.ts → ui-ngx/src/app/shared/models/units/digital.ts

0
ui-ngx/src/app/core/services/unit/definitions/electric-current.ts → ui-ngx/src/app/shared/models/units/electric-current.ts

0
ui-ngx/src/app/core/services/unit/definitions/energy.ts → ui-ngx/src/app/shared/models/units/energy.ts

0
ui-ngx/src/app/core/services/unit/definitions/force.ts → ui-ngx/src/app/shared/models/units/force.ts

0
ui-ngx/src/app/core/services/unit/definitions/frequency.ts → ui-ngx/src/app/shared/models/units/frequency.ts

0
ui-ngx/src/app/core/services/unit/definitions/illuminance.ts → ui-ngx/src/app/shared/models/units/illuminance.ts

0
ui-ngx/src/app/core/services/unit/definitions/length.ts → ui-ngx/src/app/shared/models/units/length.ts

0
ui-ngx/src/app/core/services/unit/definitions/mass.ts → ui-ngx/src/app/shared/models/units/mass.ts

0
ui-ngx/src/app/core/services/unit/definitions/parts-per.ts → ui-ngx/src/app/shared/models/units/parts-per.ts

0
ui-ngx/src/app/core/services/unit/definitions/power.ts → ui-ngx/src/app/shared/models/units/power.ts

0
ui-ngx/src/app/core/services/unit/definitions/pressure.ts → ui-ngx/src/app/shared/models/units/pressure.ts

0
ui-ngx/src/app/core/services/unit/definitions/speed.ts → ui-ngx/src/app/shared/models/units/speed.ts

0
ui-ngx/src/app/core/services/unit/definitions/temperature.ts → ui-ngx/src/app/shared/models/units/temperature.ts

0
ui-ngx/src/app/core/services/unit/definitions/time.ts → ui-ngx/src/app/shared/models/units/time.ts

0
ui-ngx/src/app/core/services/unit/definitions/torque.ts → ui-ngx/src/app/shared/models/units/torque.ts

0
ui-ngx/src/app/core/services/unit/definitions/voltage.ts → ui-ngx/src/app/shared/models/units/voltage.ts

0
ui-ngx/src/app/core/services/unit/definitions/volume-flow-rate.ts → ui-ngx/src/app/shared/models/units/volume-flow-rate.ts

0
ui-ngx/src/app/core/services/unit/definitions/volume.ts → ui-ngx/src/app/shared/models/units/volume.ts

56
ui-ngx/src/app/shared/models/widget-settings.models.ts

@ -53,8 +53,8 @@ import {
WidgetSubscriptionCallbacks, WidgetSubscriptionCallbacks,
WidgetSubscriptionOptions WidgetSubscriptionOptions
} from '@core/api/widget-api.models'; } from '@core/api/widget-api.models';
import { UnitService } from '@core/services/unit/unit.service'; import { UnitService } from '@core/services/unit.service';
import { TbUnit, TbUnitConvertor, TbUnitMapping } from '@shared/models/unit.models'; import { TbUnit, TbUnitConverter, TbUnitMapping } from '@shared/models/unit.models';
export type ComponentStyle = {[klass: string]: any}; export type ComponentStyle = {[klass: string]: any};
@ -862,50 +862,50 @@ export class AutoDateFormatProcessor extends DateFormatProcessor {
} }
} }
export interface FormatValueSettingProcessor { export interface ValueFormatSettingProcessor {
dec?: number; dec?: number;
units?: TbUnit; units?: TbUnit;
showZeroDecimals?: boolean; showZeroDecimals?: boolean;
} }
export abstract class FormatValueProcessor { export abstract class ValueFormatProcessor {
static fromSettings($injector: Injector, settings: FormatValueSettingProcessor): FormatValueProcessor { static fromSettings($injector: Injector, settings: ValueFormatSettingProcessor): ValueFormatProcessor {
if (typeof settings.units !== 'string' && isDefinedAndNotNull(settings.units?.from)) { if (settings.units !== null && typeof settings.units === 'object') {
return new ConvertUnitProcessor($injector, settings) return new ConverterValueFormatProcessor($injector, settings)
} else { } else {
return new SimpleUnitProcessor($injector, settings); return new SimpleValueFormatProcessor($injector, settings);
} }
} }
protected constructor(protected $injector: Injector, 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 isDefinedUnit: boolean;
private readonly isDefinedDec: boolean; private readonly isDefinedDec: boolean;
private readonly showZeroDecimals: boolean; private readonly hideZeroDecimals: boolean;
constructor(protected $injector: Injector, constructor(protected $injector: Injector,
protected settings: FormatValueSettingProcessor) { protected settings: ValueFormatSettingProcessor) {
super($injector, settings); super($injector, settings);
this.isDefinedUnit = isNotEmptyStr(settings.units); this.isDefinedUnit = isNotEmptyStr(settings.units);
this.isDefinedDec = isDefinedAndNotNull(settings.dec); 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)) { if (isDefinedAndNotNull(value) && isNumeric(value) && (this.isDefinedDec || this.isDefinedUnit || Number(value).toString() === value)) {
let formatted = value; let formatted = value;
if (this.isDefinedDec) { if (this.isDefinedDec) {
formatted = Number(formatted).toFixed(this.settings.dec); formatted = Number(formatted).toFixed(this.settings.dec);
} }
if (!this.showZeroDecimals) { if (this.hideZeroDecimals) {
formatted = Number(formatted) formatted = Number(formatted)
} }
formatted = formatted.toString(); 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 isDefinedDec: boolean;
private readonly showZeroDecimals: boolean; private readonly hideZeroDecimals: boolean;
private readonly unitConvertor: TbUnitConvertor; private readonly unitConverter: TbUnitConverter;
private readonly unitAbbr: string; private readonly unitAbbr: string;
constructor(protected $injector: Injector, constructor(protected $injector: Injector,
protected settings: FormatValueSettingProcessor) { protected settings: ValueFormatSettingProcessor) {
super($injector, settings); super($injector, settings);
const unitService = this.$injector.get(UnitService); const unitService = this.$injector.get(UnitService);
const userUnitSystem = unitService.getUnitSystem();
const unit = settings.units as TbUnitMapping; const unit = settings.units as TbUnitMapping;
const fromUnit = unit.from; this.unitAbbr = unitService.getTargetUnitSymbol(unit);
this.unitAbbr = isNotEmptyStr(unit[userUnitSystem]) ? unit[userUnitSystem] : fromUnit;
try { try {
this.unitConvertor = unitService.geUnitConvertor(fromUnit, this.unitAbbr); this.unitConverter = unitService.geUnitConverter(unit);
} catch (e) {/**/} } catch (e) {/**/}
this.isDefinedDec = isDefinedAndNotNull(settings.dec); 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)) { if (isDefinedAndNotNull(value) && isNumeric(value)) {
let formatted: number | string = Number(value); let formatted: number | string = Number(value);
if (this.unitConvertor) { if (this.unitConverter) {
formatted = this.unitConvertor(value); formatted = this.unitConverter(value);
} }
if (this.isDefinedDec) { if (this.isDefinedDec) {
formatted = Number(formatted).toFixed(this.settings.dec); formatted = Number(formatted).toFixed(this.settings.dec);
} }
if (!this.showZeroDecimals) { if (this.hideZeroDecimals) {
formatted = Number(formatted) formatted = Number(formatted)
} }
formatted = formatted.toString(); formatted = formatted.toString();

Loading…
Cancel
Save