File

src/app/test-controller/test-controller.service.ts

Index

Properties
Methods
Accessors

Constructor

constructor(router: Router, bs: BackendService)
Parameters :
Name Type Optional
router Router No
bs BackendService No

Methods

addClearedCodeTestlet
addClearedCodeTestlet(testletId: string)
Parameters :
Name Type Optional
testletId string No
Returns : void
addPlayer
addPlayer(id: string, player: string)
Parameters :
Name Type Optional
id string No
player string No
Returns : void
addUnitDefinition
addUnitDefinition(sequenceId: number, uDef: string)
Parameters :
Name Type Optional
sequenceId number No
uDef string No
Returns : void
addUnitStateData
addUnitStateData(unitSequenceId: number, dataPartsAllString: string)
Parameters :
Name Type Optional
unitSequenceId number No
dataPartsAllString string No
Returns : void
cancelMaxTimer
cancelMaxTimer()
Returns : void
getPlayer
getPlayer(id: string)
Parameters :
Name Type Optional
id string No
Returns : string
getUnitDefinition
getUnitDefinition(sequenceId: number)
Parameters :
Name Type Optional
sequenceId number No
Returns : string
getUnitPresentationComplete
getUnitPresentationComplete(sequenceId: number)
Parameters :
Name Type Optional
sequenceId number No
Returns : string
getUnitStateData
getUnitStateData(sequenceId: number)
Parameters :
Name Type Optional
sequenceId number No
Returns : string
hasPlayer
hasPlayer(id: string)
Parameters :
Name Type Optional
id string No
Returns : boolean
hasUnitDefinition
hasUnitDefinition(sequenceId: number)
Parameters :
Name Type Optional
sequenceId number No
Returns : boolean
hasUnitPresentationComplete
hasUnitPresentationComplete(sequenceId: number)
Parameters :
Name Type Optional
sequenceId number No
Returns : boolean
hasUnitStateData
hasUnitStateData(sequenceId: number)
Parameters :
Name Type Optional
sequenceId number No
Returns : boolean
interruptMaxTimer
interruptMaxTimer()
Returns : void
newUnitStateData
newUnitStateData(unitDbKey: string, unitSequenceId: number, dataPartsAllString: string, unitStateDataType: string)
Parameters :
Name Type Optional
unitDbKey string No
unitSequenceId number No
dataPartsAllString string No
unitStateDataType string No
Returns : void
newUnitStatePage
newUnitStatePage(unitDbKey: string, pageNr: number, pageId: string, pageCount: number)
Parameters :
Name Type Optional
unitDbKey string No
pageNr number No
pageId string No
pageCount number No
Returns : void
newUnitStateResponseProgress
newUnitStateResponseProgress(unitDbKey: string, unitSequenceId: number, responseProgress: string)
Parameters :
Name Type Optional
unitDbKey string No
unitSequenceId number No
responseProgress string No
Returns : void
normaliseId
normaliseId(id: string, standardext: string)
Parameters :
Name Type Optional Default value
id string No
standardext string No ''
Returns : string
resetDataStore
resetDataStore()
Returns : void
setOldUnitPresentationComplete
setOldUnitPresentationComplete(sequenceId: number, state: string)
Parameters :
Name Type Optional
sequenceId number No
state string No
Returns : void
setUnitNavigationRequest
setUnitNavigationRequest(navString: string, force)
Parameters :
Name Type Optional Default value
navString string No
force No false
Returns : void
startMaxTimer
startMaxTimer(testletId: string, timeLeftMinutes: number)
Parameters :
Name Type Optional
testletId string No
timeLeftMinutes number No
Returns : void
terminateTest
terminateTest(logEntryKey: string, lockTest: boolean)
Parameters :
Name Type Optional Default value
logEntryKey string No
lockTest boolean No false
Returns : void
updateMinMaxUnitSequenceId
updateMinMaxUnitSequenceId(startWith: number)
Parameters :
Name Type Optional
startWith number No
Returns : void
updateUnitStatePresentationProgress
updateUnitStatePresentationProgress(unitDbKey: string, unitSequenceId: number, presentationProgress: string)
Parameters :
Name Type Optional
unitDbKey string No
unitSequenceId number No
presentationProgress string No
Returns : void

Properties

