From 5996ec78082bbcf73c1e3fc564868b983c975941 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Thu, 21 Mar 2019 10:21:20 +0100
Subject: [PATCH] Fix #167

---
 src/app/app.module.ts                         |  2 +
 .../dialog-load-session.component.html        | 17 ++++-
 .../dialog-load-session.component.scss        | 21 ++++++
 .../dialog-load-session.component.ts          | 74 +++++++++++++++++++
 .../services/formulaire/formulaire.service.ts | 28 +++++--
 src/locale/messages.en.json                   |  2 +
 src/locale/messages.fr.json                   |  2 +
 7 files changed, 138 insertions(+), 8 deletions(-)

diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 2759ef9f5..a1f172ab0 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -19,6 +19,7 @@ import {
   MatCardModule,
   MatTableModule,
   MatSnackBarModule,
+  MatBadgeModule,
   ErrorStateMatcher,
   MatButtonToggleModule
 } from "@angular/material";
@@ -104,6 +105,7 @@ const appRoutes: Routes = [
     ChartModule,
     HttpClientModule,
     FlexLayoutModule,
+    MatBadgeModule,
     MatButtonModule,
     MatButtonToggleModule,
     MatCardModule,
diff --git a/src/app/components/dialog-load-session/dialog-load-session.component.html b/src/app/components/dialog-load-session/dialog-load-session.component.html
index 0473b4cde..97e2d6e42 100644
--- a/src/app/components/dialog-load-session/dialog-load-session.component.html
+++ b/src/app/components/dialog-load-session/dialog-load-session.component.html
@@ -13,7 +13,8 @@
     </mat-form-field>
 
     <div class="cb-container">
-      <mat-checkbox [name]="c.uid" *ngFor="let c of calculators" [(ngModel)]="c.selected" [ngModelOptions]="{standalone: true}">
+      <mat-checkbox [name]="c.uid" *ngFor="let c of calculators" (change)="checkLinkedParamsDependencies()"
+        [(ngModel)]="c.selected" [ngModelOptions]="{standalone: true}">
         {{ c.title }}
       </mat-checkbox>
     </div>
@@ -26,6 +27,20 @@
         {{ uitextNone }}
       </button>
     </div>
+
+    <div class="dependencies-problems" *ngIf="dependenciesProblems.length > 0">
+      <mat-list role="list">
+        <mat-list-item role="listitem" *ngFor="let dp of dependenciesProblems">
+          <mat-icon color="warn">error_outline</mat-icon> {{ dp.message }}
+        </mat-list-item>
+      </mat-list>
+      <p>
+        <button mat-raised-button (click)="fixDependencies()"
+            [matBadge]="dependenciesProblems.length" matBadgeColor="warn">
+          {{ uitextFixMissingDependencies }}
+        </button>
+      </p>
+    </div>
   </div>
 
   <div mat-dialog-actions>
diff --git a/src/app/components/dialog-load-session/dialog-load-session.component.scss b/src/app/components/dialog-load-session/dialog-load-session.component.scss
index e612ad2cf..f9cad2765 100644
--- a/src/app/components/dialog-load-session/dialog-load-session.component.scss
+++ b/src/app/components/dialog-load-session/dialog-load-session.component.scss
@@ -17,3 +17,24 @@ mat-form-field {
         margin-right: 5px;
     }
 }
