UI: Refactoring after review
This commit is contained in:
		
							parent
							
								
									be4407c3ca
								
							
						
					
					
						commit
						8b14e72f8c
					
				@ -33,7 +33,6 @@ import { svgIcons, svgIconsUrl } from '@shared/models/icon.models';
 | 
			
		||||
import { ActionSettingsChangeLanguage } from '@core/settings/settings.actions';
 | 
			
		||||
import { SETTINGS_KEY } from '@core/settings/settings.effects';
 | 
			
		||||
import { initCustomJQueryEvents } from '@shared/models/jquery-event.models';
 | 
			
		||||
import { UnitService } from '@core/services/unit.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-root',
 | 
			
		||||
@ -47,8 +46,7 @@ export class AppComponent {
 | 
			
		||||
              private translate: TranslateService,
 | 
			
		||||
              private matIconRegistry: MatIconRegistry,
 | 
			
		||||
              private domSanitizer: DomSanitizer,
 | 
			
		||||
              private authService: AuthService,
 | 
			
		||||
              private unitService: UnitService) {
 | 
			
		||||
              private authService: AuthService) {
 | 
			
		||||
 | 
			
		||||
    console.log(`ThingsBoard Version: ${env.tbVersion}`);
 | 
			
		||||
 | 
			
		||||
@ -96,14 +94,12 @@ export class AppComponent {
 | 
			
		||||
    this.store.select(selectUserReady).pipe(
 | 
			
		||||
      filter((data) => data.isUserLoaded),
 | 
			
		||||
      tap((data) => {
 | 
			
		||||
        const userDetails = getCurrentAuthState(this.store).userDetails;
 | 
			
		||||
        let userLang = userDetails?.additionalInfo?.lang ?? null;
 | 
			
		||||
        let userLang = getCurrentAuthState(this.store).userDetails?.additionalInfo?.lang ?? null;
 | 
			
		||||
        if (!userLang && !data.isAuthenticated) {
 | 
			
		||||
          const settings = this.storageService.getItem(SETTINGS_KEY);
 | 
			
		||||
          userLang = settings?.userLang ?? null;
 | 
			
		||||
        }
 | 
			
		||||
        this.notifyUserLang(userLang);
 | 
			
		||||
        this.unitService.setUnitSystem(userDetails?.additionalInfo?.unitSystem);
 | 
			
		||||
      }),
 | 
			
		||||
      skip(1),
 | 
			
		||||
    ).subscribe((data) => {
 | 
			
		||||
 | 
			
		||||
@ -62,10 +62,11 @@ import { AlarmDataService } from '@core/api/alarm-data.service';
 | 
			
		||||
import { IDashboardController } from '@home/components/dashboard-page/dashboard-page.models';
 | 
			
		||||
import { PopoverPlacement } from '@shared/components/popover.models';
 | 
			
		||||
import { PersistentRpc } from '@shared/models/rpc.models';
 | 
			
		||||
import { EventEmitter, Injector } from '@angular/core';
 | 
			
		||||
import { EventEmitter } from '@angular/core';
 | 
			
		||||
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
 | 
			
		||||
import { MatDialogRef } from '@angular/material/dialog';
 | 
			
		||||
import { TbUnit } from '@shared/models/unit.models';
 | 
			
		||||
import { UnitService } from '@core/services/unit.service';
 | 
			
		||||
 | 
			
		||||
export interface TimewindowFunctions {
 | 
			
		||||
  onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void;
 | 
			
		||||
@ -234,8 +235,8 @@ export class WidgetSubscriptionContext {
 | 
			
		||||
  utils: UtilsService;
 | 
			
		||||
  dashboardUtils: DashboardUtilsService;
 | 
			
		||||
  raf: RafService;
 | 
			
		||||
  unitService: UnitService;
 | 
			
		||||
  widgetUtils: IWidgetUtils;
 | 
			
		||||
  $injector: Injector;
 | 
			
		||||
  getServerTimeDiff: () => Observable<number>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1419,7 +1419,7 @@ export class WidgetSubscription implements IWidgetSubscription {
 | 
			
		||||
              if (this.displayLegend) {
 | 
			
		||||
                const decimals = isDefinedAndNotNull(dataKey.decimals) ? dataKey.decimals : this.decimals;
 | 
			
		||||
                const units = isNotEmptyTbUnits(dataKey.units) ? dataKey.units : this.units;
 | 
			
		||||
                const valueFormat = ValueFormatProcessor.fromSettings(this.ctx.$injector, {decimals, units})
 | 
			
		||||
                const valueFormat = ValueFormatProcessor.fromSettings(this.ctx.unitService, {decimals, units})
 | 
			
		||||
                const legendKey: LegendKey = {
 | 
			
		||||
                  dataKey,
 | 
			
		||||
                  dataIndex: dataKeyIndex,
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,10 @@ import {
 | 
			
		||||
import { isNotEmptyStr, isObject } from '@core/utils';
 | 
			
		||||
import { TranslateService } from '@ngx-translate/core';
 | 
			
		||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
 | 
			
		||||
import { Store } from '@ngrx/store';
 | 
			
		||||
import { AppState } from '@core/core.state';
 | 
			
		||||
import { selectAuth, selectIsAuthenticated } from '@core/auth/auth.selectors';
 | 
			
		||||
import { filter, switchMap, take } from 'rxjs/operators';
 | 
			
		||||
 | 
			
		||||
@Injectable({
 | 
			
		||||
  providedIn: 'root'
 | 
			
		||||
@ -41,12 +45,19 @@ export class UnitService {
 | 
			
		||||
  private currentUnitSystem: UnitSystem = UnitSystem.METRIC;
 | 
			
		||||
  private converter: Converter;
 | 
			
		||||
 | 
			
		||||
  constructor(private translate: TranslateService) {
 | 
			
		||||
  constructor(private translate: TranslateService,
 | 
			
		||||
              private store: Store<AppState>) {
 | 
			
		||||
    this.translate.onLangChange.pipe(
 | 
			
		||||
      takeUntilDestroyed()
 | 
			
		||||
    ).subscribe(() => {
 | 
			
		||||
      this.converter = getUnitConverter(this.translate);
 | 
			
		||||
    });
 | 
			
		||||
    this.store.select(selectIsAuthenticated).pipe(
 | 
			
		||||
      filter((data) => data),
 | 
			
		||||
      switchMap(() => this.store.select(selectAuth).pipe(take(1)))
 | 
			
		||||
    ).subscribe((data) => {
 | 
			
		||||
      this.setUnitSystem(data.userDetails?.additionalInfo?.unitSystem)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getUnitSystem(): UnitSystem {
 | 
			
		||||
@ -65,8 +76,8 @@ export class UnitService {
 | 
			
		||||
    return this.converter?.listUnits(measure, unitSystem);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getUnitsGroupedByMeasure(measure?: AllMeasures, unitSystem?: UnitSystem): UnitInfoGroupByMeasure<AllMeasures> {
 | 
			
		||||
    return this.converter?.unitsGroupByMeasure(measure, unitSystem);
 | 
			
		||||
  getUnitsGroupedByMeasure(measure?: AllMeasures, unitSystem?: UnitSystem, tagFilter?: string): UnitInfoGroupByMeasure<AllMeasures> {
 | 
			
		||||
    return this.converter?.unitsGroupByMeasure(measure, unitSystem, tagFilter);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getUnitInfo(symbol: AllMeasuresUnits | string): UnitInfo {
 | 
			
		||||
 | 
			
		||||
@ -48,7 +48,7 @@
 | 
			
		||||
  </mat-form-field>
 | 
			
		||||
  <div class="tb-units-field">
 | 
			
		||||
    <tb-unit-input
 | 
			
		||||
      [supportsUnitConversion]="supportsUnitConversion"
 | 
			
		||||
      supportsUnitConversion
 | 
			
		||||
      formControlName="units">
 | 
			
		||||
    </tb-unit-input>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
@ -118,10 +118,6 @@ export class AggregatedDataKeyRowComponent implements ControlValueAccessor, OnIn
 | 
			
		||||
    return this.widgetConfigComponent.modelValue?.latestDataKeySettingsDirective;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get supportsUnitConversion(): boolean {
 | 
			
		||||
    return this.widgetConfigComponent.modelValue?.typeParameters?.supportsUnitConversion ?? false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private propagateChange = (_val: any) => {};
 | 
			
		||||
 | 
			
		||||
  constructor(private fb: UntypedFormBuilder,
 | 
			
		||||
@ -220,7 +216,7 @@ export class AggregatedDataKeyRowComponent implements ControlValueAccessor, OnIn
 | 
			
		||||
          hideDataKeyName: true,
 | 
			
		||||
          hideDataKeyLabel: true,
 | 
			
		||||
          hideDataKeyColor: true,
 | 
			
		||||
          supportsUnitConversion: this.supportsUnitConversion
 | 
			
		||||
          supportsUnitConversion: true
 | 
			
		||||
        }
 | 
			
		||||
      }).afterClosed().subscribe((updatedDataKey) => {
 | 
			
		||||
      if (updatedDataKey) {
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@
 | 
			
		||||
    <div class="tb-form-row space-between">
 | 
			
		||||
      <div translate>widget-config.units-short</div>
 | 
			
		||||
      <tb-unit-input
 | 
			
		||||
        supportsUnitConversion=""
 | 
			
		||||
        supportsUnitConversion
 | 
			
		||||
        formControlName="units">
 | 
			
		||||
      </tb-unit-input>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
@ -84,7 +84,7 @@
 | 
			
		||||
    <div class="tb-form-row space-between">
 | 
			
		||||
      <div translate>widget-config.units-short</div>
 | 
			
		||||
      <tb-unit-input
 | 
			
		||||
        supportsUnitConversion=""
 | 
			
		||||
        supportsUnitConversion
 | 
			
		||||
        formControlName="units">
 | 
			
		||||
      </tb-unit-input>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
@ -105,6 +105,7 @@ import { ExceptionData } from '@shared/models/error.models';
 | 
			
		||||
import { WidgetComponentService } from './widget-component.service';
 | 
			
		||||
import { Timewindow } from '@shared/models/time/time.models';
 | 
			
		||||
import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
 | 
			
		||||
import { UnitService } from '@core/services/unit.service';
 | 
			
		||||
import { DashboardService } from '@core/http/dashboard.service';
 | 
			
		||||
import { WidgetSubscription } from '@core/api/widget-subscription';
 | 
			
		||||
import { EntityService } from '@core/http/entity.service';
 | 
			
		||||
@ -216,6 +217,7 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
 | 
			
		||||
              private dashboardUtils: DashboardUtilsService,
 | 
			
		||||
              private mobileService: MobileService,
 | 
			
		||||
              private raf: RafService,
 | 
			
		||||
              private unitService: UnitService,
 | 
			
		||||
              private ngZone: NgZone,
 | 
			
		||||
              private cd: ChangeDetectorRef,
 | 
			
		||||
              private http: HttpClient) {
 | 
			
		||||
@ -341,8 +343,8 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
 | 
			
		||||
    this.subscriptionContext.utils = this.utils;
 | 
			
		||||
    this.subscriptionContext.dashboardUtils = this.dashboardUtils;
 | 
			
		||||
    this.subscriptionContext.raf = this.raf;
 | 
			
		||||
    this.subscriptionContext.unitService = this.unitService;
 | 
			
		||||
    this.subscriptionContext.widgetUtils = this.widgetContext.utils;
 | 
			
		||||
    this.subscriptionContext.$injector = this.injector;
 | 
			
		||||
    this.subscriptionContext.getServerTimeDiff = this.dashboardService.getServerTimeDiff.bind(this.dashboardService);
 | 
			
		||||
 | 
			
		||||
    this.widgetComponentService.getWidgetInfo(this.widget.typeFullFqn).subscribe({
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,9 @@ import { Observable, of, shareReplay } from 'rxjs';
 | 
			
		||||
import {
 | 
			
		||||
  AllMeasures,
 | 
			
		||||
  getSourceTbUnitSymbol,
 | 
			
		||||
  getTbUnitFromSearch,
 | 
			
		||||
  isTbUnitMapping,
 | 
			
		||||
  searchUnit,
 | 
			
		||||
  TbUnit,
 | 
			
		||||
  UnitInfo,
 | 
			
		||||
  UnitSystem
 | 
			
		||||
@ -43,7 +45,7 @@ import { map, mergeMap } from 'rxjs/operators';
 | 
			
		||||
import { UnitService } from '@core/services/unit.service';
 | 
			
		||||
import { TbPopoverService } from '@shared/components/popover.service';
 | 
			
		||||
import { UnitSettingsPanelComponent } from '@shared/components/unit-settings-panel.component';
 | 
			
		||||
import { isDefinedAndNotNull, isEqual, isNotEmptyStr } from '@core/utils';
 | 
			
		||||
import { isDefinedAndNotNull, isEqual } from '@core/utils';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-unit-input',
 | 
			
		||||
@ -200,7 +202,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang
 | 
			
		||||
        hostView: this.viewContainerRef,
 | 
			
		||||
        preferredPlacement: ['left', 'bottom', 'top'],
 | 
			
		||||
        context: {
 | 
			
		||||
          unit: this.extractTbUnit(this.unitsFormControl.value),
 | 
			
		||||
          unit: getTbUnitFromSearch(this.unitsFormControl.value),
 | 
			
		||||
          required: this.required,
 | 
			
		||||
          disabled: this.disabled,
 | 
			
		||||
          tagFilter: this.tagFilter,
 | 
			
		||||
@ -217,7 +219,7 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private updateModel(value: UnitInfo | TbUnit ) {
 | 
			
		||||
    let res = this.extractTbUnit(value);
 | 
			
		||||
    let res = getTbUnitFromSearch(value);
 | 
			
		||||
    if (this.onlySystemUnits && !isTbUnitMapping(res)) {
 | 
			
		||||
      const unitInfo = this.unitService.getUnitInfo(res as string);
 | 
			
		||||
      if (unitInfo) {
 | 
			
		||||
@ -238,106 +240,17 @@ export class UnitInputComponent implements ControlValueAccessor, OnInit, OnChang
 | 
			
		||||
  private fetchUnits(searchText?: string): Observable<Array<[AllMeasures, Array<UnitInfo>]>> {
 | 
			
		||||
    this.searchText = searchText;
 | 
			
		||||
    return this.getGroupedUnits().pipe(
 | 
			
		||||
      map(unit => this.searchUnit(unit, searchText))
 | 
			
		||||
      map(unit => searchUnit(unit, searchText))
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getGroupedUnits(): Observable<Array<[AllMeasures, Array<UnitInfo>]>> {
 | 
			
		||||
    if (this.fetchUnits$ === null) {
 | 
			
		||||
      this.fetchUnits$ = of(this.unitService.getUnitsGroupedByMeasure(this.measure, this.unitSystem)).pipe(
 | 
			
		||||
        map(data => {
 | 
			
		||||
          let objectData = Object.entries(data) as Array<[AllMeasures, UnitInfo[]]>;
 | 
			
		||||
 | 
			
		||||
          if (this.tagFilter) {
 | 
			
		||||
            objectData = objectData
 | 
			
		||||
              .map((measure) => [measure[0], measure[1].filter(u => u.tags.includes(this.tagFilter))] as [AllMeasures, UnitInfo[]])
 | 
			
		||||
              .filter((measure) => measure[1].length > 0);
 | 
			
		||||
          }
 | 
			
		||||
          return objectData;
 | 
			
		||||
        }),
 | 
			
		||||
      this.fetchUnits$ = of(this.unitService.getUnitsGroupedByMeasure(this.measure, this.unitSystem, this.tagFilter)).pipe(
 | 
			
		||||
        map(data => Object.entries(data) as Array<[AllMeasures, UnitInfo[]]>),
 | 
			
		||||
        shareReplay(1)
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return this.fetchUnits$;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private searchUnit(units: Array<[AllMeasures, Array<UnitInfo>]>, searchText?: string): Array<[AllMeasures, Array<UnitInfo>]> {
 | 
			
		||||
    if (isNotEmptyStr(searchText)) {
 | 
			
		||||
      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;
 | 
			
		||||
    }
 | 
			
		||||
    if (value === undefined) {
 | 
			
		||||
      return undefined;
 | 
			
		||||
    }
 | 
			
		||||
    if (typeof value === 'string') {
 | 
			
		||||
      return value;
 | 
			
		||||
    }
 | 
			
		||||
    if ('abbr' in value) {
 | 
			
		||||
      return value.abbr;
 | 
			
		||||
    }
 | 
			
		||||
    return value;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -104,7 +104,7 @@ import voltage, { VoltageUnits } from '@shared/models/units/voltage';
 | 
			
		||||
import volume, { VolumeUnits } from '@shared/models/units/volume';
 | 
			
		||||
import volumeFlow, { VolumeFlowUnits } from '@shared/models/units/volume-flow';
 | 
			
		||||
import { TranslateService } from '@ngx-translate/core';
 | 
			
		||||
import { isNotEmptyStr } from '@core/utils';
 | 
			
		||||
import { deepClone, isNotEmptyStr } from '@core/utils';
 | 
			
		||||
 | 
			
		||||
export type AllMeasuresUnits =
 | 
			
		||||
  | AbsorbedDoseRateUnits
 | 
			
		||||
@ -548,7 +548,7 @@ export class Converter {
 | 
			
		||||
    return results;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  unitsGroupByMeasure(measureName?: AllMeasures, unitSystem?: UnitSystem): UnitInfoGroupByMeasure<AllMeasures> {
 | 
			
		||||
  unitsGroupByMeasure(measureName?: AllMeasures, unitSystem?: UnitSystem, tagFilter?: string): UnitInfoGroupByMeasure<AllMeasures> {
 | 
			
		||||
    const results: UnitInfoGroupByMeasure<AllMeasures> = {};
 | 
			
		||||
 | 
			
		||||
    const measures = measureName
 | 
			
		||||
@ -573,9 +573,15 @@ export class Converter {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (const abbr of Object.keys(units) as AllMeasuresUnits[]) {
 | 
			
		||||
          results[name].push(this.describe(abbr));
 | 
			
		||||
          const unitInfo = this.describe(abbr);
 | 
			
		||||
          if (!tagFilter || unitInfo.tags.includes(tagFilter)) {
 | 
			
		||||
            results[name].push(unitInfo);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (!results[name].length) {
 | 
			
		||||
        delete results[name];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return results;
 | 
			
		||||
  }
 | 
			
		||||
@ -641,7 +647,7 @@ function buildUnitCache(measures: Record<AllMeasures, TbMeasure<AllMeasuresUnits
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getUnitConverter(translate: TranslateService): Converter {
 | 
			
		||||
  const unitCache = buildUnitCache(allMeasures, translate);
 | 
			
		||||
  const unitCache = buildUnitCache(deepClone(allMeasures), translate);
 | 
			
		||||
  return new Converter(allMeasures, unitCache);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -669,3 +675,84 @@ export const isTbUnitMapping = (unit: any): boolean => {
 | 
			
		||||
  if (typeof unit !== 'object' || unit === null) return false;
 | 
			
		||||
  return isNotEmptyStr(unit.from);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export const searchUnit =
 | 
			
		||||
  (units: Array<[AllMeasures, Array<UnitInfo>]>, searchText?: string): Array<[AllMeasures, Array<UnitInfo>]> => {
 | 
			
		||||
    if (isNotEmptyStr(searchText)) {
 | 
			
		||||
      const filterValue = searchText.trim().toUpperCase();
 | 
			
		||||
 | 
			
		||||
      const scoredGroups = units
 | 
			
		||||
        .map(([measure, unitInfos]) => {
 | 
			
		||||
          const scoredUnits = unitInfos
 | 
			
		||||
            .map(unit => ({
 | 
			
		||||
              unit,
 | 
			
		||||
              score: 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 => 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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
function 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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const getTbUnitFromSearch = (value: TbUnit | UnitInfo | null): TbUnit => {
 | 
			
		||||
  if (value === null) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
  if (value === undefined) {
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
  if (typeof value === 'string') {
 | 
			
		||||
    return value;
 | 
			
		||||
  }
 | 
			
		||||
  if ('abbr' in value) {
 | 
			
		||||
    return value.abbr;
 | 
			
		||||
  }
 | 
			
		||||
  return value;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -875,15 +875,16 @@ export abstract class ValueFormatProcessor {
 | 
			
		||||
  protected hideZeroDecimals: boolean;
 | 
			
		||||
  protected unitSymbol: string;
 | 
			
		||||
 | 
			
		||||
  static fromSettings($injector: Injector, settings: ValueFormatSettings): ValueFormatProcessor {
 | 
			
		||||
  static fromSettings($injector: Injector, settings: ValueFormatSettings): ValueFormatProcessor;
 | 
			
		||||
  static fromSettings(unitService: UnitService, settings: ValueFormatSettings): ValueFormatProcessor;
 | 
			
		||||
  static fromSettings(unitServiceOrInjector: Injector | UnitService, settings: ValueFormatSettings): ValueFormatProcessor {
 | 
			
		||||
    if (settings.units !== null && typeof settings.units === 'object') {
 | 
			
		||||
      return new UnitConverterValueFormatProcessor($injector, settings)
 | 
			
		||||
      return new UnitConverterValueFormatProcessor(unitServiceOrInjector, settings)
 | 
			
		||||
    }
 | 
			
		||||
    return new SimpleValueFormatProcessor($injector, settings);
 | 
			
		||||
    return new SimpleValueFormatProcessor(settings);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected constructor(protected $injector: Injector,
 | 
			
		||||
                        protected settings: ValueFormatSettings) {
 | 
			
		||||
  protected constructor(protected settings: ValueFormatSettings) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  abstract format(value: any): string;
 | 
			
		||||
@ -908,9 +909,8 @@ export class SimpleValueFormatProcessor extends ValueFormatProcessor {
 | 
			
		||||
 | 
			
		||||
  private readonly isDefinedUnit: boolean;
 | 
			
		||||
 | 
			
		||||
  constructor(protected $injector: Injector,
 | 
			
		||||
              protected settings: ValueFormatSettings) {
 | 
			
		||||
    super($injector, settings);
 | 
			
		||||
  constructor(protected settings: ValueFormatSettings) {
 | 
			
		||||
    super(settings);
 | 
			
		||||
    this.unitSymbol = !settings.ignoreUnitSymbol && isNotEmptyStr(settings.units) ? (settings.units as string) : null;
 | 
			
		||||
    this.isDefinedDecimals = isDefinedAndNotNull(settings.decimals);
 | 
			
		||||
    this.hideZeroDecimals = !settings.showZeroDecimals;
 | 
			
		||||
@ -928,10 +928,10 @@ export class UnitConverterValueFormatProcessor extends ValueFormatProcessor {
 | 
			
		||||
 | 
			
		||||
  private readonly unitConverter: TbUnitConverter;
 | 
			
		||||
 | 
			
		||||
  constructor(protected $injector: Injector,
 | 
			
		||||
  constructor(protected unitServiceOrInjector: Injector | UnitService,
 | 
			
		||||
              protected settings: ValueFormatSettings) {
 | 
			
		||||
    super($injector, settings);
 | 
			
		||||
    const unitService = this.$injector.get(UnitService);
 | 
			
		||||
    super(settings);
 | 
			
		||||
    const unitService = this.unitServiceOrInjector instanceof UnitService ? this.unitServiceOrInjector : this.unitServiceOrInjector.get(UnitService);
 | 
			
		||||
    const unit = settings.units;
 | 
			
		||||
    this.unitSymbol = settings.ignoreUnitSymbol ? null : unitService.getTargetUnitSymbol(unit);
 | 
			
		||||
    this.unitConverter = unitService.geUnitConverter(unit);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user