Private _currentUnitSequenceId
Type : number
bookletConfig
Default value : new BookletConfig()
clearCodeTestlets
Type : string[]
Default value : []
currentMaxTimerTestletId
Type : string
Default value : ''
currentUnitDbKey
Type : string
Default value : ''
currentUnitTitle
Type : string
Default value : ''
LastMaxTimerState
Type : KeyValuePairNumber
Default value : {}
loadComplete
Default value : false
loadProgressValue
Type : number
Default value : 0
Private maxTimeIntervalSubscription
Type : Subscription
Default value : null
maxTimeTimer$
Default value : new Subject<MaxTimerData>()
maxUnitSequenceId
Type : number
Default value : 0
minUnitSequenceId
Type : number
Default value : 0
Private players
Type : literal type
Default value : {}
rootTestlet
Type : Testlet
Default value : null
testId
Type : string
Default value : ''
testMode
Default value : new TestMode()
testStatus$
Default value : new BehaviorSubject<TestControllerState>(TestControllerState.INIT)
testStatusEnum
Default value : TestControllerState
Private unitDefinitions
Type : literal type
Default value : {}
unitListForNaviButtons
Type : UnitNaviButtonData[]
Default value : []
unitNextEnabled
Default value : false
Private unitPresentationCompleteStates
Type : literal type
Default value : {}
unitPrevEnabled
Default value : false
Private unitResponseCompleteStates
Type : literal type
Default value : {}
Private unitStateDataParts
Type : literal type
Default value : {}
Private unitStateDataToSave$
Default value : new Subject<UnitStateData>()
windowFocusState$
Default value : new Subject<WindowFocusState>()

Accessors

currentUnitSequenceId
getcurrentUnitSequenceId()
setcurrentUnitSequenceId(v: number)
Parameters :
Name Type Optional
v number No
Returns : void
import { debounceTime, map, takeUntil } from 'rxjs/operators';
import {
  BehaviorSubject, interval, Subject, Subscription, timer
} from 'rxjs';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { MaxTimerData, Testlet } from './test-controller.classes';
import {
  KeyValuePairNumber,
  MaxTimerDataType, StateReportEntry,
  TestControllerState, TestStateKey,
  UnitNaviButtonData,
  UnitNavigationTarget,
  UnitStateData, UnitStateKey, WindowFocusState
} from './test-controller.interfaces';
import { BackendService } from './backend.service';
import { TestMode } from '../config/test-mode';
import { BookletConfig } from '../config/booklet-config';

@Injectable({
  providedIn: 'root'
})
export class TestControllerService {
  testId = '';
  testStatus$ = new BehaviorSubject<TestControllerState>(TestControllerState.INIT);
  testStatusEnum = TestControllerState;
  loadComplete = false;
  loadProgressValue = 0;
  clearCodeTestlets: string[] = [];

  testMode = new TestMode();
  bookletConfig = new BookletConfig();
  rootTestlet: Testlet = null;
  maxUnitSequenceId = 0;
  minUnitSequenceId = 0;

  maxTimeTimer$ = new Subject<MaxTimerData>();
  currentMaxTimerTestletId = '';
  private maxTimeIntervalSubscription: Subscription = null;

  private _currentUnitSequenceId: number;
  currentUnitDbKey = '';
  currentUnitTitle = '';
  unitPrevEnabled = false;
  unitNextEnabled = false;
  unitListForNaviButtons: UnitNaviButtonData[] = [];

  get currentUnitSequenceId(): number {
    return this._currentUnitSequenceId;
  }

  set currentUnitSequenceId(v: number) {
    this.unitPrevEnabled = v > this.minUnitSequenceId;
    this.unitNextEnabled = v < this.maxUnitSequenceId;
    if (this.rootTestlet && (this.bookletConfig.unit_navibuttons !== 'OFF') ) {
      const myUnitListForNaviButtons: UnitNaviButtonData[] = [];
      for (let sequ = 1; sequ <= this.rootTestlet.getMaxSequenceId(); sequ++) {
        const myUnitData = this.rootTestlet.getUnitAt(sequ);
        if (myUnitData) {
          const disabled = (sequ < this.minUnitSequenceId) || (sequ > this.maxUnitSequenceId) || myUnitData.unitDef.locked;
          myUnitListForNaviButtons.push({
            sequenceId: sequ,
            shortLabel: myUnitData.unitDef.naviButtonLabel,
            longLabel: myUnitData.unitDef.title,
            testletLabel: myUnitData.testletLabel,
            disabled,
            isCurrent: sequ === v
          });
        }
      }
      this.unitListForNaviButtons = myUnitListForNaviButtons;
    }
    this._currentUnitSequenceId = v;
  }

