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

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
## 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.
## 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",
"version": "1.5.2",
"version": "1.5.4",
"author": "Ionic Framework",
"homepage": "https://ionicframework.com/",
"scripts": {
......
......@@ -7,7 +7,6 @@ import {HttpClient} from '@angular/common/http';
import {HelperService} from '../helper.service';
import {ExerciseService} from '../exercise.service';
import {ApplicationState} from '../models/applicationState';
import {Observable} from 'rxjs';
import {take} from 'rxjs/operators';
/**
......@@ -30,10 +29,13 @@ export class AuthorPage {
public http: HttpClient,
public exerciseService: ExerciseService,
public helperService: HelperService) {
if (!this.corpusService.availableAuthors) {
this.corpusService.loadCorporaFromLocalStorage();
if (!this.corpusService.availableAuthors.length) {
this.corpusService.loadCorporaFromLocalStorage().then(() => {
this.toggleTreebankAuthors();
});
} else {
this.toggleTreebankAuthors();
}
this.toggleTreebankAuthors();
}
public authorsDisplayed: Author[];
......
......@@ -32,6 +32,8 @@ import {ApplicationState} from './models/applicationState';
import {take} from 'rxjs/operators';
import {TextData} from './models/textData';
import {Storage} from '@ionic/storage';
import {UpdateInfo} from './models/updateInfo';
import configMC from '../configMC';
@Injectable({
providedIn: 'root'
......@@ -39,7 +41,7 @@ import {Storage} from '@ionic/storage';
export class CorpusService {
public annisResponse: AnnisResponse;
public availableCorpora: CorpusMC[];
public availableAuthors: Author[];
public availableAuthors: Author[] = [];
public baseUrn: string;
public citationsUnavailableString: string;
public corporaUnavailableString: string;
......@@ -81,13 +83,15 @@ export class CorpusService {
this.helperService.initApplicationState();
this.initCurrentCorpus();
this.initCurrentTextRange();
this.checkForUpdates().finally(() => {
this.checkAnnisResponse().then(() => {
this.restoreLastCorpus().then(() => {
this.initUpdateInfo().then(() => {
this.checkForUpdates().finally(() => {
this.checkAnnisResponse().then(() => {
this.restoreLastCorpus().then(() => {
this.isMostRecentSetupLoaded = true;
});
}, () => {
this.isMostRecentSetupLoaded = true;
});
}, () => {
this.isMostRecentSetupLoaded = true;
});
});
this.initPhenomenonMap();
......@@ -147,17 +151,15 @@ export class CorpusService {
checkForUpdates(): Promise<void> {
return new Promise((resolve, reject) => {
HelperService.config.pipe(take(1)).subscribe((config: object) => {
this.storage.get(config['localStorageKeyUpdateInfo']).then((jsonString: string) => {
// check local storage for necessary updates
const updateInfoJSON: object = JSON.parse(jsonString);
this.getCorpora(updateInfoJSON ? new Date(updateInfoJSON['corpora'].lastAccessTime).getTime() : 0)
.then(() => {
return resolve();
}, () => {
return reject();
});
});
this.storage.get(configMC.localStorageKeyUpdateInfo).then((jsonString: string) => {
// check local storage for necessary updates
const updateInfo: UpdateInfo = JSON.parse(jsonString) as UpdateInfo;
this.getCorpora(updateInfo ? updateInfo.corpora : 0)
.then(() => {
return resolve();
}, () => {
return reject();
});
});
});
}
......@@ -166,60 +168,52 @@ export class CorpusService {
return new Promise((resolve, reject) => {
this.availableCorpora = [];
this.availableAuthors = [];
HelperService.config.pipe(take(1)).subscribe((config: object) => {
// get corpora from REST API
const url: string = config['backendBaseUrl'] + config['backendApiCorporaPath'];
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[];
this.storage.set(config['localStorageKeyCorpora'], JSON.stringify(corpusList)).then();
const updateInfo: object = {corpora: {lastAccessTime: new Date().getTime()}};
this.storage.set(config['localStorageKeyUpdateInfo'], JSON.stringify(updateInfo)).then();
this.processCorpora(corpusList);
return resolve();
} else {
this.loadCorporaFromLocalStorage();
// get corpora from REST API
const url: string = configMC.backendBaseUrl + configMC.backendApiCorporaPath;
const params: HttpParams = new HttpParams().set('last_update_time', lastUpdateTimeMS.toString());
HelperService.makeGetRequest(this.http, this.toastCtrl, url, params, this.corporaUnavailableString).then((data: object) => {
if (data) {
const corpusList: CorpusMC[] = data['corpora'] as CorpusMC[];
this.storage.set(configMC.localStorageKeyCorpora, JSON.stringify(corpusList)).then();
this.storage.get(configMC.localStorageKeyUpdateInfo).then((jsonString: string) => {
const updateInfo: UpdateInfo = JSON.parse(jsonString) as UpdateInfo;
updateInfo.corpora = new Date().getTime();
this.storage.set(configMC.localStorageKeyUpdateInfo, JSON.stringify(updateInfo)).then();
});
this.processCorpora(corpusList);
return resolve();
} else {
this.loadCorporaFromLocalStorage().then(() => {
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> {
return new Promise(((resolve, reject) => {
HelperService.config.pipe(take(1)).subscribe((config: object) => {
const url: string = config['backendBaseUrl'] + 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);
});
const url: string = configMC.backendBaseUrl + configMC.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): Promise<string[]> {
return new Promise((resolve, reject) => {
HelperService.config.pipe(take(1)).subscribe((config: object) => {
const fullUrl: string = config['backendBaseUrl'] + 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);
});
const fullUrl: string = configMC.backendBaseUrl + configMC.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);
});
});
}
......@@ -229,20 +223,18 @@ export class CorpusService {
if (this.annisResponse.frequency_analysis.length) {
return resolve();
} else {
HelperService.config.pipe(take(1)).subscribe((config: object) => {
const url: string = config['backendBaseUrl'] + 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.applicationState.pipe(take(1)).subscribe((as: ApplicationState) => {
as.mostRecentSetup.annisResponse = this.annisResponse;
this.helperService.saveApplicationState(as).then();
});
return resolve();
}, () => {
return reject();
const url: string = configMC.backendBaseUrl + configMC.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.applicationState.pipe(take(1)).subscribe((state: ApplicationState) => {
state.mostRecentSetup.annisResponse = this.annisResponse;
this.helperService.saveApplicationState(state).then();
});
return resolve();
}, () => {
return reject();
});
}
});
......@@ -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) {
return corpus.source_urn.includes('proiel');
}
public loadCorporaFromLocalStorage() {
HelperService.config.pipe(take(1)).subscribe((config: object) => {
this.storage.get(config['localStorageKeyCorpora']).then((jsonString: string) => {
public loadCorporaFromLocalStorage(): Promise<void> {
return new Promise<void>(resolve => {
this.storage.get(configMC.localStorageKeyCorpora).then((jsonString: string) => {
if (jsonString) {
const corpusList: CorpusMC[] = JSON.parse(jsonString) as CorpusMC[];
this.processCorpora(corpusList);
}
return resolve();
});
});
}
......
......@@ -54,8 +54,8 @@
</ion-col>
</ion-row>
<ion-row id="vocCorpus" style="display: none">
<ion-col style="display: inline-grid">
<ion-grid style="padding: 0; text-align: left">
<ion-col>
<ion-grid style="max-width: 30em; text-align: left">
<ion-row>
<ion-col>
<label>
......@@ -107,7 +107,7 @@
</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-row *ngFor="let exercise of exercises" (click)="showExercise(exercise)" class="exercises">
<ion-grid style="padding: 0">
......
......@@ -16,7 +16,11 @@ 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';
import {Storage} from '@ionic/storage';
import configMC from '../../configMC';
import {UpdateInfo} from '../models/updateInfo';
import {take} from 'rxjs/operators';
import {ApplicationState} from '../models/applicationState';
@Component({
selector: 'app-exercise-list',
......@@ -28,7 +32,7 @@ export class ExerciseListPage implements OnInit {
public availableExercises: ExerciseMC[];
public currentSearchValue: string;
public currentSortingCategory: SortingCategory = SortingCategory.dateDesc;
public exercises: ExerciseMC[];
public exercises: ExerciseMC[] = [];
public ExerciseTypeTranslation = ExerciseTypeTranslation;
public hasVocChanged = false;
public HelperService = HelperService;
......@@ -61,6 +65,7 @@ export class ExerciseListPage implements OnInit {
public translateService: TranslateService,
public helperService: HelperService,
public corpusService: CorpusService,
public storage: Storage,
public toastCtrl: ToastController,
public vocService: VocabularyService) {
}
......@@ -83,25 +88,31 @@ export class ExerciseListPage implements OnInit {
}
getExerciseList(): void {
HelperService.config.pipe(take(1)).subscribe((config: object) => {
const url: string = config['backendBaseUrl'] + config['backendApiExerciseListPath'];
this.hasVocChanged = 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) => {
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();
const url: string = configMC.backendBaseUrl + configMC.backendApiExerciseListPath;
this.hasVocChanged = false;
let params: HttpParams = new HttpParams().set('lang', this.translateService.currentLang);
HelperService.applicationState.pipe(take(1)).subscribe((state: ApplicationState) => {
this.storage.get(configMC.localStorageKeyUpdateInfo).then((jsonString: string) => {
const updateInfo: UpdateInfo = JSON.parse(jsonString) as UpdateInfo;
// if there are no exercises in the cache, force refresh
const lut: number = state.exerciseList.length ? updateInfo.exerciseList : 0;
params = params.set('last_update_time', lut.toString());
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[]) => {
updateInfo.exerciseList = new Date().getTime();
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 {
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) {
HelperService.config.pipe(take(1)).subscribe((config: object) => {
const url: string = config['backendBaseUrl'] + config['backendApiExercisePath'];
const params: HttpParams = new HttpParams().set('eid', exercise.eid);
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
// users just want to have a quick look at it
this.corpusService.annisResponse = ar;
const met: MoodleExerciseType = MoodleExerciseType[exercise.exercise_type];
this.corpusService.exercise.type = ExerciseType[met.toString()];
HelperService.goToPreviewPage(this.navCtrl).then();
}, () => {
});
const url: string = configMC.backendBaseUrl + configMC.backendApiExercisePath;
const params: HttpParams = new HttpParams().set('eid', exercise.eid);
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
// users just want to have a quick look at it
this.corpusService.annisResponse = ar;
const met: MoodleExerciseType = MoodleExerciseType[exercise.exercise_type];
this.corpusService.exercise.type = ExerciseType[met.toString()];
HelperService.goToPreviewPage(this.navCtrl).then();
}, () => {
});
}
sortExercises(): void {
if (!this.exercises ||
if (!this.exercises.length ||
this.sortingCategoriesVocCheck.has(this.currentSortingCategory) && !this.exercises.some(x => !!x.matching_degree)) {
return;
}
......
......@@ -21,6 +21,7 @@ import {CorpusMC} from '../models/corpusMC';
import {ApplicationState} from '../models/applicationState';
import {take} from 'rxjs/operators';
import {TextRange} from '../models/textRange';
import configMC from '../../configMC';
@Component({
selector: 'app-exercise-parameters',
......@@ -54,27 +55,25 @@ export class ExerciseParametersPage implements OnInit {
async generateExercise() {
const phenomenon: Phenomenon = this.corpusService.exercise.queryItems[0].phenomenon;
HelperService.config.pipe(take(1)).subscribe(async (config: object) => {
if (0 < config['maxTextLength'] && config['maxTextLength'] < this.corpusService.currentText.length) {
const toast = await this.toastCtrl.create({
message: this.textTooLongString,
duration: 3000,
position: 'top'
});
toast.present().then();
} else if ((phenomenon === Phenomenon.lemma && !this.corpusService.exercise.queryItems[0].values) ||
this.corpusService.exercise.type === ExerciseType.matching && !this.corpusService.exercise.queryItems[1].values[0]) {
const toast = await this.toastCtrl.create({
message: this.emptyQueryValueString,
duration: 3000,
position: 'top'
});
toast.present().then();
} else {
this.corpusService.annisResponse.solutions = null;
this.getExerciseData();
}
});
if (0 < configMC.maxTextLength && configMC.maxTextLength < this.corpusService.currentText.length) {
const toast = await this.toastCtrl.create({
message: this.textTooLongString,
duration: 3000,
position: 'top'
});
toast.present().then();
} else if ((phenomenon === Phenomenon.lemma && !this.corpusService.exercise.queryItems[0].values) ||
this.corpusService.exercise.type === ExerciseType.matching && !this.corpusService.exercise.queryItems[1].values[0]) {
const toast = await this.toastCtrl.create({
message: this.emptyQueryValueString,
duration: 3000,
position: 'top'
});
toast.present().then();
} else {
this.corpusService.annisResponse.solutions = null;
this.getExerciseData();
}
}
public getDisplayValue(query: QueryMC, key: string, queryIndex: number = 0): string {
......@@ -132,62 +131,56 @@ export class ExerciseParametersPage implements OnInit {
}
getH5Pexercise(formData: FormData) {
HelperService.config.pipe(take(1)).subscribe((config: object) => {
const url = config['backendBaseUrl'] + config['backendApiExercisePath'];
HelperService.currentError = null;
HelperService.isLoading = true;
this.http.post(url, formData).subscribe((ar: AnnisResponse) => {
HelperService.isLoading = false;
// save the old frequency analysis in case we want to change the exercise parameters at a later time
ar.frequency_analysis = this.corpusService.annisResponse.frequency_analysis;
HelperService.applicationState.pipe(take(1)).subscribe((as: ApplicationState) => {
as.mostRecentSetup.annisResponse = ar;
this.helperService.saveApplicationState(as).then();
this.corpusService.annisResponse.exercise_id = ar.exercise_id;
this.corpusService.annisResponse.uri = ar.uri;
this.corpusService.annisResponse.solutions = ar.solutions;
HelperService.goToPreviewPage(this.navCtrl).then();
});
}, 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();
const url: string = configMC.backendBaseUrl + configMC.backendApiExercisePath;
HelperService.currentError = null;
HelperService.isLoading = true;
this.http.post(url, formData).subscribe((ar: AnnisResponse) => {
HelperService.isLoading = false;
// save the old frequency analysis in case we want to change the exercise parameters at a later time
ar.frequency_analysis = this.corpusService.annisResponse.frequency_analysis;
HelperService.applicationState.pipe(take(1)).subscribe((as: ApplicationState) => {
as.mostRecentSetup.annisResponse = ar;
this.helperService.saveApplicationState(as).then();
this.corpusService.annisResponse.exercise_id = ar.exercise_id;
this.corpusService.annisResponse.uri = ar.uri;
this.corpusService.annisResponse.solutions = ar.solutions;
HelperService.goToPreviewPage(this.navCtrl).then();
});
}, 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();
});
}
getKwicExercise(formData: FormData) {
HelperService.config.pipe(take(1)).subscribe((config: object) => {
const kwicUrl: string = config['backendBaseUrl'] + config['backendApiKwicPath'];
HelperService.currentError = null;
HelperService.isLoading = true;
this.http.post(kwicUrl, formData).subscribe((svgString: string) => {
HelperService.isLoading = false;
this.exerciseService.kwicGraphs = svgString;
this.navCtrl.navigateForward('kwic').then();
}, 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();
const kwicUrl: string = configMC.backendBaseUrl + configMC.backendApiKwicPath;
HelperService.currentError = null;
HelperService.isLoading = true;
this.http.post(kwicUrl, formData).subscribe((svgString: string) => {
HelperService.isLoading = false;
this.exerciseService.kwicGraphs = svgString;
this.navCtrl.navigateForward('kwic').then();
}, 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();
});
}
initTranslation() {
HelperService.config.pipe(take(1)).subscribe((config: object) => {
this.translateService.get('TEXT_TOO_LONG').subscribe(
value => this.textTooLongString = value + config['maxTextLength']);