Yrkki/cv-generator-fe

View on GitHub
src/app/services/sorter/sorter.service.ts

Summary

Maintainability
A
0 mins
Test Coverage
import { Injectable } from '@angular/core';
import { SorterKind } from '../../enums/sorter-kind.enum';
import { SortOrder } from '../../enums/sort-order.enum';
import { Go } from '../../enums/go.enum';

import { UiService } from '../../services/ui/ui.service';
import { PersistenceService } from '../../services/persistence/persistence.service';

/**
 * Sorter service
 */
@Injectable({
  providedIn: 'root',
})
export class SorterService {
  /** Sorter kind. */
  public sorterKind!: SorterKind;

  /** Defaults getter. */
  private get defaults() {
    return [
      '["Id","Name","URL","Authority name","Authority URL","Authority image"' +
      ',"Authority with","Platform","Category","Type","Level","Strength"' +
      ',"Location","Started","Completed","Expiration"' +
      ',"Certificate number","Certificate URL","Certificate image","Certificate image URL"' +
      ',"Certificate badge","Certificate logo","Certificate tag"' +
      ',"Certificate Path","Result image","Result score","Result grade"' +
      ',"Color"]',
      '["Id","From","To","Article","Article author","Article date"' +
      ',"Title","Subtitle"' +
      ',"Translation Article","Translation Title","Translation Subtitle","Translator"' +
      ',"Editor","Publisher","Publication date","Type","Author"' +
      ',"City","Page count","Pages","Size","Format","ISBN"' +
      ',"URL","Publication image","Description"' +
      ',"Color"]',
      '["Count","Significance","Maximality","Lightness","Size"' +
      ',"Weight","Label","ShortLabel"]',
      '["Id"' +
      ',"Project name","Scope","Client","Logo","Country","Industry","Project type","System type"' +
      ',"Platform","Architecture","Languages and notations","IDEs and Tools","Methodology and practices"' +
      ',"Team size","Responsibilities","Role","Position","From","To","Months total","Duration total","Reference"' +
      ',"Links","Photos","Attribution","Client link","Period","Color"]'
    ][this.sorterKind];
  }

  /** Sort field subservice. */
  public get subSortField() {
    return {
      /** Sorter kind. */
      sorterKind: this.sorterKind,
      /** Defaults. */
      defaults: this.defaults,
      /** Fully qualified name, used to prefix other identifiers, getter. */
      get full() { return 'sortField' + SorterKind[this.sorterKind]; },
      /** Persistence name getter. */
      get sFull() { return this.full + 's'; },
      /** Index name getter. */
      get indexFull() { return this.full + 'Index'; },
      /** Index order name getter. */
      get indexOrderFull() { return this.full + 'IndexOrder'; },
      /** Order direction getter. */
      get orderDirection() { return ['△', '▽']; },
      /** Index next direction getter. */
      get indexNextDirection() { return ['ᐸᐸ', 'ᐸ', 'ᐳ']; },
    };
  }

  /** Sort fields getter. */
  private get sortFields() {
    const serializedStringArray = this.persistenceService.getItem(this.subSortField.sFull) ?? this.subSortField.defaults;
    return JSON.parse(serializedStringArray) as string[];
  }
  /** Sort fields setter. */
  private set sortFields(value) {
    this.persistenceService.setItem(this.subSortField.sFull, JSON.stringify(value));
  }

  /** Sort field index getter. */
  public get sortFieldIndex(): number {
    return Number.parseInt(this.persistenceService.getItem(this.subSortField.indexFull) ?? '0', 10);
  }
  /** Sort field index setter. */
  public set sortFieldIndex(value) {
    value = this.clamp(value, this.sortFields.length);
    this.persistenceService.setItem(this.subSortField.indexFull, value.toString());
  }

  /** Sort order getter. */
  public get sortOrder(): SortOrder {
    return Number.parseInt(this.persistenceService.getItem(this.subSortField.indexOrderFull) ?? '0', 10);
  }
  /** Sort order setter. */
  public set sortOrder(value) {
    this.persistenceService.setItem(this.subSortField.indexOrderFull, value.toString());
  }

