From 566b9c54f85b1fa74cbe7e77ad0336841498d647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr> Date: Mon, 17 Oct 2022 13:12:19 +0200 Subject: [PATCH] refactor: merge SelectField and SelectFieldCustom classes, split select code into specific classes refs #483 --- src/app/calculators/bief/config.json | 2 +- src/app/calculators/cloisons/config.json | 6 +- src/app/calculators/courberemous/config.json | 2 - src/app/calculators/dever/config.json | 6 +- .../calculators/parallelstructure/config.json | 6 +- src/app/calculators/prebarrage/config.json | 12 +- src/app/calculators/solveur/config.json | 11 +- src/app/calculators/verificateur/config.json | 10 +- .../field-set/field-set.component.ts | 2 +- .../generic-select.component.html | 2 +- .../select-field-line.component.ts | 9 +- .../formulaire/definition/form-definition.ts | 4 +- .../formulaire/definition/form-fixedvar.ts | 7 +- .../definition/form-parallel-structures.ts | 2 +- .../formulaire/definition/form-pb-cloison.ts | 4 +- src/app/formulaire/definition/form-solveur.ts | 7 +- .../definition/form-verificateur.ts | 5 +- src/app/formulaire/elements/fieldset.ts | 27 +- .../elements/select-field-custom.ts | 343 ------------------ src/app/formulaire/elements/select-field.ts | 244 ------------- .../elements/{ => select}/select-entry.ts | 0 .../select/select-field-device-loi-debit.ts | 42 +++ .../select-field-device-structure-type.ts | 32 ++ .../select/select-field-downstream-basin.ts | 53 +++ .../elements/select/select-field-factory.ts | 66 ++++ .../elements/select/select-field-nub-prop.ts | 38 ++ .../select/select-field-remous-target.ts | 34 ++ .../select/select-field-searched-param.ts | 63 ++++ .../select-field-solver-targeted-result.ts | 64 ++++ .../select/select-field-solveur-target.ts | 62 ++++ .../select/select-field-species-list.ts | 73 ++++ .../select/select-field-target-pass.ts | 62 ++++ .../select/select-field-upstream-basin.ts | 53 +++ .../elements/select/select-field.ts | 268 ++++++++++++++ src/app/services/formulaire.service.ts | 2 +- 35 files changed, 947 insertions(+), 676 deletions(-) delete mode 100644 src/app/formulaire/elements/select-field-custom.ts delete mode 100644 src/app/formulaire/elements/select-field.ts rename src/app/formulaire/elements/{ => select}/select-entry.ts (100%) create mode 100644 src/app/formulaire/elements/select/select-field-device-loi-debit.ts create mode 100644 src/app/formulaire/elements/select/select-field-device-structure-type.ts create mode 100644 src/app/formulaire/elements/select/select-field-downstream-basin.ts create mode 100644 src/app/formulaire/elements/select/select-field-factory.ts create mode 100644 src/app/formulaire/elements/select/select-field-nub-prop.ts create mode 100644 src/app/formulaire/elements/select/select-field-remous-target.ts create mode 100644 src/app/formulaire/elements/select/select-field-searched-param.ts create mode 100644 src/app/formulaire/elements/select/select-field-solver-targeted-result.ts create mode 100644 src/app/formulaire/elements/select/select-field-solveur-target.ts create mode 100644 src/app/formulaire/elements/select/select-field-species-list.ts create mode 100644 src/app/formulaire/elements/select/select-field-target-pass.ts create mode 100644 src/app/formulaire/elements/select/select-field-upstream-basin.ts create mode 100644 src/app/formulaire/elements/select/select-field.ts diff --git a/src/app/calculators/bief/config.json b/src/app/calculators/bief/config.json index 95f2ca549..7434f2abc 100644 --- a/src/app/calculators/bief/config.json +++ b/src/app/calculators/bief/config.json @@ -72,4 +72,4 @@ "selectIds": [ "select_section", "select_regime" ], "help": "hsl/cote_amont_aval.html" } -] \ No newline at end of file +] diff --git a/src/app/calculators/cloisons/config.json b/src/app/calculators/cloisons/config.json index 82ccf331e..2423ef4bc 100644 --- a/src/app/calculators/cloisons/config.json +++ b/src/app/calculators/cloisons/config.json @@ -21,15 +21,11 @@ "fields": [ { "id": "select_structure", - "type": "select", - "property": "structureType", - "source": "device_structure_type" + "type": "select" }, { "id": "select_loidebit", "type": "select", - "property": "loiDebit", - "source": "device_loi_debit", "help": { "OrificeSubmerged": "structures/orifice_noye.html", "WeirSubmergedLarinier": "structures/fente_noyee.html", diff --git a/src/app/calculators/courberemous/config.json b/src/app/calculators/courberemous/config.json index 272de8687..b66d709bb 100644 --- a/src/app/calculators/courberemous/config.json +++ b/src/app/calculators/courberemous/config.json @@ -72,8 +72,6 @@ { "id": "select_target", "type": "select", - "property": "varCalc", - "source": "remous_target", "help": { "B": "hsl/section_parametree.html#largeur-au-miroir-surface-et-perimetre-mouille", "P": "hsl/section_parametree.html#largeur-au-miroir-surface-et-perimetre-mouille", diff --git a/src/app/calculators/dever/config.json b/src/app/calculators/dever/config.json index ce6bea142..6b502f27a 100644 --- a/src/app/calculators/dever/config.json +++ b/src/app/calculators/dever/config.json @@ -19,15 +19,11 @@ "fields": [ { "id": "select_structure", - "type": "select", - "property": "structureType", - "source": "device_structure_type" + "type": "select" }, { "id": "select_loidebit", "type": "select", - "property": "loiDebit", - "source": "device_loi_debit", "help": { "WeirFree": "structures/seuil_denoye.html", "TriangularWeirFree": "structures/dever_triang.html", diff --git a/src/app/calculators/parallelstructure/config.json b/src/app/calculators/parallelstructure/config.json index 26c3cf9a4..d265d9a66 100644 --- a/src/app/calculators/parallelstructure/config.json +++ b/src/app/calculators/parallelstructure/config.json @@ -18,15 +18,11 @@ "fields": [ { "id": "select_structure", - "type": "select", - "property": "structureType", - "source": "device_structure_type" + "type": "select" }, { "id": "select_loidebit", "type": "select", - "property": "loiDebit", - "source": "device_loi_debit", "help": { "KIVI": "structures/kivi.html", "WeirVillemonte": "structures/kivi.html", diff --git a/src/app/calculators/prebarrage/config.json b/src/app/calculators/prebarrage/config.json index 7dada6df9..1b0431549 100644 --- a/src/app/calculators/prebarrage/config.json +++ b/src/app/calculators/prebarrage/config.json @@ -61,15 +61,11 @@ "fields": [ { "id": "select_structure", - "type": "select", - "property": "structureType", - "source": "device_structure_type" + "type": "select" }, { "id": "select_loidebit", "type": "select", - "property": "loiDebit", - "source": "device_loi_debit", "help": { "KIVI": "structures/kivi.html", "WeirVillemonte": "structures/kivi.html", @@ -171,13 +167,11 @@ "fields": [ { "id": "select_upstream_basin", - "type": "select_custom", - "source": "upstream_basin" + "type": "select" }, { "id": "select_downstream_basin", - "type": "select_custom", - "source": "downstream_basin" + "type": "select" } ] }, diff --git a/src/app/calculators/solveur/config.json b/src/app/calculators/solveur/config.json index 71a23177b..99d215264 100644 --- a/src/app/calculators/solveur/config.json +++ b/src/app/calculators/solveur/config.json @@ -5,15 +5,11 @@ "fields": [ { "id": "select_target_nub", - "type": "select_custom", - "source": "solveur_target" + "type": "select" }, { "id": "select_target_result", - "type": "select", - "property": "targettedResult", - "source": "solveur_targetted_result", - "default": "" + "type": "select" }, "Ytarget" ] @@ -24,8 +20,7 @@ "fields": [ { "id": "select_searched_param", - "type": "select_custom", - "source": "solveur_searched" + "type": "select" }, "Xinit" ] diff --git a/src/app/calculators/verificateur/config.json b/src/app/calculators/verificateur/config.json index 438d2bf32..07f6f672c 100644 --- a/src/app/calculators/verificateur/config.json +++ b/src/app/calculators/verificateur/config.json @@ -5,9 +5,7 @@ "fields": [ { "id": "select_target_pass", - "type": "select_custom", - "source": "verificateur_target", - "messageWhenEmpty": "INFO_VERIF_CREATE_PASS_FRIST" + "type": "select" } ] }, @@ -16,10 +14,8 @@ "type": "fieldset", "fields": [ { - "type": "select_custom", - "id": "select_species_list", - "source": "verificateur_species", - "multiple": true + "type": "select", + "id": "select_species_list" } ] }, diff --git a/src/app/components/field-set/field-set.component.ts b/src/app/components/field-set/field-set.component.ts index 293fce6e2..448d40a7d 100644 --- a/src/app/components/field-set/field-set.component.ts +++ b/src/app/components/field-set/field-set.component.ts @@ -5,7 +5,7 @@ import { FieldSet } from "../../formulaire/elements/fieldset"; import { ParamFieldLineComponent } from "../param-field-line/param-field-line.component"; import { Field } from "../../formulaire/elements/field"; import { InputField } from "../../formulaire/elements/input-field"; -import { SelectField } from "../../formulaire/elements/select-field"; +import { SelectField } from "../../formulaire/elements/select/select-field"; import { FormulairePab } from "../../formulaire/definition/form-pab"; import { SelectFieldLineComponent } from "../select-field-line/select-field-line.component"; import { FieldsetContainer } from "../../formulaire/elements/fieldset-container"; diff --git a/src/app/components/generic-select/generic-select.component.html b/src/app/components/generic-select/generic-select.component.html index 9c6880e13..7ce918d76 100644 --- a/src/app/components/generic-select/generic-select.component.html +++ b/src/app/components/generic-select/generic-select.component.html @@ -23,4 +23,4 @@ </mat-form-field> <div *ngIf="messageWhenEmpty" class="message-when-empty"> {{ messageWhenEmpty }} -</div> \ No newline at end of file +</div> diff --git a/src/app/components/select-field-line/select-field-line.component.ts b/src/app/components/select-field-line/select-field-line.component.ts index eccc37a99..2b8cb3a03 100644 --- a/src/app/components/select-field-line/select-field-line.component.ts +++ b/src/app/components/select-field-line/select-field-line.component.ts @@ -1,10 +1,9 @@ import { Component, Input, OnInit } from "@angular/core"; -import { SelectField } from "../../formulaire/elements/select-field"; -import { SelectEntry } from "../../formulaire/elements/select-entry"; +import { SelectField } from "../../formulaire/elements/select/select-field"; +import { SelectEntry } from "../../formulaire/elements/select/select-entry"; import { I18nService } from "../../services/internationalisation.service"; import { ApplicationSetupService } from "../../services/app-setup.service"; -import { SelectFieldCustom } from "../../formulaire/elements/select-field-custom"; import { decodeHtml } from "../../util"; @Component({ @@ -124,8 +123,6 @@ export class SelectFieldLineComponent implements OnInit { // called every time we navigate to the module ngOnInit(): void { - if (this._select instanceof SelectFieldCustom) { - this._select.updateEntries(); - } + this._select.updateEntries(); } } diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts index 814db2e70..cc5837883 100644 --- a/src/app/formulaire/definition/form-definition.ts +++ b/src/app/formulaire/definition/form-definition.ts @@ -24,8 +24,8 @@ import { TopFormulaireElementIterator } from "../form-iterator/top-element-itera import { CalculatorResults } from "../../results/calculator-results"; import { ServiceFactory } from "../../services/service-factory"; import { PabTable } from "../elements/pab-table"; -import { SelectEntry } from "../elements/select-entry"; -import { SelectField } from "../elements/select-field"; +import { SelectEntry } from "../elements/select/select-entry"; +import { SelectField } from "../elements/select/select-field"; /** * classe de base pour tous les formulaires diff --git a/src/app/formulaire/definition/form-fixedvar.ts b/src/app/formulaire/definition/form-fixedvar.ts index 575f7cb10..9289094a7 100644 --- a/src/app/formulaire/definition/form-fixedvar.ts +++ b/src/app/formulaire/definition/form-fixedvar.ts @@ -2,12 +2,11 @@ import { FormulaireDefinition } from "./form-definition"; import { FixedResults } from "../../results/fixed-results"; import { VarResults } from "../../results/var-results"; import { ChartType } from "../../results/chart-type"; -import { CalculatorResults } from "../../results/calculator-results"; -import { ParamRadioConfig, NgParameter } from "../elements/ngparam"; +import { NgParameter } from "../elements/ngparam"; import { FieldSet } from "../elements/fieldset"; import { FormulaireNode } from "../elements/formulaire-node"; -import { SelectFieldCustom } from "../elements/select-field-custom"; +import { SelectField } from "../elements/select/select-field"; import { Nub, IObservable, VariatedDetails } from "jalhyd"; export class FormulaireFixedVar extends FormulaireDefinition { @@ -95,7 +94,7 @@ export class FormulaireFixedVar extends FormulaireDefinition { sel.addObserver(this); if (firstNotif) { // force 1st observation - (sel as SelectFieldCustom).notifyValueChanged(); + (sel as SelectField).notifyValueChanged(); } } } diff --git a/src/app/formulaire/definition/form-parallel-structures.ts b/src/app/formulaire/definition/form-parallel-structures.ts index 9e4dd86d6..d25b4df33 100644 --- a/src/app/formulaire/definition/form-parallel-structures.ts +++ b/src/app/formulaire/definition/form-parallel-structures.ts @@ -2,7 +2,7 @@ import { Structure, Nub, ParallelStructure, StructureProperties, Props, Session, import { FieldsetContainer } from "../elements/fieldset-container"; import { FieldSet } from "../elements/fieldset"; -import { SelectField } from "../elements/select-field"; +import { SelectField } from "../elements/select/select-field"; import { NgParameter } from "../elements/ngparam"; import { FieldsetTemplate } from "../elements/fieldset-template"; import { FormulaireNode } from "../elements/formulaire-node"; diff --git a/src/app/formulaire/definition/form-pb-cloison.ts b/src/app/formulaire/definition/form-pb-cloison.ts index 02934c401..25910c87d 100644 --- a/src/app/formulaire/definition/form-pb-cloison.ts +++ b/src/app/formulaire/definition/form-pb-cloison.ts @@ -5,7 +5,7 @@ import { FieldSet } from "../elements/fieldset"; import { FormulaireNode } from "../elements/formulaire-node"; import { FieldsetContainer } from "../elements/fieldset-container"; import { FormulairePrebarrage } from "./form-prebarrage"; -import { SelectFieldCustom } from "../elements/select-field-custom"; +import { SelectField } from "../elements/select/select-field"; import { ServiceFactory } from "app/services/service-factory"; export class FormulairePbCloison extends FormulaireParallelStructure { @@ -41,7 +41,7 @@ export class FormulairePbCloison extends FormulaireParallelStructure { } } - if (sender instanceof SelectFieldCustom) { + if (sender instanceof SelectField) { const nub = this._currentNub as PbCloison; const pb = nub.parent; const emptyFields: boolean = ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit; diff --git a/src/app/formulaire/definition/form-solveur.ts b/src/app/formulaire/definition/form-solveur.ts index 481ea5f6d..f658c98ee 100644 --- a/src/app/formulaire/definition/form-solveur.ts +++ b/src/app/formulaire/definition/form-solveur.ts @@ -1,9 +1,8 @@ import { IObservable, ParamDefinition, Nub } from "jalhyd"; -import { SelectFieldCustom } from "../elements/select-field-custom"; import { NgParameter } from "../elements/ngparam"; import { FormulaireFixedVar } from "./form-fixedvar"; -import { SelectField } from "../elements/select-field"; +import { SelectField } from "../elements/select/select-field"; import { FieldSet } from "../elements/fieldset"; /** @@ -52,7 +51,7 @@ export class FormulaireSolveur extends FormulaireFixedVar { this.reset(); } - if (sender instanceof SelectFieldCustom) { + if (sender instanceof SelectField) { if (sender.id === "select_target_nub" && data.action === "select") { // update Solveur property: Nub to calculate try { @@ -95,7 +94,7 @@ export class FormulaireSolveur extends FormulaireFixedVar { * Re-populate searched parameter selector with fresh entries */ private refreshParameterEntries() { - const pSel = this.getFormulaireNodeById("select_searched_param") as SelectFieldCustom; + const pSel = this.getFormulaireNodeById("select_searched_param") as SelectField; if (pSel) { pSel.updateEntries(); // reflect changes in GUI diff --git a/src/app/formulaire/definition/form-verificateur.ts b/src/app/formulaire/definition/form-verificateur.ts index 3bcd463a6..893754665 100644 --- a/src/app/formulaire/definition/form-verificateur.ts +++ b/src/app/formulaire/definition/form-verificateur.ts @@ -1,9 +1,8 @@ import { IObservable, Nub, Verificateur, Result, VariatedDetails } from "jalhyd"; -import { SelectFieldCustom } from "../elements/select-field-custom"; +import { SelectField } from "../elements/select/select-field"; import { FormulaireFixedVar } from "./form-fixedvar"; import { VerificateurResults } from "../../results/verificateur-results"; -import { CalculatorResults } from "../../results/calculator-results"; import { ServiceFactory } from "../../services/service-factory"; /** @@ -94,7 +93,7 @@ export class FormulaireVerificateur extends FormulaireFixedVar { this.reset(); } - if (sender instanceof SelectFieldCustom) { + if (sender instanceof SelectField) { this.reset(); // reset results if (sender.id === "select_target_pass" && data.action === "select") { // update Verificateur property: Pass to check diff --git a/src/app/formulaire/elements/fieldset.ts b/src/app/formulaire/elements/fieldset.ts index 65200665a..4a67910e4 100644 --- a/src/app/formulaire/elements/fieldset.ts +++ b/src/app/formulaire/elements/fieldset.ts @@ -9,12 +9,12 @@ import { import { FormulaireElement } from "./formulaire-element"; import { Field } from "./field"; -import { SelectField } from "./select-field"; +import { SelectField } from "./select/select-field"; import { NgParameter, ParamRadioConfig } from "./ngparam"; import { FieldsetContainer } from "./fieldset-container"; -import { SelectFieldCustom } from "./select-field-custom"; +import { SelectFieldFactory } from "./select/select-field-factory"; import { FormulaireFixedVar } from "../definition/form-fixedvar"; -import { SelectEntry } from "./select-entry"; +import { SelectEntry } from "./select/select-entry"; import { FormulaireNode } from "./formulaire-node"; import { ServiceFactory } from "app/services/service-factory"; @@ -109,18 +109,7 @@ export class FieldSet extends FormulaireElement implements Observer { } private parse_select(json: {}): SelectField { - const res: SelectField = new SelectField(this); - res.parseConfig(json); - res.addObserver(this); - return res; - } - - private parse_select_custom(json: {}): SelectField { - const source = json["source"]; - if (source === undefined || source === "") { - throw new Error(`Fieldset.parse_select_custom(): "source" must not be empty`); - } - const res: SelectField = new SelectFieldCustom(this); + const res: SelectField = SelectFieldFactory.newSelectField(json, this); res.parseConfig(json); res.addObserver(this); return res; @@ -225,12 +214,6 @@ export class FieldSet extends FormulaireElement implements Observer { param = this.parse_select(field); this.addField(param); break; - - case "select_custom": - param = this.parse_select_custom(field); - this.addField(param); - break; - } } } @@ -311,7 +294,7 @@ export class FieldSet extends FormulaireElement implements Observer { const fe = this.getFormulaireNodeById(sId); if (fe) { const prop = (fe as SelectField).associatedProperty; - const defaultValue = (fe as SelectField).defaultValue; + const defaultValue = (fe as SelectField).configDefaultValue; // Sets Nub default property, unless this property is already set const currentValue = this.properties.getPropValue(prop); if (defaultValue !== undefined && currentValue === undefined) { diff --git a/src/app/formulaire/elements/select-field-custom.ts b/src/app/formulaire/elements/select-field-custom.ts deleted file mode 100644 index 5c395281e..000000000 --- a/src/app/formulaire/elements/select-field-custom.ts +++ /dev/null @@ -1,343 +0,0 @@ -import { SelectEntry } from "./select-entry"; -import { ServiceFactory } from "../../services/service-factory"; -import { SelectField } from "./select-field"; -import { decodeHtml, arraysAreEqual } from "../../util"; - -import { FishSpecies, Session, Solveur, FishPass, CalculatorType, Verificateur, Nub, PbCloison, PreBarrage, acSection } from "jalhyd"; - -import { sprintf } from "sprintf-js"; - -/** - * A select field that populates itself with custom stuff (ex: references to Nubs, Parameters…) - */ -export class SelectFieldCustom extends SelectField { - - /** - * Loads UI with the value held by the model - */ - protected initSelectedValue() { - const nub = this.parentForm.currentNub; - - switch (this.source) { - - case "solveur_target": // Solveur, module cible (à calculer) - const ntc = (nub as Solveur).nubToCalculate; - if (ntc !== undefined) { - this.setValueFromId(this._entriesBaseId + ntc.uid); - } - break; - - case "solveur_searched": // Solveur, paramètre recherché (à faire varier) - const sp = (nub as Solveur).searchedParameter; - if (sp !== undefined) { - let parentNubUid; - const parentNub = sp.getParentComputeNode(false); - if (parentNub instanceof acSection) { - parentNubUid = parentNub.uid; - } - else { - parentNubUid = sp.nubUid - } - this.setValueFromId(this._entriesBaseId + parentNubUid + "_" + sp.symbol); - } - break; - - case "verificateur_target": // Vérificateur, passe cible (à vérifier) - const ntv = (nub as Verificateur).nubToVerify; - if (ntv !== undefined) { - this.setValueFromId(this._entriesBaseId + ntv.uid); - } - break; - - case "verificateur_species": // Vérificateur, liste d'espèces (choix multiple) - const sl = (nub as Verificateur).speciesList; - if (sl !== undefined) { - this.setValueFromId(sl.map((s) => { - const spgId = s.substring(s.lastIndexOf("_") + 1); - return this._entriesBaseId + spgId; - })); - } - break; - - case "upstream_basin": // PbCloisons, bassin amont - const ub = (nub as PbCloison).bassinAmont; - // console.log("-- load UB", ub, this._entriesBaseId + ub?.uid); - this.setValueFromId(this._entriesBaseId + (ub ? ub.uid : "none")); - break; - - case "downstream_basin": // PbCloisons, bassin aval - const db = (nub as PbCloison).bassinAval; - // console.log("-- load DB", db, this._entriesBaseId + db?.uid); - this.setValueFromId(this._entriesBaseId + (db ? db.uid : "none")); - break; - } - } - - /** - * Populates entries with available options - */ - protected populate() { - const fs = ServiceFactory.formulaireService; - let candidateNubs: any[]; - switch (this.source) { - - case "solveur_target": // Solveur, module cible (à calculer) - // find all Nubs having at least one link to another Nub's result - candidateNubs = - Session.getInstance().getDownstreamNubs().concat( - Session.getInstance().getUpstreamNubsHavingExtraResults() - ).filter( - (element, index, self) => self.findIndex((e) => e.uid === element.uid) === index - ); - for (const cn of candidateNubs) { - const nub = fs.getFormulaireFromId(cn.uid); - if (nub) { - const calc = nub.calculatorName; - let label = calc; - // calculated param - if (cn.calculatedParam !== undefined) { - const varName = fs.expandVariableName(cn.calcType, cn.calculatedParam.symbol); - label += ` / ${varName} (${cn.calculatedParam.symbol})`; - } - this.addEntry(new SelectEntry(this._entriesBaseId + cn.uid, cn.uid, decodeHtml(label))); - } else { - // silent fail, this Solveur nub was probably loaded before all the candidate nubs are done loading - } - } - break; - - case "solveur_searched": // Solveur, paramètre recherché (à faire varier) - // find all non-calculated, non-linked parameters of all Nubs that - // the current "target" Nub depends on (if any) - const solv = this.parentForm.currentNub as Solveur; - const ntc: Nub = solv.nubToCalculate; - const searchableParams = Solveur.getDependingNubsSearchableParams( - ntc, - solv.targettedResult !== undefined && solv.targettedResult !== "" - ); - for (const p of searchableParams) { - if (p.visible) { - const calc = fs.getFormulaireFromId(p.originNub.uid).calculatorName; - const varName = fs.expandVariableName(p.originNub.calcType, p.symbol); - const label = `${p.symbol} - ${varName} (${calc})`; - this.addEntry(new SelectEntry(this._entriesBaseId + p.getParentComputeNode(false).uid + "_" + p.symbol, p, decodeHtml(label))); - } - } - break; - - case "verificateur_target": // Vérificateur, passe cible (à vérifier) - // find all Nubs of type FishPass - candidateNubs = Session.getInstance().getAllNubs().filter((element) => { - return ( - (element instanceof FishPass) - && element.calcType !== CalculatorType.Par // ParSimulation extends Par @TODO find something better - ); - }); - for (const cn of candidateNubs) { - const nub = fs.getFormulaireFromId(cn.uid); - if (nub) { - const label = nub.calculatorName + " (" + fs.getLocalisedTitleFromCalculatorType(nub.calculatorType) + ")"; - this.addEntry(new SelectEntry(this._entriesBaseId + cn.uid, cn.uid, decodeHtml(label))); - } else { - // silent fail, this Verificateur nub was probably loaded before all the candidate nubs are done loading - } - } - break; - - case "verificateur_species": - // add UIDs of all Espece type Nubs in the session - const especeNubs = Session.getInstance().getAllNubs().filter((element) => element.calcType === CalculatorType.Espece); - for (const en of especeNubs) { - const form = ServiceFactory.formulaireService.getFormulaireFromNubId(en.uid); - this.addEntry( - new SelectEntry( - this._entriesBaseId + en.uid, - en.uid, - sprintf( - ServiceFactory.i18nService.localizeText("INFO_VERIFICATEUR_CUSTOM_SPECIES"), - form ? form.calculatorName : "*** form not loaded yet ***" - ) - ) - ); - } - // add all FishSpecies - for (let j = 1; j < Object.keys(FishSpecies).length / 2; j++) { // exclude "0" (SPECIES_CUSTOM) - const spgId = FishSpecies[j].substring(FishSpecies[j].lastIndexOf("_") + 1); - this.addEntry( - new SelectEntry( - this._entriesBaseId + spgId, - FishSpecies[j], - ServiceFactory.i18nService.localizeText("INFO_ENUM_" + FishSpecies[j]) - ) - ); - } - break; - - case "upstream_basin": // PbCloisons, bassin amont - const pbWallU = this.parentForm.currentNub as PbCloison; - const preBarrageU = pbWallU.parent as PreBarrage; - const posDb = pbWallU.bassinAval?.findPositionInParent(); - // river upstream - this.addEntry( - new SelectEntry( - this._entriesBaseId + "none", - undefined, - ServiceFactory.i18nService.localizeText("INFO_LIB_AMONT") - ) - ); - // all available basins, depending on current downstream basin - for (const b of preBarrageU.bassins) { - const pos = b.findPositionInParent(); - if (posDb === undefined || pos < posDb) { - this.addEntry( - new SelectEntry( - this._entriesBaseId + b.uid, - b.uid, - ServiceFactory.i18nService.localizeMessage(b.description) - ) - ); - } - } - break; - - case "downstream_basin": // PbCloisons, bassin aval - const pbWallD = this.parentForm.currentNub as PbCloison; - const preBarrageD = pbWallD.parent as PreBarrage; - const posUb = pbWallD.bassinAmont?.findPositionInParent(); - // all available basins, depending on current upstream basin - for (const b of preBarrageD.bassins) { - const pos = b.findPositionInParent(); - if (posUb === undefined || pos > posUb) { - this.addEntry( - new SelectEntry( - this._entriesBaseId + b.uid, - b.uid, - ServiceFactory.i18nService.localizeMessage(b.description) - ) - ); - } - } - // river downstream - this.addEntry( - new SelectEntry( - this._entriesBaseId + "none", - undefined, - ServiceFactory.i18nService.localizeText("INFO_LIB_AVAL") - ) - ); - break; - } - } - - protected setDefaultValue() { - // default to first available entry if any - if (this._entries.length > 0) { - if (this._multiple) { - this.setValue([this._entries[0]]); - } else { - this.setValue(this._entries[0]); - } - } else { - // notify observers that no value is selected anymore - this.notifyValueChanged(); - } - } - - /** - * Once config is parsed, init original value from model - * (needs config, for this._entriesBaseId to be set) - */ - protected afterParseConfig() { - this.populate(); - this.initSelectedValue(); - } - - /** - * Reloads available entries, trying to keep the current selected - * value; does not notify observers if value did not change - */ - public updateEntries() { - // store previous selected entry - const pse = this._selectedEntry; - // empty - this.clearEntries(); - // populate - this.populate(); - // if no entry is available anymore, unset value - if (this.entries.length === 0) { - if (this.multiple) { - super.setValue([]); - } else { - super.setValue(undefined); - } - } else { - // keep previously selected entry(ies) if possible - if (pse) { - if (!Array.isArray(pse) && pse.id) { - this.setValueFromId(pse.id); - } else if (Array.isArray(pse) && pse.length > 0) { - this.setValueFromId(pse.map((e) => e.id)); - } else { - this.setDefaultValue(); - } - } else { - this.setDefaultValue(); - } - } - } - - /** - * Updates selectedValue; notifies observers only if - * value.id has changed - */ - public setValue(v: SelectEntry | SelectEntry[]) { - const previousSelectedEntry = this._selectedEntry; - this._selectedEntry = v; - // if value changed - const valueChanged = ( - !previousSelectedEntry - || ( - !Array.isArray(previousSelectedEntry) - && !Array.isArray(v) - && previousSelectedEntry.id !== v.id - ) - || ( - Array.isArray(previousSelectedEntry) - && Array.isArray(v) - && !arraysAreEqual(previousSelectedEntry, v, "id", true) - ) - ); - if (valueChanged) { - this.notifyValueChanged(); - } - } - - /** - * Sets value from given ID(s); if it was not found, sets the - * first available entry as selectedValue - */ - public setValueFromId(id: string | string[]) { - let found = false; - const entries = []; - if (this._multiple && Array.isArray(id)) { - for (const e of this._entries) { - if (id.includes(e.id)) { - entries.push(e); - found = true; - } - } - this.setValue(entries); - } else { - for (const e of this._entries) { - if (e.id === id) { - found = true; - this.setValue(e); - } - } - } - if (!found) { - this.setDefaultValue(); - } - } - -} diff --git a/src/app/formulaire/elements/select-field.ts b/src/app/formulaire/elements/select-field.ts deleted file mode 100644 index ce08ddefd..000000000 --- a/src/app/formulaire/elements/select-field.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { - CourbeRemous, - Nub, - ParallelStructure, - StructureType, - LoiDebit, - Session, - Solveur, - StructureProperties - } from "jalhyd"; - -import { Field } from "./field"; -import { SelectEntry } from "./select-entry"; -import { FormulaireNode } from "./formulaire-node"; -import { FormulaireDefinition } from "../definition/form-definition"; -import { ServiceFactory } from "../../services/service-factory"; - -export class SelectField extends Field { - - /** string to build the select entries IDs from */ - protected _entriesBaseId: string; - - protected _entries: SelectEntry[]; - - protected _selectedEntry: SelectEntry | SelectEntry[]; - - /** name of the Nub property associated to this field, if any */ - protected _associatedProperty: string; - - /** default value for this field */ - protected _defaultValue: string; - - /** if true, user can select multiple values */ - protected _multiple = false; - - /** if true, select box is grayed out */ - public disabled = false; - - /** source identifier for populating with available values */ - protected source: string; - - /** - * message to display below the select field when no option is selected, - * if this message is defined and not empty - */ - protected _messageWhenEmpty: string; - - constructor(parent: FormulaireNode) { - super(parent); - this.clearEntries(); - } - - public get entriesBaseId(): string { - return this._entriesBaseId; - } - - public get entries() { - return this._entries; - } - - public get associatedProperty(): string { - return this._associatedProperty; - } - - public get defaultValue(): string { - return this._defaultValue; - } - - public get multiple(): boolean { - return this._multiple; - } - - public get messageWhenEmpty(): string { - let msg: string; - if (this._selectedEntry === undefined && this._messageWhenEmpty) { - msg = ServiceFactory.i18nService.localizeText(this._messageWhenEmpty); - } - return msg; - } - - public clearEntries() { - this._entries = []; - } - - public addEntry(e: SelectEntry) { - this._entries.push(e); - if (! this._selectedEntry) { - if (this._multiple) { - this.setValue([ e ]); - } else { - this.setValue(e); - } - } - } - - /** - * Triggered at the end of parseConfig() - */ - protected afterParseConfig() { } - - public getEntryFromValue(val: any): SelectEntry { - for (const se of this._entries) { - if (se.value === val) { - return se; - } - } - } - - public getValue(): SelectEntry | SelectEntry[] { - return this._selectedEntry; - } - - public setValue(v: SelectEntry | SelectEntry[]) { - if (this._selectedEntry !== v) { - this._selectedEntry = v; - this.notifyValueChanged(); - } - } - - public notifyValueChanged() { - this.notifyObservers({ - "action": "select", - "value": this._selectedEntry - }, this); - } - - public updateLocalisation() { - super.updateLocalisation(); - for (const e of this._entries) { - if (this.source === "solveur_targetted_result") { - // @WARNING clodo hack for Solveur - // 1. calculated param - const nub: Nub = (this.parentForm as FormulaireDefinition).currentNub; - const ntc = (nub as Solveur).nubToCalculate; - if (e.value !== undefined && ntc !== undefined) { - if (e.value === "" && ntc.calculatedParam !== undefined) { - const varName = ServiceFactory.formulaireService.expandVariableName(ntc.calcType, ntc.calculatedParam.symbol); - e.label = `${varName} (${ntc.calculatedParam.symbol})`; - } else { - // 2. extra results - const varName = ServiceFactory.formulaireService.expandVariableName(ntc.calcType, e.value); - e.label = `${varName} (${e.value})`; - } - } - } else { - // general case - const aId = e.id.split("_"); - const trad = ServiceFactory.formulaireService.localizeText( - `${aId[1].toUpperCase()}_${aId[2]}`, - this.parentForm.currentNub.calcType - ); - e.label = trad; - } - } - } - - public parseConfig(field: {}, data?: {}) { - this._confId = field["id"]; - this._entriesBaseId = this._confId + "_"; - this._helpLink = field["help"]; - this._associatedProperty = field["property"]; - this._defaultValue = field["default"]; - this._messageWhenEmpty = field["messageWhenEmpty"]; - if (field["multiple"] !== undefined) { - this._multiple = field["multiple"]; - } - this.source = field["source"]; - - this.loadEntriesFromSource(); - - this.afterParseConfig(); - } - - /** - * Adds available entries to the selector, depending on the "source" identifier - */ - protected loadEntriesFromSource() { - const nub: Nub = (this.parentForm as FormulaireDefinition).currentNub; - // ad-hoc cases - switch (this.source) { - // driven by string[], not enum (easier for variable names) - case "remous_target": - this.addEntry(new SelectEntry(this._entriesBaseId + "none", "")); - for (const at of CourbeRemous.availableTargets) { - const e: SelectEntry = new SelectEntry(this._entriesBaseId + at, at); - this.addEntry(e); - } - break; - - // driven by string[], not enum - case "solveur_targetted_result": - // @WARNING for localisation, @see hack in this.updateLocalisation() - // 1. calculated param - const ntc = (nub as Solveur).nubToCalculate; - if (ntc?.calculatedParam !== undefined) { // some nubs have no calculatedParam, for ex. SectionParam - this.addEntry(new SelectEntry(this._entriesBaseId + "none", "")); - } - // 2. extra results - if (ntc?.resultsFamilies !== undefined) { - for (const er of Object.keys(ntc.resultsFamilies)) { - const e: SelectEntry = new SelectEntry(this._entriesBaseId + er, er); - this.addEntry(e); - } - } - break; - - // possible values depend on CalcType - case "device_structure_type": - for (const st in (nub as ParallelStructure).getLoisAdmissibles()) { - const e: SelectEntry = new SelectEntry(this._entriesBaseId + st, StructureType[st]); - this.addEntry(e); - } - break; - - // possible values depend on CalcType - case "device_loi_debit": - // get current structure type from appropriate Nub child - const child = nub.getChildren()[this.parent.indexAsKid()]; - const la = (nub as ParallelStructure).getLoisAdmissibles(); - const loiDebit = child.properties.getPropValue("loiDebit"); - const stCode = StructureProperties.findCompatibleStructure(loiDebit, nub as ParallelStructure); - const stName = StructureType[stCode]; - if (la[stName] !== undefined) { - for (const ld of la[stName]) { - const e: SelectEntry = new SelectEntry(this._entriesBaseId + LoiDebit[ld], ld); - this.addEntry(e); - } - } - break; - - // general case : property values taken from an enum - default: - // find enum associated to property - const enumClass = Session.enumFromProperty[this._associatedProperty]; - if (enumClass !== undefined) { - // add one select entry per enum entry, in the enum order - for (let j = 0; j < Object.keys(enumClass).length / 2; j++) { - this.addEntry(new SelectEntry(this._entriesBaseId + j, j)); - } - } - } - } - -} diff --git a/src/app/formulaire/elements/select-entry.ts b/src/app/formulaire/elements/select/select-entry.ts similarity index 100% rename from src/app/formulaire/elements/select-entry.ts rename to src/app/formulaire/elements/select/select-entry.ts diff --git a/src/app/formulaire/elements/select/select-field-device-loi-debit.ts b/src/app/formulaire/elements/select/select-field-device-loi-debit.ts new file mode 100644 index 000000000..15194174f --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-device-loi-debit.ts @@ -0,0 +1,42 @@ +import { LoiDebit, ParallelStructure, StructureProperties, StructureType } from "jalhyd"; +import { SelectField } from "./select-field"; +import { SelectEntry } from "./select-entry"; + +/* + "id": "select_loidebit", + "type": "select", + "property": "loiDebit", + "source": "device_loi_debit", + "help": { +*/ + +export class SelectFieldDeviceLoiDebit extends SelectField { + protected populate() { + // possible values depend on CalcType + + // get current structure type from appropriate Nub child + const child = this.nub.getChildren()[this.parent.indexAsKid()]; + const la = (this.nub as ParallelStructure).getLoisAdmissibles(); + const loiDebit = child.properties.getPropValue("loiDebit"); + const stCode = StructureProperties.findCompatibleStructure(loiDebit, this.nub as ParallelStructure); + const stName = StructureType[stCode]; + if (la[stName] !== undefined) { + for (const ld of la[stName]) { + const e: SelectEntry = new SelectEntry(this._entriesBaseId + LoiDebit[ld], ld); + this.addEntry(e); + } + } + } + + protected initSelectedValue() { + this.findAndSetDefaultValue(); + } + + public get associatedProperty(): string { + return "loiDebit"; + } + + public get configDefaultValue(): string { + return undefined; + } +} \ No newline at end of file diff --git a/src/app/formulaire/elements/select/select-field-device-structure-type.ts b/src/app/formulaire/elements/select/select-field-device-structure-type.ts new file mode 100644 index 000000000..b818eedf5 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-device-structure-type.ts @@ -0,0 +1,32 @@ +import { ParallelStructure, StructureType } from "jalhyd"; +import { SelectField } from "./select-field"; +import { SelectEntry } from "./select-entry"; + +/* + "id": "select_structure", + "type": "select", + "property": "structureType", + "source": "device_structure_type" +*/ + +export class SelectFieldDeviceStructureType extends SelectField { + protected populate() { + // possible values depend on CalcType + for (const st in (this.nub as ParallelStructure).getLoisAdmissibles()) { + const e: SelectEntry = new SelectEntry(this._entriesBaseId + st, StructureType[st]); + this.addEntry(e); + } + } + + protected initSelectedValue() { + this.findAndSetDefaultValue(); + } + + public get associatedProperty(): string { + return "structureType"; + } + + public get configDefaultValue(): string { + return undefined; + } +} \ No newline at end of file diff --git a/src/app/formulaire/elements/select/select-field-downstream-basin.ts b/src/app/formulaire/elements/select/select-field-downstream-basin.ts new file mode 100644 index 000000000..77fcab4bd --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-downstream-basin.ts @@ -0,0 +1,53 @@ +import { ServiceFactory } from "app/services/service-factory"; +import { PbCloison, PreBarrage } from "jalhyd"; +import { SelectEntry } from "./select-entry"; +import { SelectField } from "./select-field"; + +/* + "id": "select_downstream_basin", + "type": "select_custom", + "source": "downstream_basin" +*/ + +// PbCloisons, bassin aval +export class SelectFieldDownstreamBasin extends SelectField { + protected populate() { + const pbWallD = this.parentForm.currentNub as PbCloison; + const preBarrageD = pbWallD.parent as PreBarrage; + const posUb = pbWallD.bassinAmont?.findPositionInParent(); + // all available basins, depending on current upstream basin + for (const b of preBarrageD.bassins) { + const pos = b.findPositionInParent(); + if (posUb === undefined || pos > posUb) { + this.addEntry( + new SelectEntry( + this._entriesBaseId + b.uid, + b.uid, + ServiceFactory.i18nService.localizeMessage(b.description) + ) + ); + } + } + // river downstream + this.addEntry( + new SelectEntry( + this._entriesBaseId + "none", + undefined, + ServiceFactory.i18nService.localizeText("INFO_LIB_AVAL") + ) + ); + } + + protected initSelectedValue() { + const db = (this.nub as PbCloison).bassinAval; + this.setValueFromId(this._entriesBaseId + (db ? db.uid : "none")); + } + + public get associatedProperty(): string { + return undefined; + } + + public get configDefaultValue(): string { + return undefined; + } +} diff --git a/src/app/formulaire/elements/select/select-field-factory.ts b/src/app/formulaire/elements/select/select-field-factory.ts new file mode 100644 index 000000000..38e8e7b49 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-factory.ts @@ -0,0 +1,66 @@ +import { FormulaireNode } from "../formulaire-node"; +import { SelectField } from "./select-field"; +import { SelectFieldDeviceLoiDebit } from "./select-field-device-loi-debit"; +import { SelectFieldDeviceStructureType } from "./select-field-device-structure-type"; +import { SelectFieldRemousTarget } from "./select-field-remous-target"; +import { SelectFieldSolverTargetedResult } from "./select-field-solver-targeted-result"; +import { SelectFieldUpstreamBasin } from "./select-field-upstream-basin"; +import { SelectFieldDownstreamBasin } from "./select-field-downstream-basin"; +import { SelectFieldSolverTarget } from "./select-field-solveur-target"; +import { SelectFieldSearchedParam } from "./select-field-searched-param"; +import { SelectFieldSpeciesList } from "./select-field-species-list"; +import { SelectFieldTargetPass } from "./select-field-target-pass"; +import { SelectFieldNubProperty } from "./select-field-nub-prop"; + +export class SelectFieldFactory { + public static newSelectField(json: {}, parent: FormulaireNode): SelectField { + switch (json["id"]) { + case "select_downstream_basin": + return new SelectFieldDownstreamBasin(parent); + + case "select_loidebit": + return new SelectFieldDeviceLoiDebit(parent); + + case "select_searched_param": + return new SelectFieldSearchedParam(parent); + + case "select_species_list": + return new SelectFieldSpeciesList(parent); + + case "select_structure": + return new SelectFieldDeviceStructureType(parent); + + case "select_target": + return new SelectFieldRemousTarget(parent); + + case "select_target_nub": + return new SelectFieldSolverTarget(parent); + + case "select_target_pass": + return new SelectFieldTargetPass(parent); + + case "select_target_result": + return new SelectFieldSolverTargetedResult(parent); + + case "select_upstream_basin": + return new SelectFieldUpstreamBasin(parent); + + case "select_divingjetsupported": + case "select_gridprofile": + case "select_gridtype": + case "select_material": + case "select_operation": + case "select_partype": + case "select_passtype": + case "select_regime": + case "select_resolution": + case "select_section": + case "select_sppoperation": + case "select_unit": + return new SelectFieldNubProperty(parent, json); + + default: + throw new Error("unknown select id ${id}"); + } + } +} \ No newline at end of file diff --git a/src/app/formulaire/elements/select/select-field-nub-prop.ts b/src/app/formulaire/elements/select/select-field-nub-prop.ts new file mode 100644 index 000000000..eca0463b3 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-nub-prop.ts @@ -0,0 +1,38 @@ +import { Session } from "jalhyd"; +import { FormulaireNode } from "../formulaire-node"; +import { SelectEntry } from "./select-entry"; +import { SelectField } from "./select-field"; + +/* + par exemple : + "id": "select_regime", + "type": "select", + "property": "regime", + "default": "Fluvial" + +*/ + +// nub property values taken from an enum +// "standard" select that normally does not require customisation +export class SelectFieldNubProperty extends SelectField { + constructor(parent: FormulaireNode, json: {}) { + super(parent); + this._associatedProperty = json["property"]; + this._configDefaultValue = json["default"]; + } + + protected populate() { + // find enum associated to property + const enumClass = Session.enumFromProperty[this._associatedProperty]; + if (enumClass !== undefined) { + // add one select entry per enum entry, in the enum order + for (let j = 0; j < Object.keys(enumClass).length / 2; j++) { + this.addEntry(new SelectEntry(this._entriesBaseId + j, j)); + } + } + } + + protected initSelectedValue() { + this.findAndSetDefaultValue(); + } +} \ No newline at end of file diff --git a/src/app/formulaire/elements/select/select-field-remous-target.ts b/src/app/formulaire/elements/select/select-field-remous-target.ts new file mode 100644 index 000000000..6d0f2c506 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-remous-target.ts @@ -0,0 +1,34 @@ +import { CourbeRemous } from "jalhyd"; +import { SelectField } from "./select-field"; +import { SelectEntry } from "./select-entry"; + +/* + "id": "select_target", + "type": "select", + "property": "varCalc", + "source": "remous_target", + "help": { +*/ + +export class SelectFieldRemousTarget extends SelectField { + protected populate() { + // driven by string[], not enum (easier for variable names) + this.addEntry(new SelectEntry(this._entriesBaseId + "none", "")); + for (const at of CourbeRemous.availableTargets) { + const e: SelectEntry = new SelectEntry(this._entriesBaseId + at, at); + this.addEntry(e); + } + } + + protected initSelectedValue() { + this.findAndSetDefaultValue(); + } + + public get associatedProperty(): string { + return "varCalc"; + } + + public get configDefaultValue(): string { + return undefined; + } +} \ No newline at end of file diff --git a/src/app/formulaire/elements/select/select-field-searched-param.ts b/src/app/formulaire/elements/select/select-field-searched-param.ts new file mode 100644 index 000000000..5380e1238 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-searched-param.ts @@ -0,0 +1,63 @@ +import { ServiceFactory } from "app/services/service-factory"; +import { decodeHtml } from "app/util"; +import { acSection, Nub, Solveur } from "jalhyd"; +import { SelectEntry } from "./select-entry"; +import { SelectField } from "./select-field"; + +/* + "id": "select_searched_param", + "type": "select_custom", + "source": "solveur_searched" +*/ + +// Solveur, paramètre recherché (à faire varier) +export class SelectFieldSearchedParam extends SelectField { + protected populate() { + const fs = ServiceFactory.formulaireService; + + // find all non-calculated, non-linked parameters of all Nubs that + // the current "target" Nub depends on (if any) + const solv = this.parentForm.currentNub as Solveur; + const ntc: Nub = solv.nubToCalculate; + const searchableParams = Solveur.getDependingNubsSearchableParams( + ntc, + solv.targettedResult !== undefined && solv.targettedResult !== "" + ); + for (const p of searchableParams) { + if (p.visible) { + const calc = fs.getFormulaireFromId(p.originNub.uid).calculatorName; + const varName = fs.expandVariableName(p.originNub.calcType, p.symbol); + const label = `${p.symbol} - ${varName} (${calc})`; + this.addEntry(new SelectEntry(this._entriesBaseId + p.getParentComputeNode(false).uid + "_" + p.symbol, p, decodeHtml(label))); + } + } + } + + protected initSelectedValue() { + const sp = (this.nub as Solveur).searchedParameter; + if (sp !== undefined) { + let parentNubUid; + const parentNub = sp.getParentComputeNode(false); + if (parentNub instanceof acSection) { + parentNubUid = parentNub.uid; + } + else { + parentNubUid = sp.nubUid + } + this.setValueFromId(this._entriesBaseId + parentNubUid + "_" + sp.symbol); + } + } + + public get associatedProperty(): string { + return undefined; + } + + public get configDefaultValue(): string { + return undefined; + } + + public updateLocalisation() { + // do not override localisation done in populate() + // ie. avoid what is done by SelectField.updateLocalisation() + } +} diff --git a/src/app/formulaire/elements/select/select-field-solver-targeted-result.ts b/src/app/formulaire/elements/select/select-field-solver-targeted-result.ts new file mode 100644 index 000000000..2b10da827 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-solver-targeted-result.ts @@ -0,0 +1,64 @@ +import { FormulaireDefinition } from "app/formulaire/definition/form-definition"; +import { ServiceFactory } from "app/services/service-factory"; +import { Nub, Solveur } from "jalhyd"; +import { FormulaireElement } from "../formulaire-element"; +import { SelectField } from "./select-field"; +import { SelectEntry } from "./select-entry"; + +/* + "id": "select_target_result", + "type": "select", + "property": "targettedResult", + "source": "solveur_targetted_result", + "default": "" +*/ +export class SelectFieldSolverTargetedResult extends SelectField { + + protected populate() { + // @WARNING for localisation, @see hack in this.updateLocalisation() + // 1. calculated param + const ntc = (this.nub as Solveur).nubToCalculate; + if (ntc?.calculatedParam !== undefined) { // some nubs have no calculatedParam, for ex. SectionParam + this.addEntry(new SelectEntry(this._entriesBaseId + "none", "")); + } + // 2. extra results + if (ntc?.resultsFamilies !== undefined) { + for (const er of Object.keys(ntc.resultsFamilies)) { + const e: SelectEntry = new SelectEntry(this._entriesBaseId + er, er); + this.addEntry(e); + } + } + } + + protected initSelectedValue() { + this.findAndSetDefaultValue(); + } + + public get configDefaultValue(): string { + return ""; + } + + public get associatedProperty(): string { + return "targettedResult"; + } + + public updateLocalisation() { + FormulaireElement.prototype.updateLocalisation.call(this); + for (const e of this._entries) { + // @WARNING clodo hack for Solveur + // 1. calculated param + const nub: Nub = (this.parentForm as FormulaireDefinition).currentNub; + const ntc = (nub as Solveur).nubToCalculate; + if (e.value !== undefined && ntc !== undefined) { + if (e.value === "" && ntc.calculatedParam !== undefined) { + const varName = ServiceFactory.formulaireService.expandVariableName(ntc.calcType, ntc.calculatedParam.symbol); + e.label = `${varName} (${ntc.calculatedParam.symbol})`; + } else { + // 2. extra results + const varName = ServiceFactory.formulaireService.expandVariableName(ntc.calcType, e.value); + e.label = `${varName} (${e.value})`; + } + } + } + } +} \ No newline at end of file diff --git a/src/app/formulaire/elements/select/select-field-solveur-target.ts b/src/app/formulaire/elements/select/select-field-solveur-target.ts new file mode 100644 index 000000000..64d37dc33 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-solveur-target.ts @@ -0,0 +1,62 @@ +/* + "id": "select_target_nub", + "type": "select_custom", + "source": "solveur_target" +*/ + +import { ServiceFactory } from "app/services/service-factory"; +import { decodeHtml } from "../../../util"; +import { Session, Solveur } from "jalhyd"; +import { SelectEntry } from "./select-entry"; +import { SelectField } from "./select-field"; + +// Solveur, module cible (à calculer) +export class SelectFieldSolverTarget extends SelectField { + protected populate() { + + const fs = ServiceFactory.formulaireService; + + // find all Nubs having at least one link to another Nub's result + const candidateNubs: any[] = + Session.getInstance().getDownstreamNubs().concat( + Session.getInstance().getUpstreamNubsHavingExtraResults() + ).filter( + (element, index, self) => self.findIndex((e) => e.uid === element.uid) === index + ); + for (const cn of candidateNubs) { + const nub = fs.getFormulaireFromId(cn.uid); + if (nub) { + const calc = nub.calculatorName; + let label = calc; + // calculated param + if (cn.calculatedParam !== undefined) { + const varName = fs.expandVariableName(cn.calcType, cn.calculatedParam.symbol); + label += ` / ${varName} (${cn.calculatedParam.symbol})`; + } + this.addEntry(new SelectEntry(this._entriesBaseId + cn.uid, cn.uid, decodeHtml(label))); + } else { + // silent fail, this Solveur nub was probably loaded before all the candidate nubs are done loading + } + } + } + + protected initSelectedValue() { + const ntc = (this.nub as Solveur).nubToCalculate; + if (ntc !== undefined) { + this.setValueFromId(this._entriesBaseId + ntc.uid); + } + } + + public get associatedProperty(): string { + return undefined; + } + + public get configDefaultValue(): string { + return undefined; + } + + public updateLocalisation() { + // do not override localisation done in populate() + // ie. avoid what is done by SelectField.updateLocalisation() + } +} \ No newline at end of file diff --git a/src/app/formulaire/elements/select/select-field-species-list.ts b/src/app/formulaire/elements/select/select-field-species-list.ts new file mode 100644 index 000000000..bcdbbee79 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-species-list.ts @@ -0,0 +1,73 @@ +/* +"type": "select_custom", +"id": "select_species_list", +"source": "verificateur_species", +"multiple": true +*/ + +import { ServiceFactory } from "app/services/service-factory"; +import { CalculatorType, FishSpecies, Session, Verificateur } from "jalhyd"; +import { sprintf } from "sprintf-js"; +import { FormulaireNode } from "../formulaire-node"; +import { SelectEntry } from "./select-entry"; +import { SelectField } from "./select-field"; + +// Vérificateur, liste d'espèces (choix multiple) +export class SelectFieldSpeciesList extends SelectField { + constructor(parent: FormulaireNode) { + super(parent); + this._multiple = true; + } + + protected populate() { + // add UIDs of all Espece type Nubs in the session + const especeNubs = Session.getInstance().getAllNubs().filter((element) => element.calcType === CalculatorType.Espece); + for (const en of especeNubs) { + const form = ServiceFactory.formulaireService.getFormulaireFromNubId(en.uid); + this.addEntry( + new SelectEntry( + this._entriesBaseId + en.uid, + en.uid, + sprintf( + ServiceFactory.i18nService.localizeText("INFO_VERIFICATEUR_CUSTOM_SPECIES"), + form ? form.calculatorName : "*** form not loaded yet ***" + ) + ) + ); + } + // add all FishSpecies + for (let j = 1; j < Object.keys(FishSpecies).length / 2; j++) { // exclude "0" (SPECIES_CUSTOM) + const spgId = FishSpecies[j].substring(FishSpecies[j].lastIndexOf("_") + 1); + this.addEntry( + new SelectEntry( + this._entriesBaseId + spgId, + FishSpecies[j], + ServiceFactory.i18nService.localizeText("INFO_ENUM_" + FishSpecies[j]) + ) + ); + } + } + + protected initSelectedValue() { + const sl = (this.nub as Verificateur).speciesList; + if (sl !== undefined) { + this.setValueFromId(sl.map((s) => { + const spgId = s.substring(s.lastIndexOf("_") + 1); + return this._entriesBaseId + spgId; + })); + } + } + + public get associatedProperty(): string { + return undefined; + } + + public get configDefaultValue(): string { + return undefined; + } + + public updateLocalisation() { + // do not override localisation done in populate() + // ie. avoid what is done by SelectField.updateLocalisation() + } +} \ No newline at end of file diff --git a/src/app/formulaire/elements/select/select-field-target-pass.ts b/src/app/formulaire/elements/select/select-field-target-pass.ts new file mode 100644 index 000000000..36430d217 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-target-pass.ts @@ -0,0 +1,62 @@ +import { ServiceFactory } from "app/services/service-factory"; +import { decodeHtml } from "app/util"; +import { CalculatorType, FishPass, Session, Verificateur } from "jalhyd"; +import { FormulaireElement } from "../formulaire-element"; +import { FormulaireNode } from "../formulaire-node"; +import { SelectEntry } from "./select-entry"; +import { SelectField } from "./select-field"; + +/* + "id": "select_target_pass", + "type": "select_custom", + "source": "verificateur_target", + "messageWhenEmpty": "INFO_VERIF_CREATE_PASS_FRIST" +*/ + +// Vérificateur, passe cible (à vérifier) +export class SelectFieldTargetPass extends SelectField { + constructor(parent: FormulaireNode) { + super(parent); + this._messageWhenEmpty = "INFO_VERIF_CREATE_PASS_FRIST"; + } + + protected populate() { + const fs = ServiceFactory.formulaireService; + // find all Nubs of type FishPass + const candidateNubs: any[] = Session.getInstance().getAllNubs().filter((element) => { + return ( + (element instanceof FishPass) + && element.calcType !== CalculatorType.Par // ParSimulation extends Par @TODO find something better + ); + }); + for (const cn of candidateNubs) { + const nub = fs.getFormulaireFromId(cn.uid); + if (nub) { + const label = nub.calculatorName + " (" + fs.getLocalisedTitleFromCalculatorType(nub.calculatorType) + ")"; + this.addEntry(new SelectEntry(this._entriesBaseId + cn.uid, cn.uid, decodeHtml(label))); + } else { + // silent fail, this Verificateur nub was probably loaded before all the candidate nubs are done loading + } + } + } + + protected initSelectedValue() { + const ntv = (this.nub as Verificateur).nubToVerify; + if (ntv !== undefined) { + this.setValueFromId(this._entriesBaseId + ntv.uid); + } + } + + public get associatedProperty(): string { + return undefined; + } + + public get configDefaultValue(): string { + return undefined; + } + + public updateLocalisation() { + // do not override localisation done in populate() + // ie. avoid what is done by SelectField.updateLocalisation() + } +} \ No newline at end of file diff --git a/src/app/formulaire/elements/select/select-field-upstream-basin.ts b/src/app/formulaire/elements/select/select-field-upstream-basin.ts new file mode 100644 index 000000000..06e335f5c --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-upstream-basin.ts @@ -0,0 +1,53 @@ +import { ServiceFactory } from "app/services/service-factory"; +import { PbCloison, PreBarrage } from "jalhyd"; +import { SelectEntry } from "./select-entry"; +import { SelectField } from "./select-field"; + +/* + "id": "select_upstream_basin", + "type": "select_custom", + "source": "upstream_basin" +*/ + +// PbCloisons, bassin amont +export class SelectFieldUpstreamBasin extends SelectField { + protected populate() { + const pbWallU = this.parentForm.currentNub as PbCloison; + const preBarrageU = pbWallU.parent as PreBarrage; + const posDb = pbWallU.bassinAval?.findPositionInParent(); + // river upstream + this.addEntry( + new SelectEntry( + this._entriesBaseId + "none", + undefined, + ServiceFactory.i18nService.localizeText("INFO_LIB_AMONT") + ) + ); + // all available basins, depending on current downstream basin + for (const b of preBarrageU.bassins) { + const pos = b.findPositionInParent(); + if (posDb === undefined || pos < posDb) { + this.addEntry( + new SelectEntry( + this._entriesBaseId + b.uid, + b.uid, + ServiceFactory.i18nService.localizeMessage(b.description) + ) + ); + } + } + } + + protected initSelectedValue() { + const ub = (this.nub as PbCloison).bassinAmont; + this.setValueFromId(this._entriesBaseId + (ub ? ub.uid : "none")); + } + + public get associatedProperty(): string { + return undefined; + } + + public get configDefaultValue(): string { + return undefined; + } +} \ No newline at end of file diff --git a/src/app/formulaire/elements/select/select-field.ts b/src/app/formulaire/elements/select/select-field.ts new file mode 100644 index 000000000..c6e2024ed --- /dev/null +++ b/src/app/formulaire/elements/select/select-field.ts @@ -0,0 +1,268 @@ +import { Field } from "../field"; +import { SelectEntry } from "./select-entry"; +import { arraysAreEqual } from "../../../util"; +import { FormulaireNode } from "../formulaire-node"; +import { ServiceFactory } from "app/services/service-factory"; +import { FormulaireDefinition } from "../../definition/form-definition"; +import { Nub } from "jalhyd"; + +export abstract class SelectField extends Field { + + /** + * select options + */ + protected _entries: SelectEntry[]; + + /** + * currently selected option + */ + protected _selectedEntry: SelectEntry | SelectEntry[]; + + /** if true, user can select multiple values */ + protected _multiple = false; + + /** if true, select box is grayed out */ + public disabled = false; + + /** string to build the select entries IDs from */ + protected _entriesBaseId: string; + + /** name of the Nub property associated to this field, if any */ + protected _associatedProperty: string; + + /** default value (from configuration) for this field */ + protected _configDefaultValue: string; + + /** + * message to display below the select field when no option is selected, + * if this message is defined and not empty + */ + protected _messageWhenEmpty: string; + + constructor(parent: FormulaireNode) { + super(parent); + this.clearEntries(); + } + + /** + * associated nub + */ + protected get nub(): Nub { + return (this.parentForm as FormulaireDefinition).currentNub; + } + + public getValue(): SelectEntry | SelectEntry[] { + return this._selectedEntry; + } + + /** + * Updates _selectedEntry; notifies observers only if + * value.id has changed + */ + public setValue(v: SelectEntry | SelectEntry[]) { + // if multiple is true, value must be an array + if (this._multiple && !Array.isArray(v)) { + throw new Error("select value is not an array"); + } + + const previousSelectedEntry = this._selectedEntry; + this._selectedEntry = v; + + // if old and new values are not both undefined + if (!(previousSelectedEntry === undefined && v === undefined)) { + // if value changed + const valueChanged = ( + (previousSelectedEntry === undefined && v !== undefined) + || ( + !Array.isArray(previousSelectedEntry) + && !Array.isArray(v) + && previousSelectedEntry.id !== v.id + ) + || ( + Array.isArray(previousSelectedEntry) + && Array.isArray(v) + && !arraysAreEqual(previousSelectedEntry, v, "id", true) + ) + ); + if (valueChanged) { + this.notifyValueChanged(); + } + } + } + + public notifyValueChanged() { + this.notifyObservers({ + "action": "select", + "value": this._selectedEntry + }, this); + } + + /** + * Sets value from given entry id(s); if it was not found, sets the + * first available entry as selectedValue + */ + protected setValueFromId(id: string | string[]) { + let found = false; + const entries = []; + if (Array.isArray(id)) { + for (const e of this._entries) { + if (id.includes(e.id)) { + entries.push(e); + found = true; + } + } + this.setValue(entries); + } else { + for (const e of this._entries) { + if (e.id === id) { + found = true; + this.setValue(e); + } + } + } + if (!found) { + this.findAndSetDefaultValue(); + } + } + + /** + * try to find a default value to select + */ + protected findAndSetDefaultValue() { + // default to first available entry if any + if (this._entries.length > 0) { + if (this._multiple) { + this.setValue([this._entries[0]]); + } else { + this.setValue(this._entries[0]); + } + } else { + // notify observers that no value is selected anymore + this.notifyValueChanged(); + } + } + + public parseConfig(field: {}, data?: {}) { + this._confId = field["id"]; + this._entriesBaseId = this._confId + "_"; + this._helpLink = field["help"]; + this.afterParseConfig(); + } + + /** + * Once config is parsed, init original value from model + * (needs config for this._entriesBaseId to be set). + * Triggered at the end of parseConfig() + */ + protected afterParseConfig() { + this.populate(); + this.initSelectedValue(); + } + + /** + * fill select with options + */ + protected abstract populate(); + + /** + * initialise select (loads UI with the value held by the model) + */ + protected abstract initSelectedValue(); + + /** + * associated nub property + */ + public get associatedProperty(): string { + return this._associatedProperty; + } + + /** + * default value from configuration + */ + public get configDefaultValue(): string { + return this._configDefaultValue; + } + + private clearEntries() { + this._entries = []; + } + + public get entries() { + return this._entries; + } + + protected addEntry(e: SelectEntry) { + this._entries.push(e); + } + + public getEntryFromValue(val: any): SelectEntry { + for (const se of this._entries) { + if (se.value === val) { + return se; + } + } + } + + /** + * Reloads available entries, trying to keep the current selected + * value; does not notify observers if value did not change + */ + public updateEntries() { + // store previous selected entry + const pse = this._selectedEntry; + // empty + this.clearEntries(); + // populate + this.populate(); + this.updateLocalisation(); + // if no entry is available anymore, unset value + if (this._entries.length === 0) { + if (this._multiple) { + this.setValue([]); + } else { + this.setValue(undefined); + } + } else { + // keep previously selected entry(ies) if possible + if (pse) { + if (!Array.isArray(pse) && pse.id) { + this.setValueFromId(pse.id); + } else if (Array.isArray(pse) && pse.length > 0) { + this.setValueFromId(pse.map((e) => e.id)); + } else { + this.findAndSetDefaultValue(); + } + } else { + this.findAndSetDefaultValue(); + } + } + } + + public get entriesBaseId(): string { + return this._entriesBaseId; + } + + public get messageWhenEmpty(): string { + let msg: string; + if (this._selectedEntry === undefined && this._messageWhenEmpty) { + msg = ServiceFactory.i18nService.localizeText(this._messageWhenEmpty); + } + return msg; + } + + public get multiple(): boolean { + return this._multiple; + } + + public updateLocalisation() { + super.updateLocalisation(); + for (const e of this._entries) { + const aId = e.id.split("_"); + const trad = ServiceFactory.formulaireService.localizeText( + `${aId[1].toUpperCase()}_${aId[2]}`, + this.parentForm.currentNub.calcType + ); + e.label = trad; + } + } +} diff --git a/src/app/services/formulaire.service.ts b/src/app/services/formulaire.service.ts index 4c9e42baa..83020d527 100644 --- a/src/app/services/formulaire.service.ts +++ b/src/app/services/formulaire.service.ts @@ -32,7 +32,7 @@ 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-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"; -- GitLab