diff --git a/ui-ngx/src/app/core/services/unit/converter-unit.ts b/ui-ngx/src/app/core/services/unit/converter-unit.ts index 207d943d10..8a6d707ed1 100644 --- a/ui-ngx/src/app/core/services/unit/converter-unit.ts +++ b/ui-ngx/src/app/core/services/unit/converter-unit.ts @@ -158,68 +158,31 @@ export class Converter< return result / destination.unit.to_anchor; } - // toBest(options?: { - // exclude?: (TUnits | (string & {}))[]; - // cutOffNumber?: number; - // system?: TSystems | (string & {}); - // }): BestResult | null { - // if (this.origin == null) - // throw new OperationOrderError('.toBest must be called after .from'); - // - // const isNegative = this.val < 0; - // - // let exclude: (TUnits | (string & {}))[] = []; - // let cutOffNumber = isNegative ? -1 : 1; - // let system: TSystems | (string & {}) = this.origin.system; - // - // if (typeof options === 'object') { - // exclude = options.exclude ?? []; - // cutOffNumber = options.cutOffNumber ?? cutOffNumber; - // system = options.system ?? this.origin.system; - // } - // - // let best: BestResult | null = null; - // /** - // Looks through every possibility for the 'best' available unit. - // i.e. Where the value has the fewest numbers before the decimal point, - // but is still higher than 1. - // */ - // for (const possibility of this.possibilities()) { - // const unit = this.describe(possibility); - // const isIncluded = exclude.indexOf(possibility) === -1; - // - // if (isIncluded && unit.system === system) { - // const result = this.to(possibility); - // if (isNegative ? result > cutOffNumber : result < cutOffNumber) { - // continue; - // } - // if ( - // best === null || - // (isNegative - // ? result <= cutOffNumber && result > best.val - // : result >= cutOffNumber && result < best.val) - // ) { - // best = { - // val: result, - // unit: possibility, - // name: unit.name, - // tags: unit.tags - // }; - // } - // } - // } - // - // if (best == null) { - // return { - // val: this.val, - // unit: this.origin.abbr, - // name: this.origin.unit.name, - // tags: this.origin.unit.tags - // }; - // } - // - // return best; - // } + getDefaultUnit(measureName: TMeasures | (string & {}), unitSystem: UnitSystem): TUnits { + if (!this.isMeasure(measureName)) { + return null; + } + const measure = this.measureData[measureName]; + let currentUnitSystem = unitSystem; + let units = measure[currentUnitSystem].units; + if (isUndefinedOrNull(units)) { + if (currentUnitSystem === UnitSystem.IMPERIAL) { + currentUnitSystem = UnitSystem.METRIC; + units = measure[currentUnitSystem].units; + } + if (!units) { + console.log(`Measure "${measureName}" in ${currentUnitSystem} system is not found.`); + return null; + } + } + for (const [abbr, unit] of Object.entries( + units as Partial> + ) as [TUnits, Unit][]) { + if (unit.to_anchor === 1 && (isUndefinedOrNull(unit.anchor_shift) || unit.anchor_shift === 0)) { + return abbr; + } + } + } getUnit(abbr: TUnits | (string & {})): Conversion | null { return this.unitCache.get(abbr) ?? null; diff --git a/ui-ngx/src/app/core/services/unit/unit.service.ts b/ui-ngx/src/app/core/services/unit/unit.service.ts index f86fd3e36b..6945c89d3a 100644 --- a/ui-ngx/src/app/core/services/unit/unit.service.ts +++ b/ui-ngx/src/app/core/services/unit/unit.service.ts @@ -76,6 +76,10 @@ export class UnitService { return this.converter.describe(abbr); } + getDefaultUnit(measure: AllMeasures, unitSystem: UnitSystem): AllMeasuresUnits { + return this.converter.getDefaultUnit(measure, unitSystem); + } + geUnitConvertor(from: string, to: string): TbUnitConvertor { return this.converter.convertor(from, to); } 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 7404f95cdf..55abf8dad0 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,6 +33,7 @@ import { ColorProcessor, ComponentStyle, DateFormatProcessor, + FormatValueProcessor, getDataKey, getLabel, getSingleTsValue, @@ -46,7 +47,6 @@ import { WidgetComponent } from '@home/components/widget/widget.component'; import { Observable } from 'rxjs'; import { ImagePipe } from '@shared/pipe/image.pipe'; import { DomSanitizer } from '@angular/platform-browser'; -import { FormatValueProcessor } from '@shared/models/unit.models'; const squareLayoutSize = 160; const horizontalLayoutHeight = 80; diff --git a/ui-ngx/src/app/shared/components/convert-unit-settings-panel.component.html b/ui-ngx/src/app/shared/components/convert-unit-settings-panel.component.html index 8ca777ffcf..fb5e73aebc 100644 --- a/ui-ngx/src/app/shared/components/convert-unit-settings-panel.component.html +++ b/ui-ngx/src/app/shared/components/convert-unit-settings-panel.component.html @@ -16,53 +16,54 @@ -->
-
Unit convertion settings
+
unit.convert.units-conversion-settings
-
From
- +
unit.convert.convert-from
+
- Convert units +
+ {{ 'unit.convert.convert-unit' | translate }} +
- @if(convertUnitForm.get('convertUnit').value) { -
-
Metrical
- - -
-
-
Imperial
- - -
-
-
Hybrid
- - -
- } +
+
unit.convert.to-metric
+ + +
+
+
unit.convert.to-imperial
+ + +
+
+
unit.convert.to-imperial
+ + +
+ [matAutocomplete]="unitsAutocomplete" + [matAutocompleteDisabled]="allowConverted"> warning + + mdi:swap-vertical-circle-outline + @for (group of filteredUnits | async; track group[0]) { - @if ((fetchUnits$ | async).length > 1) { - - @for(unit of group[1]; track unit.abbr) { - - - - - } - - } @else { + @for(unit of group[1]; track unit.abbr) { } - } - } + + } diff --git a/ui-ngx/src/app/shared/components/unit-input.component.scss b/ui-ngx/src/app/shared/components/unit-input.component.scss index e5a4122ef8..7ca8ea86a2 100644 --- a/ui-ngx/src/app/shared/components/unit-input.component.scss +++ b/ui-ngx/src/app/shared/components/unit-input.component.scss @@ -15,6 +15,7 @@ */ .tb-autocomplete.tb-unit-input-autocomplete { .mat-mdc-optgroup-label { + min-height: 36px; .mdc-list-item__primary-text { font-size: 14px; font-weight: 500; 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 312040da2a..9cd044d3dd 100644 --- a/ui-ngx/src/app/shared/components/unit-input.component.ts +++ b/ui-ngx/src/app/shared/components/unit-input.component.ts @@ -37,7 +37,7 @@ import { AllMeasures } from '@core/services/unit/definitions/all'; import { UnitService } from '@core/services/unit/unit.service'; import { TbPopoverService } from '@shared/components/popover.service'; import { ConvertUnitSettingsPanelComponent } from '@shared/components/convert-unit-settings-panel.component'; -import { isNotEmptyStr } from '@core/utils'; +import { isNotEmptyStr, isObject } from '@core/utils'; @Component({ selector: 'tb-unit-input', @@ -59,7 +59,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang unitsFormControl: FormControl; - @Input() + @Input({transform: booleanAttribute}) disabled: boolean; @Input({transform: booleanAttribute}) @@ -81,13 +81,13 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang searchText = ''; - isGroupOption = false; + isUnitMapping = false; private dirty = false; private modelValue: TbUnit | null; - fetchUnits$: Observable]>> = null; + private fetchUnits$: Observable]>> = null; private propagateChange = (_val: any) => {}; @@ -109,9 +109,6 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang }), mergeMap(symbol => this.fetchUnits(symbol)) ); - if (!!this.measure || !!this.tagFilter) { - this.isGroupOption = true; - } } ngOnChanges(changes: SimpleChanges) { @@ -121,11 +118,6 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang if (propName === 'measure' || propName === 'unitSystem') { this.fetchUnits$ = null; this.dirty = true; - if (!!this.measure || !!this.tagFilter) { - this.isGroupOption = true; - } else { - this.isGroupOption = false; - } } } } @@ -136,8 +128,10 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang this.modelValue = symbol; if (typeof symbol === 'string') { this.unitsFormControl.patchValue(this.unitService.getUnitDescription(symbol) ?? symbol, {emitEvent: false}); + this.isUnitMapping = false; } else { this.unitsFormControl.patchValue(symbol, {emitEvent: false}); + this.isUnitMapping = symbol !== null; } this.dirty = true; } @@ -172,18 +166,25 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang } } - clear() { + clear($event: Event) { + $event.stopPropagation(); this.unitsFormControl.patchValue(null, {emitEvent: true}); - setTimeout(() => { - this.unitInput.nativeElement.blur(); - this.unitInput.nativeElement.focus(); - }, 0); + if (!this.allowConverted) { + setTimeout(() => { + this.unitInput.nativeElement.blur(); + this.unitInput.nativeElement.focus(); + }, 0); + } } openConvertSettingsPopup($event: Event) { + if (!this.allowConverted) { + return; + } if ($event) { $event.stopPropagation(); } + this.unitInput.nativeElement.blur(); const trigger = this.elementRef.nativeElement; if (this.popoverService.hasPopover(trigger)) { this.popoverService.hidePopover(trigger); @@ -196,7 +197,8 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang preferredPlacement: ['left', 'bottom', 'top'], context: { unit: this.getTbUnit(this.unitsFormControl.value), - required: this.required + required: this.required, + disabled: this.disabled, }, isModal: true }); @@ -212,6 +214,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang const res = this.getTbUnit(value); if (this.modelValue !== res) { this.modelValue = res; + this.isUnitMapping = (res !== null && isObject(res)); this.propagateChange(this.modelValue); } } diff --git a/ui-ngx/src/app/shared/models/unit.models.ts b/ui-ngx/src/app/shared/models/unit.models.ts index 60b9e4f753..8a62b2d9d7 100644 --- a/ui-ngx/src/app/shared/models/unit.models.ts +++ b/ui-ngx/src/app/shared/models/unit.models.ts @@ -15,9 +15,6 @@ /// import { AllMeasures } from '@core/services/unit/definitions/all'; -import { Injector } from '@angular/core'; -import { isDefinedAndNotNull, isNotEmptyStr, isNumeric } from '@core/utils'; -import { UnitService } from '@core/services/unit/unit.service'; export enum UnitsType { capacity = 'capacity' @@ -74,104 +71,3 @@ export const searchUnits = (_units: Array, searchText: string): u.name.toUpperCase().includes(searchText) || searchUnitTags(u, searchText) ); - -export interface FormatValueSettingProcessor { - dec?: number; - units?: TbUnit; - showZeroDecimals?: boolean; -} - -export abstract class FormatValueProcessor { - - static fromSettings($injector: Injector, settings: FormatValueSettingProcessor): FormatValueProcessor { - if (typeof settings.units !== 'string' && isDefinedAndNotNull(settings.units?.from)) { - return new ConvertUnitProcessor($injector, settings) - } else { - return new SimpleUnitProcessor($injector, settings); - } - } - - protected constructor(protected $injector: Injector, - protected settings: FormatValueSettingProcessor) { - } - - abstract format(value: any): string; -} - -export class SimpleUnitProcessor extends FormatValueProcessor { - - private readonly isDefinedUnit: boolean; - private readonly isDefinedDec: boolean; - private readonly showZeroDecimals: boolean; - - constructor(protected $injector: Injector, - protected settings: FormatValueSettingProcessor) { - super($injector, settings); - this.isDefinedUnit = isNotEmptyStr(settings.units); - this.isDefinedDec = isDefinedAndNotNull(settings.dec); - this.showZeroDecimals = !!settings.showZeroDecimals; - } - - format(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) { - formatted = Number(formatted) - } - formatted = formatted.toString(); - if (this.isDefinedUnit) { - formatted += ` ${this.settings.units}`; - } - return formatted; - } - return value ?? ''; - } -} - -export class ConvertUnitProcessor extends FormatValueProcessor { - - private readonly isDefinedDec: boolean; - private readonly showZeroDecimals: boolean; - private readonly unitConvertor: TbUnitConvertor; - private readonly unitAbbr: string; - - constructor(protected $injector: Injector, - protected settings: FormatValueSettingProcessor) { - 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; - try { - this.unitConvertor = unitService.geUnitConvertor(fromUnit, this.unitAbbr); - } catch (e) {/**/} - - this.isDefinedDec = isDefinedAndNotNull(settings.dec); - this.showZeroDecimals = !!settings.showZeroDecimals; - } - - format(value: any): string { - if (isDefinedAndNotNull(value) && isNumeric(value)) { - let formatted: number | string = Number(value); - if (this.unitConvertor) { - formatted = this.unitConvertor(value); - } - if (this.isDefinedDec) { - formatted = Number(formatted).toFixed(this.settings.dec); - } - if (!this.showZeroDecimals) { - formatted = Number(formatted) - } - formatted = formatted.toString(); - if (this.unitAbbr) { - formatted += ` ${this.unitAbbr}`; - } - return formatted; - } - return value ?? ''; - } -} 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 ed2e769c1e..89c2824545 100644 --- a/ui-ngx/src/app/shared/models/widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/widget-settings.models.ts @@ -14,7 +14,15 @@ /// limitations under the License. /// -import { isDefinedAndNotNull, isNumber, isNumeric, isUndefinedOrNull, mergeDeep, parseFunction } from '@core/utils'; +import { + isDefinedAndNotNull, + isNotEmptyStr, + isNumber, + isNumeric, + isUndefinedOrNull, + mergeDeep, + parseFunction +} from '@core/utils'; import { DataEntry, DataKey, @@ -45,6 +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'; export type ComponentStyle = {[klass: string]: any}; @@ -852,6 +862,107 @@ export class AutoDateFormatProcessor extends DateFormatProcessor { } } +export interface FormatValueSettingProcessor { + dec?: number; + units?: TbUnit; + showZeroDecimals?: boolean; +} + +export abstract class FormatValueProcessor { + + static fromSettings($injector: Injector, settings: FormatValueSettingProcessor): FormatValueProcessor { + if (typeof settings.units !== 'string' && isDefinedAndNotNull(settings.units?.from)) { + return new ConvertUnitProcessor($injector, settings) + } else { + return new SimpleUnitProcessor($injector, settings); + } + } + + protected constructor(protected $injector: Injector, + protected settings: FormatValueSettingProcessor) { + } + + abstract format(value: any): string; +} + +export class SimpleUnitProcessor extends FormatValueProcessor { + + private readonly isDefinedUnit: boolean; + private readonly isDefinedDec: boolean; + private readonly showZeroDecimals: boolean; + + constructor(protected $injector: Injector, + protected settings: FormatValueSettingProcessor) { + super($injector, settings); + this.isDefinedUnit = isNotEmptyStr(settings.units); + this.isDefinedDec = isDefinedAndNotNull(settings.dec); + this.showZeroDecimals = !!settings.showZeroDecimals; + } + + format(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) { + formatted = Number(formatted) + } + formatted = formatted.toString(); + if (this.isDefinedUnit) { + formatted += ` ${this.settings.units}`; + } + return formatted; + } + return value ?? ''; + } +} + +export class ConvertUnitProcessor extends FormatValueProcessor { + + private readonly isDefinedDec: boolean; + private readonly showZeroDecimals: boolean; + private readonly unitConvertor: TbUnitConvertor; + private readonly unitAbbr: string; + + constructor(protected $injector: Injector, + protected settings: FormatValueSettingProcessor) { + 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; + try { + this.unitConvertor = unitService.geUnitConvertor(fromUnit, this.unitAbbr); + } catch (e) {/**/} + + this.isDefinedDec = isDefinedAndNotNull(settings.dec); + this.showZeroDecimals = !!settings.showZeroDecimals; + } + + format(value: any): string { + if (isDefinedAndNotNull(value) && isNumeric(value)) { + let formatted: number | string = Number(value); + if (this.unitConvertor) { + formatted = this.unitConvertor(value); + } + if (this.isDefinedDec) { + formatted = Number(formatted).toFixed(this.settings.dec); + } + if (!this.showZeroDecimals) { + formatted = Number(formatted) + } + formatted = formatted.toString(); + if (this.unitAbbr) { + formatted += ` ${this.unitAbbr}`; + } + return formatted; + } + return value ?? ''; + } +} + const intervalToFormatTimeUnit = (interval: Interval): FormatTimeUnit => { const intervalValue = IntervalMath.numberValue(interval); if (intervalValue < SECOND) { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 78cc2d6e2e..68c160a9ca 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -5847,6 +5847,16 @@ "background-blur": "Background blur" }, "unit": { + "convert": { + "set-units-conversion-settings": "Set units conversion settings", + "units-conversion-settings": "Units conversion settings", + "convert-from": "Convert from", + "to-metric": "To metric", + "to-imperial": "To imperial", + "to-hybrid": "To hybrid", + "convert-unit": "Convert unit", + "convert-unit-hint": "Work only with the default unit" + }, "unit-system": "Unit system", "unit-system-type": { "AUTO": "Auto",