Commit e42b1b16 authored by Konstantin Schulz's avatar Konstantin Schulz

the exercise repository now uses caching, application configuration is more easily accessible

parent 871e96e9
...@@ -39,4 +39,4 @@ To change the URL for the backend, use the `ionic.config.json` file (proxies > p ...@@ -39,4 +39,4 @@ To change the URL for the backend, use the `ionic.config.json` file (proxies > p
## Frontend URL ## Frontend URL
Use the `--host 0.0.0.0 --disable-host-check` flag for `ng serve` if you want to use it in a production environment with an Nginx server using proxy_pass. Use the `--host 0.0.0.0 --disable-host-check` flag for `ng serve` if you want to use it in a production environment with an Nginx server using proxy_pass.
## Other ## Other
For all other kinds of configuration, use `src/assets/config.json`. For all other kinds of configuration, use `src/configMC.ts`.
{ {
"name": "mc_frontend", "name": "mc_frontend",
"version": "1.5.2", "version": "1.5.4",
"author": "Ionic Framework", "author": "Ionic Framework",
"homepage": "https://ionicframework.com/", "homepage": "https://ionicframework.com/",
"scripts": { "scripts": {
......
...@@ -7,7 +7,6 @@ import {HttpClient} from '@angular/common/http'; ...@@ -7,7 +7,6 @@ import {HttpClient} from '@angular/common/http';
import {HelperService} from '../helper.service'; import {HelperService} from '../helper.service';
import {ExerciseService} from '../exercise.service'; import {ExerciseService} from '../exercise.service';
import {ApplicationState} from '../models/applicationState'; import {ApplicationState} from '../models/applicationState';
import {Observable} from 'rxjs';
import {take} from 'rxjs/operators'; import {take} from 'rxjs/operators';
/** /**
...@@ -30,10 +29,13 @@ export class AuthorPage { ...@@ -30,10 +29,13 @@ export class AuthorPage {
public http: HttpClient, public http: HttpClient,
public exerciseService: ExerciseService, public exerciseService: ExerciseService,
public helperService: HelperService) { public helperService: HelperService) {
if (!this.corpusService.availableAuthors) { if (!this.corpusService.availableAuthors.length) {
this.corpusService.loadCorporaFromLocalStorage(); this.corpusService.loadCorporaFromLocalStorage().then(() => {
this.toggleTreebankAuthors();
});
} else {
this.toggleTreebankAuthors();
} }
this.toggleTreebankAuthors();
} }
public authorsDisplayed: Author[]; public authorsDisplayed: Author[];
......
...@@ -32,6 +32,8 @@ import {ApplicationState} from './models/applicationState'; ...@@ -32,6 +32,8 @@ import {ApplicationState} from './models/applicationState';
import {take} from 'rxjs/operators'; import {take} from 'rxjs/operators';
import {TextData} from './models/textData'; import {TextData} from './models/textData';
import {Storage} from '@ionic/storage'; import {Storage} from '@ionic/storage';
import {UpdateInfo} from './models/updateInfo';
import configMC from '../configMC';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
...@@ -39,7 +41,7 @@ import {Storage} from '@ionic/storage'; ...@@ -39,7 +41,7 @@ import {Storage} from '@ionic/storage';
export class CorpusService { export class CorpusService {
public annisResponse: AnnisResponse; public annisResponse: AnnisResponse;
public availableCorpora: CorpusMC[]; public availableCorpora: CorpusMC[];
public availableAuthors: Author[]; public availableAuthors: Author[] = [];
public baseUrn: string; public baseUrn: string;
public citationsUnavailableString: string; public citationsUnavailableString: string;
public corporaUnavailableString: string; public corporaUnavailableString: string;
...@@ -81,13 +83,15 @@ export class CorpusService { ...@@ -81,13 +83,15 @@ export class CorpusService {
this.helperService.initApplicationState(); this.helperService.initApplicationState();
this.initCurrentCorpus(); this.initCurrentCorpus();
this.initCurrentTextRange(); this.initCurrentTextRange();
this.checkForUpdates().finally(() => { this.initUpdateInfo().then(() => {
this.checkAnnisResponse().then(() => { this.checkForUpdates().finally(() => {
this.restoreLastCorpus().then(() => { this.checkAnnisResponse().then(() => {
this.restoreLastCorpus().then(() => {
this.isMostRecentSetupLoaded = true;
});
}, () => {
this.isMostRecentSetupLoaded = true; this.isMostRecentSetupLoaded = true;
}); });
}, () => {
this.isMostRecentSetupLoaded = true;
}); });
}); });
this.initPhenomenonMap(); this.initPhenomenonMap();
...@@ -147,17 +151,15 @@ export class CorpusService { ...@@ -147,17 +151,15 @@ export class CorpusService {
checkForUpdates(): Promise<void> { checkForUpdates(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
HelperService.config.pipe(take(1)).subscribe((config: object) => { this.storage.get(configMC.localStorageKeyUpdateInfo).then((jsonString: string) => {
this.storage.get(config['localStorageKeyUpdateInfo']).then((jsonString: string) => { // check local storage for necessary updates
// check local storage for necessary updates const updateInfo: UpdateInfo = JSON.parse(jsonString) as UpdateInfo;
const updateInfoJSON: object = JSON.parse(jsonString); this.getCorpora(updateInfo ? updateInfo.corpora : 0)
this.getCorpora(updateInfoJSON ? new Date(updateInfoJSON['corpora'].lastAccessTime).getTime() : 0) .then(() => {
.then(() => { return resolve();
return resolve(); }, () => {
}, () => { return reject();
return reject(); });
});
});
}); });
}); });
} }
...@@ -166,60 +168,52 @@ export class CorpusService { ...@@ -166,60 +168,52 @@ export class CorpusService {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.availableCorpora = []; this.availableCorpora = [];
this.availableAuthors = []; this.availableAuthors = [];
HelperService.config.pipe(take(1)).subscribe((config: object) => { // get corpora from REST API
// get corpora from REST API const url: string = configMC.backendBaseUrl + configMC.backendApiCorporaPath;
const url: string = config['backendBaseUrl'] + config['backendApiCorporaPath']; const params: HttpParams = new HttpParams().set('last_update_time', lastUpdateTimeMS.toString());
const params: HttpParams = new HttpParams().set('last_update_time', lastUpdateTimeMS.toString()); HelperService.makeGetRequest(this.http, this.toastCtrl, url, params, this.corporaUnavailableString).then((data: object) => {
HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((data: object) => { if (data) {
if (data) { const corpusList: CorpusMC[] = data['corpora'] as CorpusMC[];
const corpusList: CorpusMC[] = data['corpora'] as CorpusMC[]; this.storage.set(configMC.localStorageKeyCorpora, JSON.stringify(corpusList)).then();
this.storage.set(config['localStorageKeyCorpora'], JSON.stringify(corpusList)).then(); this.storage.get(configMC.localStorageKeyUpdateInfo).then((jsonString: string) => {
const updateInfo: object = {corpora: {lastAccessTime: new Date().getTime()}}; const updateInfo: UpdateInfo = JSON.parse(jsonString) as UpdateInfo;
this.storage.set(config['localStorageKeyUpdateInfo'], JSON.stringify(updateInfo)).then(); updateInfo.corpora = new Date().getTime();
this.processCorpora(corpusList); this.storage.set(configMC.localStorageKeyUpdateInfo, JSON.stringify(updateInfo)).then();
return resolve(); });
} else { this.processCorpora(corpusList);
this.loadCorporaFromLocalStorage(); return resolve();
} else {
this.loadCorporaFromLocalStorage().then(() => {
return resolve(); return resolve();
}
}, async (error: HttpErrorResponse) => {
this.loadCorporaFromLocalStorage();
const toast = await this.toastCtrl.create({
message: this.corporaUnavailableString,
duration: 3000,
position: 'top'
}); });
toast.present().then(); }
return reject(error); }, async (error: HttpErrorResponse) => {
}); this.loadCorporaFromLocalStorage();
return reject(error);
}); });
}); });
} }
getCTStextPassage(urn: string): Promise<AnnisResponse> { getCTStextPassage(urn: string): Promise<AnnisResponse> {
return new Promise(((resolve, reject) => { return new Promise(((resolve, reject) => {
HelperService.config.pipe(take(1)).subscribe((config: object) => { const url: string = configMC.backendBaseUrl + configMC.backendApiRawtextPath;
const url: string = config['backendBaseUrl'] + config['backendApiRawtextPath']; const params: HttpParams = new HttpParams().set('urn', urn);
const params: HttpParams = new HttpParams().set('urn', urn); HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((ar: AnnisResponse) => {
HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((ar: AnnisResponse) => { return resolve(ar);
return resolve(ar); }, (error: HttpErrorResponse) => {
}, (error: HttpErrorResponse) => { return reject(error);
return reject(error);
});
}); });
})); }));
} }
getCTSvalidReff(urn: string): Promise<string[]> { getCTSvalidReff(urn: string): Promise<string[]> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
HelperService.config.pipe(take(1)).subscribe((config: object) => { const fullUrl: string = configMC.backendBaseUrl + configMC.backendApiValidReffPath;
const fullUrl: string = config['backendBaseUrl'] + config['backendApiValidReffPath']; const params: HttpParams = new HttpParams().set('urn', urn);
const params: HttpParams = new HttpParams().set('urn', urn); HelperService.makeGetRequest(this.http, this.toastCtrl, fullUrl, params).then((reff: string[]) => {
HelperService.makeGetRequest(this.http, this.toastCtrl, fullUrl, params).then((reff: string[]) => { resolve(reff);
resolve(reff); }, (error: HttpErrorResponse) => {
}, (error: HttpErrorResponse) => { reject(error);
reject(error);
});
}); });
}); });
} }
...@@ -229,20 +223,18 @@ export class CorpusService { ...@@ -229,20 +223,18 @@ export class CorpusService {
if (this.annisResponse.frequency_analysis.length) { if (this.annisResponse.frequency_analysis.length) {
return resolve(); return resolve();
} else { } else {
HelperService.config.pipe(take(1)).subscribe((config: object) => { const url: string = configMC.backendBaseUrl + configMC.backendApiFrequencyPath;
const url: string = config['backendBaseUrl'] + config['backendApiFrequencyPath']; const params: HttpParams = new HttpParams().set('urn', this.currentUrn);
const params: HttpParams = new HttpParams().set('urn', this.currentUrn); HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((fis: FrequencyItem[]) => {
HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((fis: FrequencyItem[]) => { HelperService.isLoading = false;
HelperService.isLoading = false; this.annisResponse.frequency_analysis = fis;
this.annisResponse.frequency_analysis = fis; HelperService.applicationState.pipe(take(1)).subscribe((state: ApplicationState) => {
HelperService.applicationState.pipe(take(1)).subscribe((as: ApplicationState) => { state.mostRecentSetup.annisResponse = this.annisResponse;
as.mostRecentSetup.annisResponse = this.annisResponse; this.helperService.saveApplicationState(state).then();
this.helperService.saveApplicationState(as).then();
});
return resolve();
}, () => {
return reject();
}); });
return resolve();
}, () => {
return reject();
}); });
} }
}); });
...@@ -344,17 +336,35 @@ export class CorpusService { ...@@ -344,17 +336,35 @@ export class CorpusService {
}); });
} }
initUpdateInfo(): Promise<void> {
return new Promise<void>(resolve => {
this.storage.get(configMC.localStorageKeyUpdateInfo).then((jsonString: string) => {
if (jsonString) {
return resolve();
}
const ui: UpdateInfo = new UpdateInfo({
corpora: new Date().getTime(),
exerciseList: new Date().getTime()
});
this.storage.set(configMC.localStorageKeyUpdateInfo, JSON.stringify(ui)).then(() => {
return resolve();
});
});
});
}
isTreebank(corpus: CorpusMC) { isTreebank(corpus: CorpusMC) {
return corpus.source_urn.includes('proiel'); return corpus.source_urn.includes('proiel');
} }
public loadCorporaFromLocalStorage() { public loadCorporaFromLocalStorage(): Promise<void> {
HelperService.config.pipe(take(1)).subscribe((config: object) => { return new Promise<void>(resolve => {
this.storage.get(config['localStorageKeyCorpora']).then((jsonString: string) => { this.storage.get(configMC.localStorageKeyCorpora).then((jsonString: string) => {
if (jsonString) { if (jsonString) {
const corpusList: CorpusMC[] = JSON.parse(jsonString) as CorpusMC[]; const corpusList: CorpusMC[] = JSON.parse(jsonString) as CorpusMC[];
this.processCorpora(corpusList); this.processCorpora(corpusList);
} }
return resolve();
}); });
}); });
} }
......
...@@ -54,8 +54,8 @@ ...@@ -54,8 +54,8 @@
</ion-col> </ion-col>
</ion-row> </ion-row>
<ion-row id="vocCorpus" style="display: none"> <ion-row id="vocCorpus" style="display: none">
<ion-col style="display: inline-grid"> <ion-col>
<ion-grid style="padding: 0; text-align: left"> <ion-grid style="max-width: 30em; text-align: left">
<ion-row> <ion-row>
<ion-col> <ion-col>
<label> <label>
...@@ -107,7 +107,7 @@ ...@@ -107,7 +107,7 @@
</ion-row> </ion-row>
<ion-row> <ion-row>
</ion-row> </ion-row>
<ion-row *ngIf="exercises; else loading"> <ion-row *ngIf="exercises?.length; else loading">
<ion-grid style="padding: 0"> <ion-grid style="padding: 0">
<ion-row *ngFor="let exercise of exercises" (click)="showExercise(exercise)" class="exercises"> <ion-row *ngFor="let exercise of exercises" (click)="showExercise(exercise)" class="exercises">
<ion-grid style="padding: 0"> <ion-grid style="padding: 0">
......
...@@ -16,7 +16,11 @@ import {TranslateService} from '@ngx-translate/core'; ...@@ -16,7 +16,11 @@ import {TranslateService} from '@ngx-translate/core';
import {AnnisResponse} from 'src/app/models/annisResponse'; import {AnnisResponse} from 'src/app/models/annisResponse';
import {CorpusService} from 'src/app/corpus.service'; import {CorpusService} from 'src/app/corpus.service';
import {VocabularyService} from 'src/app/vocabulary.service'; import {VocabularyService} from 'src/app/vocabulary.service';
import {Storage} from '@ionic/storage';
import configMC from '../../configMC';
import {UpdateInfo} from '../models/updateInfo';
import {take} from 'rxjs/operators'; import {take} from 'rxjs/operators';
import {ApplicationState} from '../models/applicationState';
@Component({ @Component({
selector: 'app-exercise-list', selector: 'app-exercise-list',
...@@ -28,7 +32,7 @@ export class ExerciseListPage implements OnInit { ...@@ -28,7 +32,7 @@ export class ExerciseListPage implements OnInit {
public availableExercises: ExerciseMC[]; public availableExercises: ExerciseMC[];
public currentSearchValue: string; public currentSearchValue: string;
public currentSortingCategory: SortingCategory = SortingCategory.dateDesc; public currentSortingCategory: SortingCategory = SortingCategory.dateDesc;
public exercises: ExerciseMC[]; public exercises: ExerciseMC[] = [];
public ExerciseTypeTranslation = ExerciseTypeTranslation; public ExerciseTypeTranslation = ExerciseTypeTranslation;
public hasVocChanged = false; public hasVocChanged = false;
public HelperService = HelperService; public HelperService = HelperService;
...@@ -61,6 +65,7 @@ export class ExerciseListPage implements OnInit { ...@@ -61,6 +65,7 @@ export class ExerciseListPage implements OnInit {
public translateService: TranslateService, public translateService: TranslateService,
public helperService: HelperService, public helperService: HelperService,
public corpusService: CorpusService, public corpusService: CorpusService,
public storage: Storage,
public toastCtrl: ToastController, public toastCtrl: ToastController,
public vocService: VocabularyService) { public vocService: VocabularyService) {
} }
...@@ -83,25 +88,31 @@ export class ExerciseListPage implements OnInit { ...@@ -83,25 +88,31 @@ export class ExerciseListPage implements OnInit {
} }
getExerciseList(): void { getExerciseList(): void {
HelperService.config.pipe(take(1)).subscribe((config: object) => { const url: string = configMC.backendBaseUrl + configMC.backendApiExerciseListPath;
const url: string = config['backendBaseUrl'] + config['backendApiExerciseListPath']; this.hasVocChanged = false;
this.hasVocChanged = false; let params: HttpParams = new HttpParams().set('lang', this.translateService.currentLang);
let params: HttpParams = new HttpParams().set('lang', this.translateService.currentLang); HelperService.applicationState.pipe(take(1)).subscribe((state: ApplicationState) => {
if (this.vocService.currentReferenceVocabulary) { this.storage.get(configMC.localStorageKeyUpdateInfo).then((jsonString: string) => {
params = params.set('vocabulary', VocabularyCorpus[this.vocService.currentReferenceVocabulary]); const updateInfo: UpdateInfo = JSON.parse(jsonString) as UpdateInfo;
params = params.set('frequency_upper_bound', this.vocService.frequencyUpperBound.toString()); // if there are no exercises in the cache, force refresh
} const lut: number = state.exerciseList.length ? updateInfo.exerciseList : 0;
HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((exercises: ExerciseMC[]) => { params = params.set('last_update_time', lut.toString());
this.availableExercises = exercises; if (this.vocService.currentReferenceVocabulary) {
this.exercises = exercises; params = params.set('vocabulary', VocabularyCorpus[this.vocService.currentReferenceVocabulary]);
this.exercises.forEach((exercise: ExerciseMC) => { params = params.set('frequency_upper_bound', this.vocService.frequencyUpperBound.toString());
this.translateService.get(ExerciseTypeTranslation[MoodleExerciseType[exercise.exercise_type]]).subscribe( }
value => exercise.exercise_type_translation = value); HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((exercises: ExerciseMC[]) => {
this.metadata[exercise.eid] = [exercise.work_author, exercise.exercise_type_translation, exercise.work_title] updateInfo.exerciseList = new Date().getTime();
.join(' ').toLowerCase(); this.storage.set(configMC.localStorageKeyUpdateInfo, JSON.stringify(updateInfo)).then();
if (exercises.length) {
state.exerciseList = this.availableExercises = this.exercises = exercises;
this.helperService.saveApplicationState(state).then();
} else {
this.availableExercises = this.exercises = state.exerciseList;
}
this.processExercises();
}, () => {
}); });
this.filterExercises(this.currentSearchValue);
}, () => {
}); });
}); });
} }
...@@ -115,24 +126,32 @@ export class ExerciseListPage implements OnInit { ...@@ -115,24 +126,32 @@ export class ExerciseListPage implements OnInit {
this.getExerciseList(); this.getExerciseList();
} }
public processExercises() {
this.exercises.forEach((exercise: ExerciseMC) => {
this.translateService.get(ExerciseTypeTranslation[MoodleExerciseType[exercise.exercise_type]]).subscribe(
value => exercise.exercise_type_translation = value);
this.metadata[exercise.eid] = [exercise.work_author, exercise.exercise_type_translation, exercise.work_title]
.join(' ').toLowerCase();
});
this.filterExercises(this.currentSearchValue);
}
showExercise(exercise: ExerciseMC) { showExercise(exercise: ExerciseMC) {
HelperService.config.pipe(take(1)).subscribe((config: object) => { const url: string = configMC.backendBaseUrl + configMC.backendApiExercisePath;
const url: string = config['backendBaseUrl'] + config['backendApiExercisePath']; const params: HttpParams = new HttpParams().set('eid', exercise.eid);
const params: HttpParams = new HttpParams().set('eid', exercise.eid); HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((ar: AnnisResponse) => {
HelperService.makeGetRequest(this.http, this.toastCtrl, url, params).then((ar: AnnisResponse) => { // save this exercise only locally in the CorpusService (not as MostRecentSetup in the HelperService) because
// save this exercise only locally in the CorpusService (not as MostRecentSetup in the HelperService) because // users just want to have a quick look at it
// users just want to have a quick look at it this.corpusService.annisResponse = ar;
this.corpusService.annisResponse = ar; const met: MoodleExerciseType = MoodleExerciseType[exercise.exercise_type];
const met: MoodleExerciseType = MoodleExerciseType[exercise.exercise_type]; this.corpusService.exercise.type = ExerciseType[met.toString()];
this.corpusService.exercise.type = ExerciseType[met.toString()]; HelperService.goToPreviewPage(this.navCtrl).then();
HelperService.goToPreviewPage(this.navCtrl).then(); }, () => {
}, () => {
});
}); });
} }
sortExercises(): void { sortExercises(): void {
if (!this.exercises || if (!this.exercises.length ||
this.sortingCategoriesVocCheck.has(this.currentSortingCategory) && !this.exercises.some(x => !!x.matching_degree)) { this.sortingCategoriesVocCheck.has(this.currentSortingCategory) && !this.exercises.some(x => !!x.matching_degree)) {
return; return;
} }
......
...@@ -21,6 +21,7 @@ import {CorpusMC} from '../models/corpusMC'; ...@@ -21,6 +21,7 @@ import {CorpusMC} from '../models/corpusMC';
import {ApplicationState} from '../models/applicationState'; import {ApplicationState} from '../models/applicationState';
import {take} from 'rxjs/operators'; import {take} from 'rxjs/operators';
import {TextRange} from '../models/textRange'; import {TextRange} from '../models/textRange';
import configMC from '../../configMC';
@Component({ @Component({
selector: 'app-exercise-parameters', selector: 'app-exercise-parameters',
...@@ -54,27 +55,25 @@ export class ExerciseParametersPage implements OnInit { ...@@ -54,27 +55,25 @@ export class ExerciseParametersPage implements OnInit {
async generateExercise() { async generateExercise() {
const phenomenon: Phenomenon = this.corpusService.exercise.queryItems[0].phenomenon; const phenomenon: Phenomenon = this.corpusService.exercise.queryItems[0].phenomenon;
HelperService.config.pipe(take(1)).subscribe(async (config: object) => { if (0 < configMC.maxTextLength && configMC.maxTextLength < this.corpusService.currentText.length) {
if (0 < config['maxTextLength'] && config['maxTextLength'] < this.corpusService.currentText.length) { const toast = await this.toastCtrl.create({
const toast = await this.toastCtrl.create({ message: this.textTooLongString,
message: this.textTooLongString, duration: 3000,
duration: 3000, position: 'top'
position: 'top' });
}); toast.present().then();
toast.present().then(); } else if ((phenomenon === Phenomenon.lemma && !this.corpusService.exercise.queryItems[0].values) ||
} else if ((phenomenon === Phenomenon.lemma && !this.corpusService.exercise.queryItems[0].values) || this.corpusService.exercise.type === ExerciseType.matching && !this.corpusService.exercise.queryItems[1].values[0]) {
this.corpusService.exercise.type === ExerciseType.matching && !this.corpusService.exercise.queryItems[1].values[0]) { const toast = await this.toastCtrl.create({
const toast = await this.toastCtrl.create({ message: this.emptyQueryValueString,
message: this.emptyQueryValueString, duration: 3000,
duration: 3000, position: 'top'
position: 'top' });
}); toast.present().then();
toast.present().then(); } else {
} else { this.corpusService.annisResponse.solutions = null;
this.corpusService.annisResponse.solutions = null; this.getExerciseData();
this.getExerciseData(); }
}
});
} }
public getDisplayValue(query: QueryMC, key: string, queryIndex: number = 0): string { public getDisplayValue(query: QueryMC, key: string, queryIndex: number = 0): string {
...@@ -132,62 +131,56 @@ export class ExerciseParametersPage implements OnInit { ...@@ -132,62 +131,56 @@ export class ExerciseParametersPage implements OnInit {
} }
getH5Pexercise(formData: FormData) { getH5Pexercise(formData: FormData) {