  /**
   * Constructs the sorter service.
   * ~constructor
   *
   * @param uiService The ui service injected dependency.
   * @param persistenceService The persistence service injected dependency.
   */
  constructor(
    private readonly uiService: UiService,
    private readonly persistenceService: PersistenceService,
  ) {
  }

  /** Sort field getter. */
  public sortField(value: number) {
    value = this.clamp(value, this.sortFields.length);
    return this.sortFields[value];
  }

  /** Floored division modulo operation getter. */
  private clamp(n: number, m: number) {
    if (m === 0) { return n; }

    const d = n % m;
    return d < 0 ? d + m : d;
  }

  /** Next potential sort field. */
  private nextPotentialSortField(sortFieldIndex: number, sortOrder: SortOrder, sortFieldIndexNext = Go.Forward) {
    if (sortFieldIndexNext === Go.Home) {
      sortOrder = this.sorterKind === SorterKind.Spectrum ? SortOrder.Descending : SortOrder.Ascending;
      sortFieldIndex = 0;
    } else {
      const nudgedPotentialSortField = this.nudgePotentialSortField(sortFieldIndex, sortOrder, sortFieldIndexNext);
      sortOrder = nudgedPotentialSortField.sortOrder;
      sortFieldIndex = nudgedPotentialSortField.sortFieldIndex;
    }
    sortFieldIndex = this.clamp(sortFieldIndex, this.sortFields.length);
    return { sortFieldIndex, sortOrder };
  }

  /** Nudge potential sort field to next adjacent one, raw. */
  private nudgePotentialSortField(sortFieldIndex: number, sortOrder: SortOrder, sortFieldIndexNext = Go.Forward) {
    sortOrder = 1 - sortOrder;
    if (sortFieldIndexNext === Go.Back && sortOrder === SortOrder.Descending) {
      sortFieldIndex--;
    } else if (sortFieldIndexNext === Go.Forward && sortOrder === SortOrder.Ascending) {
      sortFieldIndex++;
    }
    return { sortFieldIndex, sortOrder };
  }

  /** Next sort. */
  nextSort(event: MouseEvent, sortFieldIndexNext = Go.Forward) {
    const nextPotentialSortField = this.nextPotentialSortField(this.sortFieldIndex, this.sortOrder, sortFieldIndexNext);
    this.sortFieldIndex = nextPotentialSortField.sortFieldIndex;
    this.sortOrder = nextPotentialSortField.sortOrder;
    event.stopPropagation();
  }

  /** Next sort title. */
  public nextSortTitle(sortFieldIndexNext = Go.Forward) {
    const nextPotentialSortField = this.nextPotentialSortField(this.sortFieldIndex, this.sortOrder, sortFieldIndexNext);
    const tokens = [
      this.uiService.uiText('Click here to change prioritization from'),

      this.sortField(this.sortFieldIndex).toLowerCase(),
      this.subSortField.orderDirection[this.sortOrder],

      this.uiService.uiText('to'),

      this.sortField(nextPotentialSortField.sortFieldIndex).toLowerCase(),
      this.subSortField.orderDirection[nextPotentialSortField.sortOrder]
    ];
    return tokens.join(' ');
  }

  /** Sorted collection. */
  public sorted(collection: any[], sortField = this.sortField(this.sortFieldIndex), sortOrder = 2 * this.sortOrder - 1): any[] {
    if (!collection) { return []; }

    collection.sort((a, b) => this.sortFunctional(a, b, sortField, sortOrder));
    return collection;
  }

  /** Sort functional. */
  private sortFunctional(a: any, b: any, sortField: string, sortOrder: number) {
    if ([SorterKind.Spectrum].includes(this.sorterKind)) {
      a = a[1];
      b = b[1];
    }
    const aFrequencyWordCount = a;
    const aFrequencyWordCountField = aFrequencyWordCount[sortField];
    const bFrequencyWordCount = b;
    const bFrequencyWordCountField = bFrequencyWordCount[sortField];
    if (aFrequencyWordCountField < bFrequencyWordCountField) { return sortOrder; }
    if (aFrequencyWordCountField > bFrequencyWordCountField) { return -sortOrder; }
    return 0;
  }
}