UI: Refactoring unit-input and panel

This commit is contained in:
Vladyslav_Prykhodko 2025-04-29 15:35:18 +03:00
parent f5f794191d
commit caa643d157
11 changed files with 245 additions and 243 deletions

View File

@ -158,68 +158,31 @@ export class Converter<
return result / destination.unit.to_anchor;
}
// toBest(options?: {
// exclude?: (TUnits | (string & {}))[];
// cutOffNumber?: number;
// system?: TSystems | (string & {});
// }): BestResult<TUnits> | 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<TUnits> | 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<Record<TUnits, Unit>>
) as [TUnits, Unit][]) {
if (unit.to_anchor === 1 && (isUndefinedOrNull(unit.anchor_shift) || unit.anchor_shift === 0)) {
return abbr;
}
}
}
getUnit(abbr: TUnits | (string & {})): Conversion<TMeasures, TUnits> | null {
return this.unitCache.get(abbr) ?? null;

View File

@ -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);
}

View File

@ -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;

View File

@ -16,53 +16,54 @@
-->
<div class="tb-convert-settings-panel">
<div class="tb-convert-settings-title">Unit convertion settings</div>
<div class="tb-convert-settings-title" translate>unit.convert.units-conversion-settings</div>
<div class="tb-convert-settings-panel-content" [formGroup]="convertUnitForm">
<div class="tb-form-row">
<div class="min-w-25">From</div>
<tb-unit-input class="flex-1" [required]="required" formControlName="from"></tb-unit-input>
<div class="min-w-25" [class.tb-disabled-label]="convertUnitForm.get('from').disabled" translate>unit.convert.convert-from</div>
<tb-unit-input #unitFrom class="flex-1" [required]="required" formControlName="from"></tb-unit-input>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="convertUnit">
Convert units
<div tb-hint-tooltip-icon="{{ 'unit.convert.convert-unit-hint' | translate }}">
{{ 'unit.convert.convert-unit' | translate }}
</div>
</mat-slide-toggle>
</div>
@if(convertUnitForm.get('convertUnit').value) {
<div class="tb-form-row">
<div class="min-w-25">Metrical</div>
<tb-unit-input class="flex-1"
formControlName="METRIC"
[unitSystem]="UnitSystem.METRIC"
[measure]="measure">
</tb-unit-input>
</div>
<div class="tb-form-row">
<div class="min-w-25">Imperial</div>
<tb-unit-input class="flex-1"
formControlName="IMPERIAL"
[unitSystem]="UnitSystem.IMPERIAL"
[measure]="measure">
</tb-unit-input>
</div>
<div class="tb-form-row">
<div class="min-w-25">Hybrid</div>
<tb-unit-input class="flex-1"
formControlName="HYBRID"
[measure]="measure">
</tb-unit-input>
</div>
}
<div class="tb-form-row">
<div class="min-w-25" [class.tb-disabled-label]="convertUnitForm.get('METRIC').disabled" translate>unit.convert.to-metric</div>
<tb-unit-input class="flex-1"
formControlName="METRIC"
[unitSystem]="UnitSystem.METRIC"
[measure]="measure">
</tb-unit-input>
</div>
<div class="tb-form-row">
<div class="min-w-25" [class.tb-disabled-label]="convertUnitForm.get('IMPERIAL').disabled" translate>unit.convert.to-imperial</div>
<tb-unit-input class="flex-1"
formControlName="IMPERIAL"
[unitSystem]="UnitSystem.IMPERIAL"
[measure]="measure">
</tb-unit-input>
</div>
<div class="tb-form-row">
<div class="min-w-25" [class.tb-disabled-label]="convertUnitForm.get('HYBRID').disabled" translate>unit.convert.to-imperial</div>
<tb-unit-input class="flex-1"
formControlName="HYBRID"
[measure]="measure">
</tb-unit-input>
</div>
</div>
<div class="tb-convert-settings-panel-buttons">
<button mat-button
color="primary"
type="button"
(click)="cancel()">
{{ 'action.cancel' | translate }}
{{ (disabled ? 'action.close': 'action.cancel') | translate }}
</button>
<button mat-raised-button
color="primary"
type="button"
*ngIf="!disabled"
(click)="applyUnitSettings()"
[disabled]="convertUnitForm.invalid || convertUnitForm.pristine">
{{ 'action.apply' | translate }}

View File

