From b4c6a9d4407a8fe6dfdba6c2067e13c9e71e942e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabrice=20PENHO=C3=8BT?= Date: Wed, 23 Mar 2022 18:58:13 +0100 Subject: [PATCH] =?UTF-8?q?Ajout=20page=20permettant=20de=20lister=20et=20?= =?UTF-8?q?sauvegarder=20ses=20r=C3=A9sultats=20aux=20quizs.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- front/public/src/myQuizs.js | 169 ++++--------------- front/public/src/quiz.js | 5 +- front/public/src/tools/userQuizsResults.js | 90 +++++++++- front/public/www/404.html | 1 + front/public/www/a-propos.html | 1 + front/public/www/contact.html | 1 + front/public/www/credits.html | 1 + front/public/www/donnees.html | 1 + front/public/www/merci.html | 1 + front/public/www/mes-quizs.html | 19 ++- front/public/www/participer-financement.html | 1 + lang/fr/answer.js | 3 + views/wikilerni/config/fr.js | 5 +- 13 files changed, 149 insertions(+), 149 deletions(-) diff --git a/front/public/src/myQuizs.js b/front/public/src/myQuizs.js index 013eb4e..01da499 100644 --- a/front/public/src/myQuizs.js +++ b/front/public/src/myQuizs.js @@ -9,7 +9,7 @@ import { availableLangs } from "../../../config/instance.js"; const lang=availableLangs[0]; // Textes : -const { localDBNotReady }=require("../../../lang/"+lang+"/answer"); +const { localDBNotReady, localFileFail, localFileImportOK, wantToSaveResultsDatas }=require("../../../lang/"+lang+"/answer"); const { serverError }=require("../../../lang/"+lang+"/general"); // Fonctions : @@ -22,11 +22,9 @@ import { loadMatomo } from "./tools/matomo.js"; import { userQuizsResults} from "./tools/userQuizsResults"; // Éléments du DOM manipulés : -//const btnSave=document.getElementById("want2Save"); -//const btnSubmit=document.getElementById("checkResponses"); -//const propose2Save=document.getElementById("propose2Save"); -//const responseTxt=document.getElementById("response"); +const datas2Restore=document.getElementById("datas2Restore"); const quizsList=document.getElementById("quizsList"); +const responseTxt=document.getElementById("response"); let userDB, allPreviousAnswers=[], myResults; const initialise = async () => @@ -34,10 +32,14 @@ const initialise = async () => try { // Instanciation de la classe s'occupant du stockage des résultats aux quizs : - myResults=await userQuizsResults.initialise("myResults", 1); + myResults=await userQuizsResults.initialise("myResults", 2); // Si la base de données est fonctionnel et que des résultats sont déjà enregistrés, on affiche la liste des quizs ayant une réponse connue : if(myResults.dbIsReady !== false) - await myResults.showMyQuizs(); + { + myResults.showMyQuizs(); + if(myResults.allResults.length !== 0) + myResults.saveMyQuizs("quizsSave", wantToSaveResultsDatas, ["button"]); + } else addElement(quizsList, "p", localDBNotReady); // Statistiques : @@ -50,143 +52,44 @@ const initialise = async () => } initialise(); helloDev(); -/* -// Fonction affichant le quiz, quand il est caché par défaut+ déclenchant le chronomètre mesurant la durée de réponse aux questions. -const showQuestionnaire = () => -{ - chronoBegin=Date.now(); - myForm.style.display="block"; - btnShow.style.display="none"; - const here=window.location; // window.location à ajouter pour ne pas quitter la page en mode "preview". - if(window.location.hash !== "") - { - window.location.hash="";// ! le "#" reste - window.location.assign(here+"questionnaire"); - } - else - window.location.assign(here+"#questionnaire"); -} -let chronoBegin; -if(btnShow) -{ - btnShow.addEventListener("click", function(e) - { - try - { - e.preventDefault(); - showQuestionnaire(); - } - catch(e) - { - addElement(responseTxt, "p", serverError, "", ["error"]); - console.error(e); - } - }); - // Un lien peut être passé pour voir directement le quiz : - if(location.hash !== "" && location.hash === "#questionnaire") - showQuestionnaire(); -} - -// Dans le cas d'un quiz groupé, le chrono est lancé dès l'affichage : -if(quizInfos.GroupId != "0") -{ - chronoBegin=Date.now(); - btnSubmit.style.display="block"; -} - -// Traitement de l'envoi de la réponse de l'utilisateur : -let answer; -myForm.addEventListener("submit", async function(e) +datas2Restore.addEventListener("change", function(e) { try { - e.preventDefault(); - btnSubmit.style.display="none"; // seulement une réponse à la fois, SVP :) - responseTxt.innerHTML=""; // supprime les éventuels messages déjà affichés - answer=userQuizsResults.checkUserAnswers(myForm); - answer.duration=Math.round((Date.now()-chronoBegin)/1000); - answer.QuestionnaireId=quizInfos.QuestionnaireId; - answer.GroupId=quizInfos.GroupId; - - // Enregistrement et affichage du résultat, suivant les cas : - let getOuput=userQuizsResults.getResultOutput(answer); - // S'il y a déjà une réponse dans la bd, c'est que l'utilisateur est ok pour les enregister. - if(myResults.allResults.length !== 0) + responseTxt.innerHTML=""; + const selectedFiles=datas2Restore.files; + if(selectedFiles !== null && selectedFiles.length === 1) { - const saveResponses=await myResults.addResult(answer); - if(saveResponses) - await myResults.showPreviousResultsForId(quizInfos.QuestionnaireId, quizInfos.GroupId); - getOuput+="

