import { Injectable } from "@angular/core"; import { decode } from "he"; import { CalculatorType, LinkedValue, Observable, ParamDefinition, Session, Nub, ParallelStructure, Pab, Props, Cloisons, CloisonAval, SPP, PreBarrage, PbBassin, PbBassinParams, LoiDebit, PbCloison, CreateStructure, Structure, SectionNub, SectionParametree, acSection, round, RegimeUniforme, CourbeRemous } from "jalhyd"; import { ApplicationSetupService } from "./app-setup.service"; import { HttpService } from "./http.service"; import { I18nService } from "./internationalisation.service"; import { NotificationsService } from "./notifications.service"; import { FormulaireDefinition } from "../formulaire/definition/form-definition"; import { FormulaireElement } from "../formulaire/elements/formulaire-element"; import { InputField } from "../formulaire/elements/input-field"; import { SelectField } from "../formulaire/elements/select/select-field"; import { FormulaireSectionParametree } from "../formulaire/definition/form-section-parametree"; import { FormulaireCourbeRemous } from "../formulaire/definition/form-courbe-remous"; import { FormulaireParallelStructure } from "../formulaire/definition/form-parallel-structures"; import { NgParameter } from "../formulaire/elements/ngparam"; import { FieldsetContainer } from "../formulaire/elements/fieldset-container"; import { FormulairePab } from "../formulaire/definition/form-pab"; import { FormulaireMacrorugoCompound } from "../formulaire/definition/form-macrorugo-compound"; import { FormulaireGrille } from "../formulaire/definition/form-grille"; import { FormulaireSolveur } from "../formulaire/definition/form-solveur"; import { AppComponent } from "../app.component"; import { FormulaireSPP } from "../formulaire/definition/form-spp"; import { FormulaireFixedVar } from "../formulaire/definition/form-fixedvar"; import { FormulaireSection } from "../formulaire/definition/form-section"; import { FormulairePAR } from "../formulaire/definition/form-par"; import { FormulaireVerificateur } from "../formulaire/definition/form-verificateur"; import { FormulaireEspece } from "../formulaire/definition/form-espece"; import { FormulairePrebarrage } from "../formulaire/definition/form-prebarrage"; import { ServiceFactory } from "./service-factory"; import { FormulairePressureLoss } from "app/formulaire/definition/form-pressureloss"; import { getNubResultUnit } from "jalhyd"; import { FormulaireMacroRugoRemous } from "app/formulaire/definition/form-macrorugo-remous"; @Injectable() export class FormulaireService extends Observable { /** list of known forms */ private _formulaires: FormulaireDefinition[]; private _currentFormId: string = null; public static getConfigPathPrefix(ct: CalculatorType): string { const ctName = CalculatorType[ct].toLowerCase(); return "app/calculators/" + ctName + "/"; } constructor( private i18nService: I18nService, private appSetupService: ApplicationSetupService, private httpService: HttpService, private intlService: I18nService, private notificationsService: NotificationsService ) { super(); this.clearFormulaires(); } public get formulaires(): FormulaireDefinition[] { return this._formulaires; } /** Removes all formulaires from the list */ public clearFormulaires() { this._formulaires = []; } /** * Retourne le titre complet du type de module de calcul, dans la langue en cours */ public getLocalisedTitleFromCalculatorType(type: CalculatorType) { const sCalculator: string = CalculatorType[type].toUpperCase(); return this.intlService.localizeText(`INFO_${sCalculator}_TITRE`); } /** * Retourne la description du type de module de calcul, dans la langue en cours */ public getLocalisedDescriptionFromCalculatorType(type: CalculatorType) { const sCalculator: string = CalculatorType[type].toUpperCase(); return this.intlService.localizeText(`INFO_${sCalculator}_DESCRIPTION`); } /** * Retourne le titre cour du type de module de calcul, dans la langue en cours * (pour les titres d'onglets par défaut) */ public getLocalisedShortTitleFromCalculatorType(type: any) { if (typeof type !== "string") { // retrocompatibility for old file format type = CalculatorType[type]; } const sCalculator: string = type.toUpperCase(); return this.intlService.localizeText(`INFO_${sCalculator}_TITRE_COURT`); } /** * Forces update of all form strings in given Formulaire, with current language */ public updateFormulaireLocalisation(f: FormulaireDefinition) { const requiredLang = this.intlService.currentLanguage; f.updateLocalisation(requiredLang); } /** * Tente de trouver une traduction pour textKey dans les fichiers de langues * spécifiques du module de calcul en cours, dans la langue en cours, puis * dans la langue par défaut; si aucune traduction n'est trouvée, demande au * service i18n de rechercher dans les fichiers de langues globaux * @param textKey la clé du texte à traduire */ public localizeText(textKey: string, ct: CalculatorType): string { const calcType = /* this.currentForm?.currentNub?.calcType || */ ct; if (calcType !== undefined) { // throw new Error("FormulaireService.localizeText(): cannot find CalculatorType for current form's Nub"); let langCache = this.i18nService.languageCache; if (langCache && langCache[calcType]) { langCache = langCache[calcType]; // …for target Nub type } // try current language if ( langCache && langCache[this.intlService.currentLanguage] && langCache[this.intlService.currentLanguage][textKey] !== undefined ) { return langCache[this.intlService.currentLanguage][textKey]; } } // fallback to global (not calculator type specific) translation system return this.i18nService.localizeText(textKey); } /** * Returns variable name from symbol * @param calcType * @param symbol */ public expandVariableName(calcType: CalculatorType, symbol: string): string { let s = ""; // language cache… let langCache = this.i18nService.languageCache; if (langCache && langCache[calcType]) { langCache = langCache[calcType]; // …for target Nub type } if (langCache && langCache[this.intlService.currentLanguage]) { langCache = langCache[this.intlService.currentLanguage]; // … for current language } if (langCache && langCache[symbol] !== undefined) { s = this.localizeText(symbol, calcType); } else { // is symbol of the form ouvrages[i]… ? const re = /([A-Z,a-z]+)\[(\d+)\]\.(.+)/; const match = re.exec(symbol); if (match) { // Les libellés correspondants sont INFO OUVRAGE et INFO_LIB_OUVRAGE_XXX s = this.intlService.localizeText(`INFO_${match[1].toUpperCase()}`) + " n°" + (+match[2] + 1) + ": " + this.expandVariableName(calcType, `${match[1].toUpperCase()}_${match[3].toUpperCase()}`); } else { s = this.intlService.localizeText("INFO_LIB_" + symbol.toLocaleUpperCase()); } } return s; } /** * Returns variable name and unit from symbol * @param calcType * @param symbol * @param forceUnit if given, will be used as unit */ public expandVariableNameAndUnit(calcType: CalculatorType, symbol: string, forceUnit?: string): string { let s = this.expandVariableName(calcType, symbol); let langCache = this.i18nService.languageCache; // language cache… if (langCache && langCache[calcType]) { langCache = langCache[calcType]; // …for target Nub type } if (langCache && langCache[this.intlService.currentLanguage]) { langCache = langCache[this.intlService.currentLanguage]; // … for current language } // remove device number before looking for unit (hacky) let symbolBase = symbol.toLocaleUpperCase(); const idx = symbolBase.indexOf("]."); if (idx !== -1) { symbolBase = symbolBase.substring(idx + 2); } let unit: string; // unit of a parameter is supposed to be read from JaLHyd ParadDefinition and passed // through "forceUnit"; keys like "UNIT_*" in the config file are for extra results if (forceUnit) { unit = forceUnit; } else { unit = getNubResultUnit(calcType, symbol); if (unit === undefined) { // last chance: if unit cannot be read in model, use translation files const unitKey = "UNIT_" + symbolBase; if (langCache && langCache[unitKey] !== undefined) { unit = this.localizeText(unitKey, calcType); } } } if (unit) { s = s + " (" + unit + ")"; } return s; } /** * From https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex */ private escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string } /** * Checks if the given calculator name (tab title) is already used by any existing * form; if so, adds a number after it */ private suffixNameIfNeeded(name: string) { let found = false; let maxNumber = 0; // extract base name let baseName = name; const re1 = new RegExp("^.+( \\d+)$"); const matches1 = re1.exec(name); if (matches1) { baseName = baseName.replace(matches1[1], ""); } // browse session calculators const re2 = new RegExp("^" + this.escapeRegExp(baseName) + "( (\\d+))?$"); for (const f of this.formulaires) { const matches2 = re2.exec(f.calculatorName); if (matches2) { found = true; if (matches2[2] !== undefined) { const nb = Number(matches2[2]); maxNumber = Math.max(maxNumber, nb); } } } // suffix if needed if (found) { name = baseName + " " + (maxNumber + 1); } return name; } public loadConfig(ct: CalculatorType): Promise<any> { const f: string = FormulaireService.getConfigPathPrefix(ct) + "config.json"; return this.httpService.httpGetRequestPromise(f); } private newFormulaire(ct: CalculatorType): FormulaireDefinition { let f: FormulaireDefinition; switch (ct) { case CalculatorType.SectionParametree: f = new FormulaireSectionParametree(); break; case CalculatorType.Bief: case CalculatorType.RegimeUniforme: f = new FormulaireSection(); break; case CalculatorType.CourbeRemous: f = new FormulaireCourbeRemous(); break; case CalculatorType.ParallelStructure: case CalculatorType.Dever: case CalculatorType.Cloisons: f = new FormulaireParallelStructure(); break; case CalculatorType.Pab: f = new FormulairePab(); break; case CalculatorType.MacroRugoCompound: f = new FormulaireMacrorugoCompound(); break; case CalculatorType.Grille: f = new FormulaireGrille(); break; case CalculatorType.Par: case CalculatorType.ParSimulation: f = new FormulairePAR(); break; case CalculatorType.Solveur: f = new FormulaireSolveur(); break; case CalculatorType.SPP: f = new FormulaireSPP(); break; case CalculatorType.Verificateur: f = new FormulaireVerificateur(); break; case CalculatorType.Espece: f = new FormulaireEspece(); break; case CalculatorType.PreBarrage: f = new FormulairePrebarrage(); break; case CalculatorType.PressureLoss: f = new FormulairePressureLoss(); break; case CalculatorType.MacroRugoRemous: f = new FormulaireMacroRugoRemous(); break; default: f = new FormulaireFixedVar(); } f.defaultProperties["calcType"] = ct; return f; } /** * crée un formulaire d'un type donné * @param ct type de formulaire * @param nub nub existant à associer au formulaire (chargement de session / duplication de module) * @param calculatorName nom du module, à afficher dans l'interface */ 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 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 if (nub) { f.currentNub = nub; } else { const confProps = f.parseConfigToProps(); confProps.setPropValue("calcType", ct); f.initNub(confProps); } // Restaure le nom du module, sinon affecte le nom par défaut let tempName: string; if (calculatorName) { tempName = calculatorName; } else { tempName = decode(this.getLocalisedShortTitleFromCalculatorType(ct)); } // Suffixe le nom du module si nécessaire f.calculatorName = this.suffixNameIfNeeded(tempName); f.parseConfig(); // add fieldsets for existing Structures if needed // (when loading session only) if (f.currentNub instanceof ParallelStructure) { for (const struct of f.currentNub.structures) { for (const e of f.allFormElements) { if (e instanceof FieldsetContainer) { // @TODO manage many containers one day ? e.addFromTemplate(undefined, struct); } } } } // add fieldsets for existing YAXN if needed // (when loading session only) if (f.currentNub instanceof SPP) { for (const c of f.currentNub.getChildren()) { for (const e of f.allFormElements) { if (e instanceof FieldsetContainer) { // @TODO manage many containers one day ? e.addFromTemplate(undefined, c); } } } } // when creating a new Pab, add one wall with one device, plus the downwall // (when loading session, those items are already present) if ( f instanceof FormulairePab && f.currentNub instanceof Pab && f.currentNub.children.length === 0 && f.currentNub.downWall === undefined ) { // 1. one wall const newWall = Session.getInstance().createNub( new Props({ calcType: CalculatorType.Cloisons }) ) as Cloisons; // add new default device for new wall const newDevice = Session.getInstance().createNub( new Props({ calcType: CalculatorType.Structure, loiDebit: newWall.getDefaultLoiDebit() }) ); newWall.addChild(newDevice); f.pabNub.addChild(newWall); // 2. downwall const newDownWall = Session.getInstance().createNub( new Props({ calcType: CalculatorType.CloisonAval }) ) as CloisonAval; // add new default device for new downwall const newDownwallDevice = Session.getInstance().createNub( new Props({ calcType: CalculatorType.Structure, loiDebit: newDownWall.getDefaultLoiDebit() }) ); newDownWall.addChild(newDownwallDevice); f.pabNub.downWall = newDownWall; } // when creating a new PreBarrage, add one basin and two walls with one // device each (when loading session, those items are already present) if ( f instanceof FormulairePrebarrage && f.currentNub instanceof PreBarrage && f.currentNub.children.length === 0 ) { const emptyFields: boolean = ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit; // 1 basin f.currentNub.addChild(new PbBassin(new PbBassinParams(13.80, 95, emptyFields))); // 1st wall f.currentNub.addChild(new PbCloison(undefined, f.currentNub.children[0] as PbBassin, undefined, emptyFields)); const s1: Structure = CreateStructure(LoiDebit.WeirCunge80, undefined, undefined, emptyFields); // s1.prms.ZDV.singleValue = 95.30; // s1.getParameter("L").singleValue = 0.4; // s1.getParameter("CdGR").singleValue = 1.04; f.currentNub.children[1].addChild(s1); // 2nd wall f.currentNub.addChild(new PbCloison(f.currentNub.children[0] as PbBassin, undefined, undefined, emptyFields)); const s2: Structure = CreateStructure(LoiDebit.WeirCunge80, undefined, undefined, emptyFields); // s2.prms.ZDV.singleValue = 95.30; // s2.getParameter("L").singleValue = 0.4; // s2.getParameter("CdGR").singleValue = 1.04; f.currentNub.children[2].addChild(s2); } this.notifyObservers({ "action": "createForm", "form": f }); return f; } /** * Trick to notify param-link components that parent form name changed * @TODO find a way to make param-link components directly observe FormDefinition */ public propagateFormNameChange(f: FormulaireDefinition, name: string) { this.notifyObservers({ "action": "formNameChanged", "form": f, "value": name }); } public getFormulaireFromId(uid: string): FormulaireDefinition { for (const f of this._formulaires) { if (f.uid === uid) { return f; } } } public getInputField(formId: string, elemId: string): InputField { const s = this.getFormulaireElementById(formId, elemId); if (!(s instanceof InputField)) { throw new Error("Form element with id '" + elemId + "' is not an input"); } return <InputField>s; } public getSelectField(formId: string, elemId: string): SelectField { const s = this.getFormulaireElementById(formId, elemId); if (!(s instanceof SelectField)) { throw new Error("Form element with id '" + elemId + "' is not a select"); } return <SelectField>s; } private getFormulaireElementById(formId: string, elemId: string): FormulaireElement { for (const f of this._formulaires) { if (f.uid === formId) { const s = f.getFormulaireNodeById(elemId); if (s !== undefined) { return s as FormulaireElement; } } } } public getParamdefParentForm(prm: ParamDefinition): FormulaireDefinition { for (const f of this._formulaires) { for (const p of f.allFormElements) { if (p instanceof NgParameter) { if (p.paramDefinition.uid === prm.uid) { return f; } } } } } /** * retrouve un formulaire à partir d'un uid de Nub */ public getFormulaireFromNubId(uid: string) { for (const f of this._formulaires) { if (f.hasNubId(uid)) { return f; } } } /** * Supprime le formulaire ciblé, et demande à JaLHyd d'effacer son Nub de la Session * @param uid formulaire à supprimer */ public requestCloseForm(uid: string) { const form = this.getFormulaireFromId(uid); const nub = form.currentNub; if (form) { this._formulaires = this._formulaires.filter(f => f.uid !== uid); this.notifyObservers({ "action": "closeForm", "form": form }); // reset the result panels of all forms depending on this one // @TODO UI should detect model change instead of doing this manually this.resetAllDependingFormsResults(form, [], undefined, true, false); } if (nub) { // reset model results (important, also resets dependent Nubs results in chain) form.currentNub.resetResult(); Session.getInstance().deleteNub(nub); } } public get currentFormId() { return this._currentFormId; } public setCurrentForm(formId: string) { const form = this.getFormulaireFromId(formId); if (form === undefined) { this._currentFormId = null; this.notifyObservers({ "action": "invalidFormId", "formId": formId }); } else { this._currentFormId = formId; this.notifyObservers({ "action": "currentFormChanged", "formId": formId }); } } public get currentForm(): FormulaireDefinition { return this.getFormulaireFromId(this._currentFormId); } public get currentFormHasResults(): boolean { const form = this.currentForm; if (form !== undefined) { return form.hasResults; } return false; } private readSingleFile(file: File): Promise<any> { return new Promise<any>((resolve, reject) => { const fr = new FileReader(); fr.onload = () => { resolve(fr.result); }; fr.onerror = () => { fr.abort(); reject(new Error(`Erreur de lecture du fichier ${file.name}`)); }; fr.readAsText(file); }); } /** * charge une session en tenant compte des modules de calcul sélectionnés * @param f fichier session * @param formInfos infos sur les modules de calcul @see DialogLoadSessionComponent.calculators */ public async loadSession(i: Blob | string, formInfos: any[] = []): Promise<{ hasErrors: boolean, loaded: string[] }> { try { // disable "empty fields" flag temporarly const emptyFields = ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit; ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit = false; let s; if (i instanceof Blob) { s = await this.readSingleFile(i as File); } else { s = i; } const uids: string[] = []; formInfos.forEach((fi) => { if (fi.selected) { uids.push(fi.uid); } }); const res = Session.getInstance().unserialise(s, uids); const newNubs = res.nubs; // for each new Nub, create the related form, set its title for (let i = 0; i < newNubs.length; i++) { const nn = newNubs[i]; let title; if (nn.meta && nn.meta.title) { title = nn.meta.title; } await this.createFormulaire(nn.nub.calcType, nn.nub, title); // await guarantees loading order } // restore "empty fields" flag ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit = emptyFields; // apply settings if (res.settings) { // model based settings if (res.settings.precision !== undefined) { this.appSetupService.computePrecision = res.settings.precision; } if (res.settings.maxIterations !== undefined) { this.appSetupService.maxIterations = res.settings.maxIterations; } // GUI settings if (res.settings.displayPrecision !== undefined) { this.appSetupService.displayPrecision = res.settings.displayPrecision; } } // forward errors return { hasErrors: res.hasErrors, loaded: newNubs.map(n => n.nub.uid) }; } catch (err) { // forward errors to caller to avoid "Uncaught (in promise)" throw err; } } /** * Sends an UTF-8 text file for download */ public downloadTextFile(session: string, filename: string = "file_1") { const mt = "text/plain;charset=utf-8"; const blob = new Blob([session], { type: "text/plain;charset=utf-8" }); AppComponent.download(blob, filename, "text/plain"); } /** * obtient des infos (nom, uid des modules de calcul, dépendances) d'un fichier session * @param f fichier session */ public async calculatorInfosFromSessionFile(f: File): Promise<{ nubs: any[], formatVersion: string }> { const s = await this.readSingleFile(f); // return value const res: { nubs: any[]; formatVersion: string; } = { nubs: [], formatVersion: "" }; const data = JSON.parse(s); // liste des noms de modules de calcul if (data.session && Array.isArray(data.session)) { data.session.forEach((e: any) => { const nubInfo = { uid: e.uid, title: e.meta && e.meta.title ? e.meta.title : undefined, requires: [], children: [], type: e.props.calcType }; // list linked params dependencies for each Nub if (e.parameters) { e.parameters.forEach((p) => { if (p.targetNub && !nubInfo.requires.includes(p.targetNub)) { nubInfo.requires.push(p.targetNub); } }); } // list children nubs for each Nub if (e.children) { e.children.forEach((p) => { nubInfo.children.push(p.uid); }); } res.nubs.push(nubInfo); }); } // version du format de fichier if (data.header && data.header.format_version) { res.formatVersion = data.header.format_version; } return res; } public saveForm(f: FormulaireDefinition) { this.notifyObservers({ "action": "saveForm", "form": f }); } /** * Demande à la Session JalHYd la liste des paramètres/résultats pouvant être liés au * paramètre fourni */ public getLinkableValues(p: NgParameter): LinkedValue[] { let linkableValues: LinkedValue[] = []; if (p) { linkableValues = Session.getInstance().getLinkableValues(p.paramDefinition); // join form names to ease usage for (let i = 0; i < linkableValues.length; i++) { const lv = linkableValues[i]; for (const f of this._formulaires) { if (f.currentNub) { if (f.currentNub.uid === lv.nub.uid) { lv.meta["formTitle"] = f.calculatorName; } else { // child structures ? for (const s of f.currentNub.getChildren()) { if (s.uid === lv.nub.uid) { lv.meta["formTitle"] = f.calculatorName; } } } } } } } linkableValues = this.filterLinkableValues(linkableValues); return linkableValues; } /** * filtre les valeurs liables à un paramètre : * - supprime les valeurs non affichées dans leur parent (ce n'est pas le cas par ex * pour Q, Z1, Z2 dans les ouvrages enfants des ouvrages //) * @param values valeurs liables (modifié par la méthode) */ public filterLinkableValues(values: any[]): any[] { for (let i = values.length - 1; i >= 0; i--) { const v = values[i].value; if (v instanceof ParamDefinition) { // pour chaque paramètre... const prm: ParamDefinition = v; const parentForm: FormulaireDefinition = this.getParamdefParentForm(prm); // ... on cherche s'il est affiché dans son parent let found = false; if (parentForm !== undefined) { for (const fe of parentForm.allFormElements) { if (fe instanceof NgParameter) { if (fe.paramDefinition.uid === prm.uid) { found = true; break; } } } } if (!found) { values.splice(i, 1); } } } return values; } /** * Resets the results of all forms depending on the given form "f" * @param f * @param symbol symbol of the parameter whose value change triggered this method * @param forceResetAllDependencies if true, even non-calculated non-modified parameters * links will be considered as dependencies @see jalhyd#98 */ public resetAllDependingFormsResults( f: FormulaireDefinition, visited: string[] = [], symbol?: string, forceResetAllDependencies: boolean = false, notify: boolean = true ) { const dependingNubs = Session.getInstance().getDependingNubs(f.currentNub.uid, symbol, forceResetAllDependencies, true); for (const dn of dependingNubs) { if (!visited.includes(dn.uid)) { const form = this.getFormulaireFromNubId(dn.uid); if (form) { const hadResults = form.hasResults; // form might not have a result, but still have another form depending on it ! // console.debug(`FormulaireService.resetAllDependingFormsResults(form->${dn.uid})`); form.resetResults(visited); if (hadResults) { if (notify) { this.notificationsService.notify( this.intlService.localizeText("INFO_SNACKBAR_RESULTS_INVALIDATED") + " " + form.calculatorName, 1500 ); } } } } } } /** * Génère un formulaire SectionParametree à partir du module courant * s'il est du type régime uniforme ou courbe de remous */ public async generateParametricSectionForm(Y?: number): Promise<FormulaireDefinition> { if (this.currentForm.currentNub instanceof SectionNub) { const sn: SectionNub = this.currentForm.currentNub; if (sn instanceof RegimeUniforme || sn instanceof CourbeRemous) { // copy section const serialisedSection = sn.section.serialise(); const sectionCopy: acSection = Session.getInstance().unserialiseSingleNub(serialisedSection, false).nub as acSection; if (Y !== undefined) { sectionCopy.prms.Y.singleValue = Y; } const secParam = new SectionParametree(sectionCopy); if (sn instanceof RegimeUniforme) { // copy value of calculated param const cp: ParamDefinition = sn.calculatedParam; const scp: ParamDefinition = secParam.section.getParameter(cp.symbol); if (cp.hasMultipleValues) { scp.setValues(sn.result.getCalculatedValues().map(v => round(v, this.appSetupService.displayPrecision))); } else { scp.singleValue = sn.result.vCalc; } } Session.getInstance().registerNub(secParam); return await this.createFormulaire(CalculatorType.SectionParametree, secParam); } } return Promise.reject("cannot create parametric section from current form"); } }