  LastMaxTimerState: KeyValuePairNumber = {};

  private players: { [filename: string]: string } = {};
  private unitDefinitions: { [sequenceId: number]: string } = {};
  private unitStateDataParts: { [sequenceId: number]: string } = {};
  private unitPresentationCompleteStates: { [sequenceId: number]: string } = {};
  private unitResponseCompleteStates: { [sequenceId: number]: string } = {};

  private unitStateDataToSave$ = new Subject<UnitStateData>();
  windowFocusState$ = new Subject<WindowFocusState>();

  constructor(
    private router: Router,
    private bs: BackendService
  ) {
    this.unitStateDataToSave$
      .pipe(debounceTime(200))
      .subscribe(unitStateData => {
        this.bs.updateUnitStateData(this.testId, Date.now(), unitStateData.unitDbKey,
          JSON.stringify(unitStateData.dataPartsAllString), unitStateData.unitStateDataType).subscribe(ok => {
          if (!ok) {
            console.warn('newUnitRestorePoint failed');
          }
        });
      });
  }

  resetDataStore(): void {
    this.players = {};
    this.unitDefinitions = {};
    this.unitStateDataParts = {};
    this.rootTestlet = null;
    this.maxUnitSequenceId = 0;
    this.clearCodeTestlets = [];
    this.currentUnitSequenceId = 0;
    this.currentUnitDbKey = '';
    this.currentUnitTitle = '';
    this.unitPrevEnabled = false;
    this.unitNextEnabled = false;
    if (this.maxTimeIntervalSubscription !== null) {
      this.maxTimeIntervalSubscription.unsubscribe();
      this.maxTimeIntervalSubscription = null;
    }
    this.currentMaxTimerTestletId = '';
    this.LastMaxTimerState = {};
    this.unitListForNaviButtons = [];
    this.unitPresentationCompleteStates = {};
    // this.dataLoading = false; TODO set test status?
    // this.bookletLoadComplete = false;
  }

  // uppercase and add extension if not part
  normaliseId(id: string, standardext = ''): string {
    id = id.trim().toUpperCase();
    id.replace(/\s/g, '_');
    if (standardext.length > 0) {
      standardext = standardext.trim().toUpperCase();
      standardext.replace(/\s/g, '_');
      standardext = '.' + standardext.replace('.', '');

      if (id.slice(-(standardext.length)) !== standardext) {
        id = id + standardext;
      }
    }
    return id;
  }

  addPlayer(id: string, player: string): void {
    this.players[this.normaliseId(id, 'html')] = player;
  }

  hasPlayer(id: string): boolean {
    return this.players.hasOwnProperty(this.normaliseId(id, 'html'));
  }

  getPlayer(id: string): string {
    return this.players[this.normaliseId(id, 'html')];
  }

  addUnitDefinition(sequenceId: number, uDef: string): void {
    this.unitDefinitions[sequenceId] = uDef;
  }

  hasUnitDefinition(sequenceId: number): boolean {
    return this.unitDefinitions.hasOwnProperty(sequenceId);
  }

  getUnitDefinition(sequenceId: number): string {
    return this.unitDefinitions[sequenceId];
  }

  // adding RestorePoint via newUnitRestorePoint below
  hasUnitStateData(sequenceId: number): boolean {
    return this.unitStateDataParts.hasOwnProperty(sequenceId);
  }

  getUnitStateData(sequenceId: number): string {
    return this.unitStateDataParts[sequenceId];
  }

  setOldUnitPresentationComplete(sequenceId: number, state: string) {
    this.unitPresentationCompleteStates[sequenceId] = state;
  }

  hasUnitPresentationComplete(sequenceId: number): boolean {
    return this.unitPresentationCompleteStates.hasOwnProperty(sequenceId);
  }

  getUnitPresentationComplete(sequenceId: number): string {
    return this.unitPresentationCompleteStates[sequenceId];
  }