@ -14,14 +14,16 @@
/// limitations under the License.
///
import { Component, EventEmitter, Input, OnInit, Output, 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 { 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 { debounceTime } from 'rxjs/operators';
import { debounceTime, first } from 'rxjs/operators';
import { isEmptyStr } from '@core/utils';
import type { UnitInputComponent } from '@shared/components/unit-input.component';
@Component({
selector: 'tb-covert-unit-settings-panel',
@ -32,12 +34,17 @@ import { debounceTime } from 'rxjs/operators';
})
export class ConvertUnitSettingsPanelComponent implements OnInit {
@ViewChild('unitFrom', {static: true}) unitFrom: UnitInputComponent;
@Input()
unit: TbUnit;
@Input()
required: boolean;
@Input()
disabled: boolean;
@Output()
unitSettingsApplied = new EventEmitter<TbUnit>();
@ -67,15 +74,22 @@ export class ConvertUnitSettingsPanelComponent implements OnInit {
this.convertUnitForm.get('convertUnit').enable({emitEvent: true});
this.measure = unitDescription.measure;
if (unitDescription.system === UnitSystem.IMPERIAL) {
this.convertUnitForm.get('METRIC').setValue(this.unitService.getDefaultUnit(this.measure, UnitSystem.METRIC), {emitEvent: false});
this.convertUnitForm.get('IMPERIAL').setValue(unit, {emitEvent: false});
this.convertUnitForm.get('HYBRID').setValue(unit, {emitEvent: false});
} else {
this.convertUnitForm.get('METRIC').setValue(unit, {emitEvent: false});
this.convertUnitForm.get('IMPERIAL').setValue(this.unitService.getDefaultUnit(this.measure, UnitSystem.IMPERIAL), {emitEvent: false});
this.convertUnitForm.get('HYBRID').setValue(unit, {emitEvent: false});
}
} else {
this.convertUnitForm.get('convertUnit').setValue(false, {onlySelf: true});
this.convertUnitForm.get('convertUnit').disable({emitEvent: false});
this.convertUnitForm.patchValue({
METRIC: '',
IMPERIAL: '',
HYBRID: ''
}, {emitEvent: false});
}
})
@ -91,9 +105,6 @@ export class ConvertUnitSettingsPanelComponent implements OnInit {
this.convertUnitForm.get('IMPERIAL').disable({emitEvent: false});
this.convertUnitForm.get('HYBRID').disable({emitEvent: false});
}
setTimeout(() => {
this.popover.updatePosition();
}, 0);
});
}
@ -120,6 +131,13 @@ export class ConvertUnitSettingsPanelComponent implements OnInit {
} else {
this.convertUnitForm.get('convertUnit').disable({emitEvent: false});
}
if (this.disabled) {
this.convertUnitForm.disable({emitEvent: false});
} else if (this.unit === null || isEmptyStr(this.unit)) {
this.popover.tbAnimationDone.pipe(first()).subscribe(() => {
this.unitFrom.unitInput.nativeElement.focus();
});
}
}
cancel() {

View File

@ -15,23 +15,20 @@
limitations under the License.
-->
<mat-form-field appearance="outline" class="tb-inline-field tb-suffix-show-on-hover w-full flex-1" subscriptSizing="dynamic">
<mat-form-field appearance="outline" class="tb-inline-field tb-suffix-show-on-hover w-full flex-1" subscriptSizing="dynamic"
(click)="openConvertSettingsPopup($event)">
<input matInput #unitInput [formControl]="unitsFormControl"
placeholder="{{ 'widget-config.set' | translate }}"
[class.!pointer-events-none]="disabled"
(focusin)="onFocus()"
[matAutocomplete]="unitsAutocomplete">
<button type="button"
*ngIf="!disabled && allowConverted"
class="tb-icon-24"
[class.mr-2]="!unitsFormControl.value || disabled || unitsFormControl.invalid"
matSuffix mat-icon-button (click)="openConvertSettingsPopup($event)">
<tb-icon>mdi:tape-measure</tb-icon>
</button>
[matAutocomplete]="unitsAutocomplete"
[matAutocompleteDisabled]="allowConverted">
<button *ngIf="unitsFormControl.value && !disabled && unitsFormControl.valid"
type="button"
class="tb-icon-24 mr-2"
[class.mr-2]="!allowConverted || !isUnitMapping"
matSuffix mat-icon-button aria-label="Clear"
(click)="clear()">
(click)="clear($event)">
<mat-icon class="material-icons">close</mat-icon>
</button>
<mat-icon matSuffix
@ -41,29 +38,27 @@
*ngIf="unitsFormControl.hasError('required')" class="material-icons tb-suffix-show-always tb-error">
warning
</mat-icon>
<tb-icon matSuffix
[color]="disabled ? null : 'primary'"
matTooltipPosition="above"
[matTooltip]="'unit.convert.set-units-conversion-settings' | translate"
*ngIf="allowConverted && isUnitMapping" class="material-icons tb-icon-24 tb-suffix-show-always">
mdi:swap-vertical-circle-outline
</tb-icon>
<mat-autocomplete
#unitsAutocomplete="matAutocomplete"
class="tb-autocomplete tb-unit-input-autocomplete"
panelWidth="fit-content"
[displayWith]="displayUnitFn.bind(this)">
@for (group of filteredUnits | async; track group[0]) {
@if ((fetchUnits$ | async).length > 1) {
<mat-optgroup [label]="'unit.measures.' + group[0] | translate">
@for(unit of group[1]; track unit.abbr) {
<mat-option [value]="unit">
<span class="tb-unit-name flex-1" [innerHTML]="unit.name | highlight:searchText:true:'ig'"></span>
<span class="tb-unit-symbol" [innerHTML]="unit.abbr | highlight:searchText:true:'ig'"></span>
</mat-option>
}
</mat-optgroup>
} @else {
<mat-optgroup [label]="'unit.measures.' + group[0] | translate">
@for(unit of group[1]; track unit.abbr) {
<mat-option [value]="unit">
<span class="tb-unit-name flex-1" [innerHTML]="unit.name | highlight:searchText:true:'ig'"></span>
<span class="tb-unit-symbol" [innerHTML]="unit.abbr | highlight:searchText:true:'ig'"></span>
</mat-option>
}
}
}
</mat-optgroup>
}
</mat-autocomplete>
</mat-form-field>

View File

@ -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;

View File

@ -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<TbUnit | UnitDescription>;
@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<Array<[AllMeasures, Array<UnitDescription>]>> = null;
private fetchUnits$: Observable<Array<[AllMeasures, Array<UnitDescription>]>> = 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);
}
}

View File

@ -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<UnitDescription>, 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 ?? '';
}
}

View File

@ -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) {

View File

@ -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",