Browse Source

UI: Refactoring search in unit component

pull/13282/head
Vladyslav_Prykhodko 1 year ago
parent
commit
111ad28816
  1. 3
      ui-ngx/src/app/core/services/unit.service.ts
  2. 8
      ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html
  3. 4
      ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.ts
  4. 8
      ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html
  5. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.ts
  6. 82
      ui-ngx/src/app/shared/components/convert-unit-settings-panel.component.html
  7. 5
      ui-ngx/src/app/shared/components/convert-unit-settings-panel.component.scss
  8. 4
      ui-ngx/src/app/shared/components/convert-unit-settings-panel.component.ts
  9. 2
      ui-ngx/src/app/shared/components/unit-input.component.html
  10. 69
      ui-ngx/src/app/shared/components/unit-input.component.ts
  11. 135
      ui-ngx/src/app/shared/models/unit.models.ts
  12. 22
      ui-ngx/src/assets/locale/locale.constant-en_US.json

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

@ -46,8 +46,6 @@ export class UnitService {
takeUntilDestroyed()
).subscribe(() => {
this.converter = getUnitConverter(this.translate);
console.warn(this.converter.listUnits());
console.warn(this.converter.listUnits(null, UnitSystem.IMPERIAL));
});
}
@ -61,7 +59,6 @@ export class UnitService {
} else {
this.currentUnitSystem = this.getUnitSystemByTimezone();
}
console.warn('[Unit system] setUnitSystem', this.currentUnitSystem);
}
getUnits(measure?: AllMeasures, unitSystem?: UnitSystem): UnitInfo[] {

8
ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.html

@ -125,7 +125,7 @@
<div class="fixed-title-width tb-required" translate>widgets.liquid-level-card.datasource-units</div>
<div class="tb-flex row flex-end align-center no-gap">
<tb-unit-input required style="max-width: 25%;" class="flex"
[tagFilter]="unitsType.capacity"
tagFilter="capacity"
formControlName="datasourceUnits">
</tb-unit-input>
</div>
@ -143,7 +143,7 @@
<tb-unit-input *ngIf="levelCardWidgetConfigForm.get('widgetUnitsSource').value === DataSourceType.static; else selectUnitsAttributes"
class="flex" required
supportsUnitConversion
[tagFilter]="unitsType.capacity"
tagFilter="capacity"
formControlName="units">
</tb-unit-input>
<ng-template #selectUnitsAttributes>
@ -199,7 +199,7 @@
</mat-form-field>
<tb-unit-input *ngIf="levelCardWidgetConfigForm.get('volumeUnitsSource').value === DataSourceType.static; else selectVolumeUnitsAttributes"
class="flex" required
[tagFilter]="unitsType.capacity"
tagFilter="capacity"
formControlName="volumeUnits">
</tb-unit-input>
<ng-template #selectVolumeUnitsAttributes>
@ -270,7 +270,7 @@
<div class="tb-flex row flex-end align-center">
<tb-unit-input required class="flex"
supportsUnitConversion
[tagFilter]="unitsType.capacity"
tagFilter="capacity"
formControlName="tooltipUnits">
</tb-unit-input>
<mat-form-field style="width: 40%;" appearance="outline" class="number flex" subscriptSizing="dynamic">

4
ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/liquid-level-card-basic-config.component.ts

@ -56,7 +56,7 @@ import {
ShapesTranslations,
updatedFormSettingsValidators
} from '@home/components/widget/lib/indicator/liquid-level-widget.models';
import { getSourceTbUnitSymbol, UnitsType } from '@shared/models/unit.models';
import { getSourceTbUnitSymbol } from '@shared/models/unit.models';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { ImageCardsSelectComponent } from '@home/components/widget/lib/settings/common/image-cards-select.component';
import { map, share, tap } from 'rxjs/operators';
@ -116,8 +116,6 @@ export class LiquidLevelCardBasicConfigComponent extends BasicWidgetConfigCompon
shapesImageMap: Map<Shapes, string> = new Map();
ShapesTranslationMap = ShapesTranslations;
unitsType = UnitsType;
levelCardWidgetConfigForm: FormGroup;
valuePreviewFn = this._valuePreviewFn.bind(this);

8
ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.html

@ -83,7 +83,7 @@
<div class="tb-form-row space-between">
<div class="fixed-title-width tb-required" translate>widgets.liquid-level-card.datasource-units</div>
<tb-unit-input required style="max-width: 30%;" class="flex"
[tagFilter]="unitsType.capacity"
tagFilter="capacity"
formControlName="datasourceUnits">
</tb-unit-input>
</div>
@ -100,7 +100,7 @@
<tb-unit-input *ngIf="levelCardWidgetSettingsForm.get('widgetUnitsSource').value !== DataSourceType.attribute; else selectUnitsAttributes"
required class="flex"
supportsUnitConversion
[tagFilter]="unitsType.capacity"
tagFilter="capacity"
formControlName="units">
</tb-unit-input>
<ng-template #selectUnitsAttributes>
@ -156,7 +156,7 @@
</mat-form-field>
<tb-unit-input *ngIf="levelCardWidgetSettingsForm.get('volumeUnitsSource').value === DataSourceType.static; else selectVolumeUnitsAttributes"
class="flex" required
[tagFilter]="unitsType.capacity"
tagFilter="capacity"
formControlName="volumeUnits">
</tb-unit-input>
<ng-template #selectVolumeUnitsAttributes>
@ -239,7 +239,7 @@
{{ 'widgets.liquid-level-card.level' | translate }}
</mat-slide-toggle>
<div class="tb-flex row flex-start align-center">
<tb-unit-input [tagFilter]="unitsType.capacity"
<tb-unit-input tagFilter="capacity"
supportsUnitConversion
required class="flex" formControlName="tooltipUnits">
</tb-unit-input>

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/liquid-level-card-widget-settings.component.ts

@ -44,7 +44,7 @@ import {
ShapesTranslations,
updatedFormSettingsValidators
} from '@home/components/widget/lib/indicator/liquid-level-widget.models';
import { getSourceTbUnitSymbol, UnitsType } from '@shared/models/unit.models';
import { getSourceTbUnitSymbol } from '@shared/models/unit.models';
import { ImageCardsSelectComponent } from '@home/components/widget/lib/settings/common/image-cards-select.component';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { Observable, of, ReplaySubject } from 'rxjs';
@ -96,8 +96,6 @@ export class LiquidLevelCardWidgetSettingsComponent extends WidgetSettingsCompon
shapesImageMap: Map<Shapes, string> = new Map();
ShapesTranslationMap = ShapesTranslations;
unitsType = UnitsType;
levelCardWidgetSettingsForm: FormGroup;
valuePreviewFn = this._valuePreviewFn.bind(this);

82
ui-ngx/src/app/shared/components/convert-unit-settings-panel.component.html

@ -16,48 +16,60 @@
-->
<div class="tb-convert-settings-panel">
<div class="tb-convert-settings-title" translate>unit.convert.units-conversion-settings</div>
<div class="tb-convert-settings-title" translate>unit.conversion.unit-settings</div>
<div class="tb-convert-settings-panel-content" [formGroup]="convertUnitForm">
<div class="tb-form-row">
<div class="min-w-25" [class.tb-disabled-label]="convertUnitForm.get('from').disabled" translate>unit.convert.convert-from</div>
<div class="min-w-44"
[class.tb-disabled-label]="convertUnitForm.get('from').disabled"
tb-hint-tooltip-icon="{{ 'unit.conversion.source-unit-hint' | translate }}"
translate>unit.conversion.source-unit</div>
<tb-unit-input #unitFrom class="flex-1" [required]="required" formControlName="from"
[measure]="measure" [tagFilter]="tagFilter"></tb-unit-input>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="convertUnit">
<div tb-hint-tooltip-icon="{{ 'unit.convert.convert-unit-hint' | translate }}">
{{ 'unit.convert.convert-unit' | translate }}
<div class="tb-form-panel stroked">
<mat-slide-toggle class="mat-slide" formControlName="convertUnit">
<div tb-hint-tooltip-icon="{{ 'unit.conversion.enable-unit-conversion-hint' | translate }}">
{{ 'unit.conversion.enable-unit-conversion' | translate }}
</div>
</mat-slide-toggle>
</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"
onlySystemUnits
formControlName="METRIC"
[tagFilter]="tagFilter"
[unitSystem]="UnitSystem.METRIC"
[measure]="targetMeasure">
</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"
onlySystemUnits
formControlName="IMPERIAL"
[tagFilter]="tagFilter"
[unitSystem]="UnitSystem.IMPERIAL"
[measure]="targetMeasure">
</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"
onlySystemUnits
[tagFilter]="tagFilter"
formControlName="HYBRID"
[measure]="targetMeasure">
</tb-unit-input>
<div class="tb-form-row">
<div class="min-w-44"
[class.tb-disabled-label]="convertUnitForm.get('METRIC').disabled"
tb-hint-tooltip-icon="{{ 'unit.conversion.target-metric-unit-hint' | translate }}"
translate>unit.conversion.target-metric-unit</div>
<tb-unit-input class="flex-1"
onlySystemUnits
formControlName="METRIC"
[tagFilter]="tagFilter"
[unitSystem]="UnitSystem.METRIC"
[measure]="targetMeasure">
</tb-unit-input>
</div>
<div class="tb-form-row">
<div class="min-w-44"
[class.tb-disabled-label]="convertUnitForm.get('IMPERIAL').disabled"
tb-hint-tooltip-icon="{{ 'unit.conversion.target-imperial-unit-hint' | translate }}"
translate>unit.conversion.target-imperial-unit</div>
<tb-unit-input class="flex-1"
onlySystemUnits
formControlName="IMPERIAL"
[tagFilter]="tagFilter"
[unitSystem]="UnitSystem.IMPERIAL"
[measure]="targetMeasure">
</tb-unit-input>
</div>
<div class="tb-form-row">
<div class="min-w-44"
[class.tb-disabled-label]="convertUnitForm.get('HYBRID').disabled"
tb-hint-tooltip-icon="{{ 'unit.conversion.target-metric-hybrid-hint' | translate }}"
translate>unit.conversion.target-hybrid-unit</div>
<tb-unit-input class="flex-1"
onlySystemUnits
[tagFilter]="tagFilter"
formControlName="HYBRID"
[measure]="targetMeasure">
</tb-unit-input>
</div>
</div>
</div>
<div class="tb-convert-settings-panel-buttons">

5
ui-ngx/src/app/shared/components/convert-unit-settings-panel.component.scss

@ -16,13 +16,16 @@
@import '../scss/constants';
.tb-convert-settings-panel {
width: 320px;
width: 360px;
display: flex;
flex-direction: column;
gap: 16px;
max-height: calc(100vh - 24px);
@media #{$mat-xs} {
width: 90vw;
.tb-form-row tb-unit-input {
width: 90px;
}
}
.tb-convert-settings-title {
font-size: 16px;

4
ui-ngx/src/app/shared/components/convert-unit-settings-panel.component.ts

@ -15,7 +15,7 @@
///
import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ViewEncapsulation } from '@angular/core';
import { AllMeasures, isNotEmptyTbUnits, TbUnit, UnitInfo, UnitsType, UnitSystem } from '@shared/models/unit.models';
import { AllMeasures, isNotEmptyTbUnits, TbUnit, UnitInfo, UnitSystem } from '@shared/models/unit.models';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { FormBuilder, Validators } from '@angular/forms';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@ -48,7 +48,7 @@ export class ConvertUnitSettingsPanelComponent implements OnInit {
unitSettingsApplied = new EventEmitter<TbUnit>();
@Input()
tagFilter: UnitsType;
tagFilter: string;
@Input()
measure: AllMeasures;

2
ui-ngx/src/app/shared/components/unit-input.component.html

@ -40,7 +40,7 @@
<tb-icon matSuffix
[color]="disabled ? null : 'primary'"
matTooltipPosition="above"
[matTooltip]="'unit.convert.set-units-conversion-settings' | translate"
[matTooltip]="'unit.conversion.set-unit-conversion' | translate"
*ngIf="supportsUnitConversion && isUnitMapping" class="material-icons tb-suffix-show-always mr-2 !p-0">
mdi:swap-vertical-circle-outline
</tb-icon>

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

@ -37,7 +37,6 @@ import {
isTbUnitMapping,
TbUnit,
UnitInfo,
UnitsType,
UnitSystem
} from '@shared/models/unit.models';
import { map, mergeMap } from 'rxjs/operators';
@ -73,7 +72,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang
required = false;
@Input()
tagFilter: UnitsType;
tagFilter: string;
@Input()
measure: AllMeasures;
@ -264,18 +263,68 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang
private searchUnit(units: Array<[AllMeasures, Array<UnitInfo>]>, searchText?: string): Array<[AllMeasures, Array<UnitInfo>]> {
if (isNotEmptyStr(searchText)) {
const filterValue = searchText.trim().toUpperCase()
return units.reduce((result: Array<[AllMeasures, Array<UnitInfo>]>, [measure, unitInfos]) => {
const filteredUnits = unitInfos.filter(unit => unit.searchText.toUpperCase().includes(filterValue));
if (filteredUnits.length > 0) {
result.push([measure, filteredUnits]);
}
return result;
}, []);
const filterValue = searchText.trim().toUpperCase();
const scoredGroups = units
.map(([measure, unitInfos]) => {
const scoredUnits = unitInfos
.map(unit => ({
unit,
score: this.calculateRelevanceScore(unit, filterValue)
}))
.filter(({ score }) => score > 0)
.sort((a, b) => b.score - a.score)
.map(({ unit }) => unit);
let groupScore = scoredUnits.length > 0
? Math.max(...scoredUnits.map(unit => this.calculateRelevanceScore(unit, filterValue)))
: 0;
if (measure.toUpperCase() === filterValue) {
groupScore += 200;
}
return { measure, units: scoredUnits, groupScore };
})
.filter(group => group.units.length > 0)
.sort((a, b) => {
if (b.groupScore !== a.groupScore) {
return b.groupScore - a.groupScore;
}
return b.units.length - a.units.length;
});
return scoredGroups.map(group => [group.measure, group.units] as [AllMeasures, Array<UnitInfo>]);
}
return units;
}
private calculateRelevanceScore(unit: UnitInfo, filterValue: string): number {
const name = unit.name.toUpperCase();
const abbr = unit.abbr.toUpperCase();
const tags = unit.tags.map(tag => tag.toUpperCase());
let score = 0;
if (name === filterValue || abbr === filterValue) {
score += 100;
} else if (tags.includes(filterValue)) {
score += 80;
} else if (name.startsWith(filterValue) || abbr.startsWith(filterValue)) {
score += 60;
} else if (tags.some(tag => tag.startsWith(filterValue))) {
score += 50;
} else if (tags.some(tag => tag.includes(filterValue))) {
score += 30;
}
if (score > 0) {
score += Math.max(0, 10 - (name.length + abbr.length) / 2);
}
return score;
}
private extractTbUnit(value: TbUnit | UnitInfo | null): TbUnit {
if (value === null) {
return null;

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

@ -204,6 +204,7 @@ export type AllMeasures =
| 'angle'
| 'angular-acceleration'
| 'area'
| 'area-density'
| 'capacitance'
| 'catalytic-activity'
| 'catalytic-concentration'
@ -287,28 +288,51 @@ const allMeasures: Record<
AllMeasures,
TbMeasure<AllMeasuresUnits>
> = Object.freeze({
'absorbed-dose-rate': absorbedDoseRate,
temperature,
pressure,
voltage,
'current-density': currentDensity,
'electric-current': electricCurrent,
power,
energy,
speed,
length,
mass,
time,
area,
volume,
'volume-flow': volumeFlow,
density,
acceleration,
acidity,
'air-quality-index': airQualityIndex,
'amount-of-substance': amountOfSubstance,
angle,
'angular-acceleration': angularAcceleration,
area,
'area-density': areaDensity,
illuminance,
'signal-level': signalLevel,
'fuel-efficiency': fuelEfficiency,
frequency,
capacitance,
inductance,
resistance,
torque,
force,
'magnetic-flux-density': magneticFluxDensity,
'magnetic-flux': magneticFlux,
radioactivity,
'radioactive-decay': radioactiveDecay,
'specific-energy': specificEnergy,
'specific-heat-capacity': specificHeatCapacity,
'kinematic-viscosity': kinematicViscosity,
'dynamic-viscosity': dynamicViscosity,
'thermal-conductivity': thermalConductivity,
turbidity,
'earthquake-magnitude': earthquakeMagnitude,
'data-transfer-rate': dataTransferRate,
'parts-per-million': partsPerMillion,
'molar-concentration': molarConcentration,
'number-concentration': numberConcentration,
'catalytic-activity': catalyticActivity,
'catalytic-concentration': catalyticConcentration,
charge,
'current-density': currentDensity,
'data-transfer-rate': dataTransferRate,
density,
digital,
'dimension-ratio': dimensionRatio,
'dynamic-viscosity': dynamicViscosity,
'earthquake-magnitude': earthquakeMagnitude,
'electric-charge-density': electricChargeDensity,
'electric-current': electricCurrent,
'electric-dipole-moment': electricDipoleMoment,
'electric-field-strength': electricFieldStrength,
'electric-flux': electricFlux,
@ -316,70 +340,43 @@ const allMeasures: Record<
'electric-polarizability': electricPolarizability,
'electrical-conductance': electricalConductance,
'electrical-conductivity': electricalConductivity,
energy,
'energy-density': energyDensity,
force,
frequency,
'fuel-efficiency': fuelEfficiency,
'heat-capacity': heatCapacity,
illuminance,
inductance,
'kinematic-viscosity': kinematicViscosity,
length,
'light-exposure': lightExposure,
'linear-charge-density': linerChargeDensity,
'logarithmic-ratio': logarithmicRatio,
'luminous-efficacy': luminousEfficacy,
'luminous-flux': luminousFlux,
'luminous-intensity': luminousIntensity,
'number-concentration': numberConcentration,
'magnetic-field-gradient': magneticFieldGradient,
'magnetic-flux': magneticFlux,
'magnetic-flux-density': magneticFluxDensity,
'magnetic-moment': magneticMoment,
'magnetic-permeability': magneticPermeability,
mass,
'mass-fraction': massFraction,
'molar-concentration': molarConcentration,
'molar-energy': molarEnergy,
'molar-heat-capacity': molarHeatCapacity,
'molar-mass': molarMass,
'parts-per-million': partsPerMillion,
power,
'power-density': powerDensity,
pressure,
radiance,
'radiant-intensity': radiantIntensity,
'radiation-dose': radiationDose,
'radioactive-decay': radioactiveDecay,
radioactivity,
'radioactivity-concentration': radioactivityConcentration,
'reciprocal-length': reciprocalLength,
resistance,
'reynolds-number': reynoldsNumber,
'signal-level': signalLevel,
'solid-angle': solidAngle,
'specific-energy': specificEnergy,
'specific-heat-capacity': specificHeatCapacity,
'specific-humidity': specificHumidity,
'specific-volume': specificVolume,
speed,
'surface-charge-density': surfaceChargeDensity,
'surface-tension': surfaceTension,
temperature,
'thermal-conductivity': thermalConductivity,
time,
torque,
turbidity,
voltage,
volume,
'volume-flow': volumeFlow,
'specific-volume': specificVolume,
'specific-humidity': specificHumidity,
'angular-acceleration': angularAcceleration,
angle,
'solid-angle': solidAngle,
'light-exposure': lightExposure,
'luminous-intensity': luminousIntensity,
'luminous-flux': luminousFlux,
'luminous-efficacy': luminousEfficacy,
'molar-energy': molarEnergy,
'molar-heat-capacity': molarHeatCapacity,
'molar-mass': molarMass,
'mass-fraction': massFraction,
'logarithmic-ratio': logarithmicRatio,
'dimension-ratio': dimensionRatio,
'absorbed-dose-rate': absorbedDoseRate,
acidity,
'amount-of-substance': amountOfSubstance,
digital,
'area-density': areaDensity,
'energy-density': energyDensity,
'heat-capacity': heatCapacity,
'linear-charge-density': linerChargeDensity,
'power-density': powerDensity,
});
export enum UnitsType {
capacity = 'capacity'
}
export type TbUnitConverter = (value: number) => number;
export type UnitInfoGroupByMeasure<TMeasure extends string> = Partial<Record<TMeasure, UnitInfo[]>>;
@ -389,7 +386,6 @@ export interface UnitInfo {
system: UnitSystem;
name: string;
tags: string[];
searchText: string;
}
export enum UnitSystem {
@ -430,7 +426,6 @@ export interface UnitCacheInfo {
measure: AllMeasures;
unit: Unit;
abbr: AllMeasuresUnits;
searchText: string;
}
export type UnitCache = Map<AllMeasuresUnits | string, UnitCacheInfo>;
@ -591,8 +586,7 @@ export class Converter {
measure: unit.measure,
system: unit.system,
name: unit.unit.name,
tags: unit.unit.tags,
searchText: unit.searchText
tags: unit.unit.tags
};
}
@ -638,7 +632,6 @@ function buildUnitCache(measures: Record<AllMeasures, TbMeasure<AllMeasuresUnits
measure: measureName,
system: systemName,
abbr: testAbbr,
searchText: unit.tags.join(';').toUpperCase(),
unit,
});
}

22
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -5848,15 +5848,19 @@
"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": "Works only with system units and measures that have multiple options"
"conversion": {
"set-unit-conversion": "Set unit conversion",
"unit-settings": "Unit settings",
"source-unit": "Source unit",
"source-unit-hint": "This is the unit of the stored value. The unit you’re converting from. Enter the symbol your source data uses (e.g. m, km, ft, in).",
"target-metric-unit": "Target metric unit",
"target-metric-unit-hint": "Choose which metric unit (SI) you want your source value converted into (e.g. cm, mm, km).",
"target-imperial-unit": "Target imperial unit",
"target-imperial-unit-hint": "Choose which imperial unit you want your source value converted into (e.g. in, ft, yd).",
"target-hybrid-unit": "Target hybrid unit",
"target-hybrid-unit-hint": "Choose which hybrid unit you want your source value converted into (e.g. cm, in, km). Hybrid units combine metric, or imperial units.",
"enable-unit-conversion": "Enable unit conversion",
"enable-unit-conversion-hint": "Toggle on to activate conversion. When off, your source value will pass through unchanged. Disabled if there is only one unit in the corresponding measurement group (e.g. Luminous flux, AQI)."
},
"unit-system": "Unit system",
"unit-system-type": {

Loading…
Cancel
Save