Commit 8c848299 authored by Konstantin Schulz's avatar Konstantin Schulz
Browse files

introducing learning mode and test mode for the test module

parent 3c42339d
{
"name": "mc_frontend",
"version": "0.8.9",
"version": "0.9.0",
"author": "Ionic Framework",
"homepage": "https://ionicframework.com/",
"scripts": {
......
......@@ -164,6 +164,21 @@ export class HelperService {
});
}
/**
* Shuffles array in place.
* @param array items An array containing the items.
*/
static shuffle(array: Array<any>) {
let j, x, i;
for (i = array.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = array[i];
array[i] = array[j];
array[j] = x;
}
return array;
}
initConfig() {
return new Promise((resolve) => {
this.http.get('assets/config.json').subscribe((config: object) => {
......
......@@ -50,7 +50,6 @@ export class ShowTextPage {
this.isDownloading = true;
this.http.post(this.urlBase, formData).subscribe((response: string) => {
this.isDownloading = false;
console.log(response);
const responseParts: string[] = response.split('.');
const link: HTMLLinkElement = document.querySelector('#download');
link.href = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiFilePath'] + '?id='
......
<ion-header>
<ion-toolbar>
<div class="toolbar-left">
<ion-title>{{ 'TEST' | translate }}</ion-title>
<ion-title>{{ (currentExerciseIndex == 0 ? 'TEST' : (isTestMode ? 'START_TEST' : 'START_LEARNING')) | translate }}</ion-title>
</div>
<div class="toolbar-right">
<ion-spinner *ngIf="HelperService.isLoading"></ion-spinner>
<span id="timer"></span>
<span *ngIf="isTestMode" id="timer"></span>
<button (click)="exit($event).then()">
<ion-icon name="close-circle"></ion-icon>
</button>
......@@ -21,7 +21,7 @@
<ion-label>Show exercise:</ion-label>
<ion-select [(ngModel)]="currentExerciseIndex" name="currentExerciseIndex"
(ngModelChange)="showNextExercise(currentExerciseIndex)">
<ion-select-option *ngFor="let number of Array.from(Array(exerciseCount).keys())"
<ion-select-option *ngFor="let number of exerciseIndices"
[value]="number">{{ number + 1 }}</ion-select-option>
</ion-select>
</ion-col>
......@@ -29,7 +29,7 @@
<ion-label>Show solutions for:</ion-label>
<ion-select [(ngModel)]="currentExerciseIndex" name="currentExerciseIndex"
(ngModelChange)="showNextExercise(currentExerciseIndex, true)">
<ion-select-option *ngFor="let number of Array.from(Array(exerciseCount).keys())"
<ion-select-option *ngFor="let number of exerciseIndices"
[value]="number">{{ number + 1 }}</ion-select-option>
</ion-select>
</ion-col>
......@@ -40,7 +40,7 @@
<ion-label>{{ 'TEST_MODULE_GO_TO_EXERCISE' | translate}}:</ion-label>
<ion-select [(ngModel)]="currentExerciseIndex" name="currentExerciseIndex"
(ngModelChange)="showNextExercise(currentExerciseIndex, true)">
<ion-select-option *ngFor="let number of Array.from(Array(exerciseCount).keys())"
<ion-select-option *ngFor="let number of exerciseIndices"
[value]="number">{{ number + 1 }}</ion-select-option>
</ion-select>
</ion-col>
......@@ -63,38 +63,28 @@
<ion-col>
<h4>{{ 'UNIT_INTRO_TITLE' | translate }}</h4>
{{ 'UNIT_INTRO_TEXT' | translate }}
<br>
<br>
{{ 'UNIT_MODUS_LEARNING' | translate}}
<br>
<br>
{{ 'UNIT_MODUS_TEST' | translate}}
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<br>
{{ 'UNIT_INTRO_SUBTEXT' | translate}}
</ion-col>
<ion-col>{{ 'UNIT_MODUS_LEARNING' | translate}}</ion-col>
</ion-row>
<ion-row>
<ion-col>
<br>
{{ 'UNIT_DATA_SECURITY' | translate}}
</ion-col>
<ion-col>{{ 'UNIT_MODUS_TEST' | translate}}</ion-col>
</ion-row>
<ion-row>
<ion-col>{{ 'UNIT_INTRO_SUBTEXT' | translate}}</ion-col>
</ion-row>
<ion-row>
<ion-col>{{ 'UNIT_DATA_SECURITY' | translate}}</ion-col>
</ion-row>
<ion-row class="button-continue">
<ion-col></ion-col>
<ion-col></ion-col>
<ion-col>
<ion-button (click)="continue()">
{{ 'START_LEARNING' | translate}}
</ion-button>
<ion-button (click)="continue(false)">{{ 'START_LEARNING' | translate}}</ion-button>
</ion-col>
<ion-col>
<ion-button (click)="continue()">
{{'START_TEST' | translate }}
</ion-button>
<ion-button (click)="continue()">{{'START_TEST' | translate }}</ion-button>
</ion-col>
</ion-row>
</div>
......@@ -108,13 +98,13 @@
</ion-col>
</ion-row>
<ion-row
*ngIf="currentState == TestModuleState.showSolutions && !getCurrentExerciseName().startsWith(nonH5Pstring)">
<ion-button (click)="continue()">
*ngIf="(!isTestMode || currentState == TestModuleState.showSolutions) && !getCurrentExerciseName().startsWith(nonH5Pstring)">
<ion-button (click)="continue(isTestMode)">
{{ 'BUTTON_CONTINUE' | translate}}
</ion-button>
</ion-row>
<!-- step 6: show results -->
<div *ngIf="currentState == TestModuleState.showResults && currentExerciseIndex === exerciseCount - 1">
<div *ngIf="currentState == TestModuleState.showResults && currentExerciseIndex === exerciseIndices.length - 1">
<ion-row>
<ion-col>
<h4>{{'UNIT_EVALUATION_HEADER' | translate}}</h4>
......
......@@ -46,7 +46,7 @@ export class TestPage implements OnDestroy, OnInit {
startIndex: 0,
durationSeconds: 1080,
exercises: ['mark_words_1', 'fill_blanks_11', 'fill_blanks_12', 'fill_blanks_13',
'fill_blanks_14', 'fill_blanks_16', 'fill_blanks_15', 'multi_choice_24']
'fill_blanks_14', 'fill_blanks_16', 'fill_blanks_15', 'multi_choice_24']
}), new ExercisePart({
/** cloze text exercise */
startIndex: 0,
......@@ -56,7 +56,7 @@ export class TestPage implements OnDestroy, OnInit {
/** pair association exercise */
startIndex: 0,
durationSeconds: 720,
exercises: ['voc_list_2', 'voc_list_6', 'voc_list_10',
exercises: HelperService.shuffle(['voc_list_2', 'voc_list_6', 'voc_list_10',
'voc_list_11', 'voc_list_12', 'voc_list_16',
'voc_list_17', 'voc_list_18', 'voc_list_27',
'voc_list_29', 'voc_list_30', 'voc_list_31', 'voc_list_34',
......@@ -64,14 +64,14 @@ export class TestPage implements OnDestroy, OnInit {
'voc_list_53', 'voc_list_54', 'voc_list_56', 'voc_list_61', 'voc_list_62',
'voc_list_65', 'voc_list_66', 'voc_list_67', 'voc_list_70',
'voc_list_72', 'voc_list_84', 'voc_list_85', 'voc_list_86',
'voc_list_87', 'voc_list_88', 'voc_list_89', 'voc_list_90', 'voc_list_91']
'voc_list_87', 'voc_list_88', 'voc_list_89', 'voc_list_90', 'voc_list_91'])
}), new ExercisePart({
/** posttest */
startIndex: 0,
durationSeconds: 240,
exercises: ['fill_blanks_1', 'fill_blanks_6', 'multi_choice_5', 'multi_choice_6',
'multi_choice_7', 'multi_choice_8', 'fill_blanks_2', 'fill_blanks_7',
'fill_blanks_4', 'multi_choice_18', 'multi_choice_9']
'multi_choice_7', 'multi_choice_8', 'fill_blanks_2', 'fill_blanks_7',
'fill_blanks_4', 'multi_choice_18', 'multi_choice_9']
}), new ExercisePart({exercises: ['nonH5P_2'], startIndex: 0})];
public currentExerciseIndex: number;
public currentExerciseParts: ExercisePart[];
......@@ -79,7 +79,8 @@ export class TestPage implements OnDestroy, OnInit {
public dataAlreadySentMessage: string;
public dataSentSuccessMessage: string;
public didTimeRunOut = false;
public exerciseCount: number;
public exerciseIndices: number[];
public finishExerciseTimeout = 200;
public h5pBlanksString = 'H5P.Blanks';
public h5pCheckButtonClassString = '.h5p-question-check-answer';
public h5pDragTextString = 'H5P.DragText';
......@@ -90,6 +91,7 @@ export class TestPage implements OnDestroy, OnInit {
public h5pShowSolutionClassString = '.h5p-question-show-solution';
public h5pTextInputClassString = '.h5p-text-input';
public hideClassString = 'hide';
public isTestMode = true;
public knownCount: [number, number];
public nonH5Pstring = 'nonH5P';
public progressBarValue: number;
......@@ -121,6 +123,7 @@ export class TestPage implements OnDestroy, OnInit {
}
adjustStartIndices() {
this.currentExerciseParts[0].startIndex = 0;
[...Array(this.currentExerciseParts.length).keys()].forEach((index: number) => {
if (index === 0) {
return;
......@@ -131,6 +134,9 @@ export class TestPage implements OnDestroy, OnInit {
}
adjustTimer(newIndex: number, review: boolean) {
if (!this.isTestMode) {
return;
}
if (!review) {
const metaIndex: number = this.currentExerciseParts.map(x => x.startIndex).indexOf(newIndex);
if (metaIndex > -1 && this.currentExerciseParts[metaIndex].durationSeconds > 0) {
......@@ -167,11 +173,27 @@ export class TestPage implements OnDestroy, OnInit {
this.currentState = TestModuleState.showResults;
}
continue() {
continue(isTestMode: boolean = true) {
if (this.isTestMode && !isTestMode) {
// no pretest in learning mode
this.deleteExercisePart(1);
}
this.isTestMode = isTestMode;
this.currentExerciseIndex = this.currentExerciseIndex + 1;
this.showNextExercise(this.currentExerciseIndex, this.currentState === TestModuleState.showSolutions);
}
deleteExercisePart(index: number) {
this.exerciseIndices = [];
this.currentExerciseParts.splice(index, 1);
this.adjustStartIndices();
// dirty hack to make the view re-render any ngFor references to the exerciseIndices
setTimeout(() => {
const exerciseCount: number = this.currentExerciseParts.map(x => x.exercises.length).reduce((x, y) => x + y);
this.exerciseIndices = Array.from(new Array(exerciseCount).keys());
}, 50);
}
async exit(ev: any = null) {
HelperService.currentPopover = await this.popoverController.create({
component: ConfirmCancelPage,
......@@ -182,35 +204,26 @@ export class TestPage implements OnDestroy, OnInit {
}
finishExercise(event: XAPIevent) {
if (!this.isTestMode) {
this.saveResult(false, event);
return;
}
// hide H5P immediately so the solutions are not visible to the user
document.querySelector(this.h5pRowIDstring).classList.add(this.hideClassString);
// dirty hack to wait for the solutions being processed by H5P
setTimeout(() => {
const iframe: HTMLIFrameElement = document.querySelector(this.h5pIframeString);
if (iframe) {
const iframeDoc: Document = iframe.contentWindow.document;
const solutionButton: HTMLButtonElement = iframeDoc.body.querySelector(this.h5pShowSolutionClassString);
if (solutionButton) {
solutionButton.click();
}
const inner: string = iframeDoc.documentElement.innerHTML;
this.vocService.currentTestResults[this.currentExerciseIndex] = new TestResultMC({
statement: event.data.statement,
innerHTML: inner
});
const knownCheckbox: HTMLInputElement = iframeDoc.body.querySelector('#known');
if (knownCheckbox) {
this.knownCount = [this.knownCount[0] + (knownCheckbox.checked ? 1 : 0), this.knownCount[1] + 1];
}
}
this.saveResult(true, event);
if (!this.didTimeRunOut) {
this.continue();
}
document.querySelector(this.h5pRowIDstring).classList.remove(this.hideClassString);
}, 200);
}, this.finishExerciseTimeout);
}
getCurrentExerciseName() {
const targetPartIndex: number = this.getCurrentExercisePartIndex();
if (!targetPartIndex) {
return '';
}
return this.currentExerciseParts[targetPartIndex]
.exercises[this.currentExerciseIndex - this.currentExerciseParts[targetPartIndex].startIndex];
}
......@@ -233,7 +246,7 @@ export class TestPage implements OnDestroy, OnInit {
initTimer(durationSeconds: number) {
// add the new duration to countdown
const countDownDate = new Date(new Date().getTime() + durationSeconds * 1000).getTime(); // 15
const countDownDate = new Date(new Date().getTime() + durationSeconds * 15).getTime(); // 15 1000
// Update the countdown every 1 second
this.timer = setInterval(() => {
// Get today's date and time
......@@ -258,7 +271,10 @@ export class TestPage implements OnDestroy, OnInit {
// prevent the check button from jumping to the next exercise
this.didTimeRunOut = true;
checkButton.click();
this.didTimeRunOut = false;
// dirty hack to wait for the XAPI handlers
setTimeout(() => {
this.didTimeRunOut = false;
}, this.finishExerciseTimeout);
}
}
const newIndex: number = this.currentExerciseParts[this.getCurrentExercisePartIndex() + 1].startIndex;
......@@ -279,7 +295,6 @@ export class TestPage implements OnDestroy, OnInit {
this.results = [];
this.wasDataSent = false;
this.currentExerciseIndex = 0;
this.adjustStartIndices();
this.vocService.currentTestResults = {};
this.currentState = TestModuleState.inProgress;
this.knownCount = [0, 0];
......@@ -295,8 +310,7 @@ export class TestPage implements OnDestroy, OnInit {
// remove either the second last or third last exercise
const index: number = Math.random() < 0.5 ? 3 : 4;
this.testType = index === 3 ? TestType.cloze : TestType.list;
this.currentExerciseParts.splice(this.currentExerciseParts.length - index, 1);
this.exerciseCount = this.currentExerciseParts.map(x => x.exercises.length).reduce((x, y) => x + y);
this.deleteExercisePart(this.currentExerciseParts.length - index);
}
removeTimer(freeze: boolean) {
......@@ -312,6 +326,28 @@ export class TestPage implements OnDestroy, OnInit {
this.ngOnInit();
}
saveResult(showSolutions: boolean, event: XAPIevent) {
const iframe: HTMLIFrameElement = document.querySelector(this.h5pIframeString);
if (iframe) {
const iframeDoc: Document = iframe.contentWindow.document;
const inner: string = iframeDoc.documentElement.innerHTML;
this.vocService.currentTestResults[this.currentExerciseIndex] = new TestResultMC({
statement: event.data.statement,
innerHTML: inner
});
const knownCheckbox: HTMLInputElement = iframeDoc.querySelector('#known');
if (knownCheckbox) {
this.knownCount = [this.knownCount[0] + (knownCheckbox.checked ? 1 : 0), this.knownCount[1] + 1];
}
if (showSolutions) {
const solutionButton: HTMLButtonElement = iframeDoc.querySelector(this.h5pShowSolutionClassString);
if (solutionButton) {
solutionButton.click();
}
}
}
}
async sendData() {
if (this.wasDataSent) {
const toast = await this.toastCtrl.create({
......@@ -378,7 +414,7 @@ export class TestPage implements OnDestroy, OnInit {
const maxProgress: number = currentExercisePart.exercises.length;
this.progressBarValue = (newIndex - currentExercisePart.startIndex) / maxProgress;
this.updateUI();
const currentExerciseName = this.getCurrentExerciseName();
const currentExerciseName: string = this.getCurrentExerciseName();
if (currentExerciseName.startsWith(this.nonH5Pstring)) {
document.querySelector(this.h5pRowIDstring).classList.add(this.hideClassString);
if (newIndex === this.currentExerciseParts[this.currentExerciseParts.length - 1].startIndex) {
......
{
"questions": [
"<p>facias --> *facere* --> *machen / tun*<\/p>",
"<p>scribo --> *scribere* --> *schreiben*<\/p>",
"<p>commoveri --> *commovere* --> *bewegen / beunruhigen*<\/p>",
"<p>gaudeas --> *gaudere* --> *sich freuen*<\/p>"
"<p>facias --> *facere* --> *make*<\/p>",
"<p>scribo --> *scribere* --> *write*<\/p>",
"<p>commoveri --> *commovere* --> *move / unsettle*<\/p>",
"<p>gaudeas --> *gaudere* --> *rejoice / be glad*<\/p>"
],
"showSolutions": "Show solutions",
"tryAgain": "Try again",
......
......@@ -3,7 +3,7 @@
"AUTHOR_SEARCH": "Autor suchen...",
"AUTHOR_SELECT": "Autor auswählen",
"AUTHOR_SHOW_ONLY_TREEBANKS": "Nur aufbereitete Texte",
"BUTTON_CONTINUE": "Weiter",
"BUTTON_CONTINUE": "Nächste Aufgabe",
"CALLIDUS_PROJECT": "CALLIDUS-Projekt",
"CANCEL": "Abbrechen",
"CASE_ABLATIVE": "Ablativ",
......@@ -136,7 +136,7 @@
"START": "Anfang",
"START_LEARNING": "Lernmodus",
"START_TEST": "Testmodus",
"TEST": "Wortschatzeinheit (Cicero)",
"TEST": "Wortschatzeinheit Cicero",
"TEST_MODULE_EXERCISE_ID": "Übungs-ID",
"TEST_MODULE_GO_TO_EXERCISE": "Gehe zu Übung",
"TEST_MODULE_PROGRESS_PART": "Fortschritt: Teil",
......
......@@ -3,7 +3,7 @@
"AUTHOR_SEARCH": "Search author...",
"AUTHOR_SELECT": "Select author",
"AUTHOR_SHOW_ONLY_TREEBANKS": "High-quality texts only",
"BUTTON_CONTINUE": "Continue",
"BUTTON_CONTINUE": "Next exercise",
"CALLIDUS_PROJECT": "CALLIDUS Project",
"CANCEL": "Cancel",
"CASE_ABLATIVE": "Ablative",
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment