From a429750ee8bae20547e9d5a7c95b660f0ac9a28f Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Wed, 23 Jan 2019 18:00:04 +0100
Subject: [PATCH] =?UTF-8?q?D=C3=A9but=20du=20passage=20=C3=A0=20angular-ma?=
 =?UTF-8?q?terial?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

nouvelle barre de navigation
nouveau menu latéral
---
 src/app/app.component.html                    | 122 +++++++--------
 src/app/app.component.scss                    | 146 ++++++++++++------
 src/app/app.component.ts                      |  90 ++++++++---
 src/app/app.module.ts                         |  11 +-
 .../calculator-list.component.css             |   4 +
 .../formulaire/definition/form-definition.ts  |   3 +-
 src/app/formulaire/formulaire-element.ts      |   2 +-
 src/index.html                                |   7 +-
 src/main.ts                                   |   1 +
 src/styles.scss                               |   3 +
 10 files changed, 248 insertions(+), 141 deletions(-)

diff --git a/src/app/app.component.html b/src/app/app.component.html
index b1e921649..b1dd20bba 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,85 +1,75 @@
+<app-layout>
+
 <header>
-  <!--Navbar -->
-  <nav class="navbar navbar-expand-sm navbar-dark indigo">
-    <!-- Ouverture du sidenav -->
-    <span id="open-menu" style="font-size:30px;cursor:pointer;color:white" (click)="openNav()">☰</span>
 
-    <!-- lien vide pour que le toggler des liens apparaisse à droite -->
+  <mat-toolbar #navbar id="main-toolbar" color="primary">
+    <span class="example-spacer"></span>
+  
+    <span id="open-menu" style="font-size:30px;cursor:pointer;color:white" (click)="sidenav.toggle()">
+      <mat-icon>menu</mat-icon>
+    </span>
+
     <a class="navbar-brand"></a>
 
-    <!-- toggler des liens -->
-    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
-      aria-expanded="false" aria-label="Toggle navigation">
-      <!-- <span class="navbar-toggler-icon"> </span> -->
-      <i class="fa fa-ellipsis-v" aria-hidden="true"></i>
-    </button>
+    <div [hidden]="tabsFitInNavbar">
+      ICI LE SUPER SELECT DE LA MORT
+    </div>
+    <div id="tabs-container" [hidden]="! tabsFitInNavbar">
+      <button mat-raised-button color="primary" *ngFor="let c of calculators" class="calculator-button"
+        [routerLink]="['/calculator/',c.uid]" [color]="c.active ? '' : 'primary'" [class.active]="c.active"
+        (click)="setActiveCalc(c.uid)" [title]="'uid: ' + c.uid">
 
-    <!-- Collapsible content -->
-    <div class="collapse navbar-collapse" id="navbarSupportedContent">
-      <ul id="navbar" class="navbar-nav mr-auto">
-        <li class="nav-item calculator-tab" *ngFor="let c of calculators">
-          <a class="nav-link waves-light {{getHighlightClass(c.uid)}}" mdbRippleRadius [routerLink]="['/calculator/',c.uid]">{{ c.title }}</a>
-        </li>
-        <li class="nav-item" id="add-calculator-tab">
-            <i id="new-calculator" class="fa fa-plus-square fa-2x fa-inverse" style='vertical-align: middle' routerLink="/list" (click)='closeNav()'></i>
-        </li>
-      </ul>
+        <span class="calc-name">
+          {{ c.title }}
+        </span>
+        <span class="calc-type" [innerHTML]="'( ' + c.type + ' )'"></span>
+      </button>
+      <!-- <i id="new-calculator" class="fa fa-plus-square fa-2x" style='vertical-align: middle' routerLink="/list" (click)='sidenav.close()'></i> -->
     </div>
-  </nav>
-  <!--/Navbar -->
+    <button mat-icon-button id="new-calculator" routerLink="/list" (click)="sidenav.close()">
+      <mat-icon>add_box</mat-icon>
+    </button>
+  </mat-toolbar>
+
 </header>
 
 <main>