"+wantToSeaPreviousResults.replace("URL","#explanations"); - // Nouveau quiz pour cette personne ? - await myResults.saveNewQuiz(quizInfos); - } - else - { - // S'il n'a pas encore de données, on stocke temporairement le résultat et propose de l'enregistrer : - if(myResults.saveResultTemp(answer) && myResults.dbIsReady) + // selectedFiles[0].type ne fonctionne pas avec certains navigateurs (Fennec), donc... : + const extension=selectedFiles[0].name.substring(selectedFiles[0].name.lastIndexOf(".")+1); + if(extension !== "json") + addElement(responseTxt, "p", localFileFail, "", ["error"]); + else { - getOuput+="

"+wantToSaveResponses; - propose2Save.style.display="block"; - } - } - addElement(responseTxt, "p", getOuput, "", ["info"]); - // On redirige vers le résultat : - const here=window.location; - if(window.location.hash !== "") - { - window.location.hash=""; // ! le "#" reste - window.location.assign(here+"response"); - } - else - window.location.assign(here+"#response"); - - // + Affichage des textes d'explication pour chaque question - const explanations=document.querySelectorAll(".help"); - for(let i in explanations) - { - if(explanations[i].style != undefined) // sinon, la console affiche une erreur "TypeError: explanations[i].style is undefined", bien que tout fonctionne (?) - explanations[i].style.display="block"; - } - } - catch(e) - { - addElement(responseTxt, "p", serverError, "", ["error"]); - console.error(e); - } -}); - -// L'utilisateur demande à sauvegarder son résultat : -btnSave.addEventListener("click", async function(e) -{ - try - { - e.preventDefault(); - if(!isEmpty(myResults.dbIsReady) && !isEmpty(answer)) // On ne devrait pas m'avoir proposé d'enregistrer dans ce cas, mais... - { - const saveResponses=await myResults.addResult(answer); - if(saveResponses) - { - // Nouvel enregistrement = actualisation nécessaire de la liste des résultats pour ce quiz : - await myResults.showPreviousResultsForId(quizInfos.QuestionnaireId, quizInfos.GroupId); - // Nouveau quiz (ce qui doit être le cas, mais...) : - await myResults.saveNewQuiz(quizInfos); - // Redirection vers la liste des résultats : - const here=window.location; // window.location à ajouter pour ne pas quitter la page en mode "preview". - if(window.location.hash !== "") + // Lecture du contenu du fichier qui est passé au parseur : + const reader=new FileReader(); + reader.onload=async function(e) { - window.location.hash="";// ! le "#" reste - window.location.assign(here+"explanations"); - } - else - window.location.assign(here+"#explanations"); + const datas=JSON.parse(reader.result); + if(myResults.dbIsReady !== false) + { + if((datas.quizs.length !==0) &&(datas.results.length !==0)) + { + await myResults.saveAllResults(datas.results); + await myResults.saveAllQuizs(datas.quizs); + // Puis actualise l'affichage : + myResults.showMyQuizs(); + addElement(responseTxt, "p", localFileImportOK, "", ["success"]); + } + } + }; + reader.readAsText(selectedFiles[0]); } - propose2Save.style.display="none"; } } catch(e) { - addElement(responseTxt, "p", serverError, "", ["error"]); console.error(e); } -});*/ \ No newline at end of file +}); \ No newline at end of file diff --git a/front/public/src/quiz.js b/front/public/src/quiz.js index 7277170..76a72de 100644 --- a/front/public/src/quiz.js +++ b/front/public/src/quiz.js @@ -29,7 +29,8 @@ const quizInfos= url: window.location.pathname, GroupId: document.getElementById("groupId").value, QuestionnaireId: document.getElementById("questionnaireId").value, - title: myForm.dataset.title + title: myForm.dataset.title, + keywords: myForm.dataset.keywords }; // Éléments du DOM manipulés : @@ -50,7 +51,7 @@ const initialise = async () => btnShow.style.display="inline"; // Le bouton est caché si le JS est inactif, car le JS est nécessaire pour la suite... } // Instanciation de la classe s'occupant du stockage des résultats aux quizs : - myResults=await userQuizsResults.initialise("myResults", 1); + myResults=await userQuizsResults.initialise("myResults", 2); // Si la base de données est fonctionnel et que des résultats sont déjà enregistrés, on affiche ceux pour le quiz en cours : if(myResults.allResults.length !== 0) await myResults.showPreviousResultsForId(quizInfos.QuestionnaireId, quizInfos.GroupId); diff --git a/front/public/src/tools/userQuizsResults.js b/front/public/src/tools/userQuizsResults.js index e736104..484c149 100644 --- a/front/public/src/tools/userQuizsResults.js +++ b/front/public/src/tools/userQuizsResults.js @@ -182,7 +182,12 @@ export class userQuizsResults store.createIndex("nbCorrectAnswers", "nbCorrectAnswers", { unique: false }); store.createIndex("nbQuestions", "nbQuestions", { unique: false }); store.createIndex("date", "date", { unique: false }); // bien que doublons peu probables... - } + } + if (e.oldVersion < 2) + { + const quizsStore=req.transaction.objectStore("userQuizs"); + quizsStore.createIndex("keywords", "keywords", { unique: false }); + } }; req.onsuccess= (e) => { @@ -350,6 +355,38 @@ export class userQuizsResults }) } + // Importation en masse des résultats : + async saveAllResults(results) + { + await this.getOpenDb(); + return new Promise( (resolve, reject) => + { + const resultsStore=getStore(this.db, "userResults", "readwrite"); + // Au commence par vider l'existant : + resultsStore.clear(); + // Puis on enregistre les données fournies : + for(const result of results) + { + if(this.checkIfResultIsComplete(result)) + { + let req; + req=resultsStore.add(result); + req.onerror= (e) => + { + this.db.close(); + reject(e); + }; + } + } + this.db.close(); + // On injecte les donnés qui ont été acceptées dans l'instance : + this.getAllResults().then( () => + { + resolve(true); + }); + }) + } + // Enregistre le quiz, s'il n'existe pas déjà : async saveNewQuiz(quizInfos) { @@ -397,6 +434,38 @@ export class userQuizsResults } }) } + + // Importation en masse des quizs : + async saveAllQuizs(quizs) + { + await this.getOpenDb(); + return new Promise( (resolve, reject) => + { + const quizsStore=getStore(this.db, "userQuizs", "readwrite"); + // Au commence par vider l'existant : + quizsStore.clear(); + // Puis on enregistre les données fournies : + for(const quiz of quizs) + { + if(!isEmpty(quiz.url) && !isEmpty(quiz.title) && (!isEmpty(quiz.QuestionnaireId) || !isEmpty(quiz.GroupId))) + { + let req; + req=quizsStore.add(quiz); + req.onerror= (e) => + { + this.db.close(); + reject(e); + }; + } + } + this.db.close(); + // On injecte les donnés qui ont été acceptées dans l'instance : + this.getAllQuizs().then( () => + { + resolve(true); + }); + }) + } // Fonction affichant les précédents résultats connus pour le quiz encours : async showPreviousResultsForId(QuestionnaireId, GroupId, txtContentId="explanationsContent", txtTitleId="explanationsTitle") @@ -443,9 +512,7 @@ export class userQuizsResults addElement(explanationsContent, "ul", previousResultsContent); } else - addElement(explanationsContent, "ul", noPreviousResults); - /// Revoir : ajouter un lien vers la page listant les quizs auxquels l'utilisateur a répondu - /// addElement(explanationsContent, "p", ""+configTemplate.userHomePageTxt+"", "", ["btn"], "", false); + addElement(explanationsContent, "ul", noPreviousResults); } } @@ -454,13 +521,24 @@ export class userQuizsResults { const listElt=document.getElementById(listId); // On affiche d'abord les quizs les plus récents : - const myQuizs=this.allQuizs.reverse(); + const myQuizs=Object.values(this.allQuizs); + myQuizs.reverse(); let html=""; for(const quiz of myQuizs) html+=`
  • ${quiz.title}
  • `; if(html !== "") - addElement(listElt, "ul", html+"ici"); + addElement(listElt, "ul", html); else addElement(listElt, "p", noPreviousResultsAtAll); } + + // Propose à l'utilisateur de télécharger ses données dans un fichier JSON + saveMyQuizs(eltId="quizsSave", saveLinkTxt="Save your datas.", cssClass=[], linkId="") + { + const datas2Save=[JSON.stringify({ quizs: this.allQuizs, results: this.allResults })]; + const datasFile=new File(datas2Save, "myDatas.json", { type: "application/json", }); + const datasFileUrl=URL.createObjectURL(datasFile); + const domElt=document.getElementById(eltId); + addElement(domElt, "a", saveLinkTxt, linkId, cssClass, { href:datasFileUrl, download:"myDatas.json"}); + } } \ No newline at end of file diff --git a/front/public/www/404.html b/front/public/www/404.html index e3bb7db..fe210de 100644 --- a/front/public/www/404.html +++ b/front/public/www/404.html @@ -20,6 +20,7 @@ WikiLerni (logo) diff --git a/front/public/www/a-propos.html b/front/public/www/a-propos.html index deb6e66..10271d4 100644 --- a/front/public/www/a-propos.html +++ b/front/public/www/a-propos.html @@ -20,6 +20,7 @@ WikiLerni (logo)