Commit c8eefdbc authored by Konstantin Schulz's avatar Konstantin Schulz

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

parent 00df96d4
{ {
"name": "mc_frontend", "name": "mc_frontend",
"version": "1.5.0", "version": "1.5.1",
"author": "Ionic Framework", "author": "Ionic Framework",
"homepage": "https://ionicframework.com/", "homepage": "https://ionicframework.com/",
"scripts": { "scripts": {
......
...@@ -79,7 +79,6 @@ export class CorpusService { ...@@ -79,7 +79,6 @@ export class CorpusService {
this.helperService.initApplicationState(); this.helperService.initApplicationState();
this.initCurrentCorpus(); this.initCurrentCorpus();
this.initCurrentTextRange(); this.initCurrentTextRange();
HelperService.waitForConfig().then(() => {
this.checkForUpdates().finally(() => { this.checkForUpdates().finally(() => {
this.checkAnnisResponse().then(() => { this.checkAnnisResponse().then(() => {
this.restoreLastCorpus().then(() => { this.restoreLastCorpus().then(() => {
...@@ -89,7 +88,6 @@ export class CorpusService { ...@@ -89,7 +88,6 @@ export class CorpusService {
this.isMostRecentSetupLoaded = true; this.isMostRecentSetupLoaded = true;
}); });
}); });
});
this.initPhenomenonMap(); this.initPhenomenonMap();
this.getTranslations(); this.getTranslations();
this.translate.onLangChange.subscribe(() => { this.translate.onLangChange.subscribe(() => {
...@@ -147,29 +145,33 @@ export class CorpusService { ...@@ -147,29 +145,33 @@ 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) => {
// check local storage for necessary updates // check local storage for necessary updates
const updateInfoJSON: object = JSON.parse(window.localStorage.getItem(HelperService.config['localStorageKeyUpdateInfo'])); const updateInfoJSON: object = JSON.parse(window.localStorage.getItem(config['localStorageKeyUpdateInfo']));
this.getCorpora(updateInfoJSON ? new Date(updateInfoJSON['corpora'].lastAccessTime).getTime() : 0).then(() => { this.getCorpora(updateInfoJSON ? new Date(updateInfoJSON['corpora'].lastAccessTime).getTime() : 0)
.then(() => {
return resolve(); return resolve();
}, () => { }, () => {
return reject(); return reject();
}); });
}); });
});
} }
getCorpora(lastUpdateTimeMS: number = 0): Promise<void> { getCorpora(lastUpdateTimeMS: number = 0): Promise<void> {
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 = HelperService.config['backendBaseUrl'] + HelperService.config['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).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[];
window.localStorage.setItem(HelperService.config['localStorageKeyCorpora'], JSON.stringify(corpusList)); window.localStorage.setItem(config['localStorageKeyCorpora'], JSON.stringify(corpusList));
const updateInfo: object = {corpora: {lastAccessTime: new Date().getTime()}}; const updateInfo: object = {corpora: {lastAccessTime: new Date().getTime()}};
window.localStorage.setItem(HelperService.config['localStorageKeyUpdateInfo'], JSON.stringify(updateInfo)); window.localStorage.setItem(config['localStorageKeyUpdateInfo'], JSON.stringify(updateInfo));
this.processCorpora(corpusList); this.processCorpora(corpusList);
return resolve(); return resolve();
} else { } else {
...@@ -187,23 +189,27 @@ export class CorpusService { ...@@ -187,23 +189,27 @@ export class CorpusService {
return reject(error); return reject(error);
}); });
}); });
});
} }
getCTStextPassage(urn: string): Promise<AnnisResponse> { getCTStextPassage(urn: string): Promise<AnnisResponse> {
return new Promise(((resolve, reject) => { return new Promise(((resolve, reject) => {
const url = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiRawtextPath']; HelperService.config.pipe(take(1)).subscribe((config: object) => {
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) => {
const fullUrl: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiValidReffPath']; HelperService.config.pipe(take(1)).subscribe((config: object) => {
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);
...@@ -211,6 +217,7 @@ export class CorpusService { ...@@ -211,6 +217,7 @@ export class CorpusService {
reject(error); reject(error);
}); });
}); });
});
} }
getFrequencyAnalysis() { getFrequencyAnalysis() {
...@@ -218,7 +225,8 @@ export class CorpusService { ...@@ -218,7 +225,8 @@ export class CorpusService {
if (this.annisResponse.frequency_analysis.length) { if (this.annisResponse.frequency_analysis.length) {
return resolve(); return resolve();
} else { } else {
const url: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiFrequencyPath']; HelperService.config.pipe(take(1)).subscribe((config: object) => {
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;
...@@ -231,6 +239,7 @@ export class CorpusService { ...@@ -231,6 +239,7 @@ export class CorpusService {
}, () => { }, () => {
return reject(); return reject();
}); });
});
} }
}); });
} }
...@@ -336,11 +345,13 @@ export class CorpusService { ...@@ -336,11 +345,13 @@ export class CorpusService {
} }
public loadCorporaFromLocalStorage() { public loadCorporaFromLocalStorage() {
HelperService.config.pipe(take(1)).subscribe((config: object) => {
const storedCorporaJSONstring: string = window.localStorage.getItem(HelperService.config['localStorageKeyCorpora']); const storedCorporaJSONstring: string = window.localStorage.getItem(HelperService.config['localStorageKeyCorpora']);
if (storedCorporaJSONstring) { if (storedCorporaJSONstring) {
const corpusList: CorpusMC[] = JSON.parse(storedCorporaJSONstring) as CorpusMC[]; const corpusList: CorpusMC[] = JSON.parse(storedCorporaJSONstring) as CorpusMC[];
this.processCorpora(corpusList); this.processCorpora(corpusList);
} }
});
} }
processAnnisResponse(ar: AnnisResponse, saveToCache: boolean = true) { processAnnisResponse(ar: AnnisResponse, saveToCache: boolean = true) {
......
...@@ -16,6 +16,7 @@ import {TranslateService} from '@ngx-translate/core'; ...@@ -16,6 +16,7 @@ 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 {take} from 'rxjs/operators';
@Component({ @Component({
selector: 'app-exercise-list', selector: 'app-exercise-list',
...@@ -82,7 +83,8 @@ export class ExerciseListPage implements OnInit { ...@@ -82,7 +83,8 @@ export class ExerciseListPage implements OnInit {
} }
getExerciseList(): void { getExerciseList(): void {
const url: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiExerciseListPath']; HelperService.config.pipe(take(1)).subscribe((config: object) => {
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);
if (this.vocService.currentReferenceVocabulary) { if (this.vocService.currentReferenceVocabulary) {
...@@ -101,6 +103,7 @@ export class ExerciseListPage implements OnInit { ...@@ -101,6 +103,7 @@ export class ExerciseListPage implements OnInit {
this.filterExercises(this.currentSearchValue); this.filterExercises(this.currentSearchValue);
}, () => { }, () => {
}); });
});
} }
getMatchingDegree(exercise: ExerciseMC): string { getMatchingDegree(exercise: ExerciseMC): string {
...@@ -113,7 +116,8 @@ export class ExerciseListPage implements OnInit { ...@@ -113,7 +116,8 @@ export class ExerciseListPage implements OnInit {
} }
showExercise(exercise: ExerciseMC) { showExercise(exercise: ExerciseMC) {
const url: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiExercisePath']; HelperService.config.pipe(take(1)).subscribe((config: object) => {
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
...@@ -124,6 +128,7 @@ export class ExerciseListPage implements OnInit { ...@@ -124,6 +128,7 @@ export class ExerciseListPage implements OnInit {
HelperService.goToPreviewPage(this.navCtrl).then(); HelperService.goToPreviewPage(this.navCtrl).then();
}, () => { }, () => {
}); });
});
} }
sortExercises(): void { sortExercises(): void {
......
...@@ -54,7 +54,8 @@ export class ExerciseParametersPage implements OnInit { ...@@ -54,7 +54,8 @@ 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;
if (0 < HelperService.config['maxTextLength'] && HelperService.config['maxTextLength'] < this.corpusService.currentText.length) { 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({ const toast = await this.toastCtrl.create({
message: this.textTooLongString, message: this.textTooLongString,
duration: 3000, duration: 3000,
...@@ -73,6 +74,7 @@ export class ExerciseParametersPage implements OnInit { ...@@ -73,6 +74,7 @@ export class ExerciseParametersPage implements OnInit {
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 {
...@@ -130,7 +132,8 @@ export class ExerciseParametersPage implements OnInit { ...@@ -130,7 +132,8 @@ export class ExerciseParametersPage implements OnInit {
} }
getH5Pexercise(formData: FormData) { getH5Pexercise(formData: FormData) {
const url = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiExercisePath']; HelperService.config.pipe(take(1)).subscribe((config: object) => {
const url = config['backendBaseUrl'] + config['backendApiExercisePath'];
HelperService.currentError = null; HelperService.currentError = null;
HelperService.isLoading = true; HelperService.isLoading = true;
this.http.post(url, formData).subscribe((ar: AnnisResponse) => { this.http.post(url, formData).subscribe((ar: AnnisResponse) => {
...@@ -155,10 +158,12 @@ export class ExerciseParametersPage implements OnInit { ...@@ -155,10 +158,12 @@ export class ExerciseParametersPage implements OnInit {
}); });
toast.present().then(); toast.present().then();
}); });
});
} }
getKwicExercise(formData: FormData) { getKwicExercise(formData: FormData) {
const kwicUrl: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiKwicPath']; HelperService.config.pipe(take(1)).subscribe((config: object) => {
const kwicUrl: string = config['backendBaseUrl'] + config['backendApiKwicPath'];
HelperService.currentError = null; HelperService.currentError = null;
HelperService.isLoading = true; HelperService.isLoading = true;
this.http.post(kwicUrl, formData).subscribe((svgString: string) => { this.http.post(kwicUrl, formData).subscribe((svgString: string) => {
...@@ -175,11 +180,14 @@ export class ExerciseParametersPage implements OnInit { ...@@ -175,11 +180,14 @@ export class ExerciseParametersPage implements OnInit {
}); });
toast.present().then(); toast.present().then();
}); });
});
} }
initTranslation() { initTranslation() {
HelperService.config.pipe(take(1)).subscribe((config: object) => {
this.translateService.get('TEXT_TOO_LONG').subscribe( this.translateService.get('TEXT_TOO_LONG').subscribe(
value => this.textTooLongString = value + HelperService.config['maxTextLength']); value => this.textTooLongString = value + config['maxTextLength']);
});
this.translateService.get('QUERY_VALUE_EMPTY').subscribe(value => this.emptyQueryValueString = value); this.translateService.get('QUERY_VALUE_EMPTY').subscribe(value => this.emptyQueryValueString = value);
} }
......
...@@ -36,8 +36,9 @@ export class ExercisePage implements OnInit { ...@@ -36,8 +36,9 @@ export class ExercisePage implements OnInit {
loadExercise(): void { loadExercise(): void {
this.activatedRoute.queryParams.subscribe((params: object) => { this.activatedRoute.queryParams.subscribe((params: object) => {
HelperService.config.pipe(take(1)).subscribe((config: object) => {
if (params['eid']) { if (params['eid']) {
let url: string = HelperService.config['backendBaseUrl'] + HelperService.config['backendApiExercisePath']; let url: string = config['backendBaseUrl'] + config['backendApiExercisePath'];
const httpParams: HttpParams = new HttpParams().set('eid', params['eid']); const httpParams: HttpParams = new HttpParams().set('eid', params['eid']);
HelperService.makeGetRequest(this.http, this.toastCtrl, url, httpParams).then((ar: AnnisResponse) => { HelperService.makeGetRequest(this.http, this.toastCtrl, url, httpParams).then((ar: AnnisResponse) => {
HelperService.applicationState.pipe(take(1)).subscribe((as: ApplicationState) => { HelperService.applicationState.pipe(take(1)).subscribe((as: ApplicationState) => {
...@@ -47,9 +48,9 @@ export class ExercisePage implements OnInit { ...@@ -47,9 +48,9 @@ export class ExercisePage implements OnInit {
const met: MoodleExerciseType = MoodleExerciseType[ar.exercise_type]; const met: MoodleExerciseType = MoodleExerciseType[ar.exercise_type];
this.corpusService.exercise.type = ExerciseType[met.toString()]; this.corpusService.exercise.type = ExerciseType[met.toString()];
// this will be called via GET request from the h5p standalone javascript library // this will be called via GET request from the h5p standalone javascript library
url = `${HelperService.config['backendBaseUrl']}${HelperService.config['backendApiH5pPath']}` + url = `${config['backendBaseUrl']}${config['backendApiH5pPath']}` +
`?eid=${this.corpusService.annisResponse.exercise_id}&lang=${this.translateService.currentLang}`; `?eid=${this.corpusService.annisResponse.exercise_id}&lang=${this.translateService.currentLang}`;
window.localStorage.setItem(HelperService.config['localStorageKeyH5P'], url); window.localStorage.setItem(config['localStorageKeyH5P'], url);
const exerciseTypePath: string = this.corpusService.exercise.type === ExerciseType.markWords ? const exerciseTypePath: string = this.corpusService.exercise.type === ExerciseType.markWords ?
'mark_words' : 'drag_text'; 'mark_words' : 'drag_text';
this.exerciseService.initH5P(exerciseTypePath); this.exerciseService.initH5P(exerciseTypePath);
...@@ -62,11 +63,12 @@ export class ExercisePage implements OnInit { ...@@ -62,11 +63,12 @@ export class ExercisePage implements OnInit {
this.exerciseService.fillBlanksString : exerciseType; this.exerciseService.fillBlanksString : exerciseType;
const file: string = params['file']; const file: string = params['file'];
const lang: string = this.translateService.currentLang; const lang: string = this.translateService.currentLang;
window.localStorage.setItem(HelperService.config['localStorageKeyH5P'], window.localStorage.setItem(config['localStorageKeyH5P'],
HelperService.baseUrl + '/assets/h5p/' + exerciseType + '/content/' + file + '_' + lang + '.json'); HelperService.baseUrl + '/assets/h5p/' + exerciseType + '/content/' + file + '_' + lang + '.json');
this.exerciseService.initH5P(exerciseTypePath); this.exerciseService.initH5P(exerciseTypePath);
} }
}); });
});
} }
ngOnInit() { ngOnInit() {
......
...@@ -10,6 +10,7 @@ import {Storage} from '@ionic/storage'; ...@@ -10,6 +10,7 @@ import {Storage} from '@ionic/storage';
import {Language} from 'src/app/models/language'; import {Language} from 'src/app/models/language';
import {ReplaySubject} from 'rxjs'; import {ReplaySubject} from 'rxjs';
import {TextData} from './models/textData'; import {TextData} from './models/textData';
import {take} from 'rxjs/operators';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
...@@ -29,7 +30,7 @@ export class HelperService { ...@@ -29,7 +30,7 @@ export class HelperService {
Voc: CaseValue.vocative, Voc: CaseValue.vocative,
Loc: CaseValue.locative, Loc: CaseValue.locative,
}; };
public static config: object; public static config: ReplaySubject<object>;
public static corpusUpdateCompletedString: string; public static corpusUpdateCompletedString: string;
public static currentError: HttpErrorResponse; public static currentError: HttpErrorResponse;
public static currentLanguage: Language; public static currentLanguage: Language;
...@@ -109,7 +110,7 @@ export class HelperService { ...@@ -109,7 +110,7 @@ export class HelperService {
private storage: Storage, private storage: Storage,
public translate: TranslateService, public translate: TranslateService,
) { ) {
this.initConfig().then(); this.initConfig();
this.initLanguage(); this.initLanguage();
} }
...@@ -269,21 +270,11 @@ export class HelperService { ...@@ -269,21 +270,11 @@ export class HelperService {
return array; return array;
} }
static waitForConfig() {
return new Promise(async (resolve) => {
while (!HelperService.config) {
await new Promise((resolveWait) => {
setTimeout(resolveWait, 50);
});
}
return resolve();
});
}
initApplicationState(): void { initApplicationState(): void {
HelperService.applicationState = new ReplaySubject<ApplicationState>(1); HelperService.applicationState = new ReplaySubject<ApplicationState>(1);
if (!HelperService.applicationStateCache) { if (!HelperService.applicationStateCache) {
this.storage.get(HelperService.config['localStorageKeyApplicationState']).then((jsonString: string) => { HelperService.config.pipe(take(1)).subscribe((config: object) => {
this.storage.get(config['localStorageKeyApplicationState']).then((jsonString: string) => {
HelperService.applicationStateCache = new ApplicationState({ HelperService.applicationStateCache = new ApplicationState({
currentSetup: new TextData() currentSetup: new TextData()
}); });
...@@ -293,21 +284,20 @@ export class HelperService { ...@@ -293,21 +284,20 @@ export class HelperService {
} }
HelperService.applicationState.next(HelperService.applicationStateCache); HelperService.applicationState.next(HelperService.applicationStateCache);
}); });
});
} else { } else {
HelperService.applicationState.next(HelperService.applicationStateCache); HelperService.applicationState.next(HelperService.applicationStateCache);
} }
} }
initConfig() { initConfig() {
return new Promise((resolve) => { HelperService.config = new ReplaySubject<object>(1);
this.http.get('assets/config.json').subscribe((config: object) => { this.http.get('assets/config.json').subscribe((config: object) => {
HelperService.config = config; if (!config['backendBaseUrl']) {
if (!HelperService.config['backendBaseUrl']) {
const part1: string = location.protocol.concat('//').concat(window.location.host); const part1: string = location.protocol.concat('//').concat(window.location.host);
HelperService.config['backendBaseUrl'] = part1.concat(HelperService.config['backendBaseApiPath']).concat('/'); config['backendBaseUrl'] = part1.concat(config['backendBaseApiPath']).concat('/');
} }
return resolve(); HelperService.config.next(config);
});
}); });
} }
...@@ -323,9 +313,11 @@ export class HelperService { ...@@ -323,9 +313,11 @@ export class HelperService {
return new Promise((resolve) => { return new Promise((resolve) => {
HelperService.applicationStateCache = mrs; HelperService.applicationStateCache = mrs;
HelperService.applicationState.next(HelperService.applicationStateCache); HelperService.applicationState.next(HelperService.applicationStateCache);
this.storage.set(HelperService.config['localStorageKeyApplicationState'], JSON.stringify(mrs)).then(() => { HelperService.config.pipe(take(1)).subscribe((config: object) => {
this.storage.set(config['localStorageKeyApplicationState'], JSON.stringify(mrs)).then(() => {
return resolve(); return resolve();
}); });
}); });
});
} }
} }
...@@ -21,11 +21,11 @@ ...@@ -21,11 +21,11 @@
<ion-content padding> <ion-content padding>
<div *ngIf="corpusService.annisResponse?.solutions; else loading"> <div *ngIf="corpusService.annisResponse?.solutions; else loading">
<ion-grid> <ion-grid *ngIf="HelperService.config | async as config">
<ion-row *ngIf="HelperService.isVocabularyCheck"> <ion-row *ngIf="HelperService.isVocabularyCheck">
<ion-col> <ion-col>
<label> <label>
<input type="checkbox" [(ngModel)]="exerciseService.excludeOOV" (ngModelChange)="switchOOV()" /> <input type="checkbox" [(ngModel)]="exerciseService.excludeOOV" (ngModelChange)="switchOOV()"/>
<span class="checkbox">{{ "EXERCISE_NO_OOV" | translate}}</span> <span class="checkbox">{{ "EXERCISE_NO_OOV" | translate}}</span>
</label> </label>
<br> <br>
...@@ -54,7 +54,7 @@ beginning that it is going to be a download (instead of an ordinary link or clic ...@@ -54,7 +54,7 @@ beginning that it is going to be a download (instead of an ordinary link or clic
translate }}</a> translate }}</a>
</ion-col> </ion-col>
<ion-col> <ion-col>
<a href="{{HelperService.config['developerMailTo']}}">{{ 'EMAIL_ERROR' | translate }}</a> <a href="{{config['developerMailTo']}}">{{ 'EMAIL_ERROR' | translate }}</a>
</ion-col> </ion-col>
</ion-row> </ion-row>
<br> <br>
...@@ -70,14 +70,15 @@ beginning that it is going to be a download (instead of an ordinary link or clic ...@@ -70,14 +70,15 @@ beginning that it is going to be a download (instead of an ordinary link or clic
</ion-col> </ion-col>
<ion-col style="text-align: left;"> <ion-col style="text-align: left;">
<ion-button