-  <!-- sidenav -->
-  <div class="container">
-    <div class="row">
-      <div id="mySidenav" class="sidenav">
-        <!-- ATTENTION ! pas de href="#" sous peine de rechargement de la page et réinitialisation de l'appli -->
-        <a id="close-side-nav" class="closebtn" (click)="closeNav()">×</a>
-        <a id="side-nav-list" routerLink="/list" (click)="closeNav()">{{ uitextSidenavNewCalc }}</a>
+
+  <mat-sidenav-container fxFlexFill class="example-container">
+
+    <mat-sidenav class="sidenav" #sidenav fxLayout="column" >
+      <div fxLayout="column">
+        <a id="close-side-nav" class="closebtn" (click)="sidenav.close()">×</a>
+        <a id="side-nav-list" routerLink="/list" (click)="sidenav.close()">{{ uitextSidenavNewCalc }}</a>
         <a id="side-nav-load-session" (click)="loadSession()">{{ uitextSidenavLoadSession }}</a>
-        <a id="side-nav-setup" routerLink="/setup" (click)="closeNav()">{{ uitextSidenavParams }}</a>
-        <a id="side-nav-help" target="_blank" href="assets/docs-fr/" (click)="closeNav()">{{ uitextSidenavHelp }}</a>
-        <div class="hyd_fillvertical"></div>
+        <a id="side-nav-setup" routerLink="/setup" (click)="sidenav.close()">{{ uitextSidenavParams }}</a>
+        <a id="side-nav-help" target="_blank" href="assets/docs-fr/" (click)="sidenav.close()">{{ uitextSidenavHelp }}</a>
+
         <div class="hyd_version">
           JaLHyd version: {{ getDateRevision()[0] }}<br/>
           ngHyd version: {{ getDateRevision()[1] }}
         </div>
       </div>
-    </div>
-  </div>
-
-  <!-- chargement des calculettes -->
-  <div appLoadCalcDialogAnchor></div>
+    </mat-sidenav>
 
-  <!-- sauvegarde des calculettes -->
-  <div appSaveCalcDialogAnchor></div>
+    <mat-sidenav-content fxFlexFill>
+      <!-- chargement des calculettes -->
+      <div appLoadCalcDialogAnchor></div>
+    
+      <!-- sauvegarde des calculettes -->
+      <div appSaveCalcDialogAnchor></div>
+    
+      <div class="container-fluid">
+        <div class="row">
+          <div class="col-12">
+            <router-outlet (activate)="onRouterOutletActivated($event)"></router-outlet>
+          </div>
+        </div>
+      </div>
+    </mat-sidenav-content>
 
-  <!-- règle gradée des colonnes Bootstrap -->
-  <div *ngIf="ruler" class="container-fluid">
-    <div class="row">
-      <div class="col 1 red">1</div>
-      <div class="col 1 green">2</div>
-      <div class="col 1 blue">3</div>
-      <div class="col 1 red">4</div>
-      <div class="col 1 green">5</div>
-      <div class="col 1 blue">6</div>
-      <div class="col 1 red">7</div>
-      <div class="col 1 green">8</div>
-      <div class="col 1 blue">9</div>
-      <div class="col 1 red">10</div>
-      <div class="col 1 green">11</div>
-      <div class="col 1 blue">12</div>
-    </div>
-  </div>
+  </mat-sidenav-container>
 
-  <div class="container-fluid">
-    <div class="row">
-      <div class="col-12">
-        <router-outlet (activate)="onRouterOutletActivated($event)"></router-outlet>
-      </div>
-    </div>
-  </div>
 </main>
 
 <footer>
@@ -88,4 +78,6 @@
     <br>
     <a href="http://www.irstea.fr/">Institut national de recherche en sciences et technologies pour l'environnement et l'agriculture</a>
   </div> -->
-</footer>
\ No newline at end of file
+</footer>
+
+</app-layout>
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
index 9a709694f..8262d7966 100644
--- a/src/app/app.component.scss
+++ b/src/app/app.component.scss
@@ -3,67 +3,121 @@
     background-image: none;
 }
 
