From 3630c641cc85d944f060858e83117bd495ea71f2 Mon Sep 17 00:00:00 2001 From: Vincent Vanwaelscappel Date: Thu, 17 Aug 2023 13:34:52 +0200 Subject: [PATCH] wait #6197 @2 --- .idea/deployment.xml | 2 +- .idea/watcherTasks.xml | 4 + js/quiz.accessibility.js | 28 ++++ js/quiz.js | 183 ++++++---------------- js/quiz.screen.intro.js | 35 +++++ js/quiz.screen.outro.js | 62 ++++++++ js/quiz.screens.js | 12 ++ js/quiz.utils.js | 15 ++ views/footer.blade.php | 7 +- views/screens/intro.blade.php | 4 +- views/screens/outro.blade.php | 18 +-- views/screens/question_multiple.blade.php | 10 +- 12 files changed, 230 insertions(+), 150 deletions(-) create mode 100644 .idea/watcherTasks.xml create mode 100644 js/quiz.accessibility.js create mode 100644 js/quiz.screen.intro.js create mode 100644 js/quiz.screen.outro.js create mode 100644 js/quiz.screens.js create mode 100644 js/quiz.utils.js diff --git a/.idea/deployment.xml b/.idea/deployment.xml index 4cff5f5..58cec64 100644 --- a/.idea/deployment.xml +++ b/.idea/deployment.xml @@ -97,7 +97,7 @@ - + diff --git a/.idea/watcherTasks.xml b/.idea/watcherTasks.xml new file mode 100644 index 0000000..9d98b43 --- /dev/null +++ b/.idea/watcherTasks.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/js/quiz.accessibility.js b/js/quiz.accessibility.js new file mode 100644 index 0000000..6866787 --- /dev/null +++ b/js/quiz.accessibility.js @@ -0,0 +1,28 @@ +function QuizAccessibility(quiz) { + this.quiz = quiz; + this.initShortcuts(); +} + + +QuizAccessibility.prototype = { + initShortcuts: function () { + let $this = this; + $(document).on('keyup', function (e) { + console.log(e); + let key = e.key.toLocaleUpperCase(); + if (key === ' ') { + key = 'Space'; + } + $('[aria-keyshortcuts="' + key + '"]').each(function () { + if ($this.quiz.utils.isVisible($(this))) { + console.log('click ',$(this)); + $(this).get(0).click(); + return true; + } + }); + }); + }, +} + +module.exports = QuizAccessibility; + diff --git a/js/quiz.js b/js/quiz.js index b99daff..f8078e0 100644 --- a/js/quiz.js +++ b/js/quiz.js @@ -1,25 +1,22 @@ import $ from "cash-dom"; import gsap from "gsap"; -import { MotionPathPlugin } from "gsap/MotionPathPlugin.js"; +import {MotionPathPlugin} from "gsap/MotionPathPlugin.js"; import {CubeSCORM} from '/application/resources/scorm/scorm'; -import lottie from "lottie-web"; -import SplitType from 'split-type' -import SimpleBar from 'simplebar' -import 'simplebar/dist/simplebar.css' -import Handlebars from "handlebars"; import QuizResize from "./quiz.resize"; import QuizAnimations from "./quiz.animations"; import QuizScore from './quiz.score'; import QuizScorm from './quiz.scorm'; import QuizQuestion from './quiz.question'; +import QuizAccessibility from './quiz.accessibility'; +import QuizUtils from './quiz.utils'; +import QuizScreens from './quiz.screens'; window.cubeSCORM = new CubeSCORM(); window.$ = window.jQuery = $; -window.key = require('keymaster-reloaded'); - import ResizeObserver from 'resize-observer-polyfill'; + window.ResizeObserver = ResizeObserver; function Quiz() { @@ -31,10 +28,13 @@ Quiz.prototype = { // Initialisation des modules this.animations = new QuizAnimations(this); - this.score=new QuizScore(this); - this.scorm=new QuizScorm(this); + this.score = new QuizScore(this); + this.scorm = new QuizScorm(this); this.resize = new QuizResize(this); this.question = new QuizQuestion(this); + this.utils = new QuizUtils(this); + this.accessibility = new QuizAccessibility(this); + this.screens = new QuizScreens(this); // Ici seront injectées dans this.data toutes les données du quiz et du thème pour qu'elles soient disponibles // dans l'object à tout moment @@ -48,12 +48,12 @@ Quiz.prototype = { this.timeoutAnimation = false // commencer - $(document).on("click", "#start", function() { + $(document).on("click", "#start", function () { $this.start() }) // - $("#quiz").css("background-image","url("+this.data.theme.backgroundImage+")") + $("#quiz").css("background-image", "url(" + this.data.theme.backgroundImage + ")") // La fonction resize est appelée à chaque fois qu'un resize de la fenêtre survient (et à l'init de l'app) $(window).on('resize', function () { @@ -61,36 +61,14 @@ Quiz.prototype = { }); this.quizResize(); - //animer le texte d'intro - let title = new SplitType("#welcome h2", { types: 'words, chars' }) - let text = new SplitType("#welcome p", { types: 'words, chars' }) - gsap.from(title.words, { - opacity: 0, - y: 20, - duration: 1, - stagger: 0.05, - onStart: () => { - $(title.elements).removeClass("none") - } - }) - gsap.to(text.words, { - opacity: 1, - y: 0, - duration: 1, - ease: "power1.inOut", - stagger: { - amount: 0.2 - }, - onStart: () => { - $(text.elements).removeClass("none") - } - }) + // Afficher l'écran d'introduction et lancer l'animation + this.screens.intro.show(); // Préparer les réponses du joueur dans l'objet this.responses // à chaque fois que le formulaire change de valeur - $(document).on("change", ".active-screen form", function(e) { + $(document).on("change", ".active-screen form", function (e) { $this.responses = [] - $(this).find("input:checked").each(function() { + $(this).find("input:checked").each(function () { $this.responses.push(parseInt($(this).val())) }); }) @@ -102,39 +80,27 @@ Quiz.prototype = { $this.next(responses); $this.responses = [] }) - key('space', function (e){ - e.preventDefault() - $this.next(); - }) // Réinitialiser les réponses - $(document).on("click", ".btn.reset", function() { - $this.resetForm() - }) - key('r', function (e){ - e.preventDefault() + $(document).on("click", ".btn.reset", function () { $this.resetForm() }) - if(key.isPressed('R')) { - alert('r') - } - - $(document).on("mousedown", ".btn", function() { - gsap.to($(this), { scale: .95, duration: .2, ease: "back.inOut" }); - }).on("mouseup", ".btn", function() { - gsap.to($(this), { scale: 1, duration: .2 }); + $(document).on("mousedown", ".btn", function () { + gsap.to($(this), {scale: .95, duration: .2, ease: "back.inOut"}); + }).on("mouseup", ".btn", function () { + gsap.to($(this), {scale: 1, duration: .2}); }) // this.activeNav() }, - start: function() { + start: function () { const $this = this gsap.timeline().to("#welcome-screen", { opacity: 0, - onComplete: function() { + onComplete: function () { let responses = $this.responses $this.next(responses); $("#welcome-screen").removeClass("next active-screen").addClass("none").next(".container-screen").removeClass("none").addClass("next active-screen") @@ -178,9 +144,9 @@ Quiz.prototype = { quiz.score.updateScore() // if form exist and responses are validated // dont miss to add this second condition - if($(".active-screen form").length > 0) { - if(status.length > 0) { - if(status[this.question.currentPosition()].ok === "not answered") { + if ($(".active-screen form").length > 0) { + if (status.length > 0) { + if (status[this.question.currentPosition()].ok === "not answered") { this.validateResponse(responses); this.updateBtnValidation("validated") return false @@ -192,21 +158,21 @@ Quiz.prototype = { this.resetForm() // on incrémente de 1 la position actuelle de la question (qui commence à l'index zero) // pour récupérer le premier enfant de la nav - this.activeNav( (parseInt(this.question.currentPosition())+1) ) + this.activeNav((parseInt(this.question.currentPosition()) + 1)) this.updateBtnValidation() // si c'est la dernière question a été validée alors on affiche le résultat au prochain screen - if(this.question.last(currentPosition)) { - this.result() + if (this.question.last(currentPosition)) { + this.screens.outro.show(); } }, // marque page automatique // si le joueur recharge la page, le rediriger là où il en était - setDisplay: function() { - const currentQuestion = quiz.score.lastAnsweredQuestion + 1, - questions = this.quiz.questions + setDisplay: function () { + const currentQuestion = quiz.score.lastAnsweredQuestion + 1, + questions = this.quiz.questions - if(questions.length > 0) { + if (questions.length > 0) { if (quiz.score.lastAnsweredQuestion > -1 && questions.length <= currentQuestion) { // } @@ -217,7 +183,7 @@ Quiz.prototype = { $("form").find("input").prop("checked", false) }, - updateBtnValidation: function(status) { + updateBtnValidation: function (status) { let $btnAction = $(".footer-question .action"), validationText = $btnAction.data("validation-text"), continueText = $btnAction.data("continue-text") @@ -225,17 +191,17 @@ Quiz.prototype = { $btnAction.find('.text').text(status === "validated" ? continueText : validationText) }, - validateResponse: function(responses) { + validateResponse: function (responses) { const form = $(".active-screen form") const activeScreen = $(".active-screen") const position = activeScreen.data("position") - if(form) { - if(form.length > 0) { - let validated = quiz.score.setAnswer(position,responses); - if(validated.ok === "ok") { + if (form) { + if (form.length > 0) { + let validated = quiz.score.setAnswer(position, responses); + if (validated.ok === "ok") { this.animationValidation("OK") - }else { + } else { this.animationValidation("NOK") } this.resultAfterValidation(validated.status[position].answers) @@ -244,10 +210,10 @@ Quiz.prototype = { } }, - activeNav: function(position = 1) { + activeNav: function (position = 1) { const questionStatus = quiz.score.questionStatus const lastAnsweredQuestion = quiz.score.lastAnsweredQuestion - if($(".active-screen").find(".progress-item").length > 0) { + if ($(".active-screen").find(".progress-item").length > 0) { let $el = $(".progress-container .progress-item:nth-child(" + position + ")") $(".progress-container .progress-item").removeClass("active") @@ -267,85 +233,40 @@ Quiz.prototype = { } }, - animationValidation: function(status) { + animationValidation: function (status) { let selector = $("#anim") let text = status === "NOK" ? "Not quite" : "Perfect" let $this = this this.animations.load(status, selector, {'\\$text': text}); selector.addClass("active") - this.timeoutAnimation = setTimeout(function(e){ + this.timeoutAnimation = setTimeout(function (e) { $this.stopAnimationValidation() }, 10000) }, - stopAnimationValidation: function() { + stopAnimationValidation: function () { $("#anim").removeClass("active").empty() - if(this.timeoutAnimation) + if (this.timeoutAnimation) clearTimeout(this.timeoutAnimation) }, - resultAfterValidation: function(datas) { - for(let k in datas) { + resultAfterValidation: function (datas) { + for (let k in datas) { let n = (parseInt(k) + 1) let icon = getSpriteIcon("quiz-ok") - let $el = $(".active-screen .question-multiple .list-item:nth-of-type("+n+") label") + let $el = $(".active-screen .question-multiple .list-item:nth-of-type(" + n + ") label") $el.addClass(datas[k]) - if(datas[k] === "nok") { + if (datas[k] === "nok") { icon = getSpriteIcon("quiz-wrong") } - if(datas[k] !== "neutral") { + if (datas[k] !== "neutral") { $el.find(".access").addClass(datas[k]).html(icon) } } }, - result: function() { - let maxScore = quiz.score.maxScore, - score = quiz.score.score - - let status = quiz.score.questionStatus - let reviewList = this.question.getAll(); - - const counter = $("#progress-counter") - if(score < (maxScore / 2)) { - counter.addClass("nok") - }else if(score >= (maxScore / 2)) { - counter.addClass("ok") - } - - reviewList = reviewList.map((c,i) => { - return { - 'question': c['question'], - 'answers': c['answers'].filter((c) => { - return c['correct'] === 1 - }), - 'status': status[i].ok - } - }) - let $ul = $("#answers-list") - - $("[id^=score-]").text(score) - $("[id^=maxScore-]").text(maxScore) - - // - // HANDLEBARS TEMPLATING - // - // on applique une incrementation de +1 - // utilisé pour la numérotation des questions - Handlebars.registerHelper("inc", (value) => { return parseInt(value) + 1; }); - - // on envoie le html avec les nouvelles données - const source = $("#template-answers-review").html(); - const template = Handlebars.compile(source); - const html = template({reviewList: reviewList}); - $ul.html(html) - - // mise en place de la scrollbar personnalisé - // https://github.com/Grsmto/simplebar/tree/master/packages/simplebar - const simpleBar = new SimpleBar($ul.get(0)) - }, - restart: function() { + restart: function () { } } diff --git a/js/quiz.screen.intro.js b/js/quiz.screen.intro.js new file mode 100644 index 0000000..5c37e6a --- /dev/null +++ b/js/quiz.screen.intro.js @@ -0,0 +1,35 @@ +import gsap from "gsap"; +import SplitType from 'split-type'; + +function QuizScreenIntro(quiz) { + this.quiz = quiz; +} + +QuizScreenIntro.prototype = { + show: function () { + console.log(':)'); + $("#welcome-screen").addClass('active-screen next').removeClass('none'); + this.animate(); + }, + + animate: function () { + //animer le texte d'intro + let title = new SplitType("#welcome h2", {types: 'words, chars'}) + let text = new SplitType("#welcome p", {types: 'words, chars'}) + gsap.from(title.words, { + opacity: 0, y: 20, duration: 1, stagger: 0.05, onStart: () => { + $(title.elements).removeClass("none") + } + }) + gsap.to(text.words, { + opacity: 1, y: 0, duration: 1, ease: "power1.inOut", stagger: { + amount: 0.2 + }, onStart: () => { + $(text.elements).removeClass("none") + } + }) + }, + +} + +export default QuizScreenIntro; \ No newline at end of file diff --git a/js/quiz.screen.outro.js b/js/quiz.screen.outro.js new file mode 100644 index 0000000..7728524 --- /dev/null +++ b/js/quiz.screen.outro.js @@ -0,0 +1,62 @@ +import SimpleBar from 'simplebar' +import 'simplebar/dist/simplebar.css' +import Handlebars from "handlebars"; + +function QuizScreenOutro(quiz) { + this.quiz = quiz; +} + +QuizScreenOutro.prototype = { + show: function () { + + let maxScore = this.quiz.score.maxScore, + score = this.quiz.score.score + + let status = this.quiz.score.questionStatus + let reviewList = this.quiz.question.getAll(); + + const counter = $("#progress-counter") + if (score < (maxScore / 2)) { + counter.addClass("nok") + } else if (score >= (maxScore / 2)) { + counter.addClass("ok") + } + + reviewList = reviewList.map((c, i) => { + return { + 'question': c['question'], + 'answers': c['answers'].filter((c) => { + return c['correct'] === 1 + }), + 'status': status[i].ok + } + }) + let $ul = $("#answers-list") + + $("[id^=score-]").text(score) + $("[id^=maxScore-]").text(maxScore) + + // + // HANDLEBARS TEMPLATING + // + // on applique une incrementation de +1 + // utilisé pour la numérotation des questions + Handlebars.registerHelper("inc", (value) => { + return parseInt(value) + 1; + }); + + // on envoie le html avec les nouvelles données + const source = $("#template-answers-review").html(); + const template = Handlebars.compile(source); + const html = template({reviewList: reviewList}); + $ul.html(html) + + // mise en place de la scrollbar personnalisé + // https://github.com/Grsmto/simplebar/tree/master/packages/simplebar + const simpleBar = new SimpleBar($ul.get(0)) + } + +}; + + +export default QuizScreenOutro; \ No newline at end of file diff --git a/js/quiz.screens.js b/js/quiz.screens.js new file mode 100644 index 0000000..2d94412 --- /dev/null +++ b/js/quiz.screens.js @@ -0,0 +1,12 @@ +import QuizScreenIntro from './quiz.screen.intro'; +import QuizScreenOutro from "./quiz.screen.outro"; + +function QuizScreens(quiz) { + this.quiz = quiz; + this.intro = new QuizScreenIntro(quiz); + this.outro = new QuizScreenOutro(quiz); +} + +QuizScreens.prototype = {}; + +export default QuizScreens; \ No newline at end of file diff --git a/js/quiz.utils.js b/js/quiz.utils.js new file mode 100644 index 0000000..2abb2ba --- /dev/null +++ b/js/quiz.utils.js @@ -0,0 +1,15 @@ +function QuizUtils() { + +} + +QuizUtils.prototype = { + isVisible(e) { + if ($(e).length == 0) { + return false; + } + let elt = $(e).get(0); + return elt.offsetWidth || elt.offsetHeight || elt.getClientRects().length; + } +} + +module.exports = QuizUtils; \ No newline at end of file diff --git a/views/footer.blade.php b/views/footer.blade.php index a4de04a..6aee4f1 100644 --- a/views/footer.blade.php +++ b/views/footer.blade.php @@ -1,11 +1,12 @@