diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7b79d655a874978c0a8cb90a26c8206cd958ac86..3b4ad89a8004509b36ead8b50cc085a7e5e2f474 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -823,6 +823,139 @@ export class AppComponent implements OnInit, OnDestroy, Observer { if (itemId) { this.scrollToQuicknav(itemId, "auto"); } + this.loadSessionFile(result.file, result.calculators); + } + } + + public async loadSessionFile(f: File, info?: any) { + // notes merge detection: was there already some notes ? + const existingNotes = Session.getInstance().documentation; + // load + try { + const data = await this.formulaireService.loadSession(f, info) + if (data.hasErrors) { + this.notificationsService.notify(this.intlService.localizeText("ERROR_PROBLEM_LOADING_SESSION"), 3500); + } else { + if (data.loaded && data.loaded.length > 0) { + if (! isDevMode()) { + this.matomoTracker.trackEvent("userAction", "loadSession"); + } + // notes merge detection: was there already some notes ? + const currentNotes = Session.getInstance().documentation; + if (existingNotes !== "" && currentNotes !== existingNotes) { + this.notificationsService.notify(this.intlService.localizeText("WARNING_SESSION_LOAD_NOTES_MERGED"), 3500); + } + // go to calc or diagram depending on what was loaded + if (data.loaded.length > 1) { + this.toDiagram(); + } else { + this.toCalc(data.loaded[0]); + } + } + } + } catch(err) { + this.notificationsService.notify(this.intlService.localizeText("ERROR_LOADING_SESSION"), 3500); + console.error("error loading session - ", err); + // rollback to ensure session is clean + this.doEmptySession(); + } + } + + /** + * Demande au client d'envoyer un email (génère un lien mailto:), pré-rempli + * avec un texte standard, et le contenu de la session au format JSON + */ + public reportBug() { + const recipient = "bug@cassiopee.g-eau.fr"; + const subject = "[ISSUE] " + this.intlService.localizeText("INFO_REPORT_BUG_SUBJECT"); + let body = this.intlService.localizeText("INFO_REPORT_BUG_BODY"); + + // add session description + + // get all forms + const list = []; + for (const c of this._calculators) { + list.push({ + title: c.title, + uid: c.uid, + selected: true + }); + } + let session = this.buildSessionFile(list); + + // compress + session = pako.deflate(session, { to: "string" }); // gzip (zlib) + session = btoa(session); // base64 + + body += session + "\n"; + body = encodeURIComponent(body); + + const mailtoURL = `mailto:${recipient}?subject=${subject}&body=${body}`; + + // temporarily disable tab closing alert, as tab won't be closed for real + this.appSetupService.warnBeforeTabClose = false; + window.location.href = mailtoURL; + this.appSetupService.warnBeforeTabClose = true; + } + + public get revisionInfo(): any { + return { + jalhyd: { + date: jalhydDateRev, + version: jalhydVersion, + }, + nghyd: { + date: nghydDateRev, + version: nghydVersion + } + }; + } + + /** + * sauvegarde du/des formulaires + * @param form formulaire à sélectionner par défaut dans la liste + */ + public saveForm(form?: FormulaireDefinition) { + // liste des formulaires + const list = []; + for (const c of this._calculators) { + const uid = c["uid"]; + const nub = Session.getInstance().findNubByUid(uid); + let required = nub.getTargettedNubs().map((req) => { + return req.uid; + }); + required = required.filter( + (item, index) => required.indexOf(item) === index // deduplicate + ); + list.push({ + "children": nub.getChildren().map((child) => { + return child.uid; + }), + "requires": required, + "selected": form ? (uid === form.uid) : true, + "title": c["title"], + "uid": uid + }); + } + // dialogue de sélection des formulaires à sauver + const dialogRef = this.saveSessionDialog.open( + DialogSaveSessionComponent, + { + data: { + calculators: list + }, + disableClose: false + } + ); + dialogRef.afterClosed().subscribe(result => { + if (result) { + let name = result.filename; + + // ajout extension ".json" + const re = /.+\.json/; + const match = re.exec(name.toLowerCase()); + if (match === null) { + name = name + ".json"; } } diff --git a/src/app/components/app-setup/app-setup.component.ts b/src/app/components/app-setup/app-setup.component.ts index 66b9df46216df020241301b28f5f222a0584c024..1b3c52d158c498cf992ed9ee31e0fe88dca086c5 100644 --- a/src/app/components/app-setup/app-setup.component.ts +++ b/src/app/components/app-setup/app-setup.component.ts @@ -135,12 +135,11 @@ export class ApplicationSetupComponent implements Observer, OnInit { }); } - public restoreDefaultValues() { + public async restoreDefaultValues() { const text = this.intlService.localizeText("INFO_SNACKBAR_DEFAULT_SETTINGS_RESTORED"); - this.appSetupService.restoreDefaultValues().then(() => { - this.snackBar.open(text, "OK", { - duration: 2500 - }); + await this.appSetupService.restoreDefaultValues(); + this.snackBar.open(text, "OK", { + duration: 2500 }); } diff --git a/src/app/components/calculator-list/calculator-list.component.ts b/src/app/components/calculator-list/calculator-list.component.ts index e4c9a47b2c6d4134cea74527eb6d4fc095ca5f97..f850cdd39bb87414df77674a52ab37b0dbffcc72 100644 --- a/src/app/components/calculator-list/calculator-list.component.ts +++ b/src/app/components/calculator-list/calculator-list.component.ts @@ -142,53 +142,49 @@ export class CalculatorListComponent implements OnInit { this.filterItems(); } - public create(t: CalculatorType) { - const p: Promise<FormulaireDefinition> = ServiceFactory.formulaireService.createFormulaire(t); - p.then(f => { - this.router.navigate(["/calculator", f.uid]); - return f; - }).then(f => { - // on ajoute un ouvrage après l'ouverture du module de calcul "ouvrages parallèles" - if (f instanceof FormulaireParallelStructure) { - for (const e of f.allFormElements) { - if (e instanceof FieldsetContainer) { - e.addFromTemplate(0); - break; - } + public async create(t: CalculatorType) { + const f: FormulaireDefinition = await ServiceFactory.instance.formulaireService.createFormulaire(t); + await this.router.navigate(["/calculator", f.uid]); + // on ajoute un ouvrage après l'ouverture du module de calcul "ouvrages parallèles" + if (f instanceof FormulaireParallelStructure) { + for (const e of f.allFormElements) { + if (e instanceof FieldsetContainer) { + e.addFromTemplate(0); + break; } } - // on ajoute un ouvrage après l'ouverture du module de calcul "passe à bassins" - if (f instanceof FormulairePab) { - for (const e of f.allFormElements) { - if (e instanceof FieldsetContainer) { - e.addFromTemplate(0); - break; - } + } + // on ajoute un ouvrage après l'ouverture du module de calcul "passe à bassins" + if (f instanceof FormulairePab) { + for (const e of f.allFormElements) { + if (e instanceof FieldsetContainer) { + e.addFromTemplate(0); + break; } } - // adding GUI for default apron, in MacroRugoCompound - if (f instanceof FormulaireMacrorugoCompound) { - for (const e of f.allFormElements) { - if (e instanceof FieldsetContainer) { - e.addFromTemplate(0, 0, f.currentNub.getChildren()[0]); - break; - } + } + // adding GUI for default apron, in MacroRugoCompound + if (f instanceof FormulaireMacrorugoCompound) { + for (const e of f.allFormElements) { + if (e instanceof FieldsetContainer) { + e.addFromTemplate(0, 0, f.currentNub.getChildren()[0]); + break; } } - // on ajoute un YAXN après l'ouverture du module de calcul "somme / produit de puissances" - if (f instanceof FormulaireSPP) { - for (const e of f.allFormElements) { - if (e instanceof FieldsetContainer) { - e.addFromTemplate(0); - break; - } + } + // on ajoute un YAXN après l'ouverture du module de calcul "somme / produit de puissances" + if (f instanceof FormulaireSPP) { + for (const e of f.allFormElements) { + if (e instanceof FieldsetContainer) { + e.addFromTemplate(0); + break; } } + } - if (this.appSetupService.enableEmptyFieldsOnFormInit) { - f.emptyFields(); - } - }); + if (this.appSetupService.enableEmptyFieldsOnFormInit) { + f.emptyFields(); + } } public get nbOpenCalculators() { @@ -276,14 +272,15 @@ export class CalculatorListComponent implements OnInit { ]; } - public loadExample(path: string) { + public async loadExample(path: string) { const realPath = "app/examples/" + path; - this.httpService.httpGetBlobRequestPromise(realPath).then((d) => { + try { + const d = await this.httpService.httpGetBlobRequestPromise(realPath); const f: any = new Blob([d], { type: "application/json" }); this.appComponent.loadSessionFile(f); - }).catch((e) => { + } catch(e) { console.error("could not load session file", e); - }); + } } public get uitextWelcomeTitle() { diff --git a/src/app/components/dialog-load-session/dialog-load-session.component.ts b/src/app/components/dialog-load-session/dialog-load-session.component.ts index 653cf5ff55a9376e8ccdce2a7072cb90fa274801..02473fe41ccec8feb2ea36bbc47cdeb77bb3f111 100644 --- a/src/app/components/dialog-load-session/dialog-load-session.component.ts +++ b/src/app/components/dialog-load-session/dialog-load-session.component.ts @@ -138,35 +138,34 @@ export class DialogLoadSessionComponent { this.checkLinkedParamsAndModelsDependencies(); } - public onFileSelected(event: any) { - if (event.target.files && event.target.files.length) { - this.file = event.target.files[0]; - // reinit file infos - this.calculators = []; - this.fileFormatVersion = ""; - // reinit flags - this.loadingError = false; - this.loadingComplete = false; - - const formService = ServiceFactory.formulaireService; - formService.calculatorInfosFromSessionFile(this.file).then( - calcInfos => { - this.fileFormatVersion = calcInfos.formatVersion; - this.calculators = calcInfos.nubs; - for (const n of this.calculators) { - n.selected = true; - // if no title was given, generate a default one - if (!n.title) { - n.title = decode(formService.getLocalisedShortTitleFromCalculatorType(n.type)); - } - } - this.loadingComplete = true; - } - ).catch((err) => { - console.error(err); - this.loadingError = true; - }); + public async onFileSelected(event: any) { + if (event.target.files && event.target.files.length) { + this.file = event.target.files[0]; + // reinit file infos + this.calculators = []; + this.fileFormatVersion = ""; + // reinit flags + this.loadingError = false; + this.loadingComplete = false; + + const formService = ServiceFactory.instance.formulaireService; + try { + const calcInfos: any = await formService.calculatorInfosFromSessionFile(this.file); + this.fileFormatVersion = calcInfos.formatVersion; + this.calculators = calcInfos.nubs; + for (const n of this.calculators) { + n.selected = true; + // if no title was given, generate a default one + if (! n.title) { + n.title = decode(formService.getLocalisedShortTitleFromCalculatorType(n.type)); + } + } + this.loadingComplete = true; + } catch(err) { + console.error(err); + this.loadingError = true; } + } } public loadSession() { diff --git a/src/app/components/fixedvar-results/results.component.ts b/src/app/components/fixedvar-results/results.component.ts index 8ba53061af029d00787e37c77061108fd1f87830..00d4f84e3e27ab3fd871333b581661144e7c2041 100644 --- a/src/app/components/fixedvar-results/results.component.ts +++ b/src/app/components/fixedvar-results/results.component.ts @@ -23,21 +23,19 @@ export class ResultsComponentDirective { } } - public setFullscreen(element): Promise<void> { + public async setFullscreen(element): Promise<void> { const sf = <Screenfull>screenfull; if (sf.isEnabled) { - return sf.request(element).then(() => { - this.fullscreenChange(true); - }); + await sf.request(element); + this.fullscreenChange(true); } } - public exitFullscreen(): Promise<void> { + public async exitFullscreen(): Promise<void> { const sf = <Screenfull>screenfull; if (sf.isEnabled) { - return sf.exit().then(() => { - this.fullscreenChange(false); - }); + await sf.exit(); + this.fullscreenChange(false); } } diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts index 404e789b906370cd1fbcf8d79c0b2e9b7bf1d686..ca5675539fe6d42dea02f759b7ed330f1ef5a669 100644 --- a/src/app/components/generic-calculator/calculator.component.ts +++ b/src/app/components/generic-calculator/calculator.component.ts @@ -716,10 +716,10 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe disableClose: false } ); - dialogRef.afterClosed().subscribe(result => { + dialogRef.afterClosed().subscribe(async result => { if (result) { if (result.generate) { - this.formulaireService.createFormulaire(CalculatorType.Pab).then((f: FormulaireDefinition) => { + const f: FormulaireDefinition = await this.formulaireService.createFormulaire(CalculatorType.Pab); const pab = (f.currentNub as Pab); const params = pab.prms; // paramètres hydrauliques @@ -731,7 +731,6 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe pab.addCloisonsFromModel(this._formulaire.currentNub as Cloisons, result.nbBassins); // go to new PAB this.router.navigate(["/calculator", f.uid]); - }); } } }); @@ -836,7 +835,7 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe * @param Ys tirant(s) d'eau * @param Ifs pente(s) */ - private generateBiefSP(Ys: number | number[], Ifs: number | number[]) { + private async generateBiefSP(Ys: number | number[], Ifs: number | number[]) { const bief = (this._formulaire.currentNub as Bief); const serialisedSection = bief.section.serialise(); const sectionCopy = Session.getInstance().unserialiseSingleNub(serialisedSection, false).nub; @@ -859,18 +858,15 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe } } - this.formulaireService.createFormulaire(CalculatorType.SectionParametree, secParam) - .then((f: FormulaireDefinition) => { + const f: FormulaireDefinition = await this.formulaireService.createFormulaire(CalculatorType.SectionParametree, secParam); const sp = (f.currentNub as SectionParametree); sp.section.prms.Y.setValues(Ys); sp.section.prms.If.setValues(Ifs); // calculate f.doCompute(); // go to new SP - this.router.navigate(["/calculator", f.uid]).then(); + this.router.navigate(["/calculator", f.uid]); } - ); - } public get generateRuSpEnabled(): boolean { return this.hasResults && ! this._formulaire.currentNub.result.hasErrorMessages(); @@ -887,7 +883,7 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe /** * Génère une SectionParametree à partir du module RegimeUniforme en cours */ - public generateRuSp() { + public async generateRuSp() { const ru = (this._formulaire.currentNub as RegimeUniforme); // copy section const serialisedSection = ru.section.serialise(); @@ -903,14 +899,11 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe } Session.getInstance().registerNub(secParam); - this.formulaireService.createFormulaire(CalculatorType.SectionParametree, secParam) - .then((f: FormulaireDefinition) => { + const f: FormulaireDefinition = await this.formulaireService.createFormulaire(CalculatorType.SectionParametree, secParam); // calculate f.doCompute(); // go to new SP } - ); - } public get generatePARSimulationEnabled(): boolean { const parCalage = (this._formulaire.currentNub as Par); @@ -1008,7 +1001,7 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe * Creates a new Formulaire with a ParSimulation Nub, using given * values as parameters */ - protected doGenerateParSimWithValues(v: any) { + protected async doGenerateParSimWithValues(v: any) { const parCalage = (this._formulaire.currentNub as Par); const psim = new ParSimulationParams( round(v.Q, 3), round(v.Z1, 3), round(v.Z2, 3), @@ -1021,14 +1014,11 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe parSimulation.parType = parCalage.parType; Session.getInstance().registerNub(parSimulation); - this.formulaireService.createFormulaire(CalculatorType.ParSimulation, parSimulation) - .then((f: FormulaireDefinition) => { + const f: FormulaireDefinition = await this.formulaireService.createFormulaire(CalculatorType.ParSimulation, parSimulation); // calculate f.doCompute(); // go to new ParSimulation - this.router.navigate(["/calculator", f.uid]).then(); - } - ); + this.router.navigate(["/calculator", f.uid]); } /** @@ -1078,11 +1068,10 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe /** * Duplicates the current calculator form */ - public cloneCalculator() { + public async cloneCalculator() { const serialisedNub: string = this._formulaire.currentNub.serialise({ title: this._formulaire.calculatorName }); const nubPointer = Session.getInstance().unserialiseSingleNub(serialisedNub); - this.formulaireService.createFormulaire(nubPointer.nub.calcType, nubPointer.nub, nubPointer.meta.title).then((f) => { + const f = await this.formulaireService.createFormulaire(nubPointer.nub.calcType, nubPointer.nub, nubPointer.meta.title); this.router.navigate(["/calculator", f.uid]); - }); } } diff --git a/src/app/services/app-setup.service.ts b/src/app/services/app-setup.service.ts index 22db10b3c856b50d69b21d3cc3fc069a2672af8c..e0aab36d07930a801b1fd9570d38e5cc4ce466bd 100644 --- a/src/app/services/app-setup.service.ts +++ b/src/app/services/app-setup.service.ts @@ -115,13 +115,12 @@ export class ApplicationSetupService extends Observable { /** * Restore configuration values */ - public restoreDefaultValues(): Promise<any> { - return this.readValuesFromConfig().then(() => { - // notify I18nService - this.notifyObservers({ - action: "languagePreferenceChanged", - languages: [ this.language ] - }); + public async restoreDefaultValues(): Promise<any> { + await this.readValuesFromConfig() + // notify I18nService + this.notifyObservers({ + action: "languagePreferenceChanged", + languages: [ this.language ] }); } @@ -172,18 +171,17 @@ export class ApplicationSetupService extends Observable { /** * 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.maxIterations = data.params.maxIterations; - this.enableNotifications = data.params.enableNotifications; - this.enableHotkeys = data.params.enableHotkeys; - this.enableEmptyFieldsOnFormInit = data.params.enableEmptyFieldsOnFormInit; - this.language = data.params.language; - // load themes for calculators list page - this.themes = data.themes; - }); + private async readValuesFromConfig(): Promise<any> { + const data: any = await this.httpService.httpGetRequestPromise(this.CONFIG_FILE_PATH); + // get all config values (volontarily non-generic to prevent side-effects) + this.displayPrecision = data.params.displayPrecision; + this.computePrecision = data.params.computePrecision; + this.maxIterations = data.params.maxIterations; + this.enableNotifications = data.params.enableNotifications; + this.enableHotkeys = data.params.enableHotkeys; + this.enableEmptyFieldsOnFormInit = data.params.enableEmptyFieldsOnFormInit; + this.language = data.params.language; + // load themes for calculators list page + this.themes = data.themes; } } diff --git a/src/app/services/formulaire.service.ts b/src/app/services/formulaire.service.ts index a9a6a716006c62da7d8fcc34ab5d9202c713408f..c7fa7a3fb9527d5f16a5870046c575b4904386e6 100644 --- a/src/app/services/formulaire.service.ts +++ b/src/app/services/formulaire.service.ts @@ -332,12 +332,12 @@ export class FormulaireService extends Observable { * @param nub nub existant à associer au formulaire (chargement de session / duplication de module) * @param calculatorName nom du module, à afficher dans l'interface */ - public createFormulaire(ct: CalculatorType, nub?: Nub, calculatorName?: string): Promise<FormulaireDefinition> { + public async createFormulaire(ct: CalculatorType, nub?: Nub, calculatorName?: string): Promise<FormulaireDefinition> { // Crée un formulaire du bon type const f: FormulaireDefinition = this.newFormulaire(ct); this._formulaires.push(f); // Charge la configuration dépendamment du type - return this.loadConfig(ct).then(s => { + const s: any = await this.loadConfig(ct); f.preparseConfig(s); // Associe le Nub fourni (chargement de session / duplication de module), sinon en crée un nouveau @@ -422,16 +422,11 @@ export class FormulaireService extends Observable { newDownWall.addChild(newDownwallDevice); f.pabNub.downWall = newDownWall; } - - return f; - - }).then(fi => { this.notifyObservers({ "action": "createForm", - "form": fi - }); - return fi; + "form": f }); + return f; } /** @@ -642,10 +637,13 @@ export class FormulaireService extends Observable { * obtient des infos (nom, uid des modules de calcul, dépendances) d'un fichier session * @param f fichier session */ - public calculatorInfosFromSessionFile(f: File): Promise<{ nubs: any[], formatVersion: string }> { - return this.readSingleFile(f).then(s => { + public async calculatorInfosFromSessionFile(f: File): Promise<{ nubs: any[], formatVersion: string }> { + const s = await this.readSingleFile(f); // return value - const res: { nubs: any[], formatVersion: string } = { + const res: { + nubs: any[]; + formatVersion: string; + } = { nubs: [], formatVersion: "" }; @@ -663,7 +661,7 @@ export class FormulaireService extends Observable { // list linked params dependencies for each Nub if (e.parameters) { e.parameters.forEach((p) => { - if (p.targetNub && ! nubInfo.requires.includes(p.targetNub)) { + if (p.targetNub && !nubInfo.requires.includes(p.targetNub)) { nubInfo.requires.push(p.targetNub); } }); @@ -682,7 +680,6 @@ export class FormulaireService extends Observable { res.formatVersion = data.header.format_version; } return res; - }); } public saveForm(f: FormulaireDefinition) { diff --git a/src/app/services/internationalisation.service.ts b/src/app/services/internationalisation.service.ts index 451931f1b508a6d8104470a5b855a174053676ae..4d1dabf05ef7342c3c295d0a8ec84f19a03c2b59 100644 --- a/src/app/services/internationalisation.service.ts +++ b/src/app/services/internationalisation.service.ts @@ -59,7 +59,7 @@ export class I18nService extends Observable implements Observer { * * @param code ISO 639-1 language code */ - public setLanguage(code: string) { + public async setLanguage(code: string) { // ensure 2-letter language code code = code.substring(0, 2); // is language supported ? @@ -79,7 +79,7 @@ export class I18nService extends Observable implements Observer { promisesList.push(this.loadLocalisation(calcType).catch((err) => { /* silent fail */ })); } } - Promise.all(promisesList).then(() => { + await Promise.all(promisesList).then(() => { this.httpGetMessages(code).then((res: any) => { that._Messages = res; // propagate language change to all application @@ -128,12 +128,10 @@ export class I18nService extends Observable implements Observer { * Loads localized messages from JSON files for the given language * (general messages files, not calculator-specific ones) */ - private httpGetMessages(lang: string): Promise<void> { + private async httpGetMessages(lang: string): Promise<any> { const fileName = "messages." + lang + ".json"; const filePath = "locale/" + fileName; - return this.httpService.httpGetRequestPromise(filePath).then((res: any) => { - return res; - }); + return await this.httpService.httpGetRequestPromise(filePath); } private getMessageFromCode(c: MessageCode): string {