Commentaires
Dans notre projet, nous avons laissé quelques points de côté qui empêchent son bon fonctionnement ou bien qui pourraient améliorer l'expérience utilisateur. Voici quelques éléments que l'on pourrait améliorer.

Les images et les vidéos


Tu l'auras peut être remarqué, l'API de la NASA ne nous retourne pas forcément que des images. Nous avons parfois des vidéos. En l'état actuel des choses, celles-ci ne sont pas prises en compte. On va modifier notre code pour que ça fonctionne mieux.

Arthur, l'apprenti développeurAh super !
Pour ça, on va utiliser un élément de l'API qu'on n'utilisait pas jusqu'à maintenant : "media_type". Et voilà le résultat :

{"language":"text/html","content":"<template>\n <modal v-show=\"open\" @closeModal=\"close\">\n <template #title>Plus d'info</template>\n <template #default>\n Quelques explications à propos du site...\n </template>\n </modal>\n <h1 id=\"welcome\">Bienvenue sur notre site d'avis de photos !</h1>\n <button @click=\"open = !open\" class=\"button centered\">{{ openCloseText }}</button>\n\n <slider :slides=\"photos\" id=\"slider\">\n <template #default=\"{slide : photo, key : index}\">\n <h2>Photo {{ index+1 }}/{{ photos.length }}</h2>\n <photo \n :url=\"photo.url\" \n :copyright=\"photo.copyright\"\n :date=\"photo.date\"\n :media-type=\"photo.media_type\"\n v-model:title=\"photo.title\"\n v-model:explanation=\"photo.explanation\"\n\n ></photo>\n </template>\n </slider>\n</template>\n\n<script>\nimport Photo from './components/Photo.vue'\nimport Slider from './components/Slider.vue'\nimport Modal from './components/Modal.vue'\n\nconst API_URL = 'https://api.nasa.gov/planetary/apod?start_date=2021-02-01&end_date=2021-02-21&api_key=DEMO_KEY'\nexport default {\n name: 'App',\n components: {\n Photo, Slider, Modal\n },\n data() {\n return {\n photos: [],\n open:false,\n }\n },\n created() {\n fetch(API_URL)\n .then(result => result.json())\n .then(result => {this.photos = result})\n },\n computed: {\n openCloseText() {\n return open ? 'En savoir +' : 'Fermer la modal'\n }\n },\n methods: {\n close(message) {\n this.open = false\n console.log(message)\n }\n }\n}\n</script>\n\n<style>\n #app{\n font-family: 'Roboto', sans-serif;\n }\n\n #welcome {\n text-align:center;\n }\n\n .button{\n display:block;\n border:none;\n padding:10px 15px;\n font-size:1.2em;\n color:white;\n background: #818CF8;\n cursor:pointer;\n }\n\n .centered{\n margin:10px auto;\n }\n\n #slider{\n width:80%;\n margin:auto;\n }\n</style>\n","filename":"App.vue"}

{"language":"text/html","content":"<template>\n <figure class=\"photo\" v-bind=\"$attrs\">\n <iframe v-if=\"mediaType == 'video'\" id=\"ytplayer\" type=\"text/html\" width=\"640\" height=\"360\" :src=\"url\"\n frameborder=\"0\"/>\n <img v-else :src=\"url\" :alt=\"title + 'prise par' +copyright\" />\n <figcaption>\n <h2>{{ title }} prise le {{ formattedDate }} par {{ copyright }}</h2>\n <p>{{ explanation }}</p>\n </figcaption>\n <div>\n <label for=\"title\">Modifier le titre : </label>\n <input\n id=\"title\"\n :value=\"title\"\n @input=\"$emit('update:title', $event.target.value)\"\n >\n </div>\n <div>\n <label for=\"explanation\">Modifier l'explication : </label>\n <textarea \n id=\"explanation\"\n :value=\"explanation\"\n @input=\"$emit('update:explanation', $event.target.value)\"\n ></textarea>\n </div>\n </figure>\n <button @click=\"currentTab = 'Views'\" class=\"button tab\" :class=\"{underline:(currentTab == 'Views')}\">Avis</button>\n <button @click=\"currentTab = 'ViewForm'\" class=\"button tab\" :class=\"{underline:(currentTab == 'ViewForm')}\">Rédiger un avis</button>\n <keep-alive>\n <component :is=\"currentTab\" v-bind=\"dynamicProps\" @add-view=\"storeView\"></component>\n </keep-alive>\n</template>\n\n<script>\nimport Views from './Views.vue'\nimport ViewForm from './ViewForm.vue'\n\nexport default {\n components:{\n Views, ViewForm\n },\n emits:['update:title', 'update:explanation'],\n props:{\n url: {\n type:String, \n required:true,\n default:'http://uneimagepardefaut.png'\n },\n title: {\n type:String,\n required:true,\n },\n mediaType:{\n type:String,\n required:true,\n default:\"image\"\n },\n date: {\n type:String,\n required:true,\n default:Date.now()\n },\n explanation: {\n type:String,\n required:true,\n },\n copyright: {\n type:String,\n required:false,\n default:'un inconnu'\n }\n },\n data() {\n return {\n currentTab:'Views',\n views: [ ]\n }\n },\n computed: {\n formattedDate() {\n let date = new Date(this.date)\n let day = Number(date.getDate()) >= 10 ? date.getDate() : '0'+date.getDate()\n return `${day}/${date.getMonth()}/${date.getFullYear()}`\n },\n dynamicProps() {\n if(this.currentTab == 'Views') {\n return { views:this.views }\n }\n return null\n }\n }, \n methods:{\n storeView(author, content) {\n this.views.push({author:author, content:content})\n }\n }\n}\n</script>\n\n<style scoped>\n .underline{\n text-decoration: underline\n }\n .tab{\n display:inline-block;\n border-radius: 20px 20px 0 0;\n }\n\n .photo img{\n max-height:300px;\n }\n\n .photo{\n text-align:center;\n }\n</style>","filename":"Photo.vue"}

