-
Guillaume Perréal authored6b9856f3
- Documentation ngHyd pour les développeurs
- Description
- Pile logicielle
- Prérequis
- pour le développement
- pour l'exécution
- Grands principes
- interface
- modèle
- formulaires
- configuration
- générale
- modules
- exemples de sessions
- langues et traduction
- electron
- PWA
- documentation
- version PDF
- tests unitaires
- scripts
- Ajouter un module de calcul
- JaLHyd
- configuration du module
- champs
- aide contextuelle
- traduction
- thème
- traduction du titre et des messages de log
- moteur de recherche
- classes de formulaire personnalisées
- si le formulaire comprend des listes déroulantes
- configuration
- liste déroulante personnalisée
- si l'affichage de certains champs dépend du choix dans la liste
- si le module agrège une section
- si le module agrège des modules enfants
- si ces modules enfants sont répétables ("fs_container")
- si ces enfants sont des structures avec des lois de débit
- documentation
- tests unitaires
Documentation ngHyd pour les développeurs
Voir aussi :
Description
ngHyd est une interface Web pour JaLHyd écrite en Angular/typescript.
Elle est déclinée à l'aide d'Electron et de l'ensemble de technologies PWA (Progressive Web Application dont les service workers) 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
- service workers
- mkdocs
- pandoc
- LaTeX
Prérequis
pour le développement
- nodejs / npm
- python (pour mkdocs)
- wine (pour Electron / windows)
- 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
La configuration générale se trouve dans src/app/config.json
.
Dans params
on trouve les valeurs par défaut des réglages.
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.
modules
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
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.
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
.
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 src/assets/icons/electron/
contient l'icône de l'application.
Le dossier release
contient (entre autres) les paquets générés par electron-builder.
PWA
Le déploiement de ngHyd sous forme d'application mobile se fait à l'aide de l'ensemble de technologies rassemblées sous le nom de Progressive Web Application.
Les fichiers et dossiers concernés sont ngsw-config.json
, src/manifest.webmanifest
(fichiers nouveaux), index.html
, angular.json
, src/app/app.module.ts
(fichiers modifiés), et les différentes icônes .png
présentes dans src/assets/icons
.
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 (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 :
[
{
"id": "fs_addition",
"type": "fieldset",
"fields": [
"A",
"B",
"Y"
]
},
{
"type": "options",
"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: ici uniquement le lien vers la page de documentation pour ce module (help
).
Les options peuvent également contenir :
-
resultsHelp
: l'aide associée aux résultats (voir ci-dessous) -
calculateDisabled
: pour masquer le bouton Calculer (ex: module Espèce)
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 :
{
"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", ...}
- 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 :
{ "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, comme suit :
{
"fs_addition": "Paramètres de l'équation",
"A": "Premier nombre",
"B": "Deuxième nombre",
"Y": "Somme"
}
Si le module produit des résultats dont l'unité ne peut être déduite du modèle, ajouter des mentions UNIT_*
comme suit :
{
…
"UNIT_V": "m/s",
"UNIT_YCOR": "m",
…
}
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"
- pour le moteur de recherche (voir ci-dessous), ajouter un champ contenant des mots-clés :
"INFO_ADDITION_DESCRIPTION": "maths mathematics add plus sum"
- 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"
moteur de recherche
Le moteur de recherche présent sur la page de liste des modules permet de filtrer les modules par mots-clés. Les termes saisis sont comparés avec les valeurs suivantes : titre, titre court, description (voir ci-dessus).
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
.
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 :
case CalculatorType.MacroRugoCompound:
f = new FormulaireMacrorugoCompound();
break;
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 le paragraphe "liste déroulante personnalisé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; cette dernière est optionnelle et en son absence, la 1ère valeur de la liste sera sélectionnée.
Exemple dans trigo/config.json
(ici le 2ème champ ne spécifie pas de valeur par défaut) :
{
…
"fields": [
{
"id": "select_operation",
"type": "select",
"property": "trigoOperation",
"default": "COS"
},
{
"id": "select_unit",
"type": "select",
"property": "trigoUnit",
},
…
]
},
IMPORTANT : les ids doivent être de la forme select_
unmotclesansespacenitirets
liste déroulante personnalisée
Il est possible de personnaliser complètement le comportement d'une liste déroulante pour des cas plus complexes.
De telles listes doivent être déclarées dans la configuration du module en utilisant uniquement le type select
et un identifiant de liste. Dans ce cas, on ne fournit pas de champ property
ni default
.
Exemple pour la cible du Solveur :
…
"fields": [
{
"type": "select",
"id": "select_target_nub",
},
…
]
…
Il faut ensuite faire 2 choses :
- créer une classe dérivée de
SelectField
et implémenter les méthodespopulate()
(remplissage de la liste) etinitSelectedValue()
(fixation de la valeur par défaut) :
export class SelectFieldSolverTarget extends SelectField {
protected populate() {
…
// une série de addEntry()
…
}
protected initSelectedValue() {
// utiliser setValueFromId() avec un id calculé
// ou findAndSetDefaultValue() pour utiliser la 1ère valeur de la liste
}
}
- modifier la méthode
newSelectField
de la classeSelectFieldFactory
pour créer une instance de la classe dérivée en utilisant le champid
précisé dans le JSON de configuration :
public static newSelectField(json: {}, parent: FormulaireNode): SelectField {
switch (json["id"]) {
…
case "select_target_nub":
return new SelectFieldSolverTarget(parent);
…
}
}
Enfin, ce type de liste déroulante peut nécessiter une classe de formulaire personnalisée, dans laquelle la méthode update()
doit être enrichie.
Exemple dans src/app/formulaire/definition/form-solveur.ts
pour la cible du Solveur :
public update(sender: IObservable, data: any) {
…
if (sender instanceof SelectField) {
if (sender.id === "select_target_nub" && data.action === "select") {
// update Solveur property: Nub to calculate
try {
// if searchedParam is set to a value that won't be available anymore
// once nubToCalculate is updated, setPropValue throws an error, but
// nubToCalculate is updated anyway; here, just inhibit the error
this._currentNub.properties.setPropValue("nubToCalculate", data.value.value);
} catch (e) { }
// refresh targetted result selector
const trSel = this.getFormulaireNodeById(this._targettedResultSelectId) as SelectField;
if (trSel) {
(trSel.parent as FieldSet).updateFields();
// trick to re-set observers
this.completeParse(false);
}
// refresh parameters selector
this.refreshParameterEntries();
}
…
}
…
}
Ici on écoute l'événement généré par l'objet dérivé de SelectField
, et on agit en conséquence : affecter la propriété concernée et rafraîchir un champ dépendant.
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()
, actionpropertyChange
), - 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" (type de section) :
{
"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"
}
}
La section par défaut du formulaire sera celle du sélecteur, que celle ci soit ou non configurée explicitement par le champ default
.
si le module agrège des modules enfants
La traduction des variables des modules enfants doit être ajoutée dans les fichiers de langues principaux de l'application, sous la forme INFO_LIB_CODEVARIABLE
. Par exemple :
{
"INFO_LIB_CDCUNGE": "Coefficient de débit"
}
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) :
{
"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
:
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
:
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);
}
}
}
}
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 :
"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 et un sélecteur de loi de débit, ajouter les propriétés "calcType" (toujours "Structure" dans ce cas) et les valeurs par défaut avec "default" dans la définition du select. A noter que les ids des select sont fixés et doivent obligatoirement être "select_structure" et "select_loidebit" :
{
"id": "fs_ouvrage",
"type": "fieldset_template",
"calcType": "Structure",
"fields": [
{
"id": "select_structure",
"type": "select",
"default": "VanneFondRectangulaire",
},
{
"id": "select_loidebit",
"type": "select",
"default": "GateCem88v",
"help": {
"Orifice_OrificeSubmerged": "structures/orifice_noye.html",
"SeuilRectangulaire_WeirVillemonte": "structures/kivi.html",
"SeuilRectangulaire_WeirSubmergedLarinier": "structures/fente_noyee.html",
"SeuilRectangulaire_WeirCunge80": "structures/cunge_80.html",
"VanneFondRectangulaire_GateCunge80": "structures/cunge_80.html",
"SeuilTriangulaire_TriangularWeirFree": "structures/dever_triang.html",
"SeuilTriangulaire_TriangularWeirBroad": "structures/dever_triang.html",
"SeuilTriangulaireTrunc_TriangularTruncWeirFree": "structures/dever_triang_tronque.html"
}
}
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)
tests unitaires
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
.