-// sidenav
-body {
-    font-family: "Lato", sans-serif;
+button:focus {
+    outline: 0;
 }
 
-.sidenav {
-    height: 100%;
-    width: 0;
-    position: fixed;
-    z-index: 2000;
-    top: 0;
-    left: 0;
-    background-color: #111;
-    overflow-x: hidden;
-    transition: 0.5s;
-    padding-top: 60px;
-    display: flex;
-    flex-direction: column;
+#main-toolbar {
+    height: 54px;
 }
 
-.sidenav a, .sidenav div.hyd_version {
-    padding: 8px 8px 8px 32px;
-    text-decoration: none;
-    font-size: 16px;
-    color: #aaaaaa;
-    display: block;
-    transition: 0.3s;
+#open-menu mat-icon, #new-calculator mat-icon {
+    transform: scale(1.6);
 }
 
-.sidenav div.hyd_version {
-    color: #888888;
-    padding: 0px 32px 0px 0px;
-    text-align: right;
-}
+#new-calculator {
+    cursor: pointer;
+    position: absolute;
+    right: 10px;
 
-.sidenav div.hyd_fillvertical {
-    flex: 1;
+    &:focus {
+        outline: 0;
+    }
 }
 
-.sidenav a:hover {
-    color: #f1f1f1;
+#tabs-container {
+    width: 100%;
 }
 
-.sidenav .closebtn {
-    position: absolute;
-    top: 0;
-    right: 25px;
-    font-size: 36px;
-    margin-left: 50px;
-}
+.calculator-button {
+    width: 11%; /* 8 tabs */
+    @media screen and (max-width: 1200px) {
+        width: 14%; /* 6 tabs */
+    }
+    @media screen and (max-width: 800px) {
+        width: 21%; /* 4 tabs */
+    }
+    @media screen and (max-width: 640px) {
+        width: 27%; /* 3 tabs */
+    }
+    @media screen and (max-width: 480px) {
+        width: 24%; /* 3 tabs */
+    }
+    @media screen and (max-width: 320px) {
+        width: 35%; /* 2 tabs */
+    }
+    /* tabs-looking buttons */
+    margin-right: 3px;
+    margin-top: 11px;
+    border-bottom-left-radius: 0;
+    border-bottom-right-radius: 0;
+    border-bottom: solid #3F51B5 2px; /* @TODO  */
+
+    &.active {
+        border-bottom-color: white;
+
+        .calc-type {
+            color: #777;
+        }
+    }
+
+    .calc-name {
+        display: block;
+        margin-top: -3px; /* ark ! */
+    }
 
-.navbar-brand {
-	color: #fff;
+    .calc-type {
+        display: block;
+        font-size: 0.7em;
+        color: #bbb;
+        line-height: 24px;
+        margin-top: -14px; /* ark ! */
+    }
 }
 
-nav div.container {
-    display: contents;
+
+// sidenav
+
+.sidenav {
+    height: 100%;
+    background-color: #111;
+    overflow-x: hidden;
+    padding-top: 60px;
+    padding-right: 4em;
+
+    a, .div.hyd_version {
+        padding: 8px 8px 8px 32px;
+        text-decoration: none;
+        font-size: 16px;
+        color: #aaaaaa;
+        display: block;
+        transition: 0.3s;
+    }
+
+    div.hyd_version {
+        position: absolute;
+        bottom: 0;
+        right: 0;
+        font-size: 0.8em;
+        color: #888888;
+        padding: 0 2em 1em 0;
+        text-align: right;
+    }
+
+    a:hover {
+        color: #f1f1f1;
+    }
+
+    .closebtn {
+        position: absolute;
+        top: 0;
+        right: 25px;
+        font-size: 36px;
+        margin-left: 50px;
+    }
 }
 
-@media screen and (max-height: 450px) {
+/*@media screen and (max-height: 450px) {
   .sidenav {padding-top: 15px;}
   .sidenav a {font-size: 18px;}
-}
-// sidenav
\ No newline at end of file
+}*/
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 811356e82..b334397c8 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,5 +1,5 @@
 import { Component, ApplicationRef, OnInit, OnDestroy, HostListener, ViewChild, ComponentRef } from "@angular/core";
-import { Router, ActivatedRoute } from "@angular/router";
+import { Router, Event, NavigationEnd } from "@angular/router";
 
 import { Observer, jalhydDateRev } from "jalhyd";
 
@@ -18,6 +18,8 @@ import { SaveCalcDialogAnchorDirective } from "./components/save-calculator/save
 import { SaveCalculatorComponent } from "./components/save-calculator/save-calculator.component";
 import { nghydDateRev } from "../date_revision";
 
+import { MatSidenav, MatToolbar } from "@angular/material";
+
 
 @Component({
   selector: "nghyd-app",
@@ -26,6 +28,13 @@ import { nghydDateRev } from "../date_revision";
   providers: [ErrorService]
 })
 export class AppComponent implements OnInit, OnDestroy, Observer {
+
+  @ViewChild("sidenav")
+  public sidenav: MatSidenav;
+
+  @ViewChild("navbar")
+  public navbar: MatToolbar;
+
   /**
    * liste des calculettes. Forme des objets :
    * "title": nom de la calculette
@@ -39,6 +48,8 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
    */
   private _currentFormId: number;
 
+  private _innerWidth;
+
   @ViewChild(LoadCalcDialogAnchorDirective)
   private appLoadCalcDialogAnchor: LoadCalcDialogAnchorDirective;
 
@@ -65,19 +76,21 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     ServiceFactory.instance.internationalisationService = intlService;
     ServiceFactory.instance.formulaireService = formulaireService;
     ServiceFactory.instance.httpService = httpService;
-  }
 
-  // process.on('unhandledRejection', (reason, p) => {
-  //   console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
-  //   // Stack Trace
-  //   console.log(reason.stack);
-  // });
+    this.router.events.subscribe((event: Event) => {
+      // close side navigation when clicking a calculator tab
+      if (event instanceof NavigationEnd) {
+        this.sidenav.close();
+      }
+    });
+  }
 
   ngOnInit() {
     this.intlService.addObserver(this);
     this.intlService.setLocale("fr");
     this.formulaireService.addObserver(this);
     this.subscribeErrorService();
+    this._innerWidth = window.innerWidth;
   }
 
   ngOnDestroy() {
@@ -85,6 +98,11 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     this.formulaireService.removeObserver(this);
   }
 
+  @HostListener("window:resize", ["$event"])
+  onResize(event) {
+    this._innerWidth = window.innerWidth;
+  }
+
   public get uitextSidenavNewCalc() {
     return this.intlService.localizeText("INFO_MENU_NOUVELLE_CALC");
   }
@@ -105,6 +123,40 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     return this._calculators;
   }
 
+  public setActiveCalc(uid: string) {
+    this._calculators.forEach((calc) => {
+      calc.active = (calc.uid === uid);
+    });
+  }
+
+  /**
+   * Returns true if sum of open calculator tabs witdh is lower than navbar
+   * available space (ie. if navbar is not overflowing), false otherwise
+   */
+  public get tabsFitInNavbar() {
+    // manual breakpoints
+    // @WARNING keep in sync with .calculator-buttons sizes in app.component.scss
+    let tabsLimit = 2;
+    if (this._innerWidth > 320) {
+      tabsLimit = 3;
+    }
+    if (this._innerWidth > 480) {
+      tabsLimit = 3;
+    }
+    if (this._innerWidth > 640) {
+      tabsLimit = 4;
+    }
+    if (this._innerWidth > 800) {
+      tabsLimit = 6;
+    }
+    if (this._innerWidth > 1200) {
+      tabsLimit = 8;
+    }
+
+    const fits = this._calculators.length <= tabsLimit;
+    return fits;
+  }
+
   /**
    * abonnement au service d'erreurs
    */
@@ -145,9 +197,12 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
           this._calculators.push(
             {
               "title": f.calculatorName,
-              "uid": String(f.uid)
+              "type": this.formulaireService.getLocalisedTitleFromCalculatorType(f.calculatorType),
+              "uid": f.uid
             }
           );
+          this.setActiveCalc(f.uid);
+          this._tabsChanged = true;
 
           // abonnement en tant qu'observateur du nouveau formulaire
           f.addObserver(this);
@@ -201,6 +256,8 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
   private updateCalculatorTitle(f: FormulaireDefinition, title: string) {
     const formIndex = this.getCalculatorIndexFromId(f.uid);
     this._calculators[formIndex]["title"] = title;
+
+    this._tabsChanged = true; // recompute size !
   }
 
   /**
@@ -267,7 +324,6 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
      * - celle après celle supprimée
      * - ou celle avant celle supprimée si on supprime la dernière
      */
-
     let newId = null;
     const l = this._calculators.length;
     if (l > 1) {
@@ -286,6 +342,7 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
 
     // MAJ affichage
 
+    this._tabsChanged = true;
     if (newId === null) {
       this.toList();
       this._currentFormId = null;
@@ -298,8 +355,9 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     this.router.navigate(["/list"]);
   }
 
-  private toCalc(id: number) {
+  private toCalc(id: string) {
     this.router.navigate(["/calculator", id]);
+    this.setActiveCalc(id);
   }
 
   /**
@@ -320,19 +378,7 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     return false;
   }
 
-  // sidenav
-
-  public openNav() {
-    document.getElementById("mySidenav").style.width = "300px";
-  }
-
-  public closeNav() {
-    document.getElementById("mySidenav").style.width = "0";
-  }
-
   public loadSession() {
-    this.closeNav();
-
     // création du dialogue de sélection des formulaires à sauver
     const compRef: ComponentRef<LoadCalculatorComponent> = this.appLoadCalcDialogAnchor.createDialog();
 
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 0e611c908..3a23b6432 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -1,6 +1,8 @@
 import { BrowserModule } from "@angular/platform-browser";
 import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
 import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { MatButtonModule, MatCheckboxModule, MatIconModule, MatTabsModule, MatSidenavModule, MatToolbarModule } from "@angular/material";
+import { FlexLayoutModule } from "@angular/flex-layout";
 import { MDBBootstrapModule } from "angular-bootstrap-md";
 import { HttpClientModule } from "@angular/common/http";
 import { FormsModule } from "@angular/forms"; // <-- NgModel lives here
@@ -70,11 +72,16 @@ const appRoutes: Routes = [
     BrowserModule,
     NgxMdModule.forRoot(),
     BrowserAnimationsModule,
+    MatButtonModule,
+    MatCheckboxModule,
+    MatIconModule,
+    MatSidenavModule,
+    MatTabsModule,
+    MatToolbarModule,
     MDBBootstrapModule.forRoot(),
     FormsModule, // <-- import the FormsModule before binding with [(ngModel)]
     HttpClientModule,
-    // MdInputModule,
-    // MdDialogModule,
+    FlexLayoutModule,
     ChartModule
   ],
   declarations: [ // composants, pipes et directives
diff --git a/src/app/components/calculator-list/calculator-list.component.css b/src/app/components/calculator-list/calculator-list.component.css
index 61c95428b..107f36397 100644
--- a/src/app/components/calculator-list/calculator-list.component.css
+++ b/src/app/components/calculator-list/calculator-list.component.css
@@ -10,3 +10,7 @@
     min-height: 100%;
     height: 100%;
 }
+
+button:focus {
+    outline: 0;
+}
diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index a8a00aaa6..5272f0772 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -70,8 +70,7 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
         this.notifyObservers({
             "action": "nameChanged",
             "name": name
-        },
-            this);
+        }, this);
     }
 
     public get jsonConfig(): {} {
diff --git a/src/app/formulaire/formulaire-element.ts b/src/app/formulaire/formulaire-element.ts
index 6933766e0..306b9bc63 100644
--- a/src/app/formulaire/formulaire-element.ts
+++ b/src/app/formulaire/formulaire-element.ts
@@ -69,7 +69,7 @@ export abstract class FormulaireElement extends FormulaireNode {
     }
 
     public getKids(): FormulaireElement[] {
-        return super.kids as FormulaireElement[];
+        return this.kids as FormulaireElement[];
     }
 
     /**
diff --git a/src/index.html b/src/index.html
index 3f118aead..3923b320b 100644
--- a/src/index.html
+++ b/src/index.html
@@ -10,8 +10,8 @@
 
   <!-- ressources pour faire fonctionner le toggler de la navbar -->
   <!-- <script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" -->
-  <script href="dependencies/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n"
-    crossorigin="anonymous"></script>
+  <!--script href="dependencies/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n"
+    crossorigin="anonymous"></script>-->
 
   <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" -->
   <script href="dependencies/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb"
@@ -24,13 +24,14 @@
   <!-- Load the Angular Material stylesheet -->
   <!--
   <link href="https://unpkg.com/@angular/material/prebuilt-themes/indigo-pink.css" rel="stylesheet">
-  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
   <style>
     body {
       font-family: Roboto, Arial, sans-serif;
     }
   </style>
 -->
+  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
+  <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
 </head>
 
 <body>
diff --git a/src/main.ts b/src/main.ts
index 6373f498c..9b8fff7b9 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,3 +1,4 @@
+import "hammerjs";
 import { enableProdMode } from "@angular/core";
 import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
 
diff --git a/src/styles.scss b/src/styles.scss
index 90d4ee007..aafdbe2db 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -1 +1,4 @@
 /* You can add global styles to this file, and also import other style files */
+@import "~@angular/material/prebuilt-themes/indigo-pink.css";
+html, body { height: 100%; }
+body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
-- 
GitLab