Le design du slider et son accessibilité


De manière à rendre le slider un peu plus "beau" en terme de design et de le rendre plus accessible, j'ai modifié un peu le composant pour accepter la navigation au clavier et redesigné les boutons de droite et gauche.
Voici le résultat :

{"language":"text/html","content":"<template>\n <div class=\"slider\">\n <button @click=\"slide(-1)\" aria-label=\"Aller à gauche\" class=\"button arrow\" tabindex=\"0\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"30\" height=\"30\" fill=\"currentColor\" class=\"bi bi-arrow-left\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z\"/>\n </svg>\n </button>\n <template v-for=\"(slide, key) in slides\" :key=\"key\">\n <div v-if=\"index === key\" class=\"inner\">\n <slot :slide=\"slide\" :key=\"key\" class=\"content\"></slot>\n </div>\n\n </template>\n <button @click=\"slide(1)\" aria-label=\"Aller à droite\" class=\"button arrow\" tabindex=\"0\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"30\" height=\"30\" fill=\"currentColor\" class=\"bi bi-arrow-right\" viewBox=\"0 0 16 16\">\n <path fill-rule=\"evenodd\" d=\"M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z\"/>\n </svg>\n </button>\n </div>\n</template>\n\n<script>\n export default {\n props:['slides'],\n data() {\n return {\n index:0\n }\n },\n mounted() {\n document.addEventListener('keydown', this.listenKeyboard);\n },\n methods:{\n slide(operation) {\n this.index = (((this.index + operation)%this.slides.length)+this.slides.length)%this.slides.length\n },\n listenKeyboard(event) {\n const key = event.key;\n if (key.startsWith('Arrow')) {\n event.preventDefault();\n }\n if (key === 'ArrowRight') {\n this.slide(1);\n }\n if (key === 'ArrowLeft') {\n this.slide(-1);\n }\n }\n }\n }\n</script>\n\n<style scoped>\n .arrow{\n display:flex;\n justify-content: center;\n align-items: center;\n border-radius:50%;\n width:50px;\n height:50px;\n }\n .slider{\n display:flex;\n justify-content: space-between;\n align-items: center;\n flex-wrap:wrap;\n }\n\n .inner{\n width:80%;\n border:1px solid black;\n }\n</style>","filename":"Slider.vue"}


Arthur, l'apprenti développeurC'est nettement mieux maintenant !

La suite de ton apprentissage


Maintenant que tu maitrises les composants avec Vue, de belles applications s'offrent à toi ! Mais elles pourraient être encore plus belles, notamment se rapprocher d'une SPA, grâce à Vue Routeur par exemple.
Il pourrait également être super intéressant de découvrir L'API de composition et la reusability ! Ne t'en fais pas, je serai encore là pour t'aiguiller sur tous ces points ;)

Arthur, l'apprenti développeurOh génial ! Merci beaucoup ! Je suis très fier de moi !
Tu peux l'être, tu as vu un beau morceau de Vue.JS dans ce cours. Félicitations ;)

J'ai terminé cette partie
Demander de l'assistance