Skip to content
Snippets Groups Projects
formulaire.service.ts 34.9 KiB
Newer Older
/* eslint-disable @typescript-eslint/member-ordering */
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
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";
mathias.chouet's avatar
mathias.chouet committed
import { FormulaireEspece } from "../formulaire/definition/form-espece";
import { FormulairePrebarrage } from "../formulaire/definition/form-prebarrage";
import { FormulairePressureLoss } from "app/formulaire/definition/form-pressureloss";
import { getNubResultUnit } from "jalhyd";
import { FormulaireMacrorugoRemous } from "app/formulaire/definition/form-macrorugo-remous";
// import { FormulaireMacrorugo } from "app/formulaire/definition/form-macrorugo";

@Injectable()
export class FormulaireService extends Observable {
    /** list of known forms */
    private _formulaires: FormulaireDefinition[];

    private _currentFormId: string = null;
    private _selectedLoadedNubs: any[] = [];

    public static getConfigPathPrefix(ct: CalculatorType): string {
        const ctName = CalculatorType[ct].toLowerCase();
        return "app/calculators/" + ctName + "/";
    }
mathias.chouet's avatar
mathias.chouet committed

    constructor(
        private i18nService: I18nService,
        private appSetupService: ApplicationSetupService,
        private httpService: HttpService,
        private intlService: I18nService,
        private notificationsService: NotificationsService
        super();
    public get formulaires(): FormulaireDefinition[] {
        return this._formulaires;
    }

    public getTitlebyIdOnSelectedLoadedNubs(uid: string) {
        return this._selectedLoadedNubs.find(selectedNub => selectedNub.uid === uid)
    }

    /** Removes all formulaires from the list */
    public clearFormulaires() {
        this._formulaires = [];
    }

mathias.chouet's avatar
mathias.chouet committed
    /**
     * 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();
mathias.chouet's avatar
mathias.chouet committed
        return this.intlService.localizeText(`INFO_${sCalculator}_DESCRIPTION`);
mathias.chouet's avatar
mathias.chouet committed
    /**
     * 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`);
mathias.chouet's avatar
mathias.chouet committed
    }

    /**
     * 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);
            // 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()}`)
mathias.chouet's avatar
mathias.chouet committed
                    + "" + (+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 + ")";
    /**
     * 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
    }

mathias.chouet's avatar
mathias.chouet committed
    /**
     * 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+))?$");
mathias.chouet's avatar
mathias.chouet committed
        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:
            case CalculatorType.Bief:
                f = new FormulaireSection();
            case CalculatorType.Dever:
            case CalculatorType.Cloisons:
                f = new FormulaireParallelStructure();
            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;

mathias.chouet's avatar
mathias.chouet committed
            case CalculatorType.Solveur:
                f = new FormulaireSolveur();
                break;

            case CalculatorType.SPP:
                f = new FormulaireSPP();
                break;

            case CalculatorType.Verificateur:
                f = new FormulaireVerificateur();
                break;

mathias.chouet's avatar
mathias.chouet committed
            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();
                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);
mathias.chouet's avatar
mathias.chouet committed

        // 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);
mathias.chouet's avatar
mathias.chouet committed

        // 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;
            f.currentNub.addChild(new PbBassin(new PbBassinParams(13.80, 95, emptyFields)));
            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",
    /**
     * 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 {
mathias.chouet's avatar
mathias.chouet committed
        for (const f of this._formulaires) {
            if (f.uid === uid) {
                return f;
mathias.chouet's avatar
mathias.chouet committed
            }
        }
    public getInputField(formId: string, elemId: string): InputField {
        const s = this.getFormulaireElementById(formId, elemId);
mathias.chouet's avatar
mathias.chouet committed
        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);
mathias.chouet's avatar
mathias.chouet committed
        if (!(s instanceof SelectField)) {
            throw new Error("Form element with id '" + elemId + "' is not a select");
mathias.chouet's avatar
mathias.chouet committed
        }
        return <SelectField>s;
    private getFormulaireElementById(formId: string, elemId: string): FormulaireElement {
mathias.chouet's avatar
mathias.chouet committed
        for (const f of this._formulaires) {
David Dorchies's avatar
David Dorchies committed
            if (f.uid === formId) {
                const s = f.getFormulaireNodeById(elemId);
mathias.chouet's avatar
mathias.chouet committed
                if (s !== undefined) {
mathias.chouet's avatar
mathias.chouet committed
                }
mathias.chouet's avatar
mathias.chouet committed
        }
    public getParamdefParentForm(prm: ParamDefinition): FormulaireDefinition {
mathias.chouet's avatar
mathias.chouet committed
        for (const f of this._formulaires) {
            for (const p of f.allFormElements) {
                if (p instanceof NgParameter) {
                    if (p.paramDefinition.uid === prm.uid) {
mathias.chouet's avatar
mathias.chouet committed
    /**
     * 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",

            // 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);
            // 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);
David Dorchies's avatar
David Dorchies committed
        if (form === undefined) {
            this._currentFormId = null;
            this.notifyObservers({
                "action": "invalidFormId",
                "formId": formId
            });
David Dorchies's avatar
David Dorchies committed
        } 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;
mathias.chouet's avatar
mathias.chouet committed
        if (form !== undefined) {
mathias.chouet's avatar
mathias.chouet committed
        }
    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);
        });
    }

mathias.chouet's avatar
mathias.chouet committed
     * charge une session en tenant compte des modules de calcul sélectionnés
     * @param formInfos infos sur les modules de calcul @see DialogLoadSessionComponent.calculators
    public async loadSession(i: Blob | string, formInfos: any[] = []): Promise<{ hasErrors: boolean, loaded: string[] }> {
mathias.chouet's avatar
mathias.chouet committed
        try {
            // disable "empty fields" flag temporarly
            const emptyFields = ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit;
            ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit = false;
            this._selectedLoadedNubs = formInfos;
            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
mathias.chouet's avatar
mathias.chouet committed
            for (let i = 0; i < newNubs.length; i++) {
                const nn = newNubs[i];
                let title;
                if (nn.meta && nn.meta.title) {
                    title = nn.meta.title;
                }
mathias.chouet's avatar
mathias.chouet committed
                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;
                }
            }
                hasErrors: res.hasErrors,
                loaded: newNubs.map(n => n.nub.uid)
mathias.chouet's avatar
mathias.chouet committed

        } catch (err) {
            // forward errors to caller to avoid "Uncaught (in promise)"
mathias.chouet's avatar
mathias.chouet committed
        }
    getSelectedLoadedNubs() {
        return this._selectedLoadedNubs;
    }

    /**
     * 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
    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
     * @param p the parameter on which we look for available links
     * @param update force calculation instead of using cached linkable values
    public getLinkableValues(p: NgParameter, update = false): LinkedValue[] {
        let linkableValues: LinkedValue[] = [];
        if (p) {
            if (!(this.currentForm.currentNub instanceof Pab)) {
                /**
                 * link caching is only for PAB otherwise link buttons of parallel
                 * structures are not created/deleted when new structure is
                 * created/deleted.
                 */
                update = true;
            }
            if (update || p.linkableValues === undefined) {
                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);
            } else {
                linkableValues = p.linkableValues;
mathias.chouet's avatar
mathias.chouet committed
        }
        p.linkableValues = linkableValues;
        return linkableValues;

    /**
     * filtre les valeurs liables à un paramètre :
mathias.chouet's avatar
mathias.chouet committed
     * - 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;
mathias.chouet's avatar
mathias.chouet committed
                if (parentForm !== undefined) {
                    for (const fe of parentForm.allFormElements) {
                        if (fe instanceof NgParameter) {
                            if (fe.paramDefinition.uid === prm.uid) {
                                found = true;
                                break;
                            }
mathias.chouet's avatar
mathias.chouet committed
                        }
                    }
                }
                if (!found) {
mathias.chouet's avatar
mathias.chouet committed
                }
mathias.chouet's avatar
mathias.chouet committed

    /**
     * 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
mathias.chouet's avatar
mathias.chouet committed
     */
    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);
mathias.chouet's avatar
mathias.chouet committed
        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,
mathias.chouet's avatar
mathias.chouet committed
        }
    }
     * 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) {
                // change _If visibility to copy section
                sn.section.prms.If.visible = true;
                // copy section
                const serialisedSection = sn.section.serialise();
                // Reinitialize _If visibility
                sn.section.prms.If.visible = false;
                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");
    }