  addUnitStateData(unitSequenceId: number, dataPartsAllString: string) {
    this.unitStateDataParts[unitSequenceId] = dataPartsAllString;
  }

  newUnitStateData(unitDbKey: string, unitSequenceId: number, dataPartsAllString: string, unitStateDataType: string): void {
    this.unitStateDataParts[unitSequenceId] = dataPartsAllString;
    if (this.testMode.saveResponses) {
      this.unitStateDataToSave$.next({ unitDbKey, dataPartsAllString, unitStateDataType });
    }
  }

  addClearedCodeTestlet(testletId: string): void {
    if (this.clearCodeTestlets.indexOf(testletId) < 0) {
      this.clearCodeTestlets.push(testletId);
      if (this.testMode.saveResponses) {
        this.bs.updateTestState(
          this.testId,
          [<StateReportEntry>{
            key: TestStateKey.TESTLETS_CLEARED_CODE,
            timeStamp: Date.now(),
            content: JSON.stringify(this.clearCodeTestlets)
          }]
        );
      }
    }
  }

  updateUnitStatePresentationProgress(unitDbKey: string, unitSequenceId: number, presentationProgress: string): void {
    let stateChanged = false;
    if (!this.unitPresentationCompleteStates[unitSequenceId] || this.unitPresentationCompleteStates[unitSequenceId] === 'none') {
      this.unitPresentationCompleteStates[unitSequenceId] = presentationProgress;
      stateChanged = true;
    } else if (this.unitPresentationCompleteStates[unitSequenceId] === 'some' && presentationProgress === 'complete') {
      this.unitPresentationCompleteStates[unitSequenceId] = presentationProgress;
      stateChanged = true;
    }
    if (stateChanged && this.testMode.saveResponses) {
      this.bs.updateUnitState(this.testId, unitDbKey, [<StateReportEntry>{
        key: UnitStateKey.PRESENTATION_PROGRESS, timeStamp: Date.now(), content: presentationProgress
      }]);
    }
  }

  newUnitStateResponseProgress(unitDbKey: string, unitSequenceId: number, responseProgress: string): void {
    if (this.testMode.saveResponses) {
      if (!this.unitResponseCompleteStates[unitSequenceId] || this.unitResponseCompleteStates[unitSequenceId] !== responseProgress) {
        this.unitResponseCompleteStates[unitSequenceId] = responseProgress;
        this.bs.updateUnitState(this.testId, unitDbKey, [<StateReportEntry>{
          key: UnitStateKey.RESPONSE_PROGRESS, timeStamp: Date.now(), content: responseProgress
        }]);
      }
    }
  }

  newUnitStatePage(unitDbKey: string, pageNr: number, pageId: string, pageCount: number): void {
    if (this.testMode.saveResponses) {
      this.bs.updateUnitState(this.testId, unitDbKey, [
          <StateReportEntry>{ key: UnitStateKey.CURRENT_PAGE_NR, timeStamp: Date.now(), content: pageNr.toString() },
          <StateReportEntry>{ key: UnitStateKey.CURRENT_PAGE_ID, timeStamp: Date.now(), content: pageId },
          <StateReportEntry>{ key: UnitStateKey.PAGE_COUNT, timeStamp: Date.now(), content: pageCount.toString() }
      ]);
    }
  }

  startMaxTimer(testletId: string, timeLeftMinutes: number): void {
    if (this.maxTimeIntervalSubscription !== null) {
      this.maxTimeIntervalSubscription.unsubscribe();
    }
    this.maxTimeTimer$.next(new MaxTimerData(timeLeftMinutes, testletId, MaxTimerDataType.STARTED));
    this.currentMaxTimerTestletId = testletId;
    this.maxTimeIntervalSubscription = interval(1000)
      .pipe(
        takeUntil(
          timer(timeLeftMinutes * 60 * 1000)
        ),
        map(val => (timeLeftMinutes * 60) - val - 1)
      ).subscribe(
        val => {
          this.maxTimeTimer$.next(new MaxTimerData(val / 60, testletId, MaxTimerDataType.STEP));
        },
        e => console.log('maxTime onError: %s', e),
        () => {
          this.maxTimeTimer$.next(new MaxTimerData(0, testletId, MaxTimerDataType.ENDED));
          this.currentMaxTimerTestletId = '';
        }
      );
  }

