Commit bf8b42a4 authored by Konstantin Schulz's avatar Konstantin Schulz
Browse files

added timer and simple evaluation for test module

parent 3fd5f583
{
"name": "mc_frontend",
"version": "0.7.7",
"version": "0.7.8",
"author": "Ionic Framework",
"homepage": "https://ionicframework.com/",
"scripts": {
......@@ -80,4 +80,4 @@
"cordova-plugin-splashscreen": {}
}
}
}
\ No newline at end of file
}
......@@ -18,6 +18,7 @@ const routes: Routes = [
{ path: 'sources', loadChildren: './sources/sources.module#SourcesPageModule' },
{ path: 'imprint', loadChildren: './imprint/imprint.module#ImprintPageModule' },
{ path: 'test', loadChildren: './test/test.module#TestPageModule' },
{ path: 'confirm-cancel', loadChildren: './confirm-cancel/confirm-cancel.module#ConfirmCancelPageModule' },
];
......
......@@ -13,6 +13,7 @@ import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {ChangeLanguagePage} from "src/app/change-language/change-language.page";
import {ChangeLanguagePageModule} from "src/app/change-language/change-language.module";
import {HelperService} from "src/app/helper.service";
import {ConfirmCancelPageModule} from "src/app/confirm-cancel/confirm-cancel.module";
@NgModule({
declarations: [AppComponent],
......@@ -29,6 +30,7 @@ import {HelperService} from "src/app/helper.service";
deps: [HttpClient]
}
}),
ConfirmCancelPageModule,
ChangeLanguagePageModule
],
providers: [
......
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
import {Routes, RouterModule} from '@angular/router';
import {IonicModule} from '@ionic/angular';
import {ConfirmCancelPage} from './confirm-cancel.page';
import {TranslateModule} from "@ngx-translate/core";
const routes: Routes = [
{
path: '',
component: ConfirmCancelPage
}
];
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
RouterModule.forChild(routes),
TranslateModule.forChild()
],
declarations: [ConfirmCancelPage]
})
export class ConfirmCancelPageModule {
}
<ion-header>
<ion-toolbar>
<ion-title><h5>{{ 'CONFIRM_CANCEL' | translate}}</h5></ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<ion-grid>
<ion-row>
<ion-col>
<ion-button (click)="confirm()">{{ 'YES' | translate }}</ion-button>
</ion-col>
<ion-col>
<ion-button (click)="exit()">{{ 'CANCEL' | translate }}</ion-button>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ConfirmCancelPage } from './confirm-cancel.page';
describe('ConfirmCancelPage', () => {
let component: ConfirmCancelPage;
let fixture: ComponentFixture<ConfirmCancelPage>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ConfirmCancelPage ],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ConfirmCancelPage);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {Component, OnInit} from '@angular/core';
import {HelperService} from '../helper.service';
import {NavController} from "@ionic/angular";
@Component({
selector: 'app-confirm-cancel',
templateUrl: './confirm-cancel.page.html',
styleUrls: ['./confirm-cancel.page.scss'],
})
export class ConfirmCancelPage implements OnInit {
constructor(public navCtrl: NavController) {
}
ngOnInit() {
}
exit() {
HelperService.currentPopover.dismiss();
HelperService.currentPopover = null;
}
confirm() {
HelperService.goToHomePage(this.navCtrl);
this.exit();
}
}
......@@ -85,8 +85,4 @@ export class FeedbackPage implements OnDestroy {
}
this.initH5P(exerciseType);
}
openUrl(url: string) {
window.open(url, '_system', 'location=yes');
}
}
......@@ -11,6 +11,7 @@ export class HelperService {
public static config: object;
public static currentError: HttpErrorResponse;
public static currentPopover: any;
public static generalErrorAlertMessage: string;
public static isLoading = false;
public static isVocabularyCheck = false;
......@@ -26,6 +27,10 @@ export class HelperService {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}
static goToAuthorPage(navCtrl) {
navCtrl.navigateForward('/author').then();
}
static goToFeedbackPage(navCtrl: NavController) {
navCtrl.navigateForward('/feedback').then();
}
......@@ -35,15 +40,15 @@ export class HelperService {
}
static goToImprintPage(navCtrl: NavController) {
navCtrl.navigateRoot('/imprint').then();
navCtrl.navigateForward('/imprint').then();
}
static goToInfoPage(navCtrl: NavController) {
navCtrl.navigateRoot('/info').then();
navCtrl.navigateForward('/info').then();
}
static goToSourcesPage(navCtrl: NavController) {
navCtrl.navigateRoot('/sources').then();
navCtrl.navigateForward('/sources').then();
}
static goToTestPage(navCtrl: NavController) {
......
<ion-header>
<ion-toolbar>
<div class="toolbar-left">
<img src="../assets/imgs/logo.png" width="32px" height="32px" alt="Logo von Callidus: Fuchs">
<img src="assets/imgs/logo.png" width="32px" height="32px" alt="Logo von Callidus: Fuchs">
</div>
<div class="toolbar-left">
<ion-title>{{ 'MACHINA_CALLIDA' | translate }}</ion-title>
......@@ -9,7 +9,7 @@
<div class="toolbar-right">
<ion-spinner *ngIf="HelperService.isLoading"></ion-spinner>
<button (click)="changeLanguage($event).then()">
<img src="{{'assets/imgs/' + translate.currentLang + '.svg'}}" width="40px" height="24px">
<img src="{{'assets/imgs/' + translate.currentLang + '.svg'}}" width="50px" height="30px">
</button>
<!--<button (click)="HelperService.goToFeedbackPage(navCtrl)">
<ion-icon name="help-circle"></ion-icon>
......@@ -33,7 +33,7 @@
<ion-row>
<ion-col>
<br>
<img src="../assets/imgs/ws_modell.png" width="320px" height="317px" alt="Modell zur Wortschatzkompetenz im
<img src="assets/imgs/ws_modell.png" width="320px" height="317px" alt="Modell zur Wortschatzkompetenz im
Lateinunterricht, drei Bestandteile: Form, Funktion und Strategie"><br>
Wortschatzkompetenz im Lateinunterricht
</ion-col>
......@@ -68,4 +68,4 @@
</ion-tab-bar>
</ion-tabs>
</ion-toolbar>
</ion-footer>
\ No newline at end of file
</ion-footer>
export enum CitationLevel {
default = <any>'default'
}
export enum ExerciseType {
cloze = <any>'cloze',
kwic = <any>'kwic',
matching = <any>'matching',
}
export enum ExerciseTypeTranslation {
cloze = <any>'EXERCISE_TYPE_CLOZE',
kwic = <any>'EXERCISE_TYPE_KWIC',
matching = <any>'EXERCISE_TYPE_MATCHING',
}
export enum FileType {
docx = <any>'docx',
pdf = <any>'pdf',
xml = <any>'xml',
}
export enum InstructionsTranslation {
cloze = <any>'INSTRUCTIONS_CLOZE',
kwic = <any>'INSTRUCTIONS_KWIC',
matching = <any>'INSTRUCTIONS_MATCHING',
}
export enum MoodleExerciseType {
cloze = <any>'ddwtos',
kwic = <any>'kwic',
matching = <any>'matching',
}
export enum Phenomenon {
case = <any>'case',
dependency = <any>'dependency',
lemma = <any>'lemma',
partOfSpeech = <any>'partOfSpeech',
}
export enum PhenomenonTranslation {
case = <any>'PHENOMENON_CASE',
dependency = <any>'PHENOMENON_DEPENDENCY',
lemma = <any>'PHENOMENON_LEMMA',
partOfSpeech = <any>'PHENOMENON_PART_OF_SPEECH',
}
export enum VocabularyCorpus {
agldt = <any>'agldt',
bws = <any>'bws',
proiel = <any>'proiel',
viva = <any>'viva',
}
export enum VocabularyCorpusTranslation {
agldt = <any>'VOCABULARY_REFERENCE_CORPUS_AGLDT',
bws = <any>'VOCABULARY_REFERENCE_CORPUS_BWS',
proiel = <any>'VOCABULARY_REFERENCE_PROIEL',
viva = <any>'VOCABULARY_REFERENCE_CORPUS_VIVA',
}
export enum CaseValue {
nominative = <any>'nominative',
genitive = <any>'genitive',
......@@ -80,6 +18,10 @@ export enum CaseTranslations {
locative = <any>'CASE_LOCATIVE',
}
export enum CitationLevel {
default = <any>'default'
}
export enum DependencyValue {
adjectivalClause = <any>'adjectivalClause',
adjectivalModifier = <any>'adjectivalModifier',
......@@ -146,6 +88,36 @@ export enum DependencyTranslation {
vocative = <any>'DEPENDENCY_VOCATIVE',
}
export enum ExerciseType {
cloze = <any>'cloze',
kwic = <any>'kwic',
matching = <any>'matching',
}
export enum ExerciseTypeTranslation {
cloze = <any>'EXERCISE_TYPE_CLOZE',
kwic = <any>'EXERCISE_TYPE_KWIC',
matching = <any>'EXERCISE_TYPE_MATCHING',
}
export enum FileType {
docx = <any>'docx',
pdf = <any>'pdf',
xml = <any>'xml',
}
export enum InstructionsTranslation {
cloze = <any>'INSTRUCTIONS_CLOZE',
kwic = <any>'INSTRUCTIONS_KWIC',
matching = <any>'INSTRUCTIONS_MATCHING',
}
export enum MoodleExerciseType {
cloze = <any>'ddwtos',
kwic = <any>'kwic',
matching = <any>'matching',
}
export enum PartOfSpeechValue {
adjective = <any>'adjective',
adverb = <any>'adverb',
......@@ -181,3 +153,36 @@ export enum PartOfSpeechTranslation {
symbol = <any>'PART_OF_SPEECH_SYMBOL',
verb = <any>'PART_OF_SPEECH_VERB',
}
export enum Phenomenon {
case = <any>'case',
dependency = <any>'dependency',
lemma = <any>'lemma',
partOfSpeech = <any>'partOfSpeech',
}
export enum PhenomenonTranslation {
case = <any>'PHENOMENON_CASE',
dependency = <any>'PHENOMENON_DEPENDENCY',
lemma = <any>'PHENOMENON_LEMMA',
partOfSpeech = <any>'PHENOMENON_PART_OF_SPEECH',
}
export enum TestType {
cloze = <any>'cloze',
list = <any>'list',
}
export enum VocabularyCorpus {
agldt = <any>'agldt',
bws = <any>'bws',
proiel = <any>'proiel',
viva = <any>'viva',
}
export enum VocabularyCorpusTranslation {
agldt = <any>'VOCABULARY_REFERENCE_CORPUS_AGLDT',
bws = <any>'VOCABULARY_REFERENCE_CORPUS_BWS',
proiel = <any>'VOCABULARY_REFERENCE_PROIEL',
viva = <any>'VOCABULARY_REFERENCE_CORPUS_VIVA',
}
......@@ -51,20 +51,22 @@ beginning that it is going to be a download (instead of an ordinary link or clic
| translate }}</a>
</ion-col>
<ion-col>
<a href="{{HelperService.config['developerMailTo']}}">{{ 'EMAIL_ERROR' | translate }}</a>
<a href="{{HelperService.config['developerMailTo']}}">{{ 'EMAIL_ERROR' | translate }}</a>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<button *ngIf="showInstructions; else dropright" (click)="showInstructions = !showInstructions">
<ion-icon name="arrow-dropdown"></ion-icon>
</button>
<ng-template #dropright>
<button (click)="showInstructions = !showInstructions">
<ion-icon name="arrow-dropright"></ion-icon>
<h4 (click)="showInstructions = !showInstructions" class="pointer">
<button *ngIf="showInstructions; else dropright">
<ion-icon name="arrow-dropdown"></ion-icon>
</button>
</ng-template>
<h4>{{ 'EXERCISE_DOWNLOAD_NEXT_STEPS' | translate }}</h4>
<ng-template #dropright>
<button>
<ion-icon name="arrow-dropright"></ion-icon>
</button>
</ng-template>
<span>{{ 'EXERCISE_DOWNLOAD_NEXT_STEPS' | translate }}</span>
</h4>
<ol *ngIf="showInstructions">
<li>{{ 'INSTRUCTION_LOGIN_MOODLE' | translate }}</li>
<li>{{ 'INSTRUCTION_COGWHEEL_MORE' | translate }}</li>
......
......@@ -71,13 +71,6 @@ export class PreviewPage implements OnDestroy {
});
});
})(H5P.jQuery);
H5P.externalDispatcher.on('xAPI', (event: XAPIevent) => {
// results are only available when a task has been completed/answered, not in the "attempted" or "interacted" stages
if (event.data.statement.verb.id === HelperService.config["xAPIverbIDanswered"] && event.data.statement.result) {
// TODO: SAVE THIS SCORE BY SENDING IT TO THE BACKEND? OR WRITE THE WHOLE STATEMENT?
// console.log(event.data.statement.result.score.scaled);
}
});
}, 50);
}
this.updateFileUrl();
......
......@@ -5,7 +5,9 @@
</div>
<div class="toolbar-right">
<ion-spinner *ngIf="HelperService.isLoading"></ion-spinner>
<button (click)="HelperService.goToHomePage(navCtrl)">
<span id="timer"></span>
<!-- HelperService.goToHomePage(navCtrl)-->
<button (click)="exit($event).then()">
<ion-icon name="close-circle"></ion-icon>
</button>
</div>
......@@ -117,20 +119,26 @@
<ion-item>
<ion-icon name="stats" slot="start"></ion-icon>
<ion-label>Ergebnis Diagnosetest:</ion-label>
... von ... Aufgaben wurden richtig bearbeitet.
{{results[0][0]}} von {{results[0][1]}} Aufgaben wurden richtig
bearbeitet.
</ion-item>
<ion-item>
<ion-icon name="book" slot="start"></ion-icon>
<ion-label>Ergebnis Anwendung:</ion-label>
... von ... Fragen wurden richtig beantwortet.
{{results[1][0]}} von {{results[1][1]}} Fragen wurden richtig beantwortet.
</ion-item>
<ion-item>
<ion-icon name="walk" slot="start"></ion-icon>
<ion-label>Ergebnis Üben:</ion-label>
... von ... Lücken wurden richtig ergänzt.<br>
<!-- or, depending on the exercise-->
... von ... Wörtern wurden gelernt. Davon wurden ... Wörter
als "Kann ich" markiert.
<div *ngIf="testType == TestType.cloze; else list">{{results[2][0]}} von {{results[2][1]}}
Lücken wurden richtig ergänzt.
</div>
<ng-template #list>
<!-- TODO: ADD RESULTS FOR LIST LEARNING -->
<!-- <div>{{results[2][0]}} von {{results[2][1]}} Wörtern wurden gelernt. Davon wurden ... Wörter-->
<!-- als "Kann ich" markiert.-->
<!-- </div>-->
</ng-template>
</ion-item>
<ion-item>
<ion-icon name="pulse" slot="start"></ion-icon>
......@@ -150,7 +158,7 @@
<ion-toolbar>
<ion-tabs>
<ion-tab-bar slot="bottom">
<ion-tab-button (click)="goToAuthorPage(navCtrl)">
<ion-tab-button (click)="HelperService.goToAuthorPage(navCtrl)">
<ion-icon name="walk"></ion-icon>
<ion-label>{{ 'EXERCISE_GENERATE' | translate }}</ion-label>
</ion-tab-button>
......
import { Component, OnDestroy } from '@angular/core';
import { NavController } from "@ionic/angular";
import { HelperService } from '../helper.service';
import { XAPIevent } from "src/app/models/xAPIevent";
import { TranslateService } from "@ngx-translate/core";
import {Component, OnDestroy, OnInit} from '@angular/core';
import {NavController, PopoverController} from "@ionic/angular";
import {HelperService} from '../helper.service';
import {XAPIevent} from "src/app/models/xAPIevent";
import {TranslateService} from "@ngx-translate/core";
import {VocabularyService} from "src/app/vocabulary.service";
import {TestType} from "src/app/models/enum";
import {ConfirmCancelPage} from "src/app/confirm-cancel/confirm-cancel.page";
declare var H5P: any;
// dirty hack to prevent H5P access errors after resize events
......@@ -20,9 +23,11 @@ window.onresize = () => {
styleUrls: ['./test.page.scss'],
})
export class TestPage implements OnDestroy {
export class TestPage implements OnDestroy, OnInit {
Array = Array;
HelperService = HelperService;
Object = Object;
TestType = TestType;
public currentExercise: string = "nonH5P_1";
public exercises: string[] = ["nonH5P_1", "fill_blanks_1", "multi_choice_1", "multi_choice_2",
"multi_choice_3", "multi_choice_4", "multi_choice_5", "multi_choice_6",
......@@ -33,24 +38,45 @@ export class TestPage implements OnDestroy {
"fill_blanks_7", "fill_blanks_8", "fill_blanks_9", "fill_blanks_10",
"multi_choice_18", "multi_choice_19", "multi_choice_20", "multi_choice_21",
"multi_choice_22", "multi_choice_23", "drag_text_1-3", "voc_list_1-86", "nonH5P_2"];
public secondPartStartIndex: number = 29;
public testType: TestType;
public thirdPartStartIndex: number = 34;
public results: [number, number][];
public timer: any;
constructor(public navCtrl: NavController, public translate: TranslateService) {
H5P.externalDispatcher.on('xAPI', (event: XAPIevent) => {
// results are only available when a task has been completed/answered, not in the "attempted" or "interacted" stages
if (event.data.statement.verb.id === HelperService.config["xAPIverbIDanswered"] && event.data.statement.result) {
// TODO: SAVE THIS SCORE BY SENDING IT TO THE BACKEND? OR WRITE THE WHOLE STATEMENT?
// console.log(event.data.statement.result.score.scaled);
this.showNextExercise(this.exercises.indexOf(this.currentExercise) + 1);
}
});
constructor(public navCtrl: NavController,
public translate: TranslateService,
public vocService: VocabularyService,
public popoverController: PopoverController) {
}
goToAuthorPage() {
this.navCtrl.navigateForward('/author').then();
analyzeResults() {
this.results = [];
let allTestIndices = Object.keys(this.vocService.currentTestResults).map(x => +x);
let relevantTestIndices = allTestIndices.filter(x => x < this.secondPartStartIndex);
let correctlySolved: number[] = relevantTestIndices.filter((i) => {
return this.vocService.currentTestResults[i].scaled == 1;
});
this.results.push([correctlySolved.length, relevantTestIndices.length]);
relevantTestIndices = allTestIndices.filter(x => this.secondPartStartIndex <= x && x < this.thirdPartStartIndex);
correctlySolved = relevantTestIndices.filter((i) => {
return this.vocService.currentTestResults[i].scaled == 1;
});
this.results.push([correctlySolved.length, relevantTestIndices.length]);
if (this.testType == TestType.cloze){
relevantTestIndices = allTestIndices.filter(x => x == this.thirdPartStartIndex);
correctlySolved = relevantTestIndices.map(i => this.vocService.currentTestResults[i].raw);
this.results.push([correctlySolved.reduce((x, y) => x + y), relevantTestIndices.map(i => this.vocService.currentTestResults[i].max).reduce((x, y) => x + y)]);
}
}
goToTestPage() {
this.navCtrl.navigateForward('/test').then();
async exit(ev: any = null) {
HelperService.currentPopover = await this.popoverController.create({
component: ConfirmCancelPage,
event: ev,
translucent: true
});
return await HelperService.currentPopover.present();
}
public initH5P(exerciseType: string) {
......@@ -70,9 +96,59 @@ export class TestPage implements OnDestroy {
}
ngOnDestroy() {
clearInterval(this.timer);
H5P.externalDispatcher.off('xAPI');
}
ngOnInit(): void {
this.randomizeTestType();
this.vocService.currentTestResults = [];
// add 45 minutes to countdown
const countDownDate = new Date(new Date().getTime() + 45 * 60000).getTime(); // 120
// Update the countdown every 1 second
this.timer = setInterval(() => {
// Get today's date and time
const now = new Date().getTime();
// Find the distance between now and the countdown date
const distance = countDownDate - now;
// Time calculations for minutes and seconds
const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((distance % (1000 * 60)) / 1000);
// Output the result in an element with id="timer"
const timerElement = document.getElementById("timer");
if (timerElement) {
timerElement.innerHTML = minutes + "m" + seconds + "s ";
}
// If the count down is over, write some text
if (distance < 0) {
clearInterval(this.timer);
if (timerElement) {
timerElement.innerHTML = "00:00";
}
const iframe: HTMLIFrameElement = document.querySelector("#h5p-iframe-1");