]> _ Git - fluidbook-toolbox-quiz.git/commitdiff
wait #6197 @2
authorVincent Vanwaelscappel <vincent@cubedesigners.com>
Thu, 17 Aug 2023 11:34:52 +0000 (13:34 +0200)
committerVincent Vanwaelscappel <vincent@cubedesigners.com>
Thu, 17 Aug 2023 11:34:52 +0000 (13:34 +0200)
12 files changed:
.idea/deployment.xml
.idea/watcherTasks.xml [new file with mode: 0644]
js/quiz.accessibility.js [new file with mode: 0644]
js/quiz.js
js/quiz.screen.intro.js [new file with mode: 0644]
js/quiz.screen.outro.js [new file with mode: 0644]
js/quiz.screens.js [new file with mode: 0644]
js/quiz.utils.js [new file with mode: 0644]
views/footer.blade.php
views/screens/intro.blade.php
views/screens/outro.blade.php
views/screens/question_multiple.blade.php

index 4cff5f51bddcfec8be2d1259413c26c5390ae20b..58cec64224454a525e62c0d00c3e56fb9a5419cd 100644 (file)
@@ -97,7 +97,7 @@
       <paths name="dev.toolbox.fluidbook.com">
         <serverdata>
           <mappings>
-            <mapping deploy="/resources/quiz/player/local/mobile" local="$PROJECT_DIR$" web="/" />
+            <mapping deploy="/resources/quiz/player/local/master" local="$PROJECT_DIR$" web="/" />
           </mappings>
         </serverdata>
       </paths>
diff --git a/.idea/watcherTasks.xml b/.idea/watcherTasks.xml
new file mode 100644 (file)
index 0000000..9d98b43
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectTasksOptions" suppressed-tasks="Sass" />
+</project>
\ No newline at end of file
diff --git a/js/quiz.accessibility.js b/js/quiz.accessibility.js
new file mode 100644 (file)
index 0000000..6866787
--- /dev/null
@@ -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;
+
index b99daff2ede37752eed76fee9943c3963c195d4e..f8078e0a9016c42068969f766264c4ea2abe996d 100644 (file)
@@ -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 (file)
index 0000000..5c37e6a
--- /dev/null
@@ -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 (file)
index 0000000..7728524
--- /dev/null
@@ -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 (file)
index 0000000..2d94412
--- /dev/null
@@ -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 (file)
index 0000000..2abb2ba
--- /dev/null
@@ -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
index a4de04a21e2ddbea50fb418c478264d208d077e0..6aee4f12a975bc89a3f6c1b797b781add9813535 100644 (file)
@@ -1,11 +1,12 @@
 <footer class="footer-question footer">
     @isset($reset)
-        <a class="btn secondary reset">
+        <a class="btn secondary reset" aria-keyshortcuts="R">
             Reset
             <span class="access">R</span>
         </a>
     @endisset
-    <a class="btn primary action" data-validation-text="Validate answer" data-continue-text="Continue">
+    <a class="btn primary action" data-validation-text="Validate answer" data-continue-text="Continue"
+       aria-keyshortcuts="Space">
         <span class="text">{{$text}}</span>
         <span class="access space">space</span>
         @isset($time)
@@ -13,7 +14,7 @@
         @endisset
     </a>
     @isset($info)
-        <a class="btn secondary none">
+        <a class="btn secondary none" aria-keyshortcuts="F1">
             More infos
             <span class="access infos">F1</span>
         </a>
index c524fd7ac381f12a44fb4c93ce39046e21798b1b..d1e2a5d0bab1feb2d225012d263cdf4c49c5191f 100644 (file)
@@ -1,7 +1,7 @@
 @php
     $absPath = \App\Models\Quiz::find($data->id)->getPreviewURL();
 @endphp
-<div class="container-screen active-screen next" id="welcome-screen">
+<div class="container-screen none" id="welcome-screen">
     @include('header_title', ['data', $data])
     <div class="screen" id="welcome">
         <h2 class="none">{{$data->intro_title}}</h2>
@@ -11,6 +11,6 @@
         <img src="{{$data->theme->introImage}}" />
     </div>
     <footer class="footer">
-        <a id="start" class="btn primary">{{$data->intro_button}}</a>
+        <a id="start" aria-keyshortcuts="Space" class="btn primary">{{$data->intro_button}}</a>
     </footer>
 </div>
index e522665c33979509e9a80fc8ecd0499f464cabb8..f1fef2f9e4f32f529bbbc3835bc34f9e07825767 100644 (file)
@@ -7,8 +7,8 @@
                     <div id="progress-counter">
                         <span id="score-counter">0</span><span>/</span><span id="maxScore-counter">0</span>
                         <svg width="100%" height="100%" id="svg">
-                            <circle cx="50%" cy="50%" r="50%" fill="transparent" stroke="currentColor" />
-                            <circle id="progress-circle" cx="50%" cy="50%" r="50%" fill="none" stroke="currentColor" />
+                            <circle cx="50%" cy="50%" r="50%" fill="transparent" stroke="currentColor"/>
+                            <circle id="progress-circle" cx="50%" cy="50%" r="50%" fill="none" stroke="currentColor"/>
                         </svg>
                     </div>
                 </div>
                 @endif
                 <div class="controls">
                     @if($data->restart_button)
-                        <a class="btn secondary reset">
+                        <a class="btn secondary reset" aria-keyshortcuts="r">
                             Restart
                             <span class="access">R</span>
                         </a>
                     @endif
-                    <a class="btn primary action">
+                    <a class="btn primary action" aria-keyshortcuts="Space">
                         Leave
                         <span class="access space">space</span>
                     </a>
             <p class="subtitle">Review your answers before you go</p>
             <ul id="answers-list">
                 @verbatim
-                <script id="template-answers-review" type="text/x-handlebars-template">
-                    {{#each reviewList}}
+                    <script id="template-answers-review" type="text/x-handlebars-template">
+                        {{#each reviewList}}
                         <li class="item {{this.status}}">
                             <p class="position">Question {{inc @index}}</p>
                             <p class="question">{{this.question}}</p>
                             {{#each this.answers}}
-                                <p class="answer">{{this.answer}}</p>
+                            <p class="answer">{{this.answer}}</p>
                             {{/each}}
                         </li>
-                    {{/each}}
-                </script>
+                        {{/each}}
+                    </script>
                 @endverbatim
             </ul>
         </div>
index 8606917900eab010dcb0041c25864516f77d4bfd..5b034f1de37f9b7be3afccf24946a27630fec38c 100644 (file)
@@ -4,13 +4,15 @@
         <form id="form-{{$position}}">
             <ul class="list">
                 @foreach($data['answers'] as $key => $answer)
-                   <li class="list-item">
-                        <input type="{{ $data['type'] === "multiple" ? 'checkbox' : 'radio' }}" name="{{ $data['type'] === "multiple" ? 'answer-'.$position.$key : 'answer' }}" id="question-{{$position.$key}}" class="none" value="{{$key}}">
-                        <label for="question-{{$position.$key}}">
+                    <li class="list-item">
+                        <input type="{{ $data['multiple'] ? 'checkbox' : 'radio' }}"
+                               name="{{  'answer' }}"
+                               id="question-{{$position.$key}}" class="none" value="{{$key}}">
+                        <label for="question-{{$position.$key}}" aria-keyshortcuts="{{$alphabet[$key]}}">
                             <span class="relative">{{$answer['answer']}}</span>
                             <span class="access">{{$alphabet[$key]}}</span>
                         </label>
-                   </li>
+                    </li>
                 @endforeach
             </ul>
         </form>