Commit 5d8daaa4 authored by Konstantin Schulz's avatar Konstantin Schulz
Browse files

new feature: exercises from the exercise list can now be compared to a...

new feature: exercises from the exercise list can now be compared to a specific vocabulary and sorted by their degree of overlap
parent 7bda721f
{
"name": "mc_frontend",
"version": "1.2.6",
"version": "1.3.0",
"author": "Ionic Framework",
"homepage": "https://ionicframework.com/",
"scripts": {
......
......@@ -3,7 +3,7 @@ import {Injectable} from '@angular/core';
import {CorpusMC} from 'src/app/models/corpusMC';
import {Author} from 'src/app/models/author';
import {TextRange} from 'src/app/models/textRange';
import {HttpClient, HttpErrorResponse} from '@angular/common/http';
import {HttpClient, HttpErrorResponse, HttpParams} from '@angular/common/http';
import {TranslateService} from '@ngx-translate/core';
import {ToastController} from '@ionic/angular';
import {HelperService} from 'src/app/helper.service';
......@@ -15,7 +15,7 @@ import {
DependencyValue,
ExerciseType,
ExerciseTypeTranslation,
InstructionsTranslation,
InstructionsTranslation, MoodleExerciseType,
PartOfSpeechTranslation,
PartOfSpeechValue,
Phenomenon
......@@ -27,6 +27,7 @@ import {Exercise} from 'src/app/models/exercise';
import {Feedback} from 'src/app/models/feedback';
import {PhenomenonMap, PhenomenonMapContent} from 'src/app/models/phenomenonMap';
import {FrequencyItem} from 'src/app/models/frequencyItem';
import {ExerciseMC} from 'src/app/models/exerciseMC';
@Injectable({
providedIn: 'root'
......@@ -68,9 +69,13 @@ export class CorpusService {
public helperService: HelperService,
) {
HelperService.waitForConfig().then(() => {
this.checkForUpdates();
this.checkAnnisResponse().then(() => {
this.restoreLastCorpus().then();
this.checkForUpdates().then(() => {
this.checkAnnisResponse().then(() => {
this.restoreLastCorpus().then(() => {
}, () => {
});
}, () => {
});
}, () => {
});
});
......@@ -124,19 +129,26 @@ export class CorpusService {
});
}
checkForUpdates() {
// check local storage for necessary updates
const updateInfoJSON: object = JSON.parse(window.localStorage.getItem(HelperService.config['localStorageKeyUpdateInfo']));
this.getCorpora(updateInfoJSON ? new Date(updateInfoJSON['corpora'].lastAccessTime).getTime() : 0).then();
checkForUpdates(): Promise<void> {
return new Promise((resolve, reject) => {
// check local storage for necessary updates
const updateInfoJSON: object = JSON.parse(window.localStorage.getItem(HelperService.config['localStorageKeyUpdateInfo']));
this.getCorpora(updateInfoJSON ? new Date(updateInfoJSON['corpora'].lastAccessTime).getTime() : 0).then(() => {
return resolve();
}, () => {
return reject();
});
});
}
getCorpora(lastUpdateTimeMS: number = 0) {
return new Promise(resolve => {
getCorpora(lastUpdateTimeMS: number = 0): Promise<void> {
return new Promise((resolve, reject) => {
this.availableCorpora = [];
this.availableAuthors = [];
// get corpora from REST API
const url = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiCorporaPath'];
this.http.get(url, {params: {last_update_time: lastUpdateTimeMS.toString()}}).subscribe((data: object) => {
const params: HttpParams = new HttpParams().set('last_update_time', lastUpdateTimeMS.toString());
HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((data: object) => {
if (data) {
const corpusList: CorpusMC[] = data['corpora'] as CorpusMC[];
window.localStorage.setItem(HelperService.config['localStorageKeyCorpora'], JSON.stringify(corpusList));
......@@ -156,20 +168,33 @@ export class CorpusService {
position: 'top'
});
toast.present().then();
return resolve(error);
return reject(error);
});
});
}
getCTStextPassage(urn: string) {
const url = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiRawtextPath'];
return this.http.get(url, {params: {urn: urn}});
getCTStextPassage(urn: string): Promise<AnnisResponse> {
return new Promise(((resolve, reject) => {
const url = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiRawtextPath'];
const params: HttpParams = new HttpParams().set('urn', urn);
HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((ar: AnnisResponse) => {
return resolve(ar);
}, (error: HttpErrorResponse) => {
return reject(error);
});
}));
}
getCTSvalidReff(urn: string) {
const config: object = HelperService.config;
const fullUrl: string = config['backendBaseUrl'] + config['backendApiValidReffPath'];
return this.http.get(fullUrl, {params: {urn: urn}});
getCTSvalidReff(urn: string): Promise<string[]> {
return new Promise((resolve, reject) => {
const fullUrl: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiValidReffPath'];
const params: HttpParams = new HttpParams().set('urn', urn);
HelperService.makeGetRequest(this.http, this.toastCtrl, fullUrl, params).then((reff: string[]) => {
resolve(reff);
}, (error: HttpErrorResponse) => {
reject(error);
});
});
}
getFrequencyAnalysis() {
......@@ -177,24 +202,15 @@ export class CorpusService {
if (this.annisResponse.frequency_analysis.length) {
return resolve();
} else {
HelperService.currentError = null;
HelperService.isLoading = true;
const url = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiFrequencyPath'];
this.http.get(url, {params: {urn: this.currentUrn}}).subscribe((fis: FrequencyItem[]) => {
const url: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiFrequencyPath'];
const params: HttpParams = new HttpParams().set('urn', this.currentUrn);
HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((fis: FrequencyItem[]) => {
HelperService.isLoading = false;
this.annisResponse.frequency_analysis = fis;
HelperService.mostRecentSetup.annisResponse = this.annisResponse;
this.helperService.saveMostRecentSetup().then();
return resolve();
}, async (error: HttpErrorResponse) => {
HelperService.isLoading = false;
HelperService.currentError = error;
const toast = await this.toastCtrl.create({
message: HelperService.generalErrorAlertMessage,
duration: 3000,
position: 'top'
});
toast.present().then();
}, () => {
return reject();
});
}
......@@ -227,28 +243,17 @@ export class CorpusService {
});
}
getText(saveToCache: boolean = true) {
getText(saveToCache: boolean = true): Promise<void> {
return new Promise((resolve, reject) => {
this.currentText = '';
if (HelperService.isVocabularyCheck) {
return resolve();
} else {
HelperService.currentError = null;
HelperService.isLoading = true;
this.getCTStextPassage(this.currentUrn).subscribe((ar: AnnisResponse) => {
HelperService.isLoading = false;
this.getCTStextPassage(this.currentUrn).then((ar: AnnisResponse) => {
this.processAnnisResponse(ar, saveToCache);
return resolve();
}, async (error: HttpErrorResponse) => {
HelperService.isLoading = false;
HelperService.currentError = error;
const toast = await this.toastCtrl.create({
message: HelperService.generalErrorAlertMessage,
duration: 3000,
position: 'top'
});
toast.present().then();
return reject();
return reject(error);
});
}
});
......
......@@ -16,22 +16,79 @@
<ion-content>
<ion-grid>
<ion-row>
<ion-col size="6">
<ion-col>
<label>
<input type="search" (ngModelChange)="filterExercises($event.toString())" class="searchbar"
placeholder="{{ 'SEARCH' | translate }}" [(ngModel)]="currentSearchValue"/>
</label>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<ion-label>{{ 'SORT_BY' | translate }}</ion-label>
<ion-select [(ngModel)]="currentSortingCategory" (ngModelChange)="sortExercises()"
placeholder="{{ currentSortingCategory.toString() | translate }}">
<ion-select [(ngModel)]="currentSortingCategory" (ngModelChange)="sortExercises()">
<ion-select-option *ngFor="let key of HelperService.getEnumValues(SortingCategory)"
value="{{key}}">{{ key | translate }}
</ion-select-option>
</ion-select>
</ion-col>
</ion-row>
<ion-row>
<ion-col class="toggle-menu">
<button *ngIf="showVocabularyCorpus; else dropright"
(click)="showVocabularyCorpus = !showVocabularyCorpus">
<ion-icon name="arrow-dropdown"></ion-icon>
</button>
<ng-template #dropright>
<button (click)="showVocabularyCorpus = !showVocabularyCorpus">
<ion-icon name="arrow-dropright"></ion-icon>
</button>
</ng-template>
<div>
<h4 (click)="showVocabularyCorpus = !showVocabularyCorpus">{{ 'VOCABULARY_CHECK' | translate }}</h4>
</div>
</ion-col>
</ion-row>
<ion-row *ngIf="showVocabularyCorpus">
<ion-col>
<label>
<span>{{ 'VOCABULARY_REFERENCE_CORPUS' | translate }}</span>
<select [(ngModel)]="vocService.currentReferenceVocabulary"
name="currentReferenceVocabulary"
(change)="vocService.updateReferenceRange(); hasChanges = true">
<option *ngFor="let key of ObjectKeys(vocService.refVocMap)" [value]=VocabularyCorpus[key]>
{{ VocabularyCorpusTranslation[key] | translate}} ({{vocService.refVocMap[key].totalCount}}
{{'VOCABULARY_ITEMS' | translate}})
</option>
</select>
</label>
</ion-col>
<ion-col>
<label>
<span>{{ 'VOCABULARY_REFERENCE_RANGE' | translate }}</span>
<select *ngIf="!vocService.getCurrentReferenceVocabulary()?.hasFrequencyOrder; else hasFrequencyOrder"
[(ngModel)]="vocService.frequencyUpperBound" name="frequencyUpperBound"
(change)="hasChanges = true">
<option *ngFor="let subcount of vocService.getCurrentReferenceVocabulary()?.possibleSubcounts"
[value]=subcount>
{{subcount}}
</option>
</select>
<ng-template #hasFrequencyOrder>
<input type="number" [(ngModel)]="vocService.frequencyUpperBound" (change)="hasChanges = true"/>
</ng-template>
</label>
</ion-col>
</ion-row>
<ion-row>
<ion-col>
<ion-button (click)="getExerciseList()"
disabled="{{!hasChanges}}">{{ 'APPLY' | translate }}</ion-button>
</ion-col>
</ion-row>
<ion-row>
<ion-col></ion-col>
</ion-row>
<ion-row *ngIf="exercises; else loading">
<ion-col>
<ion-list>
......@@ -42,15 +99,15 @@
</ion-item>
<ion-item lines="none" class="item-nested-bottom">
{{exercise.exercise_type_translation}} ({{ getDateString(exercise.last_access_time) }})
{{getMatchingDegree(exercise)}}
</ion-item>
</ion-list>
</ion-item>
</ion-list>
</ion-col>
</ion-row>
<ng-template #loading>
<ion-spinner></ion-spinner>
<h2 *ngIf="!HelperService.isLoading">{{ 'NO_EXERCISES_FOUND' | translate }}</h2>
</ng-template>
</ion-grid>
</ion-content>
......@@ -3,11 +3,19 @@ import {Component, OnInit} from '@angular/core';
import {HelperService} from 'src/app/helper.service';
import {NavController, ToastController} from '@ionic/angular';
import {ExerciseMC} from 'src/app/models/exerciseMC';
import {HttpClient, HttpErrorResponse} from '@angular/common/http';
import {ExerciseType, ExerciseTypeTranslation, MoodleExerciseType, SortingCategory} from '../models/enum';
import {HttpClient, HttpErrorResponse, HttpParams} from '@angular/common/http';
import {
ExerciseType,
ExerciseTypeTranslation,
MoodleExerciseType,
SortingCategory,
VocabularyCorpus,
VocabularyCorpusTranslation
} from '../models/enum';
import {TranslateService} from '@ngx-translate/core';
import {AnnisResponse} from 'src/app/models/annisResponse';
import {CorpusService} from 'src/app/corpus.service';
import {VocabularyService} from 'src/app/vocabulary.service';
@Component({
selector: 'app-exercise-list',
......@@ -21,17 +29,23 @@ export class ExerciseListPage implements OnInit {
public currentSortingCategory: SortingCategory = SortingCategory.dateDesc;
public exercises: ExerciseMC[];
public ExerciseTypeTranslation = ExerciseTypeTranslation;
public hasChanges = false;
public HelperService = HelperService;
public metadata: { [eid: string]: string } = {};
public ObjectKeys = Object.keys;
public showVocabularyCorpus = false;
public SortingCategory = SortingCategory;
public sortingCategoriesVocCheck: Set<SortingCategory> = new Set([SortingCategory.vocAsc, SortingCategory.vocDesc]);
public VocabularyCorpus = VocabularyCorpus;
public VocabularyCorpusTranslation = VocabularyCorpusTranslation;
constructor(public navCtrl: NavController,
public http: HttpClient,
public translateService: TranslateService,
public helperService: HelperService,
public corpusService: CorpusService,
public toastCtrl: ToastController) {
public toastCtrl: ToastController,
public vocService: VocabularyService) {
}
filterExercises(searchValue: string) {
......@@ -51,9 +65,15 @@ export class ExerciseListPage implements OnInit {
return new Date(dateMilliseconds).toLocaleDateString();
}
ngOnInit() {
private getExerciseList(): void {
const url: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiExerciseListPath'];
this.http.get(url, {params: {lang: this.translateService.currentLang}}).subscribe((exercises: ExerciseMC[]) => {
this.hasChanges = false;
let params: HttpParams = new HttpParams().set('lang', this.translateService.currentLang);
if (this.vocService.currentReferenceVocabulary) {
params = params.set('vocabulary', VocabularyCorpus[this.vocService.currentReferenceVocabulary]);
params = params.set('frequency_upper_bound', this.vocService.frequencyUpperBound.toString());
}
HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((exercises: ExerciseMC[]) => {
this.availableExercises = exercises;
this.exercises = exercises;
this.exercises.forEach((exercise: ExerciseMC) => {
......@@ -63,34 +83,52 @@ export class ExerciseListPage implements OnInit {
.join(' ').toLowerCase();
});
this.sortExercises();
}, () => {
});
}
getMatchingDegree(exercise: ExerciseMC): string {
if (exercise.matching_degree) {
return `[${Math.round(exercise.matching_degree)}%]`;
}
return '';
}
ngOnInit(): void {
this.vocService.currentReferenceVocabulary = null;
this.getExerciseList();
}
showExercise(exercise: ExerciseMC) {
const url = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiExercisePath'];
HelperService.isLoading = true;
this.http.get(url, {params: {eid: exercise.eid}}).subscribe((ar: AnnisResponse) => {
HelperService.isLoading = false;
const params: HttpParams = new HttpParams().set('eid', exercise.eid);
HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((ar: AnnisResponse) => {
HelperService.mostRecentSetup.annisResponse = ar;
this.helperService.saveMostRecentSetup().then();
this.corpusService.annisResponse = ar;
const met: MoodleExerciseType = MoodleExerciseType[exercise.exercise_type];
this.corpusService.exercise.type = ExerciseType[met.toString()];
HelperService.goToPreviewPage(this.navCtrl);
}, async (error: HttpErrorResponse) => {
HelperService.isLoading = false;
HelperService.currentError = error;
const toast = await this.toastCtrl.create({
message: HelperService.generalErrorAlertMessage,
duration: 3000,
position: 'top'
});
toast.present().then();
}, () => {
});
}
sortExercises() {
sortExercises(): void {
if (!this.exercises ||
this.sortingCategoriesVocCheck.has(this.currentSortingCategory) && !this.exercises.some(x => !!x.matching_degree)) {
return;
}
switch (this.currentSortingCategory) {
case SortingCategory.authorAsc:
this.exercises.sort((a: ExerciseMC, b: ExerciseMC) => {
return a.work_author === b.work_author ? 0 : (a.work_author < b.work_author ? -1 : 1);
});
break;
case SortingCategory.authorDesc:
this.exercises.sort((a: ExerciseMC, b: ExerciseMC) => {
return a.work_author === b.work_author ? 0 : (a.work_author < b.work_author ? 1 : -1);
});
break;
case SortingCategory.dateAsc:
this.exercises.sort((a: ExerciseMC, b: ExerciseMC) => {
return a.last_access_time === b.last_access_time ? 0 : (a.last_access_time < b.last_access_time ? -1 : 1);
......@@ -113,14 +151,16 @@ export class ExerciseListPage implements OnInit {
(a.exercise_type_translation < b.exercise_type_translation ? 1 : -1);
});
break;
case SortingCategory.authorAsc:
case SortingCategory.vocAsc:
this.exercises.sort((a: ExerciseMC, b: ExerciseMC) => {
return a.work_author === b.work_author ? 0 : (a.work_author < b.work_author ? -1 : 1);
return a.matching_degree === b.matching_degree ? 0 :
(a.matching_degree < b.matching_degree ? -1 : 1);
});
break;
case SortingCategory.authorDesc:
case SortingCategory.vocDesc:
this.exercises.sort((a: ExerciseMC, b: ExerciseMC) => {
return a.work_author === b.work_author ? 0 : (a.work_author < b.work_author ? 1 : -1);
return a.matching_degree === b.matching_degree ? 0 :
(a.matching_degree < b.matching_degree ? 1 : -1);
});
break;
default:
......
......@@ -110,7 +110,9 @@
<ion-icon name="arrow-dropright"></ion-icon>
</button>
</ng-template>
<h2 (click)="showFeedback = !showFeedback">{{ 'EXERCISE_FEEDBACK' | translate }}</h2>
<div>
<h2 (click)="showFeedback = !showFeedback">{{ 'EXERCISE_FEEDBACK' | translate }}</h2>
</div>
</ion-col>
</ion-row>
<div *ngIf="showFeedback">
......
/* tslint:disable:no-string-literal */
import {HttpClient, HttpErrorResponse} from '@angular/common/http';
import {HttpClient, HttpErrorResponse, HttpParams} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {NavController} from '@ionic/angular';
import {NavController, ToastController} from '@ionic/angular';
import {ApplicationState} from 'src/app/models/applicationState';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
import {CaseValue, DependencyValue, PartOfSpeechValue} from 'src/app/models/enum';
......@@ -197,6 +197,27 @@ export class HelperService {
});
}
static makeGetRequest(http: HttpClient, toastCtrl: ToastController, url: string, params: HttpParams) {
HelperService.currentError = null;
HelperService.isLoading = true;
return new Promise(((resolve, reject) => {
http.get(url, {params}).subscribe((result: any) => {
HelperService.isLoading = false;
return resolve(result);
}, async (error: HttpErrorResponse) => {
HelperService.isLoading = false;
HelperService.currentError = error;
const toast = await toastCtrl.create({
message: HelperService.generalErrorAlertMessage,
duration: 3000,
position: 'top'
});
toast.present().then();
return reject(error);
});
}));
}
/**
* Shuffles array in place.
* @param array items An array containing the items.
......
/* tslint:disable:no-string-literal */
import {Component, OnInit} from '@angular/core';
import {ChangeDetectorRef, Component, OnInit} from '@angular/core';
import {HelperService} from 'src/app/helper.service';
import {NavController, ToastController} from '@ionic/angular';
import {HttpClient} from '@angular/common/http';
import {HttpClient, HttpErrorResponse} from '@angular/common/http';
import {TranslateService} from '@ngx-translate/core';
import {ExerciseService} from 'src/app/exercise.service';
import {CorpusService} from 'src/app/corpus.service';
import {ExerciseMC} from 'src/app/models/exerciseMC';
@Component({
selector: 'app-home',
......@@ -58,16 +57,16 @@ export class HomePage implements OnInit {
refreshCorpora() {
this.isCorpusUpdateInProgress = true;
this.corpusService.getCorpora(0).then(async (error?: any) => {
this.corpusService.getCorpora(0).then(async () => {
this.isCorpusUpdateInProgress = false;
const toast = await this.toastCtrl.create({
message: HelperService.corpusUpdateCompletedString,
duration: 3000,
position: 'top'
});
toast.present().then();
}, async (error: HttpErrorResponse) => {
this.isCorpusUpdateInProgress = false;
if (!error) {
const toast = await this.toastCtrl.create({
message: HelperService.corpusUpdateCompletedString,
duration: 3000,
position: 'top'
});
toast.present().then();
}
});
}
......
......@@ -179,6 +179,8 @@ export enum SortingCategory {
dateDesc = 'SORTING_CATEGORY_DATE_DESCENDING' as any,
typeAsc = 'SORTING_CATEGORY_TYPE_ASCENDING' as any,
typeDesc = 'SORTING_CATEGORY_TYPE_DESCENDING' as any,
vocAsc = 'SORTING_CATEGORY_VOCABULARY_ASCENDING' as any,
vocDesc = 'SORTING_CATEGORY_VOCABULARY_DESCENDING' as any,
}
export enum TestModuleState {
......
......@@ -11,6 +11,7 @@ export class ExerciseMC {
public incorrect_feedback: string;
public instructions: string;
public last_access_time: number;
public matching_degree: number;
public partially_correct_feedback: string;
public search_values: string[];
public solutions: Solution[];
......
......@@ -28,24 +28,13 @@ export class RankingPage {
showText(rank: Sentence[]) {
this.corpusService.currentUrn = this.corpusService.baseUrn + `@${rank[0].id}-${rank[rank.length - 1].id}`;
HelperService.currentError = null;
HelperService.isLoading = true;
this.vocService.getVocabularyCheck(this.corpusService.currentUrn, true).subscribe((ar: AnnisResponse) => {
HelperService.isLoading = false;
this.vocService.getVocabularyCheck(this.corpusService.currentUrn, true).then((ar: AnnisResponse) => {
const urnStart: string = ar.nodes[0].id.split('/')[1];
const urnEnd: string = ar.nodes.slice(-1)[0].id.split('/')[1];
this.corpusService.currentUrn = urnStart.concat('-', urnEnd.split(':').slice(-1)[0]);
this.corpusService.processAnnisResponse(ar);
HelperService.goToShowTextPage(this.navCtrl, true);
}, async (error: HttpErrorResponse) => {
HelperService.isLoading = false;
HelperService.currentError = error;
const toast = await this.toastCtrl.create({
message: HelperService.generalErrorAlertMessage,
duration: 3000,
position: 'top'
});
toast.present().then();
});
}
......
......@@ -71,7 +71,9 @@
<ion-icon name="arrow-dropright"></ion-icon>
</button>
</ng-template>
<h2 (click)="showTextComplexity = !showTextComplexity">{{ 'TEXT_COMPLEXITY' | translate }}</h2>
<div>
<h2 (click)="showTextComplexity = !showTextComplexity">{{ 'TEXT_COMPLEXITY' | translate }}</h2>
</div>
</ion-col>