  cancelMaxTimer(): void {
    if (this.maxTimeIntervalSubscription !== null) {
      this.maxTimeIntervalSubscription.unsubscribe();
      this.maxTimeIntervalSubscription = null;
      this.maxTimeTimer$.next(new MaxTimerData(0, this.currentMaxTimerTestletId, MaxTimerDataType.CANCELLED));
    }
    this.currentMaxTimerTestletId = '';
  }

  interruptMaxTimer(): void {
    if (this.maxTimeIntervalSubscription !== null) {
      this.maxTimeIntervalSubscription.unsubscribe();
      this.maxTimeIntervalSubscription = null;
      this.maxTimeTimer$.next(new MaxTimerData(0, this.currentMaxTimerTestletId, MaxTimerDataType.INTERRUPTED));
    }
    this.currentMaxTimerTestletId = '';
  }

  updateMinMaxUnitSequenceId(startWith: number): void {
    if (this.rootTestlet) {
      this.minUnitSequenceId = this.rootTestlet.getFirstUnlockedUnitSequenceId(startWith);
      this.maxUnitSequenceId = this.rootTestlet.getLastUnlockedUnitSequenceId(startWith);
    }
  }

  terminateTest(logEntryKey: string, lockTest: boolean = false): void {
    if (
      (this.testStatus$.getValue() === TestControllerState.TERMINATED) ||
      (this.testStatus$.getValue() === TestControllerState.FINISHED)
    ) {
      // sometimes terminateTest get called two times from player
      return;
    }

    if (!this.testMode.saveResponses || !lockTest) {
      this.testStatus$.next(TestControllerState.FINISHED);
      this.router.navigate(['/'], { state: { force: true } });
      return;
    }

    this.testStatus$.next(TestControllerState.TERMINATED);
    this.bs.lockTest(this.testId, Date.now(), logEntryKey).subscribe(bsOk => {
      this.testStatus$.next(bsOk ? TestControllerState.FINISHED : TestControllerState.ERROR);
      this.router.navigate(['/'], { state: { force: true } });
    });
  }

  setUnitNavigationRequest(navString: string, force = false): void {
    if (!this.rootTestlet) {
      this.router.navigate([`/t/${this.testId}/status`], { skipLocationChange: true });
    } else {
      switch (navString) {
        case UnitNavigationTarget.ERROR:
        case UnitNavigationTarget.PAUSE:
          this.router.navigate([`/t/${this.testId}/status`], { skipLocationChange: true, state: { force } });
          break;
        case UnitNavigationTarget.MENU:
          this.router.navigate([`/t/${this.testId}/menu`], { state: { force } }).then(navOk => {
            if (!navOk) {
              this.router.navigate([`/t/${this.testId}/status`], { skipLocationChange: true, state: { force } });
            }
          });
          break;
        case UnitNavigationTarget.NEXT:
          let startWith = this.currentUnitSequenceId;
          if (startWith < this.minUnitSequenceId) {
            startWith = this.minUnitSequenceId - 1;
          }
          const nextUnitSequenceId = this.rootTestlet.getNextUnlockedUnitSequenceId(startWith);
          if (nextUnitSequenceId > 0) {
            this.router.navigate([`/t/${this.testId}/u/${nextUnitSequenceId}`], { state: { force } });
          }
          break;
        case UnitNavigationTarget.PREVIOUS:
          this.router.navigate([`/t/${this.testId}/u/${this.currentUnitSequenceId - 1}`],
            { state: { force } });
          break;
        case UnitNavigationTarget.FIRST:
          this.router.navigate([`/t/${this.testId}/u/${this.minUnitSequenceId}`],
            { state: { force } });
          break;
        case UnitNavigationTarget.LAST:
          this.router.navigate([`/t/${this.testId}/u/${this.maxUnitSequenceId}`],
            { state: { force } });
          break;
        case UnitNavigationTarget.END:
          this.terminateTest(force ? 'BOOKLETLOCKEDforced' : 'BOOKLETLOCKEDbyTESTEE');
          break;

        default:
          this.router.navigate(
            [`/t/${this.testId}/u/${navString}`],
            { state: { force } }
          )
            .then(navOk => {
              if (!navOk) {
                console.log(`navigation failed ("${navString}")`);
              }
            });
          break;
      }
    }
  }
}

result-matching ""

    No results matching ""