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

project configuration is now loaded more securely, i.e. using RxJS

parent 00df96d4
{
"name": "mc_frontend",
"version": "1.5.0",
"version": "1.5.1",
"author": "Ionic Framework",
"homepage": "https://ionicframework.com/",
"scripts": {
......
......@@ -79,15 +79,13 @@ export class CorpusService {
this.helperService.initApplicationState();
this.initCurrentCorpus();
this.initCurrentTextRange();
HelperService.waitForConfig().then(() => {
this.checkForUpdates().finally(() => {
this.checkAnnisResponse().then(() => {
this.restoreLastCorpus().then(() => {
this.isMostRecentSetupLoaded = true;
});
}, () => {
this.checkForUpdates().finally(() => {
this.checkAnnisResponse().then(() => {
this.restoreLastCorpus().then(() => {
this.isMostRecentSetupLoaded = true;
});
}, () => {
this.isMostRecentSetupLoaded = true;
});
});
this.initPhenomenonMap();
......@@ -147,12 +145,15 @@ export class CorpusService {
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();
HelperService.config.pipe(take(1)).subscribe((config: object) => {
// check local storage for necessary updates
const updateInfoJSON: object = JSON.parse(window.localStorage.getItem(config['localStorageKeyUpdateInfo']));
this.getCorpora(updateInfoJSON ? new Date(updateInfoJSON['corpora'].lastAccessTime).getTime() : 0)
.then(() => {
return resolve();
}, () => {
return reject();
});
});
});
}
......@@ -161,54 +162,60 @@ export class CorpusService {
return new Promise((resolve, reject) => {
this.availableCorpora = [];
this.availableAuthors = [];
// get corpora from REST API
const url = HelperService.config['backendBaseUrl'] + HelperService.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[];
window.localStorage.setItem(HelperService.config['localStorageKeyCorpora'], JSON.stringify(corpusList));
const updateInfo: object = {corpora: {lastAccessTime: new Date().getTime()}};
window.localStorage.setItem(HelperService.config['localStorageKeyUpdateInfo'], JSON.stringify(updateInfo));
this.processCorpora(corpusList);
return resolve();
} else {
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[];
window.localStorage.setItem(config['localStorageKeyCorpora'], JSON.stringify(corpusList));
const updateInfo: object = {corpora: {lastAccessTime: new Date().getTime()}};
window.localStorage.setItem(config['localStorageKeyUpdateInfo'], JSON.stringify(updateInfo));
this.processCorpora(corpusList);
return resolve();
} else {
this.loadCorporaFromLocalStorage();
return resolve();
}
}, async (error: HttpErrorResponse) => {
this.loadCorporaFromLocalStorage();
return resolve();
}
}, async (error: HttpErrorResponse) => {
this.loadCorporaFromLocalStorage();
const toast = await this.toastCtrl.create({
message: this.corporaUnavailableString,
duration: 3000,
position: 'top'
const toast = await this.toastCtrl.create({
message: this.corporaUnavailableString,
duration: 3000,
position: 'top'
});
toast.present().then();
return reject(error);
});
toast.present().then();
return reject(error);
});
});
}
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);
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);
});
});
}));
}
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);
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);
});
});
});
}
......@@ -218,18 +225,20 @@ export class CorpusService {
if (this.annisResponse.frequency_analysis.length) {
return resolve();
} else {
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.applicationState.pipe(take(1)).subscribe((as: ApplicationState) => {
as.mostRecentSetup.annisResponse = this.annisResponse;
this.helperService.saveApplicationState(as).then();
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();
});
return resolve();
}, () => {
return reject();
});
}
});
......@@ -336,11 +345,13 @@ export class CorpusService {
}
public loadCorporaFromLocalStorage() {
const storedCorporaJSONstring: string = window.localStorage.getItem(HelperService.config['localStorageKeyCorpora']);
if (storedCorporaJSONstring) {
const corpusList: CorpusMC[] = JSON.parse(storedCorporaJSONstring) as CorpusMC[];
this.processCorpora(corpusList);
}
HelperService.config.pipe(take(1)).subscribe((config: object) => {
const storedCorporaJSONstring: string = window.localStorage.getItem(HelperService.config['localStorageKeyCorpora']);
if (storedCorporaJSONstring) {
const corpusList: CorpusMC[] = JSON.parse(storedCorporaJSONstring) as CorpusMC[];
this.processCorpora(corpusList);
}
});
}
processAnnisResponse(ar: AnnisResponse, saveToCache: boolean = true) {
......
......@@ -16,6 +16,7 @@ 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 {take} from 'rxjs/operators';
@Component({
selector: 'app-exercise-list',
......@@ -82,24 +83,26 @@ export class ExerciseListPage implements OnInit {
}
getExerciseList(): void {
const url: string = HelperService.config['backendBaseUrl'] + HelperService.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();
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();
});
this.filterExercises(this.currentSearchValue);
}, () => {
});
this.filterExercises(this.currentSearchValue);
}, () => {
});
}
......@@ -113,16 +116,18 @@ export class ExerciseListPage implements OnInit {
}
showExercise(exercise: ExerciseMC) {
const url: string = HelperService.config['backendBaseUrl'] + HelperService.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();
}, () => {
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();
}, () => {
});
});
}
......
......@@ -54,25 +54,27 @@ export class ExerciseParametersPage implements OnInit {
async generateExercise() {
const phenomenon: Phenomenon = this.corpusService.exercise.queryItems[0].phenomenon;
if (0 < HelperService.config['maxTextLength'] && HelperService.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();
}
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();
}
});
}
public getDisplayValue(query: QueryMC, key: string, queryIndex: number = 0): string {
......@@ -130,56 +132,62 @@ export class ExerciseParametersPage implements OnInit {
}
getH5Pexercise(formData: FormData) {
const url = HelperService.config['backendBaseUrl'] + HelperService.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();
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();
});
}, 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) {
const kwicUrl: string = HelperService.config['backendBaseUrl'] + HelperService.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'
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();
});
toast.present().then();
});
}
initTranslation() {
this.translateService.get('TEXT_TOO_LONG').subscribe(
value => this.textTooLongString = value + HelperService.config['maxTextLength']);
HelperService.config.pipe(take(1)).subscribe((config: object) => {
this.translateService.get('TEXT_TOO_LONG').subscribe(
value => this.textTooLongString = value + config['maxTextLength']);
});
this.translateService.get('QUERY_VALUE_EMPTY').subscribe(value => this.emptyQueryValueString = value);
}
......
......@@ -36,36 +36,38 @@ export class ExercisePage implements OnInit {
loadExercise(): void {
this.activatedRoute.queryParams.subscribe((params: object) => {
if (params['eid']) {
let url: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiExercisePath'];
const httpParams: HttpParams = new HttpParams().set('eid', params['eid']);
HelperService.makeGetRequest(this.http, this.toastCtrl, url, httpParams).then((ar: AnnisResponse) => {
HelperService.applicationState.pipe(take(1)).subscribe((as: ApplicationState) => {
as.mostRecentSetup.annisResponse = ar;
this.helperService.saveApplicationState(as).then();
this.corpusService.annisResponse = ar;
const met: MoodleExerciseType = MoodleExerciseType[ar.exercise_type];
this.corpusService.exercise.type = ExerciseType[met.toString()];
// this will be called via GET request from the h5p standalone javascript library
url = `${HelperService.config['backendBaseUrl']}${HelperService.config['backendApiH5pPath']}` +
`?eid=${this.corpusService.annisResponse.exercise_id}&lang=${this.translateService.currentLang}`;
window.localStorage.setItem(HelperService.config['localStorageKeyH5P'], url);
const exerciseTypePath: string = this.corpusService.exercise.type === ExerciseType.markWords ?
'mark_words' : 'drag_text';
this.exerciseService.initH5P(exerciseTypePath);
HelperService.config.pipe(take(1)).subscribe((config: object) => {
if (params['eid']) {
let url: string = config['backendBaseUrl'] + config['backendApiExercisePath'];
const httpParams: HttpParams = new HttpParams().set('eid', params['eid']);
HelperService.makeGetRequest(this.http, this.toastCtrl, url, httpParams).then((ar: AnnisResponse) => {
HelperService.applicationState.pipe(take(1)).subscribe((as: ApplicationState) => {
as.mostRecentSetup.annisResponse = ar;
this.helperService.saveApplicationState(as).then();
this.corpusService.annisResponse = ar;
const met: MoodleExerciseType = MoodleExerciseType[ar.exercise_type];
this.corpusService.exercise.type = ExerciseType[met.toString()];
// this will be called via GET request from the h5p standalone javascript library
url = `${config['backendBaseUrl']}${config['backendApiH5pPath']}` +
`?eid=${this.corpusService.annisResponse.exercise_id}&lang=${this.translateService.currentLang}`;
window.localStorage.setItem(config['localStorageKeyH5P'], url);
const exerciseTypePath: string = this.corpusService.exercise.type === ExerciseType.markWords ?
'mark_words' : 'drag_text';
this.exerciseService.initH5P(exerciseTypePath);
});
}, () => {
});
}, () => {
});
} else {
const exerciseType: string = params['type'];
const exerciseTypePath: string = exerciseType === this.exerciseService.vocListString ?
this.exerciseService.fillBlanksString : exerciseType;
const file: string = params['file'];
const lang: string = this.translateService.currentLang;
window.localStorage.setItem(HelperService.config['localStorageKeyH5P'],
HelperService.baseUrl + '/assets/h5p/' + exerciseType + '/content/' + file + '_' + lang + '.json');
this.exerciseService.initH5P(exerciseTypePath);
}
} else {
const exerciseType: string = params['type'];
const exerciseTypePath: string = exerciseType === this.exerciseService.vocListString ?
this.exerciseService.fillBlanksString : exerciseType;
const file: string = params['file'];
const lang: string = this.translateService.currentLang;
window.localStorage.setItem(config['localStorageKeyH5P'],
HelperService.baseUrl + '/assets/h5p/' + exerciseType + '/content/' + file + '_' + lang + '.json');
this.exerciseService.initH5P(exerciseTypePath);
}
});
});
}
......
......@@ -10,6 +10,7 @@ import {Storage} from '@ionic/storage';
import {Language} from 'src/app/models/language';
import {ReplaySubject} from 'rxjs';
import {TextData} from './models/textData';
import {take} from 'rxjs/operators';
@Injectable({
providedIn: 'root'
......@@ -29,7 +30,7 @@ export class HelperService {
Voc: CaseValue.vocative,
Loc: CaseValue.locative,
};
public static config: object;
public static config: ReplaySubject<object>;
public static corpusUpdateCompletedString: string;
public static currentError: HttpErrorResponse;
public static currentLanguage: Language;
......@@ -109,7 +110,7 @@ export class HelperService {
private storage: Storage,
public translate: TranslateService,
) {
this.initConfig().then();
this.initConfig();
this.initLanguage();
}
......@@ -269,29 +270,20 @@ export class HelperService {
return array;
}
static waitForConfig() {
return new Promise(async (resolve) => {
while (!HelperService.config) {
await new Promise((resolveWait) => {
setTimeout(resolveWait, 50);
});
}
return resolve();
});
}
initApplicationState(): void {
HelperService.applicationState = new ReplaySubject<ApplicationState>(1);
if (!HelperService.applicationStateCache) {
this.storage.get(HelperService.config['localStorageKeyApplicationState']).then((jsonString: string) => {
HelperService.applicationStateCache = new ApplicationState({
currentSetup: new TextData()
HelperService.config.pipe(take(1)).subscribe((config: object) => {
this.storage.get(config['localStorageKeyApplicationState']).then((jsonString: string) => {
HelperService.applicationStateCache = new ApplicationState({
currentSetup: new TextData()
});
if (jsonString) {
const parsedJson: object = JSON.parse(jsonString);
HelperService.applicationStateCache = parsedJson as ApplicationState;
}
HelperService.applicationState.next(HelperService.applicationStateCache);
});
if (jsonString) {
const parsedJson: object = JSON.parse(jsonString);
HelperService.applicationStateCache = parsedJson as ApplicationState;
}
HelperService.applicationState.next(HelperService.applicationStateCache);
});