Skip to content
Snippets Groups Projects
DEVELOPERS.md 21.8 KiB
Newer Older
# Documentation ngHyd pour les développeurs

Voir aussi : 

 * [ngHyd installation instructions](README.md)
 * [JaLHyd developers documentation](https://gitlab.irstea.fr/cassiopee/jalhyd/blob/master/DEVELOPERS.md)

## Description

ngHyd est une interface Web pour [JaLHyd](https://gitlab.irstea.fr/cassiopee/jalhyd) écrite en Angular/typescript.

Elle est déclinée à l'aide d'Electron et Cordova sous forme d'application hors-ligne pour Linux, Mac, Windows et Android.

Les tests unitaires sont réalisés avec Protractor.

La documentation est générée avec Mkdocs, pandoc et LaTeX.

## Pile logicielle

 * typescript
 * angular
 * angular-material
 * protractor
 * electron
 * cordova
 * mkdocs
 * pandoc
 * LaTeX

## Prérequis

### pour le développement

 * nodejs / npm
 * python (pour mkdocs)
 * wine (pour Electron / windows)
 * java (pour cordova / Android)
 * SDK Android (pour cordova / Android)
 * pandoc (pour la documentation PDF)
 * une distribution LaTeX, par exemple texlive (pour la documentation PDF)

### pour l'exécution

 * version Web : une connexion Internet et un navigateur à jour (Firefox, Chrome/Chromium, Safari, Edge…)
 * version hors-ligne : Linux, MacOS, Windows 7 ou plus récent, Android 7 ou plus récent
 
## Grands principes

### interface

ngHyd propose deux vues principales : la **page d'accueil** `/list` qui permet de créer de nouveaux modules de calcul, et la **vue module** `/calculator/{id}` qui affiche un des modules de la session.

Une **barre de navigation** en haut de l'écran permet de passer d'un module à l'autre, et de revenir à la page d'accueil.

Un **volet latéral** permet d'accéder à des commandes et vues supplémentaires.

L'interface s'appuie sur les principes de **material design** et de **responsive design**. Le contenu est supposé s'afficher correctement à partir d'une largeur d'écran de **360px**.

### modèle

L'état de la session, des modules, des paramètres et des résultats numériques est conservé par JaLHyd. Seules les représentations graphiques sont conservées par la couche ngHyd.

Pour chaque module chargé en session, un arbre d'objets partant d'une instance de `FormulaireDefinition` représente la structure des éléments à l'écran : groupes de champs (répétables ou non), champs, listes déroulantes. Ces éléments sont déclarés dans les fichiers du dossier `src/app/formulaire`.
#### formulaires
Les formulaires dérivent de `FormulaireDefinition`. Lors de leur instanciation, ils se chargent de lire leur configuration (voir "configuration/modules" ci-dessous) et instancier les éléments enfants qui y sont déclarés : groupes de champs ("fieldset"), groupes de champs répétables ("fieldset_container"), paramètres d'entrée ("ngparam"), listes déroulantes ("select_field"). Ensuite le formulaire est lié au Nub de JaLHyd correspondant au module créé.

### configuration

#### générale

mathias.chouet's avatar
mathias.chouet committed
La configuration générale se trouve dans `src/app/config.json`.

Dans `params` on trouve les valeurs par défaut des réglages.

mathias.chouet's avatar
mathias.chouet committed
Dans `themes` on trouve l'organisation des modules en thèmes sur la page d'accueil. Dans chaque thème, la clé `calculators` doit contenir une liste de valeurs numériques correspondant à des éléments de l'enum `CalculatorType` de JaLHyd. Un même module peut être présent dans plusieurs thèmes. Si un module n'est présent dans aucun thème, il apparaîtra automatiquement dans un thème "autres" qui n'est visible que dans un tel cas.
Chaque module de calcul de JaLHyd doit être configuré dans `src/app/calculators`, dans un dossier portant le nom du module.
Le fichier de configuration principal est `src/app/calculators/lemodule/config.json` (exemple pour le module "bief" : `src/app/calculators/bief/config.json`). Il contient la liste des composants graphiques à afficher à l'écran, de haut en bas (généralement des "fieldset"), la répartition des paramètres du module dans ces éléments, et un bloc de configuration à la fin.

Pour chaque langue, un fichier supplémentaire est présent contenant les traductions contextualisées pour le module (voir "langues et traduction" plus bas).

### exemples de sessions

mathias.chouet's avatar
mathias.chouet committed
Des exemples de sessions prêtes à charger sont présents sous forme de fichiers JSON dans `src/app/examples`. Lors de l'ajout d'un nouveau fichier d'exemple, celui-ci doit être référencé dans la méthode `get exampleFiles()` dans le fichier `src/app/components/calculator-list.component.ts`. Ces exemples sont affichés sur la page d'accueil dans la dernière carte, lorsqu'aucun module n'a encore été créé.

### langues et traduction

Les chaînes de l'application sont traduites à l'aide du service `I18nService`, méthodes `localizeText()` pour les chaînes en général et `localizeMessage()` pour les messages de log de JaLHyd.

mathias.chouet's avatar
mathias.chouet committed
Les traductions doivent être placées dans les fichiers de langues du dossier `src/locale`.
Lorsqu'un module de calcul a besoin d'une traduction contextualisée, par exemple dans le cas d'une variable ayant le même symbole que dans d'autres modules de calcul, les traductions doivent être placées dans `src/app/calculators/lemodule/lalangue.json` (exemple pour le module "bief" en anglais : `src/app/calculators/bief/en.json`).

### electron

Le déploiement de ngHyd sous forme d'application de bureau se fait à l'aide d'Electron. Les fichiers concernés sont `main.js`, `electron-builder.yml`, les dossier `electron` et `release`.

mathias.chouet's avatar
mathias.chouet committed
`main.js` est le fichier de "bootstrap" d'Electron. Il contient la création de la fenêtre de l'application, et le mécanisme de détection de mises à jour. Pour débugger, on peut décommenter `win.webContents.openDevTools()`.

`electron-builder.yml` contient la configuration de l'empaquetage pour les différentes plateformes cibles.

Le dossier `electron` contient l'icône de l'application.

Le dossier `release` contient (entre autres) les paquets générés par electron-builder.

### cordova

mathias.chouet's avatar
mathias.chouet committed
Le déploiement de ngHyd sous forme d'application mobile se fait à l'aide de Cordova. Les fichiers et dossiers concernés sont `hooks`, `platforms`, `plugins`, `config.xml`, et les différentes icônes .png présentes dans `src`.

### documentation

La documentation est générée à l'aide de MkDocs (`npm run mkdocs`).
Les fichiers source pour une langue donnée se trouvent dans le dossier `docs/lang` (ex: `docs/fr`). Pour traduire la documentation dans une autre langue, il faut recopier l'intégralité des fichiers source puis les traduire. Afin de faciliter les liens de l'application vers la documentation, les noms des fichiers ne sont pas traduits et restent en français pour toutes les langues.

L'organisation hiérarchique de la documentation est définie dans les fichiers `mkdocs-lang.yml` (ex: `mkdocs-fr.yml`).

#### version PDF

La documentation de chaque langue est compilée au format PDF via pandoc, qui convertit les sources markdown vers du LaTeX. Cette conversion est réalisée par le script python 3 `mkdocs2pdf.py`.

Les préambules LaTeX pour chaque langue se trouvent dans `docs/latex`, par exemple `docs/latex/cassiopee_doc_fr.tex`. Les PDF générés sont placés dans `src/assets/docs/pdf`.

### tests unitaires

Les tests unitaires dits "end-to-end" ou "e2e" sont réalisés avec Protractor, basé sur Selenium et fourni avec Angular. Les tests se trouvent dans le dossier `e2e` et sont configurés dans `protractor.conf.js`.

Bien qu'elle soit supposée fonctionner avec d'autres navigateurs, l'exécution des tests n'est garantie qu'avec Chrome / Chromium. Le pilote Selenium pour Chrome ("chromedriver") posant parfois problème, `protractor.conf.js` contient une astuce qui recherche le pilote dans le système avant de le rechercher dans node_modules.

Pour plus d'informations sur les problèmes liés à la version du pilote Selenium pour Chrome, consulter le chapitre "chromedriver version in e2e tests" dans la documentation utilisateurs (en anglais).
### scripts

Le dossier `scripts` contient des scripts d'aide à la compilation, utilisés par les commandes `npm` déclarées dans `package.json`.

## Ajouter un module de calcul

Dans cet exemple nous considérerons le module fictif "Addition" proposé dans la documentation de JaLHyd (voir ci-dessous), tout en ajoutant des informations utiles pour des cas plus complexes.

### JaLHyd

Voir la [documentation JaLHyd pour les développeurs](https://gitlab.irstea.fr/cassiopee/jalhyd/blob/master/DEVELOPERS.md) (en anglais)

### configuration du module

Créer les fichiers de configuration du module de calcul :

Dans `src/app/calculators`, créer un dossier nommé comme la clé du type de calculateur (`CalculatorType`), en minuscules. Par exemple pour `CalculatorType.Addition`, on aura : `src/app/calculators/addition`.

Dans ce nouveau dossier, créer le fichier `config.json`, comme suit :
 
```json
[
    {
        "id": "fs_addition",
        "type": "fieldset",
        "fields": [
            "A",
            "B",
            "Y"
        ]
    },
    {
        "type": "options",
        "idCal": "Y",
        "help": "addition.html"
    }
]
```
 
Dans cet exemple, on définit un seul groupe de champs nommé arbitrairement "fs_addition", dans lequel on ajoute tous les paramètres de l'équation, désignés par leur symbole, qui doit correspondre au symbole fourni comme deuxième argument de `ParamDefinition()` dans JaLHyd.

Le deuxième et dernier bloc contient les options pour ce module: le paramètre à calculer par défaut (`idCal`) qui peut être différent de celui choisi dans JaLHyd, et le lien vers la page de documentation pour ce module (`help`).

Les options peuvent également contenir :
 * `defaultNodeType` : le type de section par défaut du module de calcul, pour les modules contenant une section
 * les valeurs par défaut des **propriétés** du module
 * les identifiants des listes déroulantes
 * l'aide associée aux résultats (voir ci-dessous)
mathias.chouet's avatar
mathias.chouet committed
#### champs

La liste fields contient les champs à afficher, dans l'ordre. Un champ peut être désigné par sa forme courte (son symbole seulement, ex: `"B"`) ou sa forme longue, comme suit :

```json
{
    "id": "B",
    "allowEmpty": false
}
```

Définir `allowEmpty` à `true` permet de lancer le calcul même si la valeur du paramètre est laissée vide. Cette option peut être omise si elle vaut `false` (par défaut).

#### aide contextuelle

Différents éléments de la configuration peuvent contenir une clé `help` donnant l'URL de l'aide pour l'élément de l'interface graphique concerné :

 * pour un champ de saisie numérique : `{... "type": "input", "id": "ID", "help": "aide.html", ...}` (remplace la forme courte `"ID"`)
 * pour n'importe quelle entrée d'un champ de type "select" : `{... "type": "select", "help": "aide.html", ...}`
 * pour des entrées spécifiques d'un champ de type "select" : `{... "type": "select", "help": { "IdDeLOption": "aide.html", ...}, ...}` (se référer au fichier de traduction du module pour la liste des ID des options, par exemple "SeuilTriangulaireTrunc_TriangularTruncWeirFree")
 * pour un groupe de champs ("fieldset") : `{... "type": "fieldset", "help": "aide.html", ...}`
mathias.chouet's avatar
mathias.chouet committed
 * pour tous les groupes de champs d'un groupe de champs répétable ("fieldset_template") : `{... "type": "fieldset_template", "help": "aide.html", ...}`
 * pour l'entête d'un groupe de champs répétables ("template_container") : `{... "type": "template_container", "help": "aide.html", ...}`
 * éventuellement l'URL de l'aide pour un résultat en particulier, dans le bloc d'options de la configuration :
	```json
	{
		"type": "options",
		
		"resultsHelp": {
			"HG": "devalaison/grille.html#hauteur-de-grille"
	}
	```
 
#### traduction

Dans le dossier de configuration `src/app/calculators/addition`, créer les fichiers d'internationalisation, par exemple `fr.json` pour le français. Il doivent reprendre tous les identifiants utilisés dans le fichier de configuration (paramètres, groupes de champs, listes déroulantes…) et fournir leur traduction ainsi que leur unité si nécessaire, comme suit :
mathias.chouet's avatar
mathias.chouet committed
  "fs_addition": "Paramètres de l'équation",
mathias.chouet's avatar
mathias.chouet committed
  "A": "Premier nombre",
  "B": "Deuxième nombre",
  "Y": "Somme",
mathias.chouet's avatar
mathias.chouet committed
  "UNIT_A": "",
  "UNIT_B": "",
  "UNIT_Y": ""
}
```

Ici les mentions `UNIT_*` pourraient être omises.

### thème

Dans `src/app/config.json`, ajouter si nécessaire le numéro de `CalculatorType` à un ou plusieurs thèmes afin de classer le module sur la page de liste; dans le cas contraire le nouveau module apparaîtra dans une section "Autres".

### traduction du titre et des messages de log

Dans les fichiers `locale/messages.*.json` :

 * ajouter deux champs pour le titre et le titre court du module de calcul. Par exemple pour un module "Addition" :
    * `"INFO_ADDITION_TITRE": "Addition de deux nombres"`
    * `"INFO_ADDITION_TITRE_COURT": "Addition"`
 * si le module produit des messages de log qui n'existaient pas jusqu'alors, ajouter leur traduction en utilisant la chaîne complète du code de message comme clé. Exemple :
    * `"ERROR_LOADING_SESSION": "Impossible de charger la session"`

### classes de formulaire personnalisées

En général la classe `FormulaireDefinition` est suffisante pour gérer un nouveau module, et est instanciée automatiquement par `FormulaireService` lors de la création d'un nouveau module.
Mais dans des cas plus complexes, par exemple si le module contient des listes déroulantes ou des sous-modules, répétables ou non, il est nécessaire de créer de nouvelles classes de formulaires dérivées de celles-ci.
Dans un tel cas, créer la classe du formulaire dans un nouveau fichier, dans le dossier `src/app/formulaire/definition` Par exemple `form-macrorugo-compound.ts`.
mathias.chouet's avatar
mathias.chouet committed
Si une nouvelle classe de formulaire a été créée, ajouter un `case` dans la méthode `newFormulaire()` de `FormulaireService`, dans `src/app/services/formulaire.service.ts`. Exemple :
```typescript
case CalculatorType.MacroRugoCompound:
  f = new FormulaireMacrorugoCompound();
### si le formulaire comprend des listes déroulantes

Les listes déroulantes sont toujours associées à des **propriétés** du Nub.

En général les valeurs autorisées sont tirées de l'**enum** correspondant, d'après le tableau `Session.enumFromProperty` de JaLHyd. Pour les autres cas, voir les paragraphes "si la liste est associée à…" ci-dessous.

#### configuration

Dans le fichier de configuration du module, ajouter la définition des listes déroulantes dans "fields", notamment la propriété associée et la valeur par défaut. Puis dans le bloc de configuration, déclarer les identifiants des listes dans "selectIds". Exemple dans `trigo/config.json`

```json
{
    
    "fields": [
        {
            "id": "select_operation",
            "type": "select",
            "property": "trigoOperation",
            "default": "COS"
        },
        {
            "id": "select_unit",
            "type": "select",
            "property": "trigoUnit",
            "default": "DEG"
        },
        
    ]
},

{
    "type": "options",
    "selectIds": [ "select_operation", "select_unit" ],
    
}
```

**IMPORTANT** : les id doivent être de la forme `select_`_`unmotclesansespacenitirets`_

#### si la liste n'est pas associée à un enum
 
Une liste déroulante peut être associée à une **source**, qui détermine quels sont les choix possibles.

Pour ajouter une source, modifier la méthode `loadEntriesFromSource()` de la classe `SelectField`, dans le fichier `src/app/formulaire/elements/select-field.ts`.

Exemple pour la source "remous_target" associée à la propriété "varCalc", dans le module CourbeRemous :

```typescript
switch (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;
```

Ici on ajoute des options de type `SelectEntry` à l'aide de la méthode `addEntry()` : une option vide au début, puis une pour chaque élément d'un tableau.

Puis dans le fichier de configuration du module, déclarer la source :

```json
{
    "id": "select_target",
    "type": "select",
    "property": "varCalc",
    "source": "remous_target",
    
```

#### si l'affichage de certains champs dépend du choix dans la liste

Les listes dont l'identifiant est déclaré dans le fichier de configuration du module déclencheront, lorsque leur valeur change, un effacement des résultats du module et une mise à jour de tous les "fieldset" du formulaire.

Cette dernière opération permet de vérifier la visibilité de chaque champ du formulaire, et afficher / masquer ceux dont la visibilité a changé.

Ainsi, pour rendre la visibilité d'un champ dépendante du choix dans la liste, il faut, **dans le code du Nub dans JaLHyd** :

 * écouter le changement de propriété (méthode `update()`, action `propertyChange`)
 * selon la nouvelle valeur, ajuster la propriété `.visible` des paramètres concernés
 
 Il n'y a rien à faire de particulier dans ngHyd.

### si le module agrège une section

Il faut utiliser ou étendre `FormulaireSection`.

Dans la configuration du module, ajouter un sélecteur de section, associé à la propriété "nodeType" :

```json
{
    "id": "select_section",
    "type": "select",
    "property": "nodeType",
    "help": {
        "1": "hsl/types_sections.html#section-rectangulaire",
        "0": "hsl/types_sections.html#section-circulaire",
        "2": "hsl/types_sections.html#section-trapezoidale",
        "3": "hsl/types_sections.html#section-parabolique"
    }
}
```

Puis dans les options, déclarer  ce sélecteur et ajouter "defaultNodeType" :

```json
{
    "type": "options",
    "defaultNodeType": "SectionRectangle",
    "selectIds": [ "select_section" ],
    
}
```

### si le module agrège des modules enfants

La traduction des variables des modules enfants doit aussi être ajoutée dans les fichiers de langues, dans le dossier de configuration du module.

mathias.chouet's avatar
mathias.chouet committed
#### si ces modules enfants sont répétables ("fs_container")
Il faut utiliser ou étendre `FormulaireRepeatableFieldset`.
Dans la configuration du module, créer un "fieldset_template" en donnant le type des Nubs enfants dans la propriété "calcType", et créer un "template_container" associé à ce "fieldset_template" (exemple pour SPP) :

```json
{
    "id": "fs_yaxn",
        "type": "fieldset_template",
        "calcType": "YAXN",
        "fields": [
        	
},
{
    "id": "yaxn_container",
    "type": "template_container",
    "templates": [
        "fs_yaxn"
    ]
}
```

Dans la méthode `create()` de `CalculatorListComponent`, dans le fichier `src/app/components/calculator-list/calculator-list.component.ts`, ajouter la création d'un enfant par défaut. Exemple pour `MacrorugoCompound` :

```typescript
if (f instanceof FormulaireMacrorugoCompound) {
  for (const e of f.allFormElements) {
    if (e instanceof FieldsetContainer) {
        e.addFromTemplate(0, 0, f.mrcNub.children[0]);
        break;
    }
  }
}
```

Dans cet exemple, on ajoute l'interface pour le premier enfant du Nub (instancié par JaLHyd), à l'élément de formulaire de type `FieldsetContainer` (ici, il n'y en a qu'un).

Ajouter ensuite la création de fieldsets pour les enfants existants, dans la méthode `createFormulaire()` de `FormulaireService`, dans le fichier `src/app/services/formulaire.service.ts`. Exemple pour `ParallelStructures` :

```typescript
if (f.currentNub instanceof ParallelStructure) {
  for (const struct of f.currentNub.structures) {
    for (const e of f.allFormElements) {
      if (e instanceof FieldsetContainer) {
        e.addFromTemplate(0, undefined, struct);
      }
    }
  }
}
```

mathias.chouet's avatar
mathias.chouet committed
Dans chaque fichier de langue du dossier `src/locale`, ajouter les traductions pour le nom du type d'enfant (voir la documentation développeurs de JaLHyd), au singulier et au pluriel sous les clés `INFO_CHILD_TYPE_typedenfant` et `INFO_CHILD_TYPE_typedenfant_PLUR`. Par exemple pour le type d'enfant `Macrorugo` en français :

```json
"INFO_CHILD_TYPE_MACRORUGO": "radier",
"INFO_CHILD_TYPE_MACRORUGO_PLUR": "radiers",
```

#### si ces enfants sont des structures avec des lois de débit
Il faut utiliser ou étendre `FormulaireParallelStructure` (ex: Cloisons, Dever…).
Dans la configuration du module, dans le "fieldset_template", ajouter un sélecteur de structure associé à la propriété "structureType" et un sélecteur de loi de débit associé à la propriété  "loiDebit", noter les propriétés "calcType" (toujours "Structure" dans ce cas), "defaultStructType" et "defaultLoiDebit" :
    "id": "fs_ouvrage",
    "type": "fieldset_template",
    "calcType": "Structure",
    "defaultStructType": "VanneRectangulaire",
    "defaultLoiDebit": "GateCem88v",
    "fields": [
        {
            "id": "select_structure",
            "type": "select",
            "property": "structureType",
            "source": "device_structure_type"
        },
        {
            "id": "select_loidebit",
            "type": "select",
            "property": "loiDebit",
            "source": "device_loi_debit"
        }
Dans les options, déclarer  les sélecteurs :
    "type": "options",
    "selectIds": [ "select_structure", "select_loidebit" ],
    
}
### documentation

Pour chaque langue, ajouter un fichier .md dans les dossiers `docs/*/calculators`, puis placer ce nouveau fichier dans la hiérarchie de la documentation, en ajoutant son chemin dans les fichiers `mkdocs-*.yml`.

Lier ce fichier au module via la clé `help` du bloc d'options de la configuration du module. Exemple pour un fichier de documentation dont le chemin est `calculators/math/addition.md` : `"help" : "math/addition.html"` (MkDocs convertit les fichiers MarkDown en HTML)
mathias.chouet's avatar
mathias.chouet committed
Plusieurs tests unitaires passent en revue les modules pour les tester un par un. Pour que le nouveau module soit testé, son `CalculatorType` doit être ajouté à la liste dans le fichier `e2e/tested_calctypes.ts`.