+
+.dependencies-problems {
+
+    .mat-list-base {
+        padding-top: 0;
+
+        .mat-list-item {
+            height: 32px;
+            font-size: .9em;
+
+            ::ng-deep .mat-list-item-content {
+                padding-left: 0;
+
+                mat-icon {
+                    transform: scale(0.8);
+                    margin-right: 5px;
+                }
+            }
+        }
+    }
+}
diff --git a/src/app/components/dialog-load-session/dialog-load-session.component.ts b/src/app/components/dialog-load-session/dialog-load-session.component.ts
index 3aa55c836..b4bb4c836 100644
--- a/src/app/components/dialog-load-session/dialog-load-session.component.ts
+++ b/src/app/components/dialog-load-session/dialog-load-session.component.ts
@@ -17,6 +17,8 @@ export class DialogLoadSessionComponent {
 
     public loadSessionForm: FormGroup;
 
+    public dependenciesProblems: any[] = [];
+
     constructor(
       public dialogRef: MatDialogRef<DialogLoadSessionComponent>,
       private intlService: I18nService,
@@ -40,6 +42,74 @@ export class DialogLoadSessionComponent {
       }
     }
 
+    public checkLinkedParamsDependencies() {
+      this.dependenciesProblems = [];
+      // for all checked Nubs
+      this.calculators.forEach((c) => {
+        if (c.selected) {
+          // do all required nubs are checked ?
+          c.requires.forEach((r) => {
+            if (! this.isCalculatorOrParentSelected(r)) {
+              const realUid = this.getUidOrParentUid(r);
+              const depTitle = this.getTitleFromUid(realUid);
+              this.dependenciesProblems.push({
+                requiring: c.title,
+                required: depTitle,
+                requiredUid: realUid,
+                message: c.title + " " + this.intlService.localizeText("INFO_REQUIRES") + " " + depTitle
+              });
+            }
+          });
+        }
+      });
+    }
+
+    public fixDependencies() {
+      for (const dp of this.dependenciesProblems) {
+        this.selectRequiredModule(dp.requiredUid);
+      }
+    }
+
+    private isCalculatorOrParentSelected(uid: string): boolean {
+      let isSelected = false;
+      this.calculators.forEach((c) => {
+        if (c.uid === uid || c.children.includes(uid)) {
+          isSelected = c.selected;
+        }
+      });
+      return isSelected;
+    }
+
+    private getUidOrParentUid(uid: string): string {
+      let realUid: string;
+      this.calculators.forEach((c) => {
+        if (c.uid === uid || c.children.includes(uid)) {
+          realUid = c.uid;
+        }
+      });
+      return realUid;
+    }
+
+    private getTitleFromUid(uid: string): string {
+      let title: string;
+      this.calculators.forEach((c) => {
+        if (c.uid === uid) {
+          title = c.title;
+        }
+      });
+      return title;
+    }
+
+    private selectRequiredModule(uid: string) {
+      this.calculators.forEach((c) => {
+        if (c.uid === uid) {
+          c.selected = true;
+        }
+      });
+      // re-run dependency checking
+      this.checkLinkedParamsDependencies();
+    }
+
     public onFileSelected(event: any) {
       if (event.target.files && event.target.files.length) {
         this.file = event.target.files[0];
@@ -94,4 +164,8 @@ export class DialogLoadSessionComponent {
     public get uitextLoadSessionTitle() {
       return this.intlService.localizeText("INFO_DIALOG_LOAD_SESSION_TITLE");
     }
+
+    public get uitextFixMissingDependencies() {
+      return this.intlService.localizeText("INFO_DIALOG_FIX_MISSING_DEPENDENCIES");
+    }
 }
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index 1c4bfc87b..7a34e00ae 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -451,16 +451,13 @@ export class FormulaireService extends Observable {
     private readSingleFile(file: File): Promise<any> {
         return new Promise<any>((resolve, reject) => {
             const fr = new FileReader();
-
             fr.onload = () => {
                 resolve(fr.result);
             };
-
             fr.onerror = () => {
                 fr.abort();
                 reject(new Error(`Erreur de lecture du fichier ${file.name}`));
             };
-
             fr.readAsText(file);
         });
     }
@@ -507,10 +504,27 @@ export class FormulaireService extends Observable {
             // liste des noms de modules de calcul
             if (data.session && Array.isArray(data.session)) {
                 data.session.forEach((e: any) => {
-                   res.push({
-                       uid: e.uid,
-                       title: e.meta && e.meta.title ? e.meta.title : undefined
-                    });
+                    const nubInfo = {
+                        uid: e.uid,
+                        title: e.meta && e.meta.title ? e.meta.title : undefined,
+                        requires: [],
+                        children: []
+                    };
+                    // list linked params dependencies for each Nub
+                    if (e.parameters) {
+                        e.parameters.forEach((p) => {
+                            if (p.targetNub && ! nubInfo.requires.includes(p.targetNub)) {
+                                nubInfo.requires.push(p.targetNub);
+                            }
+                        });
+                    }
+                    // list children nubs for each Nub
+                    if (e.structures) {
+                        e.structures.forEach((p) => {
+                            nubInfo.children.push(p.uid);
+                        });
+                    }
+                    res.push(nubInfo);
                 });
             }
             return res;
diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json
index 506bda9e7..d265692b8 100644
--- a/src/locale/messages.en.json
+++ b/src/locale/messages.en.json
@@ -62,6 +62,7 @@
     "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_2": "Submerged",
     "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_3": "Zero flow",
     "INFO_DIALOG_COMPUTED_VALUE_TITLE": "Edit initial value",
+    "INFO_DIALOG_FIX_MISSING_DEPENDENCIES": "Fix missing dependencies",
     "INFO_DIALOG_LOAD_SESSION_FILENAME": "Choose a file",
     "INFO_DIALOG_LOAD_SESSION_TITLE": "Load calculator modules",
     "INFO_DIALOG_SAVE_SESSION_FILENAME": "File name",
@@ -196,6 +197,7 @@
     "INFO_REMOUS_LARGEUR_BERGE": "Width at embankment level = %B% m",
     "INFO_REMOUS_RESSAUT_DEHORS": "Hydraulic jump detected %sens% abscissa %x% m",
     "INFO_REMOUS_RESSAUT_HYDRO": "Hydraulic jump detected between abscissa %xmin% and %xmax% m",
+    "INFO_REQUIRES": "requires",
     "INFO_SECTIONPARAMETREE_TITRE": "Parametric section",
     "INFO_SECTIONPARAMETREE_TITRE_COURT": "Param. section",
     "INFO_SETUP_NEWTON_MAX_ITER": "Newton iteration limit",
diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json
index 90b8466f0..5c40ca5d0 100644
--- a/src/locale/messages.fr.json
+++ b/src/locale/messages.fr.json
@@ -62,6 +62,7 @@
     "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_2": "Noyé",
     "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_3": "Débit nul",
     "INFO_DIALOG_COMPUTED_VALUE_TITLE": "Modifier la valeur initiale",
+    "INFO_DIALOG_FIX_MISSING_DEPENDENCIES": "Résoudre les dépendances",
     "INFO_DIALOG_LOAD_SESSION_FILENAME": "Choisir un fichier",
     "INFO_DIALOG_LOAD_SESSION_TITLE": "Charger des modules de calcul",
     "INFO_DIALOG_SAVE_SESSION_FILENAME": "Nom de fichier",
@@ -196,6 +197,7 @@
     "INFO_REMOUS_LARGEUR_BERGE": "Largeur au niveau des berges = %B% m",
     "INFO_REMOUS_RESSAUT_DEHORS": "Ressaut hydraulique détecté à l'%sens% de l'abscisse %x% m",
     "INFO_REMOUS_RESSAUT_HYDRO": "Ressaut hydraulique détecté entre les abscisses %xmin% et %xmax% m",
+    "INFO_REQUIRES": "dépend de",
     "INFO_SECTIONPARAMETREE_TITRE": "Section paramétrée",
     "INFO_SECTIONPARAMETREE_TITRE_COURT": "Sec. param.",
     "INFO_SETUP_NEWTON_MAX_ITER": "Newton : nombre d'itérations maximum",
-- 
GitLab