|
Master 2 Miage MBDS 2019-2020
De $1
Introduction
Dans ce cours nous verrons le developpement de WebApps fullstack avec NodeJS/MongoDB/Firecloud-Firebase/cloud et avec les principaux frameworks JS front-end (React et Angular, VueJS ayant été étudié l'an dernier).
Séance 1 : rappels essentiels sur JavaScript asynchrone / premiers WebServices avec NodeJS
Rappels sur l'asynchronisme en JavaScript, les reqêtes Ajax
Support de cours :
- Des callbacks aux promesses et révision sur la gestion des erreurs, map/reduce, le parallélisme...
- async/await : des promesses plus simples au niveau syntaxique (mais moins puissantes)
- Requêtes Ajax : de XmlHttpRequest à fetch, la fetch API
- Exemples fait en classe en live coding :
On commence l'étude de NodeJS
Support de cours :
- Transparents de présentation de NodeJS
- Transparents de présentation de Express
- Livres à demander au prof pour ceux qui sont déjà à l'aise avec NodeJS.
Exercice à faire :
- Récupérez cette archive, dezippez là quelque part, avec la ligne de commande, faites cd dans le répertoire TP_ROUTAGE_EXPRESS_CRUD_AJAX créé, et exécutez la commande "npm install".
Ceci devrait créer un sous directory node_modules contenant les modules nécessaires (dans notre cas: express et multer). - Suivez les consignes du prof et les explications au tableau.
Seance 2 : Premiers pas avec MongoDB, Node et React
Support de cours :
Installer MongoDB sur sa machine
Exemple de code NodeJS pour faire le CRUD sur une base MongoDB
- Récupérez cette archive qui est une version modifiée de l'exercice précédent: TPRoutageExpressWithMongo3.zip
- Dezipper, allez dans le répertoire créé (
TPRoutageExpressWithMongo3 ) et faites "npm install " - Exécutez
node serverCrudWithMongo (ou nodemon) - Ouvrez localhost:8080
- Etudiez le code, regardez en particulier comment, quand on clique sur le premier bouton GET AJAX un tableau est construit dynamiquement.
Ajouter le support "cross domain" à votre projet node/MongoDB
Ajouter les lignes suivantes dans la configuration de votre serveur (fichier serverCrudWithMongo.js), ça se passe dans la configuration de express, c'est l'équivalent des "Servlet Filters" en JavaEE :
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE");
next();
});
Note : vous pouvez également utiliser un des nombreux modules npm pour ajouter à votre projet le support cross-domain, par exemple le module cors. Notez bien qu'on va écrire presqu'autant de lignes de code qu'en le faisant à la main.
Séances 3 et 4 Introduction à React
Supports de cours :
Exercices d'introduction, dans une IDE en ligne
On peut faire du react dans JsBin/JsFiddle/CodePen, mais croyez-moi, juste pour de petits exemples !
Exercice à faire en vous aidant des exercices précédents:
Pour vous donner une idée du résultat attendu,voici l'équivalent en VueJS: http://jsbin.com/guwetoy/1/edit?html,output
Je conseille de partir de cet exemple React: le composant en classe ES6
En vous aidant de l'exemple fait en classe en live coding, voud devrez faire incrémentalement, en testant après chaque étape :
- Créez un <div> et affichez dedans un composant Root avec React (l'App des exemples)
- Affichez un tableau de hobbies dans ce <div> (foot, tennis, jeux video, etc.) sous la forme d'une liste. Chaque hobby sera un <li>. Vous fournirez des hobbies par défaut.
- Ajoutez un bouton 'Nouveau Hobby' et un champ input pour saisir le nom du hobbie à rajouter. La liste doit se mettre à jour.
- Rendez les hobbies clickables pour qu'on puisse les supprimer
- Un message <p>Hobby supprimé !</p> devra apparaitre lorsqu'un hobby est supprimé
- Ajoutez un compteur de hobbies (<p>Hobbies: 4</p>) au-dessus de la liste des hobbies, qui indiquera le nombre de hobbies dans la liste.
- Vous changerez les styles CSS des éléments de la liste pour qu'ils s'affichent en rouge et vert à chaque ligne, en alternance. Vous utiliserez aussi des classes CSS dynamiques pour afficher le compteur en rouge si le nombre de hobbies est supérieur à trois, en vert sinon.
- Chaque <li>, chaque hobby, sera maintenant dans un composant <Hobby/>
ICI UNE CORRECTION: https://codepen.io/w3devcampus/pen/xxKeJZW
Exercices avec un environnement "CLI"
Si on va voir la page de facebook sur "how to start with React", il y a un chapitre qui concerne le mode "CLI": https://reactjs.org/docs/add-react-to-a-new-app.html
- Vous devez avoir nodeJS correctement installé (node, npm, etc, tout ceci doit fonctionner...)
- Installez l'extension Google Chrome "React Developer Tools" (https://chrome.google.com/webstore/d...jfkapdkoienihi), elle existe aussi pour Firefox.
- Installez une bonne fenêtre terminal pour Windows -si vous êtes sous windows comme http://cmder.net/ ou autre (github bash, etc.) si je vous vois avec une fenêtre DOS basique vous allez m'entendre !
- Installer les packages "babel" et "emmet" si vous êtres sous Sublime Text 3 ou Atom. Ca doit etre ok pour Visual Studio Code, WebStorm etc.
- Ouvrir la ligne de commande et exécuter la commande "npm install -g create-react-app",
- Pour vérifier que ça fonctionne, créez un autre répertoire nommé "react_cli" et cd dedans,
- Executez "
create-react-app hello-world " (ça prend du temps, quelques minutes)... - Allez dans le répertoire créé (
cd hello-world ), et faites "npm install " (là aussi, quelques minutes) - Exécutez le programme en tapant "
npm start ", ça doit ouvrir automatiquement le browser sur le port 3000 et afficher une page avec le logo React qui tourne. - Editez le code du fichier
src/App.js , changez le texte et sauvez -> la page Web de l'application doit se rafraichir automatiquement. - Maintenant on va étudier le code, regardez les fichiers html, js, les configs etc.
- Essayez de modifier la partie "render" de App.js... hmmm ok. Bon, on va faire un simple composant
<Username name="toto"/> qu'on va mettre dans un sous répertoire "components" dans les sources, il va simplement afficher un nom passé en attribut/props dans un paragraphe en couleur. Vous ajouterez dans <App/> Plusieurs instances de ce composant.
Exercice: refaire dans le projet les hobbies en mode CLI !
Exercice: afficher les restaurants avec un fetch, en mode CLI !
Exercice: remplacer la table par une jolie table faite avec la librairie MaterialUI, très populaire au sein des développeurs React
- Regarder le getting started du site Web de MaterialUI pour voir comment inclure cette librairie de composants graphiques dans votre projet.
- Regarder la doc de l'élément table, elle contient de nombreux exemples avec le code source.
- Regardez aussi s'il y a un chapitre concernant les tables dans le livre "MaterialUI Cookbook" qui vous a été distribué.
- Ajoutez maintenant une table MaterialUI dans un composant à vous intitulé MyTable.js pour tester, et ajoutez-le au template d'un de vos composants existants pour tester.
- Vous commencerez au début avec une table statique, puis regarderez ensuite comment utiliser des données passées en paramètre, ou bien en les chargeant par fetch comme dans les exemples vus dans le TP. Il se peut que vous ayez besoin d'utiliser des "hooks". Voir le paragraphe en rouge ci-dessous.
- On trouve de nombreuses vidéos d'exemple d'utilisation de l'élément table de Material UI.
A voir aussi : React depuis la version 16.18 propose une manière simplifiée de gérer les états, un peu "à la VueJS/Angular", avec les "'hooks":
Premiers pas avec Angular
1 : création d'une application "squelette"
-
Installer angular-cli : dans une fenêtre de terminal, tapez la commande "npm install -g @angular/cli " (éventuellement avec "sudo" avant si vous êtes sous Linux ou Mac OS) - Créez un répertoire "exo1Angular" et faites cd dans ce répertoire,
- Générez une application "squelette" angular en tapant la commande "
ng new angular-framework-intro " - Faites
cd angular-framework-intro et ouvrez visual studio code sur ce répertoire (commande "code . " sur Mac) - Faites
npm install pour installer les modules nécessaires (pour l'application et pour l'environnement de développement) - Exécutez le projet en lançant la commande
ng serve --open dans le répertoire - Ok, maintenant faites "view source" sur la page, que remarquez-vous ? Comparez avec React et VueJS... Qu'en déduisez-vous ? A propos, est-ce qu'on a installé une extension dans le browser ?
- Etudions maintenant la structure du projet....
2 : affichage d'une liste d'éléments
- Regardez le fichier
index.html , remarquez l'élément <app-root></app-root> , est-ce du HTML standard ? - Regardez les fichiers
src/app.component.ts , src/app.component.html et src/app.component.css - Modifiez le fichier de templates
app.components.html et faites ctrl-s, - Regardez le fichier app.module.ts, Angular va vous obliger à mettre à jour ce fichier très régulièrement.
- Maintenant on va effacer le contenu du fichier de template, et on va essayer d'afficher une liste d'éléments : CA SE PASSE EN LIVE CODING (une correction sera postée ici à la fin), utilisation de *ngFor (
<li *ngFor="let el of elements">...</li> si elements est la propriété du composant -un tableau de chaines de caractères-), un peu l'équivalent du v-for de VueJS. - Ajout d'un bouton pour ajouter un nouvel élément, utilisation de
(click)="expression" comme attribut, - Regardons comment afficher une ligne verte, une ligne rouge, etc. Utilisation de
[ngStyle]="{propriété CSS : expression}" , écrire une fonction getColor(element) comme dans les exemples qu'on avait fait avec VueJS et React, et l'appeler dans l'expression).
3 : création d'un nouveau composant
-
On va créer "manuellement" un nouveau composant intitulé "username", qui sera juste un<li> qui affichera la valeur de la propriété username. Pour cela, créer le fichier "app/username.component.ts, et il doit ressembler à cela. REMARQUE: vous pouvez utiliser la ligne de commande pour ajouter un nouveau composant: > ng generate component username
import { Component, Input } from "@angular/core";
@Component ({
selector: 'app-username',
template:`
<p>{{username}}</p>
`
})
export class UsernameComponent {
@Input() username :string;
}
Remarquez qu'on n'a pas utilisé ici de template html séparé, à la place on a mis le template directement sous forme de chaine de caractère entre les caractères `....`
Le @Input sert à définir une propriété qu'on pourra passer via des attributs HTML, c'est l'équivalent des "props" de VueJS et de React.
Maintenant, ajoutez des instances de ce composant dans le template app.components.html:
<app-username [username]="'Michel'"></app-username>
<app-username [username]="'John'"></app-username>
Regardez ce que cela donne....
Regardez aussi le fichier app.module.ts, le composant a été ajouté dans la liste des composants par la commande ng add component username.
Maintenant on veut pouvoir cliquer sur un nom et afficher un message dans le template du composant principal. On va faire comme en VueJS : envoyer un événement quand on clique sur le nom.
4 : définition d'un événement qui sera envoyé du composant "fils" vers le composant "père"
- Dans le composant (app/username/username.component.ts), définir un événement comme ceci:
export class UsernameComponent {
@Input() username :string;
@Output() userClicked = new EventEmitter<string>(); // définition d'un événement
onUsernameClicked() {
this.userClicked.emit(this.username); // émission de l'événément
}
}
Et dans le template (app/username/username.component.ts ) :
template:`
<p (click)="onUsernameClicked()">{{username}}</p>
`
Ainsi, dans le template du composant (app.component.html ) qui instancie ce composant, on pourra écouter l'événement "userClicked" :
<app-username [username]="'Michel1'" (userClicked)="onUserWasClicked($event)"></app-username>
<app-username [username]="'John'" (userClicked)="onUserWasClicked($event)"></app-username>
Et maintenant on peut écrire la méthode onUserWasClicked(username) dans app.component.ts :
onUserWasClicked(username: string) {
alert(username);
}
Testez l'application : en cliquant sur un nom, ça doit afficher une alerte avec la valeur associée.
5 : binding bi-directionnel avec [(ngModel)]="nom"
- Définissez un champs de saisie dans le template pour une propriété "nom" que vous ajouterez dans le fichier .ts de app.component.ts
- Dans le template, affichez la valeur de cette propriété en-dessous du champs de saisie.
- Tapez quelque chose dans le champ de saisie
- Plus simple qu'en React non ? Ca ressemble au v-model de VueJS non ?
ICI UNE CORRECTION DES ETAPES PRECEDENTES (exemple fait en live). Dezippez, cd dans le repertoire, npm install puis ng serve --open
6 : Completer l'exemple des hobbies complet, mais en angular
En vous aidant de l'exemple précédent :
- Ajouter un compteur de hobbies (<p>Nombre de Hobbies: ...</p>) au-dessus de la liste des hobbies
- Changez le style CSS du compteur, selon sa valeur: vert si moins de 3 hobbies, rouge sinon
- Transformez les éléments <li> en un composant réutilisable <app-hobby>
- On veut remplir les hobbies au démarrage de l'application en récupérant les hobbies au format JSON sur l'URL :
https://my-json-server.typicode.com/...ffa/hobbies/db (au passage, j'ai utilisé les services de jsonplaceholder.typicode.com pour servir un fichier json qui se trouve sur un de mes repository github). LIVE CODING AVEC LE PROF pour voir comment créer un service - Commande pour créer un service : "ng generate service hobby" (va créer le fichier hobby.service.ts et le fichier pour les tests)
- Pour aller récupérer le JSON des hobbies sur le WebService on va utiliser une méthode "simple" (angular propose aussi des méthodes plus complexes permettant de typer les données lorsqu'elles arrivent):
- Importez le module HttpClient en ajoutant "
import { HttpClient } from "@angular/common/http"; " en haut de votre fichier hobby.service.ts - Modifiez le code de la classe du service comme suit :
export class HobbyService { constructor(private http: HttpClient) {} getHobbies(): Promise<any> { // on va chercher par GET le fichier JSON sur un REST endpoint return this.http.get(this.url).toPromise(); } } - Modifiez le fichier app.module.ts pour rajouter le module "HttpClient" à l'application: import { HttpClientModule } from "@angular/common/http";
import { HobbyService } from "./hobby.service"; et ajoutez aussi le service HobbyService et ml module HttpClient e aussi à la directive @NgModule: @NgModule({ declarations: [AppComponent, UsernameComponent], imports: [BrowserModule, FormsModule, HttpClientModule], providers: [HobbyService], bootstrap: [AppComponent] }) - Enfin, dans le composant principal app.component.ts, qui affiche les hobbies, il faut aussi importer HobbyService:
import { HobbyService } from './hobby.service'; "Injecter le service" via le constructeur : // On "injecte le service" qui servira par la suite à // récupérer des données sur le Web constructor( private hobbyService: HobbyService) {} Indiquer qu'on va charger les hobbies avant d'afficher la liste : // Appelé lorsque le composant est créé ngOnInit(): void { this.getHobbies(); } et voici la méthode getHobbies() qui utilise le service, l'ajouter aussi au fichier getHobbies() { this.hobbyService.getHobbies().then(response => { this.hobbies = response.data; }); -
- Correction de cet exercice
- Exercice à faire : afficher les restaurants de votre base MongoDB (juste les noms pour commencer) à la place des hobbies...
- Si vous avez des soucis de cross origin, ajouter ces lignes dans le fichier serverCrodWithMongo.js d'un des TPs précédents, après les autres lignes de la configuration express qui font des app.use...
-
// Pour autoriser le cross origin app.use(function (req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); res.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE"); next(); });
Un peu plus loin avec Angular
1 - déplacer le code qui liste les projets dans un autre composant
Vous allez créer un second composant "projets" qui contiendra toute la logique métier et le template de l'ancien composant "root".
- Allez dans le dossier de l'exemple du tp précédent (les projets)
- tapez la commande "ng generate component projets" (ou "ng g c projets" en abrégé)
- Cela va créer 4 nouveaux fichiers.
- Déplacez à l'intérieur (dans les fichiers correspondants) le code métier et le code du template auparavant situés dans le composant "App".
- Regardez le fichier app.module.ts et vérifiez bien que le nouveau composant a été ajouté.
- Modifiez le template du composant App pour qu'il affiche comme avant les projets, en ajoutant la ligne :
<app-projets></app-projets>
2 - Utiliser un routeur
Angular vient avec un routeur intégré (contrairement à VueJS et React).
Création de route
Je vous propose de rajouter dans l'exemple des Hobbies/projets vus lors du précédent TP un titre en tête de chaque composant.
Pour le moment, on va ajouter une propriété appName à la classe app.component.ts :
export class AppComponent {
nomApp = "Gestionnaire de Projets";
Et on va modifier le template de ce composant en rajoutant la valeur de cette propriété (en fait, le nom de la page d'accueil), et une route :
<h1><nav><a routerLink = "/home">{{nomApp}}</a></nav></h1>
On va aussi initialiser les routes dans le fichier app.module.ts. Placez ce code avant la déclaration de @NgModule dans app.module.ts :
const routes: Routes = [
// home page
{path:'', component:ProjetsComponent},
{path:'home', component:ProjetsComponent}
];
Modifiez également la propriété "imports" du module:
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
RouterModule.forRoot(routes)
]
Et mettez à jour les imports en tête du fichier :
import {Routes, RouterModule} from '@angular/router';
Tant qu'à faire : modifiez le fichier styles.css global en ajoutant :
* {
font-family:"Andale Mono", sans-serif;
}
Voilà, si vous ne vous êtes pas trompés, vous devriez avoir en haut de la page web qui s'affiche un lien "Gestionnaire de projets", si vous cliquez dessus, ça doit afficher l'url "http://localhost:4200/home".
Il ne reste plus qu'à supprimer <app-projets></app-projets> du template du composant App et à le remplacer par :
<div>
<h1>
<nav><a routerLink = "/home">{{nomApp}}</a>
</nav>
</h1>
<router-outlet></router-outlet>
</div>
3 - Exercices à faire :
1 - rajouter deux ou trois composants et associez-y des routes (par exemple : "connexion", "search", "help"). Mettez à jour l'élément <nav>...</nav>
En changeant l'URL, vous devriez afficher tour à tout le contenu des composants connexion, search, help, etc.
2 - Plus difficile : Créez un composant AddProjet qui fera l'action d'ajouter un projet (comme le fait actuellement le bouton AddProjet), et vous modifierez le bouton AddProjet actuel pour qu'il fasse naviguer vers le composant AddProjet par l'URL "http://localhost:4200/add"
Aide : vous pouvez ajouter un attribut routerLink="/home" dans un <button....> pour que lors du click pour l'ajout on revienne à la page d'accueil qui affiche la liste des projets.
Il y a plusieurs moyens de s'en sortir. L'un consiste à écrire un service spécialement pour gérer les projets... et qui serait utilisable par les composants d'affichage, d'ajout, de recherche, etc.
- Creez un nouveau service "ProjetsService",
- Déplacez la liste des projets à l'intérieur du service
- Ajoutez une méthode getProjets()
- Injectez ce service dans le composant App et essayez d'afficher la liste des projets (attention, vous devrez réfléchir à comment faire pour récupérer la liste des projets, où la stocker etc.)
3 - Rendre les projets Observables
Un Observable vous permet de vous abonner à des objets et à suivre leurs mises à jour de manière asynchrone :
- Par exemple, une collection d'objets Observable, comme nos projets.
- Si on s'abonne (subscribe), on pourra être prévenus lorsqu'ils sont modifiés
- Les Observables ont des operateurs comme map(), filter() et forEach() (ce qui les rapproche des tableaux JS)
Les Observables font partie d'un package angular nommé RxJS.
Modifions notre ProjetService pour rendre le tableau des projets observables:
Ajouter à ProjetService.ts :
import {Observable, of} from 'rxjs';
On remplace :
getProjets() {
return this.projets;
}
par :
getProjets():Observable<any> {
return of(this.projets); // of = ObServable
}
Dans le ngOnInit de ProjetsComponent.ts, on va utiliser le service:
projets: [];
ngOnInit() {
this.getProjets();
}
getProjets() {
this.projetsService.getProjets().subscribe(projets => { this.projets = projets});
}
Là, normalement l'application devrait fonctionner comme avant...
Ajout de méthode pour l'ajout d'un projet :
Ajouter dans le service une méthode :
addProjet(nom:string): Observable<string> { let projet = { matiere:nom, rendu:false, dateDeRendu:"non précisé" } this.projets.push(projet); return of(a: "projet ajouté"); }
qui renverra une chaine de caractères pour confirmer l'ajout.
Il reste à mettre à jour la méthode onAddProjet() existant dans le composant Projets.component.ts :
onAddProjet() {
this.projetsService.addProjet(this.nom).subscribe(message => console.log(message));
// on vide le champ
this.nom = "";
}
Exercice à faire : ajouter une méthode pour supprimer un projet. 4 - Utiliser des composants MaterialUI dans votre projet
- Exécutez la commande suivante à la racine de votre projet :
ng add @angular/material - Répondez par la touche entrée aux questions
- Ajoutez @import'@angular/material/prebuilt-themes/deeppurple-amber.css'; au fichier styles.css pour avoir le thème par défaut.
Maintenant vous pouvez utiliser des widgets de la librairie MaterialUI (doc complète : https://material.angular.io/guide/getting-started)
Par exemple, commencez par importer les modules suivants dans le fichier app.module.ts
import {MatButtonModule} from '@angular/material';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
Et ajoutez-les à la liste des modules dans le code un peu plus bas :
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
RouterModule.forRoot(routes),
BrowserAnimationsModule,
MatButtonModule
],
Puis modifiez la déclaration du bouton pour ajouter un projet dans projets.component.html :
<button mat-stroked-button color="primary" (click)="onAddProjet();">Ajouter</button>
Et regardez ce que cela donne. Cliquez dessus ! Voilà ! vous avez fait votre premier composant Material UI avec angular :-) PROJET A RENDRE (en binomes): une base de données d'observation d'objets non identifiés
DATE DE RENDU : LE 14 AVRIL 2020 !!!
Depuis l'affaire Roswell les observations d'objets celestes non identifés se sont multipliées. En France, un organisme dépendant du CNES, le GEIPAN, mène des enquêtes sur chaque cas connu et rend public les résultats en OpenData. Vous pouvez consulter les archives ici : http://www.cnes-geipan.fr/. Les cas de type "D, D1 ou D2" sont les cas inexpliqués donc les plus intéressants :-)
Afin de nous amuser un peu avec ces données on ne peut plus sérieuses (le GEIPAN dépend du Centre National d'Etudes Spatiales et travaille avec l'armée et la gendarmerie nationale), vous allez devoir réaliser un serveur REST miroir hébergeant les données du GEIPAN, ainsi qu'un moteur de recherche client en React ou en Angular.
Travail à faire :
- Récupérer les données sur cette page : http://www.cnes-geipan.fr/index.php?id=181&no_cache=1&tx_ttnews%5BbackPid%5D=211&tx_ttnews%5Btt_news%5D=330 il s'agit de fichiers .csv. Un autre fichier contient la description des différentes colonnes. La base se compose de deux collections liées : les cas et les témoignages.
- Vous devrez d'abord les importer dans MongoDB, réfléchissez bien car Mongo n'est pas une BD relationnelle. Il faudra peut-être merger ces données en une seule collection, ou bien, si vous conservez les deux collections, afficher un lien pour consulter dans la page affichant un témoignage le détail du cas d'observation, et vice versa : dans la page d'un cas, afficher un lien vers les témoignages.
- Regardez la présentation des pages de résultat du GEIPAN, vous pouvez sans doute faire mieux....
- Recherche par date, type de cas, et mot clé dans le titre....
- Affichage dans un tableau, pagination, page de détails sur un cas.
- Ce serait bien d'avoir quelques statistiques, par exemple région par région.... et une ou deux visualisation graphique.
- Bref, c'est un sujet ouvert, à vous de vous amuser avec ces données.... certains cas et témoignages viennent avec des vidéos ou des photos....
IMPORTANT : à la fin de l'utilisation de votre application, on doit impérativement savoir si nous avons été visités ou non par une vie intelligente extra-terrestre.
L'évaluation sera faire par Michel Buffa et par un lémurien vivant sur la face cachée de la lune.
|