From 8504d3acc43173e88d8d36e39c1a3a2e9d5a75e1 Mon Sep 17 00:00:00 2001 From: "mathias.chouet" <mathias.chouet@irstea.fr> Date: Wed, 5 Jun 2019 15:51:42 +0200 Subject: [PATCH] =?UTF-8?q?Ajout=20composant=20et=20=C3=A9l=C3=A9ment=20de?= =?UTF-8?q?=20formulaire=20pour=20la=20nouvelle=20Pab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/app.module.ts | 2 + src/app/calculators/pab/pab.config.json | 45 +----- .../field-set/field-set.component.ts | 6 +- .../calculator.component.html | 8 +- .../calculator.component.ts | 19 +-- .../pab-table/pab-table.component.html | 15 ++ .../pab-table/pab-table.component.scss | 25 +++ .../pab-table/pab-table.component.ts | 153 ++++++++++++++++++ .../definition/concrete/form-pab.ts | 129 +++++---------- .../formulaire/definition/form-definition.ts | 11 ++ src/app/formulaire/pab-table.ts | 18 +++ src/locale/messages.en.json | 1 + src/locale/messages.fr.json | 1 + 13 files changed, 293 insertions(+), 140 deletions(-) create mode 100644 src/app/components/pab-table/pab-table.component.html create mode 100644 src/app/components/pab-table/pab-table.component.scss create mode 100644 src/app/components/pab-table/pab-table.component.ts create mode 100644 src/app/formulaire/pab-table.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 69328bd5f..25698c7d5 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -76,6 +76,7 @@ import { VarResultsComponent } from "./components/fixedvar-results/var-results.c import { LogEntryComponent } from "./components/log-entry/log-entry.component"; import { ParamLinkComponent } from "./components/param-link/param-link.component"; import { SelectModelFieldLineComponent } from "./components/select-model-field-line/select-model-field-line.component"; +import { PabTableComponent } from "./components/pab-table/pab-table.component"; import { DialogConfirmEmptySessionComponent } from "./components/dialog-confirm-empty-session/dialog-confirm-empty-session.component"; import { DialogConfirmCloseCalcComponent } from "./components/dialog-confirm-close-calc/dialog-confirm-close-calc.component"; @@ -176,6 +177,7 @@ const appRoutes: Routes = [ NgParamInputComponent, PabResultsComponent, PabResultsTableComponent, + PabTableComponent, ParamComputedComponent, ParamFieldLineComponent, ParamLinkComponent, diff --git a/src/app/calculators/pab/pab.config.json b/src/app/calculators/pab/pab.config.json index 443720b8d..197b72eba 100644 --- a/src/app/calculators/pab/pab.config.json +++ b/src/app/calculators/pab/pab.config.json @@ -24,46 +24,7 @@ ] }, { - "id": "fs_bassin", - "type": "fieldset_template", - "calcType": "PabCloisons", - "option": "fix", - "fields": [ - { - "id": "select_modele_cloisons", - "type": "select_cloisons", - "select": [] - }, - { - "type": "input", - "id": "QA", - "unit": "m³/s" - } - ] - }, - { - "id": "bassin_container", - "type": "template_container", - "templates": [ - "fs_bassin" - ] - }, - { - "id": "fs_cloison_aval", - "type": "fieldset", - "calcType": "Pab", - "fields": [ - { - "id": "select_modele_cloison_aval", - "type": "select_cloison_aval", - "select": [] - } - ] - }, - { - "type": "options", - "modeleCloisonsSelectId": "select_modele_cloisons", - "modeleCloisonAvalSelectId": "select_modele_cloison_aval", - "idCal": "Q" + "id": "tableau_de_la_mort", + "type": "pab_table" } -] \ No newline at end of file +] diff --git a/src/app/components/field-set/field-set.component.ts b/src/app/components/field-set/field-set.component.ts index ee5e3de85..16fa303ad 100644 --- a/src/app/components/field-set/field-set.component.ts +++ b/src/app/components/field-set/field-set.component.ts @@ -211,15 +211,17 @@ export class FieldSetComponent implements DoCheck { * détermine si un Field est du type SelectFieldModel */ private isSelectModelField(f: Field): boolean { - return ( + /* return ( f instanceof SelectFieldModel && f.parentForm instanceof FormulairePab && ( f.id === (f.parentForm as FormulairePab).modeleCloisonsSelectId || f.id === (f.parentForm as FormulairePab).modeleCloisonAvalSelectId ) - ); + ); */ + return false; // tmp patch } + /* * gestion des événements clic sur les radios : * réception d'un message du composant enfant (param-field) diff --git a/src/app/components/generic-calculator/calculator.component.html b/src/app/components/generic-calculator/calculator.component.html index 7356b4b5a..72238c220 100644 --- a/src/app/components/generic-calculator/calculator.component.html +++ b/src/app/components/generic-calculator/calculator.component.html @@ -39,14 +39,18 @@ <ng-template ngFor let-fe [ngForOf]="formElements"> <field-set *ngIf="isFieldset(fe)" [style.display]="getFieldsetStyleDisplay(fe.id)" [fieldSet]=fe - (radio)=onRadioClick($event) (validChange)=OnFieldsetValid() (inputChange)=onInputChange($event) + (radio)=onRadioClick($event) (validChange)=onElementValid() (inputChange)=onInputChange($event) (tabPressed)="onTabPressed($event)"> </field-set> <fieldset-container *ngIf="isFieldsetContainer(fe)" [_container]=fe (radio)=onRadioClick($event) - (validChange)=onFieldsetContainerValid() (inputChange)=onInputChange($event) + (validChange)=onElementValid() (inputChange)=onInputChange($event) (tabPressed)="onTabPressed($event)"> </fieldset-container> + + <pab-table *ngIf="isPabTable(fe)" [pabTable]=fe (radio)=onRadioClick($event) + (validChange)=onElementValid() (inputChange)=onInputChange($event)> + </pab-table> </ng-template> <mat-card-actions> diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts index a83ea6303..3b65cc69b 100644 --- a/src/app/components/generic-calculator/calculator.component.ts +++ b/src/app/components/generic-calculator/calculator.component.ts @@ -21,6 +21,7 @@ import { MatDialog } from "@angular/material"; import { DialogConfirmCloseCalcComponent } from "../dialog-confirm-close-calc/dialog-confirm-close-calc.component"; import { DialogGeneratePABComponent } from "../dialog-generate-pab/dialog-generate-pab.component"; import { SelectFieldModel } from "../../formulaire/select-field-model"; +import { PabTable } from "../../formulaire/pab-table"; @Component({ selector: "hydrocalc", @@ -131,6 +132,13 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit, return fe instanceof FieldsetContainer; } + /** + * détermine si un FormulaireElement est du type PabTable + */ + public isPabTable(fe: any): boolean { + return fe instanceof PabTable; + } + public get hasForm() { return this._formulaire !== undefined; } @@ -354,16 +362,9 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit, } /** - * réception d'un événement de validité d'un FieldSetComponent - */ - public OnFieldsetValid() { - this.updateUIValidity(); - } - - /** - * réception d'un événement de validité d'un FieldsetContainerComponent + * réception d'un événement de validité d'un FormElement */ - public onFieldsetContainerValid() { + public onElementValid() { this.updateUIValidity(); } diff --git a/src/app/components/pab-table/pab-table.component.html b/src/app/components/pab-table/pab-table.component.html new file mode 100644 index 000000000..7eba1ea01 --- /dev/null +++ b/src/app/components/pab-table/pab-table.component.html @@ -0,0 +1,15 @@ +<mat-card-header class="bg-accent-light"> + <mat-card-title> + {{ title }} + </mat-card-title> +</mat-card-header> + +<mat-card-content> + Ici le tableau méga guedin ! + <!-- <field-set *ngFor="let fs of fieldsets" class="fieldset-inner" [fieldSet]=fs + (radio)=onRadioClick($event) (validChange)=onFieldsetValid() (inputChange)=onInputChange($event) + (addFieldset)=onAddFieldset($event) (removeFieldset)=onRemoveFieldset($event) + (moveFieldsetDown)=onMoveFieldsetDown($event) (moveFieldsetUp)=onMoveFieldsetUp($event) + (tabPressed)="onTabPressed($event)"> + </field-set> --> +</mat-card-content> diff --git a/src/app/components/pab-table/pab-table.component.scss b/src/app/components/pab-table/pab-table.component.scss new file mode 100644 index 000000000..fcc76e896 --- /dev/null +++ b/src/app/components/pab-table/pab-table.component.scss @@ -0,0 +1,25 @@ +:host { + display: block; + // reduce margins to avoid inner field-sets being too narrow on 360px display + margin-left: -8px; + margin-right: -8px; +} + +mat-card-header { + margin-left: -8px; + margin-right: -8px; + padding-left: 16px; + padding-top: 8px; + color: white; + + // Pourquoi n'est-ce pas hérité de calculator.component.scss ? + // À cause de la surcharge de mat-card-header ci-dessus ? + mat-card-title { + font-size: 16px; + margin-bottom: 8px; + } +} + +mat-card-content { + margin-top: 1em; +} diff --git a/src/app/components/pab-table/pab-table.component.ts b/src/app/components/pab-table/pab-table.component.ts new file mode 100644 index 000000000..695c33fc3 --- /dev/null +++ b/src/app/components/pab-table/pab-table.component.ts @@ -0,0 +1,153 @@ +import { Component, Input, Output, EventEmitter, QueryList, ViewChildren, DoCheck, AfterViewInit } from "@angular/core"; + +import { FieldSetComponent } from "../field-set/field-set.component"; +import { I18nService } from "../../services/internationalisation/internationalisation.service"; +import { PabTable } from "../../formulaire/pab-table"; + +/** + * The big editable data grid for calculator type "Pab" (component) + */ +@Component({ + selector: "pab-table", + templateUrl: "./pab-table.component.html", + styleUrls: [ + "./pab-table.component.scss" + ] +}) +export class PabTableComponent implements DoCheck, AfterViewInit { + + @Input() + private pabTable: PabTable; + + /** + * liste des composants FieldSet enfants + */ + @ViewChildren(FieldSetComponent) + private _fieldsetComponents: QueryList<FieldSetComponent>; + + /** + * flag de validité des FieldSet enfants + */ + private _isValid = false; + + /** + * événément de changement d'état d'un radio + */ + // tslint:disable-next-line:no-output-on-prefix + @Output() + private radio = new EventEmitter<any>(); + + /** + * événément de changement de validité + */ + @Output() + private validChange = new EventEmitter(); + + /** + * événément de changement de valeur d'un input + */ + @Output() + private inputChange = new EventEmitter(); + + /** événement signalant un appui sur TAB ou SHIFT+TAB */ + @Output() + protected tabPressed = new EventEmitter<any>(); + + public constructor( + private i18nService: I18nService + ) { } + + public get title(): string { + return this.i18nService.localizeText("INFO_PAB_TABLE"); + } + + public get isValid() { + return this._isValid; + } + + /** + * Ajoute un nouveau sous-nub (Structure, PabCloisons selon le cas) + * dans un nouveau fieldset + */ + /* private addSubNub(after?: FieldSet, clone: boolean = false) { + if (after) { + const newFs = this._container.addFromTemplate(0, after.indexAsKid()); + if (clone) { + // replace in-place to change properties (overkill) + newFs.setPropValue("structureType", after.properties.getPropValue("structureType")); + newFs.setPropValue("loiDebit", after.properties.getPropValue("loiDebit")); + // after.nub.properties + for (const p of after.nub.prms) { + newFs.nub.getParameter(p.symbol).singleValue = p.singleValue; + } + } + } else { + this._container.addFromTemplate(0); + } + } */ + + public ngAfterViewInit() { + /* this.onFieldsetListChange(); + this._fieldsetComponents.changes.subscribe(_ => this.onFieldsetListChange()); */ + } + + public ngDoCheck() { + this.updateValidity(); + } + + /** + * calcul de la validité de tous les FieldSet de la vue + */ + private updateValidity() { + this._isValid = false; + + /* if (this._fieldsetComponents !== undefined) { + this._isValid = this._fieldsetComponents.reduce( + // callback + ( + // accumulator (valeur précédente du résultat) + acc, + // currentValue (élément courant dans le tableau) + fieldset, + // currentIndex (indice courant dans le tableau) + currIndex, + // array (tableau parcouru) + array + ) => { + return acc && fieldset.isValid; + } + // valeur initiale + , this._fieldsetComponents.length > 0); + } */ + + this.validChange.emit(); + } + + /** + * réception d'un événement de changement de valeur d'un input + */ + private onInputChange($event) { + this.inputChange.emit($event); + } + + /** + * relit les valeurs dans l'interface et met à jour les NgParameter + */ + public updateParametersFromUI() { + this._fieldsetComponents.forEach(fsc => fsc.updateParametersFromUI()); + } + + /** + * met à jour les paramètres liés + */ + public updateLinkedParameters() { + this._fieldsetComponents.forEach(fsc => fsc.updateLinkedParameters()); + } + + /** + * Renvoie l'événement au composant du dessus + */ + public onTabPressed(event) { + console.log("tab pressed dans le tablo !"); + } +} diff --git a/src/app/formulaire/definition/concrete/form-pab.ts b/src/app/formulaire/definition/concrete/form-pab.ts index 18b63e068..c8dce277d 100644 --- a/src/app/formulaire/definition/concrete/form-pab.ts +++ b/src/app/formulaire/definition/concrete/form-pab.ts @@ -1,11 +1,11 @@ -import { Nub, Props, Session, PabCloisons, Pab } from "jalhyd"; +import { /* Nub, Props, Session, PabCloisons, */ Pab } from "jalhyd"; -import { FieldsetContainer } from "../../fieldset-container"; +/* import { FieldsetContainer } from "../../fieldset-container"; import { FieldSet } from "../../fieldset"; import { SelectField } from "../../select-field"; import { NgParameter } from "../../ngparam"; import { FieldsetTemplate } from "../../fieldset-template"; -import { FormulaireNode } from "../../formulaire-node"; +import { FormulaireNode } from "../../formulaire-node"; */ import { FormulaireBase } from "./form-base"; import { FormComputePab } from "../form-compute-pab"; import { FormResultPab } from "../form-result-pab"; @@ -17,10 +17,10 @@ import { FormResultPab } from "../form-result-pab"; export class FormulairePab extends FormulaireBase { /** id du select configurant le modèle de cloisons */ - private _modeleCloisonsSelectId: string; + // private _modeleCloisonsSelectId: string; /** id du select configurant le modèle de la cloison aval */ - private _modeleCloisonAvalSelectId: string; + // private _modeleCloisonAvalSelectId: string; constructor() { super(); @@ -31,71 +31,70 @@ export class FormulairePab extends FormulaireBase { this._formCompute = new FormComputePab(this, (this._formResult as FormResultPab)); } - public get modeleCloisonsSelectId(): string { + private get pabNub(): Pab { + return this.currentNub as Pab; + } + + public doCompute() { + this.dumpPabStructure(this.pabNub); + super.doCompute(); + } + + /* public get modeleCloisonsSelectId(): string { return this._modeleCloisonsSelectId; } public get modeleCloisonAvalSelectId(): string { return this._modeleCloisonAvalSelectId; - } + } */ /** * Creates a virgin PabCloisons when a new fieldset is added through the GUI, * to ensure consistency; this object is not related to any Cloisons */ - private createDummyPabCloisons(templ: FieldsetTemplate): Nub { + /* private createDummyPabCloisons(templ: FieldsetTemplate): Nub { const params = {}; params["calcType"] = templ.calcTypeFromConfig; return this.createBassin(new Props(params)); - } + } */ /** * ajoute un Nub PabCloisons * @param after position après laquelle insérer la structure, à la fin sinon */ - private addPabCloisons(st: PabCloisons, after?: number) { + /* private addPabCloisons(st: PabCloisons, after?: number) { this.pabNub.addChild(st, after); - } - - private get pabNub(): Pab { - return this.currentNub as Pab; - } + } */ /** * Asks JaLHyd to create a PabCloisons Nub as a child of the current Calculator Module * and return it; does not store it in the Session (for PabCloisons, not for Calculator Modules) * @param p properties for the new Nub */ - protected createBassin(p: Props): PabCloisons { + /* protected createBassin(p: Props): PabCloisons { return Session.getInstance().createNub(p, this.currentNub as Pab) as PabCloisons; - } + } */ /** * Replaces the current Nub in the current calculator module, with a new one built with properties "params" * @param params properties to build the new Nub (calcType, loiDebit...) */ - protected replaceNub(sn: PabCloisons, params: Props): Nub { + /* protected replaceNub(sn: PabCloisons, params: Props): Nub { const parent = (this.currentNub as Pab); const newBassin = this.createBassin(params); parent.replaceChildInplace(sn, newBassin); return newBassin; - } + } */ /** * Deleted the given child Nub in the current calculator module * @param params properties to build the new Nub (calcType, loiDebit...) */ - protected deleteNub(sn: PabCloisons) { + /* protected deleteNub(sn: PabCloisons) { const parent = (this.currentNub as PabCloisons); parent.deleteChild(parent.getIndexForChild(sn)); - } - - public doCompute() { - this.dumpPabStructure(this.pabNub); - super.doCompute(); - } - - public createFieldset(parent: FormulaireNode, json: {}, data?: {}, nub?: Nub): FieldSet { + } */ + /* public createFieldset(parent: FormulaireNode, json: {}, data?: {}, nub?: Nub): FieldSet { if (json["calcType"] === "PabCloisons") { // indice après lequel insérer le nouveau FieldSet const after = data["after"]; @@ -122,17 +121,17 @@ export class FormulairePab extends FormulaireBase { } else { return super.createFieldset(parent, json, data); } - } + } */ - protected parseOptions(json: {}) { + /* protected parseOptions(json: {}) { super.parseOptions(json); // id du select configurant les modèles de cloisons this._modeleCloisonsSelectId = this.getOption(json, "modeleCloisonsSelectId"); // id du select configurant le modèle de cloison aval this._modeleCloisonAvalSelectId = this.getOption(json, "modeleCloisonAvalSelectId"); - } + } */ - public afterParseFieldset(fs: FieldSet) { + /* public afterParseFieldset(fs: FieldSet) { // si le FieldSet contient le select de modèles de cloisons if (this._modeleCloisonsSelectId) { const node = fs.getFormulaireNodeById(this._modeleCloisonsSelectId); @@ -151,81 +150,41 @@ export class FormulairePab extends FormulaireBase { fs.properties.addObserver(this); } } - } - - public moveFieldsetUp(fs: FieldSet) { - if (fs.nub instanceof PabCloisons) { - // déplacement du nub - fs.nub.parent.moveChildUp(fs.nub); - // déplacement du fieldset - this.fieldsetContainer.moveFieldsetUp(fs); - - this.resetResults(); - } else { - super.moveFieldsetUp(fs); - } - } - - public moveFieldsetDown(fs: FieldSet) { - if (fs.nub instanceof PabCloisons) { - // déplacement du nub - fs.nub.parent.moveChildDown(fs.nub); - // déplacement du fieldset - this.fieldsetContainer.moveFieldsetDown(fs); - - this.resetResults(); - } else { - super.moveFieldsetDown(fs); - } - } + } */ - public removeFieldset(fs: FieldSet) { - if (fs.nub instanceof PabCloisons) { - // suppression du sous-nub dans le Nub parent - this.deleteNub(fs.nub); - - // suppression du fieldset - this.fieldsetContainer.removeFieldset(fs); - - this.resetResults(); - } else { - super.removeFieldset(fs); - } - } - - protected completeParse(json: {}) { + /* protected completeParse(json: {}) { this.subscribeFieldsetContainer(); - } + } */ - private get fieldsetContainer(): FieldsetContainer { + /* private get fieldsetContainer(): FieldsetContainer { const n = this.getFormulaireNodeById("bassin_container"); if (n === undefined || !(n instanceof FieldsetContainer)) { throw new Error("l'élément 'bassin_container' n'est pas du type FieldsetContainer"); } return n as FieldsetContainer; - } + } */ /** * abonnement en tant qu'observateur du FieldsetContainer */ - private subscribeFieldsetContainer() { + /* private subscribeFieldsetContainer() { this.fieldsetContainer.addObserver(this); - } + } */ /** * abonnement en tant qu'observateur des NgParameter des FieldSet contenus dans le FieldsetContainer */ - private subscribeBasinFields(fs: FieldSet) { + /* private subscribeBasinFields(fs: FieldSet) { for (const n of fs.allFormElements) { if (n instanceof NgParameter || n instanceof SelectField) { n.addObserver(this); } } - } + } */ // interface Observer - public update(sender: any, data: any) { + /* public update(sender: any, data: any) { super.update(sender, data); @@ -266,12 +225,12 @@ export class FormulairePab extends FormulaireBase { break; } } - } + } */ // debug method private dumpPabStructure(pab: Pab) { console.log(`PAB: ${pab.uid}, ${pab.children.length} children`); - for (const c of pab.children) { + /* for (const c of pab.children) { console.log( ` * child: ${c.uid}, based on ${c.properties.getPropValue("modeleCloisons")}` + ` (cote amont ${c.prms.Z1.singleValue}, longueur ${c.prms.LB.singleValue})` @@ -279,6 +238,6 @@ export class FormulairePab extends FormulaireBase { } if (pab.downWall) { console.log(`+ downstream wall: ${pab.downWall.uid}`); - } + } */ } } diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts index 859cad4fb..6569081e7 100644 --- a/src/app/formulaire/definition/form-definition.ts +++ b/src/app/formulaire/definition/form-definition.ts @@ -13,6 +13,7 @@ import { DeepFormulaireElementIterator } from "../form-iterator/deep-element-ite import { TopFormulaireElementIterator } from "../form-iterator/top-element-iterator"; import { CalculatorResults } from "../../results/calculator-results"; import { ServiceFactory } from "../../services/service-factory"; +import { PabTable } from "../pab-table"; /** * classe de base pour tous les formulaires @@ -187,6 +188,12 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs this.kids.push(fsc); } + private parse_pab_table(json: {}) { + const tab: PabTable = new PabTable(this); + tab.parseConfig(json); + this.kids.push(tab); + } + public parseDependencies(json: {}) { // tslint:disable-next-line:forin for (const conf_index in json) { @@ -271,6 +278,10 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs this.parse_template_container(conf, templates); break; + case "pab_table": // not generic at all + this.parse_pab_table(conf); + break; + default: throw new Error(`type d'objet de module de calcul ${type} non pris en charge`); } diff --git a/src/app/formulaire/pab-table.ts b/src/app/formulaire/pab-table.ts new file mode 100644 index 000000000..1591b4a18 --- /dev/null +++ b/src/app/formulaire/pab-table.ts @@ -0,0 +1,18 @@ +import { FormulaireElement } from "./formulaire-element"; +import { FormulaireNode } from "./formulaire-node"; + +/** + * The big editable data grid for calculator type "Pab" (model) + */ +export class PabTable extends FormulaireElement { + + constructor(parent: FormulaireNode) { + super(parent); + } + + public parseDependencies(json: {}) { } // implements abstract method of FormulaireNode + + public parseConfig(json: {}) { + this._confId = json["id"]; + } +} diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json index 18aff4ce8..88bc01abe 100644 --- a/src/locale/messages.en.json +++ b/src/locale/messages.en.json @@ -195,6 +195,7 @@ "INFO_OUVRAGE": "Structure", "INFO_PAB_TITRE_COURT": "Fish ladder", "INFO_PAB_TITRE": "Fish ladder", + "INFO_PAB_TABLE": "Fish ladder geometry", "INFO_PABCHUTE_TITRE_COURT": "FL: fall", "INFO_PABCHUTE_TITRE": "Fish ladder: fall", "INFO_PABDIMENSIONS_TITRE_COURT": "FL: dimensions", diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json index 1f1db9001..9e7f4dd5c 100644 --- a/src/locale/messages.fr.json +++ b/src/locale/messages.fr.json @@ -195,6 +195,7 @@ "INFO_OUVRAGE": "Ouvrage", "INFO_PAB_TITRE_COURT": "PAB", "INFO_PAB_TITRE": "Passe à bassins", + "INFO_PAB_TABLE": "Géométrie de la passe", "INFO_PABCHUTE_TITRE_COURT": "PAB : chute", "INFO_PABCHUTE_TITRE": "Passe à bassins : chute", "INFO_PABDIMENSIONS_TITRE_COURT": "PAB : dimensions", -- GitLab