diff --git a/src/app/test-controller/backend.service.ts b/src/app/test-controller/backend.service.ts index e52f82b876847f698b5bfe8dd816b1396370215b..1751f38e8f5466da9421d49f5f67bc036afefe69 100644 --- a/src/app/test-controller/backend.service.ts +++ b/src/app/test-controller/backend.service.ts @@ -2,7 +2,7 @@ import { Injectable, Inject } from '@angular/core'; import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; import { Observable, of } from 'rxjs'; import { catchError, map, tap, switchMap } from 'rxjs/operators'; -import { BookletData, UnitData } from './test-controller.interfaces'; +import { BookletData, UnitData, TaggedString } from './test-controller.interfaces'; import { ServerError } from './test-controller.classes'; @@ -62,7 +62,7 @@ export class BackendService { } // ------------------------------ - getResource(resId: string, versionning = false): Observable<string | ServerError> { + getResource(internalKey: string, resId: string, versionning = false): Observable<TaggedString | ServerError> { const myHttpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' @@ -72,6 +72,7 @@ export class BackendService { const urlSuffix = versionning ? '?v=1' : ''; return this.http.get<string>(this.serverSlimUrl_GET + 'resource/' + resId + urlSuffix, myHttpOptions) .pipe( + map(def => <TaggedString>{tag: internalKey, value: def}), catchError(this.handle) ); } diff --git a/src/app/test-controller/test-controller.classes.ts b/src/app/test-controller/test-controller.classes.ts index 860cdda76b052d97beff4d21daa532ce3c521802..18096d11f799e72a39f9a72c8cea276a31738644 100644 --- a/src/app/test-controller/test-controller.classes.ts +++ b/src/app/test-controller/test-controller.classes.ts @@ -468,54 +468,51 @@ export class UnitDefLoadQueue { private waitForNewUnits = true; // --------------------------------------------- - constructor (private bs: BackendService, private tcs: TestControllerService) {} - - // --------------------------------------------- - private startLoad() { - if (!this.loading) { - // find next load request - let myUnitSequenceId = 0; - let myUnitDefinitionRef = ''; - for (const unitSeqenceIdStr of Object.keys(this.unitsToLoad)) { - if (this.unitsToLoad[unitSeqenceIdStr]) { - myUnitSequenceId = Number(unitSeqenceIdStr); - myUnitDefinitionRef = this.unitsToLoad[unitSeqenceIdStr]; - this.unitsToLoad[unitSeqenceIdStr] = null; - break; - } - } - - if (myUnitSequenceId > 0) { - this.loading = true; - this.bs.getResource(myUnitDefinitionRef).subscribe(def => { - this.loading = false; - console.log('start loading ' + myUnitDefinitionRef); - if (def instanceof ServerError) { - console.log('error getting unit "' + myUnitSequenceId.toString() + '": getting "' + myUnitDefinitionRef + '" failed'); - } else { - this.tcs.addUnitDefinition(myUnitSequenceId, def as string); - console.log('loading complete ' + myUnitDefinitionRef); - this.startLoad(); - } - }); - } else if (!this.waitForNewUnits) { - this.tcs.addBookletLog(LogEntryKey.BOOKLETLOADCOMPLETE); - } - } - } - - // --------------------------------------------- - public addUnitDefToLoad(unitSequenceId: number, filename: string) { - this.unitsToLoad[unitSequenceId.toString()] = filename; - this.startLoad(); - } - - // --------------------------------------------- - public setWaiterOff() { - this.waitForNewUnits = false; - if (!this.loading) { - this.tcs.addBookletLog(LogEntryKey.BOOKLETLOADCOMPLETE); - } - } + // public startLoad(bs: BackendService, tcs: TestControllerService): Observable<TaggedString> { + // if (!this.loading) { + // // find next load request + // let myUnitSequenceId = 0; + // let myUnitDefinitionRef = ''; + // for (const unitSeqenceIdStr of Object.keys(this.unitsToLoad)) { + // if (this.unitsToLoad[unitSeqenceIdStr]) { + // myUnitSequenceId = Number(unitSeqenceIdStr); + // myUnitDefinitionRef = this.unitsToLoad[unitSeqenceIdStr]; + // this.unitsToLoad[unitSeqenceIdStr] = null; + // break; + // } + // } + + // if (myUnitSequenceId > 0) { + // this.loading = true; + // this.bs.getResource(myUnitDefinitionRef).subscribe(def => { + // this.loading = false; + // console.log('start loading ' + myUnitDefinitionRef); + // if (def instanceof ServerError) { + // console.log('error getting unit "' + myUnitSequenceId.toString() + '": getting "' + myUnitDefinitionRef + '" failed'); + // } else { + // this.tcs.addUnitDefinition(myUnitSequenceId, def as string); + // console.log('loading complete ' + myUnitDefinitionRef); + // this.startLoad(); + // } + // }); + // } else if (!this.waitForNewUnits) { + // this.tcs.addBookletLog(LogEntryKey.BOOKLETLOADCOMPLETE); + // } + // } + // } + + // // --------------------------------------------- + // public addUnitDefToLoad(unitSequenceId: number, filename: string) { + // this.unitsToLoad[unitSequenceId.toString()] = filename; + // this.startLoad(); + // } + + // // --------------------------------------------- + // public setWaiterOff() { + // this.waitForNewUnits = false; + // if (!this.loading) { + // this.tcs.addBookletLog(LogEntryKey.BOOKLETLOADCOMPLETE); + // } + // } } diff --git a/src/app/test-controller/test-controller.component.html b/src/app/test-controller/test-controller.component.html index c8f21d7efb0f57ae93dfd6ad9887095bf898820e..c950aa4b649e66df4a7d62d73352a757491465ca 100644 --- a/src/app/test-controller/test-controller.component.html +++ b/src/app/test-controller/test-controller.component.html @@ -30,7 +30,7 @@ </button> </div> </div> -<div class="spinner-container" *ngIf="dataLoading"> +<div class="spinner-container" *ngIf="tcs.dataLoading"> <mat-spinner></mat-spinner> </div> <mat-card *ngIf="showProgress" class="progress-bar"> diff --git a/src/app/test-controller/test-controller.component.ts b/src/app/test-controller/test-controller.component.ts index 3e0f1168c8f578e0903324a0ebd1af6c40006345..420fab9911831a4a398a0716813bab0d76a5c548 100644 --- a/src/app/test-controller/test-controller.component.ts +++ b/src/app/test-controller/test-controller.component.ts @@ -9,9 +9,9 @@ import { BackendService } from './backend.service'; import { TestControllerService } from './test-controller.service'; import { Component, OnInit, OnDestroy } from '@angular/core'; import { UnitDef, Testlet, UnitControllerData, EnvironmentData, MaxTimerData, UnitDefLoadQueue } from './test-controller.classes'; -import { LastStateKey, LogEntryKey, BookletData, UnitData, MaxTimerDataType } from './test-controller.interfaces'; -import { Subscription, Observable, of, forkJoin, interval, timer } from 'rxjs'; -import { switchMap, takeUntil, map } from 'rxjs/operators'; +import { LastStateKey, LogEntryKey, BookletData, UnitData, MaxTimerDataType, TaggedString } from './test-controller.interfaces'; +import { Subscription, Observable, of, forkJoin, interval, timer, from } from 'rxjs'; +import { switchMap, takeUntil, map, concatMap } from 'rxjs/operators'; @Component({ templateUrl: './test-controller.component.html', @@ -21,8 +21,9 @@ export class TestControllerComponent implements OnInit, OnDestroy { private loginDataSubscription: Subscription = null; private navigationRequestSubsription: Subscription = null; private maxTimerSubscription: Subscription = null; + private unitLoadQueueSubscription1: Subscription = null; + private unitLoadQueueSubscription2: Subscription = null; - public dataLoading = false; public showProgress = true; private lastUnitSequenceId = 0; @@ -32,7 +33,7 @@ export class TestControllerComponent implements OnInit, OnDestroy { private allUnitIds: string[] = []; private progressValue = 0; private loadedUnitCount = 0; - private unitLoadQueue: UnitDefLoadQueue; + private unitLoadQueue: TaggedString[] = []; constructor ( private mds: MainDataService, @@ -41,9 +42,7 @@ export class TestControllerComponent implements OnInit, OnDestroy { private reviewDialog: MatDialog, private snackBar: MatSnackBar, private router: Router - ) { - this.unitLoadQueue = new UnitDefLoadQueue(this.bs, this.tcs); - } + ) { } private getCostumText(key: string): string { const value = this.tcs.getCostumText(key); @@ -232,8 +231,8 @@ export class TestControllerComponent implements OnInit, OnDestroy { // ---------------------- case 'Loading': if (configParameter) { - if (configParameter.toUpperCase() === 'LAZY') { - this.tcs.lazyloading = true; + if (configParameter.toUpperCase() === 'EAGER') { + this.tcs.lazyloading = false; } } break; @@ -268,16 +267,16 @@ export class TestControllerComponent implements OnInit, OnDestroy { } else { // to avoid multiple calls before returning: this.tcs.addPlayer(playerId, ''); - return this.bs.getResource(this.tcs.normaliseId(playerId, 'html'), true) + return this.bs.getResource('', this.tcs.normaliseId(playerId, 'html'), true) .pipe( switchMap(myData => { if (myData instanceof ServerError) { console.log('## problem getting player "' + playerId + '"'); return of(false); } else { - const player = myData as string; - if (player.length > 0) { - this.tcs.addPlayer(playerId, player); + const player = myData as TaggedString; + if (player.value.length > 0) { + this.tcs.addPlayer(playerId, player.value); return of(true); } else { console.log('## size of player "' + playerId + '" = 0'); @@ -349,24 +348,14 @@ export class TestControllerComponent implements OnInit, OnDestroy { return this.loadPlayerOk(playerId).pipe( switchMap(ok => { if (ok && definitionRef.length > 0) { - if (this.tcs.lazyloading) { - this.unitLoadQueue.addUnitDefToLoad(sequenceId, definitionRef); + const newUnditDef: TaggedString = { + tag: sequenceId.toString(), + value: definitionRef + }; + this.unitLoadQueue.push(newUnditDef); myUnit.setCanEnter('y', ''); return of(true); } else { - return this.bs.getResource(definitionRef).pipe( - switchMap(def => { - if (def instanceof ServerError) { - console.log('error getting unit "' + myUnit.id + '": getting "' + definitionRef + '" failed'); - return of(false); - } else { - this.tcs.addUnitDefinition(sequenceId, def as string); - myUnit.setCanEnter('y', ''); - return of(true); - } - })); - } - } else { if (ok) { myUnit.setCanEnter('y', ''); } @@ -484,12 +473,12 @@ export class TestControllerComponent implements OnInit, OnDestroy { this.tcs.mode = loginData.mode; this.tcs.loginname = loginData.loginname; - this.dataLoading = true; + this.tcs.dataLoading = true; this.bs.getBookletData().subscribe(myData => { if (myData instanceof ServerError) { const e = myData as ServerError; this.mds.globalErrorMsg$.next(e); - this.dataLoading = false; + this.tcs.dataLoading = false; } else { const bookletData = myData as BookletData; @@ -498,10 +487,13 @@ export class TestControllerComponent implements OnInit, OnDestroy { this.mds.globalErrorMsg$.next(new ServerError(0, 'Das Testheft ist für die Bearbeitung gesperrt.', '')); this.tcs.resetDataStore(); } else { - let navTarget = ''; + let navTarget = 1; if (bookletData.laststate !== null) { if (bookletData.laststate.hasOwnProperty(LastStateKey.LASTUNIT)) { - navTarget = bookletData.laststate[LastStateKey.LASTUNIT]; + const navTargetTemp = Number(bookletData.laststate[LastStateKey.LASTUNIT]); + if (!isNaN(navTargetTemp)) { + navTarget = navTargetTemp; + } } if (bookletData.laststate.hasOwnProperty(LastStateKey.MAXTIMELEFT) && (loginData.mode === 'hot')) { this.tcs.LastMaxTimerState = JSON.parse(bookletData.laststate[LastStateKey.MAXTIMELEFT]); @@ -512,65 +504,65 @@ export class TestControllerComponent implements OnInit, OnDestroy { if (this.tcs.rootTestlet === null) { this.mds.globalErrorMsg$.next(new ServerError(0, 'Error Parsing Booklet Xml', '')); - this.dataLoading = false; + this.tcs.dataLoading = false; } else { this.mds.globalErrorMsg$.next(null); this.tcs.maxUnitSequenceId = this.lastUnitSequenceId - 1; - // set last maxTimer value if available - // this.tcs.rootTestlet.setTimeLeft(bookletData.laststate); - const myUnitLoadings = []; this.showProgress = true; this.loadedUnitCount = 0; + const sequArray = []; for (let i = 1; i < this.tcs.maxUnitSequenceId + 1; i++) { - const ud = this.tcs.rootTestlet.getUnitAt(i); - myUnitLoadings.push(this.loadUnitOk(ud.unitDef, i)); + sequArray.push(i); } - forkJoin(myUnitLoadings).subscribe(allOk => { - this.dataLoading = false; - - let loadingOk = true; - for (const ok of allOk) { - if (!ok) { - loadingOk = false; - break; - } - } - this.showProgress = false; - - if (loadingOk) { - // ===================== - this.tcs.bookletDbId = loginData.booklet; - if (this.tcs.lazyloading) { - this.unitLoadQueue.setWaiterOff(); - } else { - this.tcs.addBookletLog(LogEntryKey.BOOKLETLOADCOMPLETE); - } - this.tcs.rootTestlet.lockUnitsIfTimeLeftNull(); - if (navTarget) { - const navTargetNumber = Number(navTarget); - if (isNaN(navTargetNumber)) { - navTarget = ''; - } else { - this.tcs.updateMinMaxUnitSequenceId(navTargetNumber); - if (navTargetNumber < this.tcs.minUnitSequenceId) { - navTarget = ''; - } + this.unitLoadQueueSubscription1 = from(sequArray).pipe( + concatMap(uSequ => { + const ud = this.tcs.rootTestlet.getUnitAt(uSequ); + return this.loadUnitOk(ud.unitDef, uSequ); + }) + ).subscribe(ok => { + if (!ok) { + console.log('unit load problem from loadUnitOk'); } - } - if (!navTarget) { - this.tcs.updateMinMaxUnitSequenceId(1); - } + }, + err => console.error('unit load error from loadUnitOk: ' + err), + () => { + this.showProgress = false; + this.tcs.dataLoading = false; + + // ===================== + this.tcs.bookletDbId = loginData.booklet; + this.tcs.rootTestlet.lockUnitsIfTimeLeftNull(); + this.tcs.updateMinMaxUnitSequenceId(navTarget); + + // ===================== + this.unitLoadQueueSubscription2 = from(this.unitLoadQueue).pipe( + concatMap(queueEntry => { + const unitSequ = Number(queueEntry.tag); + // avoid to load unit def if not necessary + if (unitSequ < this.tcs.minUnitSequenceId) { + return of({tag: unitSequ.toString(), value: ''}); + } else { + return this.bs.getResource(queueEntry.tag, queueEntry.value); + } + }) + ).subscribe( + def => { + if (def instanceof ServerError) { + console.log('getting unit data failed ' + def.labelNice + '/' + def.labelSystem); + } else { + const udef = def as TaggedString; + this.tcs.addUnitDefinition(Number(udef.tag), udef.value); + } + }, + err => console.error('unit load error: ' + err), + () => this.tcs.addBookletLog(LogEntryKey.BOOKLETLOADCOMPLETE) // complete + ); - this.tcs.setUnitNavigationRequest(navTarget); + this.tcs.setUnitNavigationRequest(navTarget.toString()); - // ===================== - } else { - console.log('loading failed'); - this.mds.globalErrorMsg$.next(new ServerError(0, 'Inhalte des Testheftes konnten nicht alle geladen werden.', '')); - this.tcs.resetDataStore(); - } - }); + } // complete + ); } } } @@ -663,5 +655,11 @@ export class TestControllerComponent implements OnInit, OnDestroy { if (this.maxTimerSubscription !== null) { this.maxTimerSubscription.unsubscribe(); } + if (this.unitLoadQueueSubscription1 !== null) { + this.unitLoadQueueSubscription1.unsubscribe(); + } + if (this.unitLoadQueueSubscription2 !== null) { + this.unitLoadQueueSubscription2.unsubscribe(); + } } } diff --git a/src/app/test-controller/test-controller.service.ts b/src/app/test-controller/test-controller.service.ts index f5ad245f057568fca4a91dfa9dad61b43a0225bc..34ade045bd73b128a44631ba00624092f1fd75d6 100644 --- a/src/app/test-controller/test-controller.service.ts +++ b/src/app/test-controller/test-controller.service.ts @@ -23,7 +23,8 @@ export class TestControllerService { public loginname = ''; public mode = ''; public logging = true; - public lazyloading = false; + public lazyloading = true; + public dataLoading = false; public navigationRequest$ = new Subject<string>(); public maxTimeTimer$ = new Subject<MaxTimerData>(); @@ -133,8 +134,9 @@ export class TestControllerService { this.navButtons = false; this.navArrows = true; this.pageNav = true; - this.lazyloading = false; + this.lazyloading = true; this._costumTexts = {}; + this.dataLoading = false; } // 7777777777777777777777777777777777777777777777777777777777777777777777 diff --git a/src/app/test-controller/unithost/unit-routing-guards.ts b/src/app/test-controller/unithost/unit-routing-guards.ts index d75533762af3a355c056f94e2bbb3bd2df8b3375..bcf4ad1cda55de94482bc42cc8f0c7c13ee95133 100644 --- a/src/app/test-controller/unithost/unit-routing-guards.ts +++ b/src/app/test-controller/unithost/unit-routing-guards.ts @@ -2,11 +2,11 @@ import { StartLockInputComponent } from '../start-lock-input/start-lock-input.co import { ConfirmDialogComponent, ConfirmDialogData } from '../../iqb-common/confirm-dialog/confirm-dialog.component'; import { MatDialog, MatSnackBar } from '@angular/material'; import { TestControllerService } from '../test-controller.service'; -import { switchMap, map } from 'rxjs/operators'; +import { switchMap, map, takeWhile, ignoreElements, filter, take } from 'rxjs/operators'; import { UnithostComponent } from './unithost.component'; import { Injectable } from '@angular/core'; import { CanActivate, CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; -import { Observable, of } from 'rxjs'; +import { Observable, of, interval } from 'rxjs'; import { UnitControllerData } from '../test-controller.classes'; import { CodeInputData, LogEntryKey, StartLockData } from '../test-controller.interfaces'; import { MainDataService } from 'src/app/maindata.service'; @@ -135,7 +135,7 @@ export class UnitActivateGuard implements CanActivate { }); return dialogRef.afterClosed().pipe( switchMap(result => { - if (result === false) { + if ((typeof result === 'undefined') || (result === false)) { return of(false); } else { const codeData = result as CodeInputData[]; @@ -168,6 +168,20 @@ export class UnitActivateGuard implements CanActivate { } } + // **************************************************************************************** + checkAndSolve_DefLoaded(newUnit: UnitControllerData): Observable<Boolean> { + if (this.tcs.hasUnitDefinition(newUnit.unitDef.sequenceId)) { + return of(true); + } else { + this.tcs.dataLoading = true; + return interval(1000) + .pipe( + filter(intervalvalue => this.tcs.hasUnitDefinition(newUnit.unitDef.sequenceId)), + map(v => true), + take(1) + ); + } + } // **************************************************************************************** checkAndSolve_maxTime(newUnit: UnitControllerData): Observable<Boolean> { @@ -192,7 +206,7 @@ export class UnitActivateGuard implements CanActivate { }); return dialogCDRef.afterClosed().pipe( switchMap(cdresult => { - if (cdresult === false) { + if ((typeof cdresult === 'undefined') || (cdresult === false)) { return of(false); } else { this.tcs.stopMaxTimer(); @@ -233,7 +247,7 @@ export class UnitActivateGuard implements CanActivate { }); return dialogCDRef.afterClosed().pipe( switchMap(cdresult => { - if (cdresult === false) { + if ((typeof cdresult === 'undefined') || (cdresult === false)) { return of(false); } else { this.tcs.stopMaxTimer(); @@ -279,7 +293,10 @@ export class UnitActivateGuard implements CanActivate { myreturn = false; } else { const newUnit: UnitControllerData = this.tcs.rootTestlet.getUnitAt(targetUnitSequenceId); - if (newUnit.unitDef.locked) { + if (!newUnit) { + myreturn = false; + console.log('target unit null (targetUnitSequenceId: ' + targetUnitSequenceId.toString()); + } else if (newUnit.unitDef.locked) { myreturn = false; console.log('unit canActivate: locked'); } else if (newUnit.unitDef.canEnter === 'n') { @@ -298,19 +315,27 @@ export class UnitActivateGuard implements CanActivate { if (!cAsC) { return of(false); } else { - return this.checkAndSolve_maxTime(newUnit).pipe( - switchMap(cAsMT => { - if (!cAsMT) { + return this.checkAndSolve_DefLoaded(newUnit).pipe( + switchMap(cAsDL => { + this.tcs.dataLoading = false; + if (!cAsDL) { return of(false); } else { - this.tcs.currentUnitSequenceId = targetUnitSequenceId; - this.tcs.updateMinMaxUnitSequenceId(this.tcs.currentUnitSequenceId); - this.tcs.addUnitLog(newUnit.unitDef.id, LogEntryKey.UNITENTER); - return of(true); + return this.checkAndSolve_maxTime(newUnit).pipe( + switchMap(cAsMT => { + if (!cAsMT) { + return of(false); + } else { + this.tcs.currentUnitSequenceId = targetUnitSequenceId; + this.tcs.updateMinMaxUnitSequenceId(this.tcs.currentUnitSequenceId); + this.tcs.addUnitLog(newUnit.unitDef.id, LogEntryKey.UNITENTER); + return of(true); + } + })); } })); - } - })); + } + })); } })); diff --git a/src/app/test-controller/unithost/unithost.component.ts b/src/app/test-controller/unithost/unithost.component.ts index 6bb9902a72c257e892ba12401898917758e06b84..0cb355c19d0e62f97361e952f68611257959efdf 100644 --- a/src/app/test-controller/unithost/unithost.component.ts +++ b/src/app/test-controller/unithost/unithost.component.ts @@ -205,30 +205,10 @@ export class UnithostComponent implements OnInit, OnDestroy { if (this.tcs.hasUnitDefinition(this.myUnitSequenceId)) { this.pendingUnitDefinition = {tag: this.itemplayerSessionId, value: this.tcs.getUnitDefinition(this.myUnitSequenceId)}; - console.log('hasUnitDefinition #1'); - this.iFrameHostElement.appendChild(this.iFrameItemplayer); - } else if (this.tcs.lazyloading) { - console.log('hasUnitDefinition #2'); - const waiter = interval(1000) - .pipe( - takeWhile(data => this.tcs.hasUnitDefinition(this.myUnitSequenceId)), - timeout(5000) - ); - waiter.subscribe(data => { - if (this.tcs.hasUnitDefinition(this.myUnitSequenceId)) { - console.log('hasUnitDefinition #3'); - this.pendingUnitDefinition = {tag: this.itemplayerSessionId, value: this.tcs.getUnitDefinition(this.myUnitSequenceId)}; - } else { - console.log('hasUnitDefinition #4'); - this.pendingUnitDefinition = {tag: this.itemplayerSessionId, value: ''}; - } - this.iFrameHostElement.appendChild(this.iFrameItemplayer); - }); } else { - console.log('hasUnitDefinition #5'); - this.pendingUnitDefinition = {tag: this.itemplayerSessionId, value: ''}; - this.iFrameHostElement.appendChild(this.iFrameItemplayer); + this.pendingUnitDefinition = null; } + this.iFrameHostElement.appendChild(this.iFrameItemplayer); } }); }