Skip to content
Snippets Groups Projects
Commit 5fb9e97d authored by mathias.chouet's avatar mathias.chouet
Browse files

Fix #59

parent d2839612
No related branches found
No related tags found
No related merge requests found
......@@ -8453,6 +8453,14 @@
"tslib": "^1.9.0"
}
},
"ngx-webstorage-service": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/ngx-webstorage-service/-/ngx-webstorage-service-4.0.1.tgz",
"integrity": "sha512-hSWmwnj+hHBdwMZDeFbJjzXKUrJMw86B3l74cb6LePM97Vu8D62MTOPj2+ABB5NMIkUhxgO1E27ih/zhrEd9Fg==",
"requires": {
"tslib": "^1.9.0"
}
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
......
......@@ -43,6 +43,7 @@
"mathjax": "^2.7.5",
"ngx-material-file-input": "^1.1.1",
"ngx-md": "^7.0.0",
"ngx-webstorage-service": "^4.0.1",
"rxjs": "^6.3.3",
"rxjs-compat": "^6.3.3",
"tslib": "^1.9.0",
......
......@@ -102,16 +102,12 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
);
}
/**
* Triggered at app startup.
* Preferences are loaded by app setup service
* @see ApplicationSetupService.construct()
*/
ngOnInit() {
// try to detect the browser's language
const navLang = navigator.language;
try {
this.intlService.setLocale(navLang);
} catch (e) {
console.error(e.toString());
this.intlService.setLocale(this.appSetupService.language);
}
this.formulaireService.addObserver(this);
this.subscribeErrorService();
this._innerWidth = window.innerWidth;
......
......@@ -18,6 +18,7 @@ import {
MatListModule,
MatCardModule,
MatTableModule,
MatSnackBarModule,
ErrorStateMatcher,
MatButtonToggleModule
} from "@angular/material";
......@@ -36,6 +37,7 @@ import { FormsModule, ReactiveFormsModule } from "@angular/forms"; // <-- NgMode
import { ChartModule } from "angular2-chartjs";
import { RouterModule, Routes } from "@angular/router";
import { NgxMdModule } from "ngx-md";
import { StorageServiceModule } from "ngx-webstorage-service";
import { FormulaireService } from "./services/formulaire/formulaire.service";
import { I18nService } from "./services/internationalisation/internationalisation.service";
......@@ -115,6 +117,7 @@ const appRoutes: Routes = [
MatMenuModule,
MatSelectModule,
MatSidenavModule,
MatSnackBarModule,
MatTableModule,
MatTabsModule,
MatToolbarModule,
......@@ -126,7 +129,8 @@ const appRoutes: Routes = [
useHash: true, // prevents reloading whole app when typing url in browser's navigation bar
enableTracing: false // debugging purposes only
}
)
),
StorageServiceModule
],
declarations: [ // composants, pipes et directives
AppComponent,
......
......@@ -6,6 +6,21 @@
<mat-card-title>
<h1>{{ uitextTitle }}</h1>
</mat-card-title>
<button mat-icon-button [matMenuTriggerFor]="menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="storePreferences()">
<mat-icon>save_alt</mat-icon>
<span>{{ uitextRememberValues }}</span>
</button>
<button mat-menu-item (click)="restoreDefaultValues()">
<mat-icon>settings_backup_restore</mat-icon>
<span>{{ uitextRestoreDefault }}</span>
</button>
</mat-menu>
</mat-card-header>
<mat-card-content>
......
......@@ -6,7 +6,7 @@ import { ApplicationSetupService } from "../../services/app-setup/app-setup.serv
import { I18nService, LanguageCode } from "../../services/internationalisation/internationalisation.service";
import { NgBaseParam } from "../base-param-input/base-param-input.component";
import { BaseComponent } from "../base/base.component";
import { ErrorStateMatcher } from "@angular/material";
import { ErrorStateMatcher, MatSnackBar } from "@angular/material";
@Component({
......@@ -29,7 +29,8 @@ export class ApplicationSetupComponent extends BaseComponent implements Observer
constructor(
private appSetupService: ApplicationSetupService,
private intlService: I18nService
private intlService: I18nService,
private snackBar: MatSnackBar
) {
super();
this.appSetupService.addObserver(this);
......@@ -40,7 +41,9 @@ export class ApplicationSetupComponent extends BaseComponent implements Observer
}
public get currentLanguageCode() {
return this.intlService.currentLanguage.code;
if (this.intlService.currentLanguage) {
return this.intlService.currentLanguage.code;
}
}
public set currentLanguageCode(lc: LanguageCode) {
......@@ -65,10 +68,34 @@ export class ApplicationSetupComponent extends BaseComponent implements Observer
return this.intlService.localizeText("INFO_SETUP_NEWTON_MAX_ITER");
}
public get uitextRememberValues(): string {
return this.intlService.localizeText("INFO_MENU_SAVE_SETTINGS");
}
public get uitextRestoreDefault(): string {
return this.intlService.localizeText("INFO_MENU_RESTORE_DEFAULT_SETTINGS");
}
public get uitextMustBeANumber(): string {
return this.intlService.localizeText("ERROR_PARAM_MUST_BE_A_NUMBER");
}
public storePreferences() {
this.appSetupService.saveValuesIntoLocalStorage();
this.snackBar.open(this.intlService.localizeText("INFO_SNACKBAR_SETTINGS_SAVED"), "OK", {
duration: 1200
});
}
public restoreDefaultValues() {
const text = this.intlService.localizeText("INFO_SNACKBAR_DEFAULT_SETTINGS_RESTORED");
this.appSetupService.restoreDefaultValues().then(() => {
this.snackBar.open(text, "OK", {
duration: 1200
});
});
}
private init() {
// modèle du composant BaseParamInputComponent de précision d'affichage
this.displayPrec = new NgBaseParam("dp", ParamDomainValue.POS, this.appSetupService.displayPrecision);
......
......@@ -116,6 +116,7 @@ export class CalculatorListComponent implements OnInit {
update(sender: any, data: any): void {
if (sender instanceof I18nService) {
// reload themes if language changed
this.loadCalculatorsThemes();
}
}
......
......@@ -270,6 +270,7 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
update(sender: any, data: any): void {
if (sender instanceof I18nService) {
// update display if language changed
this.formulaireService.updateLocalisation();
} else if (sender instanceof FormulaireService) {
switch (data["action"]) {
......
import { HttpService } from "../http/http.service";
import { Injectable } from "@angular/core";
import { Injectable, Inject } from "@angular/core";
import { Observable } from "jalhyd";
import { StorageService, LOCAL_STORAGE } from "ngx-webstorage-service";
/**
* Stores app preferences
......@@ -9,48 +10,122 @@ import { Observable } from "jalhyd";
export class ApplicationSetupService extends Observable {
private CONFIG_FILE_PATH = "app/config.json";
private LOCAL_STORAGE_PREFIX = "nghyd_";
// default builtin values
public displayPrecision = 0.001;
public computePrecision = 0.0001;
public newtonMaxIterations = 50;
private _language = "fr";
/**
* just stores the current language preference, does not transmit it to I18nService, that is
* not available here.
*
* @see ApplicationSetupComponent.currentLanguageCode() setter
* @see I18nService.update() observer
*/
public language = "fr";
/** themes to group calculators, for displaying on the front page */
public themes: any[];
public constructor(
private httpService: HttpService
private httpService: HttpService,
@Inject(LOCAL_STORAGE) private storage: StorageService
) {
super();
this.readValuesFromConfig();
// precedence: builtin values >> JSON config >> browser's language >> local storage
const builtinLanguage = this.language;
// load JSON config
this.readValuesFromConfig().then((data) => {
const configLanguage = this.language;
// guess browser's language
this.language = navigator.language.substring(0, 2); // @TODO clodo trick, check validity
const browserLanguage = this.language;
// load saved preferences
const loadedPrefKeys = this.readValuesFromLocalStorage();
let storageLanguage: string;
if (loadedPrefKeys.includes("language")) {
storageLanguage = this.language;
}
// notify I18nService
this.notifyObservers({
action: "languagePreferenceChanged",
languages: [ storageLanguage, browserLanguage, configLanguage, builtinLanguage ]
});
});
}
public get displayDigits() {
return -Math.log10(this.displayPrecision);
}
public set language(lang: string) {
this._language = lang;
this.notifyObservers(null);
/**
* Save configuration values into local storage
*/
public saveValuesIntoLocalStorage() {
this.storage.set(this.LOCAL_STORAGE_PREFIX + "displayPrecision", this.displayPrecision);
this.storage.set(this.LOCAL_STORAGE_PREFIX + "computePrecision", this.computePrecision);
this.storage.set(this.LOCAL_STORAGE_PREFIX + "newtonMaxIterations", this.newtonMaxIterations);
this.storage.set(this.LOCAL_STORAGE_PREFIX + "language", this.language);
}
public get language(): string {
return this._language;
/**
* Restore configuration values
*/
public restoreDefaultValues(): Promise<any> {
return this.readValuesFromConfig().then(() => {
// notify I18nService
this.notifyObservers({
action: "languagePreferenceChanged",
languages: [ this.language ]
});
});
}
// @TODO save preferences in cookie / localStorage ?
/**
* Read configuration values from local storage
*/
private readValuesFromLocalStorage(): string[] {
const loadedKeys = [];
// get all config values (volontarily non-generic to prevent side-effects)
const displayPrecision = this.storage.get(this.LOCAL_STORAGE_PREFIX + "displayPrecision");
if (displayPrecision !== undefined) {
this.displayPrecision = displayPrecision;
loadedKeys.push("displayPrecision");
}
const computePrecision = this.storage.get(this.LOCAL_STORAGE_PREFIX + "computePrecision");
if (computePrecision !== undefined) {
this.computePrecision = computePrecision;
loadedKeys.push("computePrecision");
}
const newtonMaxIterations = this.storage.get(this.LOCAL_STORAGE_PREFIX + "newtonMaxIterations");
if (newtonMaxIterations !== undefined) {
this.newtonMaxIterations = newtonMaxIterations;
loadedKeys.push("newtonMaxIterations");
}
const language = this.storage.get(this.LOCAL_STORAGE_PREFIX + "language");
if (language !== undefined) {
this.language = language;
loadedKeys.push("language");
}
return loadedKeys;
}
// read default values from config and notify observers
private readValuesFromConfig() {
this.httpService.httpGetRequestPromise(this.CONFIG_FILE_PATH).then((data: any) => {
/**
* Read configuration values from config (async)
*/
private readValuesFromConfig(): Promise<any> {
return this.httpService.httpGetRequestPromise(this.CONFIG_FILE_PATH).then((data: any) => {
// get all config values (volontarily non-generic to prevent side-effects)
this.displayPrecision = data.params.displayPrecision;
this.computePrecision = data.params.computePrecision;
this.newtonMaxIterations = data.params.newtonMaxIterations;
this.language = data.params.language;
// load themes for calculators list page
this.themes = data.themes;
this.notifyObservers(null);
});
}
}
import { Injectable } from "@angular/core";
import { Message, MessageCode, Observable } from "jalhyd";
import { Message, MessageCode, Observable, Observer } from "jalhyd";
import { StringMap } from "../../stringmap";
import { ApplicationSetupService } from "../app-setup/app-setup.service";
......@@ -43,7 +43,7 @@ export class Language {
}
@Injectable()
export class I18nService extends Observable {
export class I18nService extends Observable implements Observer {
private _currLang: Language;
private _Messages: StringMap;
......@@ -57,6 +57,8 @@ export class I18nService extends Observable {
this._languages = [];
this._languages.push(new Language(LanguageCode.FRENCH, "fr", "Français"));
this._languages.push(new Language(LanguageCode.ENGLISH, "en", "English"));
// add language preferences observer
this.applicationSetupService.addObserver(this);
}
public get languages() {
......@@ -110,6 +112,7 @@ export class I18nService extends Observable {
const is: I18nService = this;
prom.then((res) => {
// propagate language change to all application
is.notifyObservers(undefined);
});
}
......@@ -231,4 +234,34 @@ export class I18nService extends Observable {
return value.toFixed(nDigits);
}
// interface Observer
/**
* Should only be triggered once at app startup, when setup service tries loading language
* @param sender should always be ApplicationSetupService
* @param data object {
* action: should always be "languagePreferenceChanged"
* languages: languages codes to try until one works
* }
*/
public update(sender: any, data: any): void {
if (sender instanceof ApplicationSetupService) {
if (data.action === "languagePreferenceChanged") {
let languageEventuallyUsed: string;
for (let i = 0; i < data.languages.length && languageEventuallyUsed === undefined; i++) {
const l = data.languages[i];
if (l !== undefined) {
try {
this.setLocale(l);
languageEventuallyUsed = l;
} catch (e) {
console.error(e.toString());
}
}
}
// confirm to setup service which language was eventually used at app startup (triggers nothing more)
this.applicationSetupService.language = languageEventuallyUsed;
}
}
}
}
......@@ -133,6 +133,8 @@
"INFO_MENU_SAVE_SESSION_TITLE": "Save session",
"INFO_MENU_SELECT_CALC": "Select calculator module",
"INFO_MENU_EMPTY_SESSION_TITLE": "New session",
"INFO_MENU_SAVE_SETTINGS": "Save settings",
"INFO_MENU_RESTORE_DEFAULT_SETTINGS": "Default settings",
"INFO_OPTION_NO": "No",
"INFO_OPTION_YES": "Yes",
"INFO_OPTION_CANCEL": "Cancel",
......@@ -197,6 +199,8 @@
"INFO_SETUP_PRECISION_AFFICHAGE": "Display accuracy",
"INFO_SETUP_PRECISION_CALCUL": "Computation accuracy",
"INFO_SETUP_TITLE": "Application setup",
"INFO_SNACKBAR_SETTINGS_SAVED": "Settings saved on this device",
"INFO_SNACKBAR_DEFAULT_SETTINGS_RESTORED": "Default settings restored",
"INFO_THEME_CREDITS": "Credit",
"INFO_THEME_MODULES_INUTILISES_TITRE": "Other calculation modules",
"INFO_THEME_MODULES_INUTILISES_DESCRIPTION": "Various calculation modules",
......
......@@ -133,6 +133,8 @@
"INFO_MENU_NOUVELLE_CALC": "Nouveau module de calcul",
"INFO_MACRORUGO_TITRE": "Passe à macro-rugosités",
"INFO_MACRORUGO_TITRE_COURT": "Macro-rugo.",
"INFO_MENU_SAVE_SETTINGS": "Enregistrer les paramètres",
"INFO_MENU_RESTORE_DEFAULT_SETTINGS": "Paramètres par défaut",
"INFO_OPTION_NO": "Non",
"INFO_OPTION_YES": "Oui",
"INFO_OPTION_CANCEL": "Annuler",
......@@ -197,6 +199,8 @@
"INFO_SETUP_PRECISION_AFFICHAGE": "Précision d'affichage",
"INFO_SETUP_PRECISION_CALCUL": "Précision de calcul",
"INFO_SETUP_TITLE": "Paramètres de l'application",
"INFO_SNACKBAR_SETTINGS_SAVED": "Paramètres enregistrés sur cet appareil",
"INFO_SNACKBAR_DEFAULT_SETTINGS_RESTORED": "Paramètres par défaut restaurés",
"INFO_THEME_CREDITS": "Crédit",
"INFO_THEME_MODULES_INUTILISES_TITRE": "Autres modules de calcul",
"INFO_THEME_MODULES_INUTILISES_DESCRIPTION": "Modules de calculs divers",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment