spanish-quizzer

An app to quiz you on Spanish vocabulary and verb conjugations
git clone https://git.ashermorgan.net/spanish-quizzer/
Log | Files | Refs | README

commit c9936b3e6443eda326bbfff9a995885ec70e371b
parent 4020e72f24b518fa6ed2c1d6906d8e3939f4c21d
Author: AsherMorgan <59518073+AsherMorgan@users.noreply.github.com>
Date:   Wed,  3 Mar 2021 19:00:00 -0800

Merge branch 'master' into improve-ui
Diffstat:
Mcss/filtersPage.css | 23+++++++----------------
Mjs/app.js | 19++++++++++++++++++-
Mjs/filtersPage.js | 48+++++++++---------------------------------------
Mjs/global.js | 14--------------
Mjs/quizzer.js | 49++++++++++++++++++++++++++++++++++++++++++-------
Mjs/reference.js | 20++++++++++++++++----
Mtests/test.filtersPage.js | 29++++++++++++++++++++++++-----
Mtests/test.quizzer.js | 24+++++++++++++++++-------
8 files changed, 133 insertions(+), 93 deletions(-)

diff --git a/css/filtersPage.css b/css/filtersPage.css @@ -72,32 +72,23 @@ margin-bottom: 20px; } -/* Start/Resume Buttons */ -.settingButtons { - display: flex; - flex-direction: row; - justify-content: center; -} -.settingButtons button { +/* Start button */ +.settingsStart { + margin-top: 20px; + margin-bottom: 10px; width: 100px; height: 40px; } -.settingButtons button:first-child { - margin-right: 10px; -} @media only screen and (max-width: 510px) { /* Expand settings component*/ .settingsInput { width: 100%; } - /* Expand Start/Resume buttons */ - .settingButtons { - width: 100%; - } - .settingButtons button { + /* Expand start button */ + .settingsStart { + width: calc(100% - 20px); font-size: 18px; - width: 100%; height: 50px; } } diff --git a/js/app.js b/js/app.js @@ -26,6 +26,9 @@ const pageHeader = Vue.component("pageHeader", { // App pages const homePage = Vue.component("homePage", { + data: function() { + return {isResumable: false}; + }, methods: { /** * Handle a keyup event (implements keyboard shortcuts). @@ -35,9 +38,22 @@ const homePage = Vue.component("homePage", { if (this._inactive) return; if (e.key === "c") this.$router.push("verbs"); if (e.key === "v") this.$router.push("vocab"); - if (e.key === "r") this.$router.push("reference"); + if (e.key === "t") this.$router.push("reference"); + if (e.key === "r") this.$router.push("quizzer"); }, }, + activated: function() { + // Update isResumable property + try { + // Get last session + let { prompts, index } = JSON.parse(localStorage.getItem("last-session")); + + // Validate prompts and promptIndex + if (prompts && !isNaN(index) && index >= 0 && index < prompts.length) { + this.isResumable = true; + } + } catch {} + }, created: function() { // Add keyup handler window.addEventListener("keyup", this.keyup); @@ -56,6 +72,7 @@ const homePage = Vue.component("homePage", { <router-link tag="button" to="/vocab">Study Vocab</router-link> <router-link tag="button" to="/reference">Reference Tables</router-link> </div> + <router-link v-if="isResumable" to="/quizzer">Resume previous session</router-link> </main> </div> `, diff --git a/js/filtersPage.js b/js/filtersPage.js @@ -20,8 +20,8 @@ let filterInput = Vue.component("filterInput", { data: function() { return { - verbFilters: [], - vocabFilters: [], + verbFilters: [{tense:"All Tenses", type:"All Types", subject:"All Subjects", direction:"Eng. → Conj."}], + vocabFilters: [{category:"All Categories", type:"All Types", direction:"Eng. ↔ Esp."}], }; }, @@ -405,8 +405,9 @@ let filtersPage = Vue.component("filtersPage", { /** * Start a new quizzer session */ - CreateSession: function() { + StartSession: function() { // Get prompts + let prompts; if (this.category === "vocab") { prompts = Shuffle(ApplyFilters(this.$root.$data.data.vocab, GetVocabFilters(this.filters), this.settings)); } @@ -416,40 +417,13 @@ let filtersPage = Vue.component("filtersPage", { } // Set progress - promptIndex = 0; + let promptIndex = 0; - // Start quizzer - this.StartSession(prompts, promptIndex); - }, - - /** - * Resume the previous quizzer session. - */ - ResumeSession: function() { - // Load prompts and progress - let { prompts, index } = JSON.parse(localStorage.getItem("last-session")); - - // Start quizzer - this.StartSession(prompts, index); - }, - - /** - * Perform validation checks and then start the quizzer. - */ - StartSession: function(prompts, promptIndex) { - // Validate prompts and promptIndex - if (!prompts) { - alert("An error occured while resuming the previous session."); - return; - } - else if (prompts.length === 0) { + // Validate prompts + if (prompts.length === 0) { alert("You must have at least one filter."); return; } - else if (isNaN(promptIndex) || promptIndex < 0 || promptIndex >= prompts.length) { - alert("An error occured while resuming the previous session."); - return; - } // Validate browser for voice input if (this.settings.inputType !== "Text") { @@ -476,8 +450,7 @@ let filtersPage = Vue.component("filtersPage", { */ keyup: function(e) { if (this._inactive) return; - if (e.key === "s") this.CreateSession(); - if (e.key === "r") this.ResumeSession(); + if (e.key === "s") this.StartSession(); } }, @@ -497,10 +470,7 @@ let filtersPage = Vue.component("filtersPage", { <main> <filter-input :category="category" v-model="filters"></filter-input> <settings-input v-model="settings"></settings-input> - <div class="settingButtons"> - <button class="settingsStart" @click="CreateSession();">Start</button> - <button class="settingsResume" @click="ResumeSession();">Resume</button> - </div> + <button class="settingsStart" @click="StartSession();">Start</button> </main> </div> `, diff --git a/js/global.js b/js/global.js @@ -23,20 +23,6 @@ function SetTheme(darkTheme = null) { /** - * Read a peice of text. - * @param {String} text - The text to read. - * @param {String} label - The language of the text. - */ -function Read(text, label) -{ - var msg = new SpeechSynthesisUtterance(text); - msg.lang = getLang(label); - window.speechSynthesis.speak(msg); -} - - - -/** * Load settings from localStorage. * @returns {Object} The settings. */ diff --git a/js/quizzer.js b/js/quizzer.js @@ -229,13 +229,25 @@ let quizzer = Vue.component("quizzer", { * @returns {String} - The language code ("en", "es", etc.) */ getLang: function(label) { - if (label.toLowerCase().includes("spanish")) { - return "es"; + if (label.toLowerCase().includes("english") || label.toLowerCase().includes("type") || label.toLowerCase().includes("category")) { + return "en"; } else { - return "en"; + return "es"; } }, + + /** + * Read a peice of text. + * @param {String} text - The text to read. + * @param {String} label - The language of the text. + */ + Read: function(text, label) + { + var msg = new SpeechSynthesisUtterance(text); + msg.lang = this.getLang(label); + window.speechSynthesis.speak(msg); + }, }, computed: { @@ -331,6 +343,13 @@ const quizzerPage = Vue.component("quizzerPage", { } }, + data: function() { + return { + prompts: this.startingPrompts, + index: this.startingIndex, + } + }, + methods: { /** * Update the user's progress in localStorage. @@ -343,9 +362,25 @@ const quizzerPage = Vue.component("quizzerPage", { } }, - mounted: function() { - if (this.startingPrompts == null || this.startingIndex == null || this.settings == null) { - this.$router.replace({name:this.referer}); + created: function() { + // Try to resume session if props are missing + if (this.prompts == undefined || this.index == undefined) { + try { + // Get last session + let { prompts, index } = JSON.parse(localStorage.getItem("last-session")); + + // Validate prompts and promptIndex + if (prompts && !isNaN(index) && index >= 0 && index < prompts.length) { + this.prompts = prompts; + this.index = index; + } + } catch {} + } + + // Go back if props are missing + if (this.prompts == undefined || this.index == undefined) { + alert("Unable to resume the previous session"); + this.$emit("back", this.referer); } }, @@ -353,7 +388,7 @@ const quizzerPage = Vue.component("quizzerPage", { <div class="quizzer-page"> <page-header @back="$emit('back', referer);" image="images/x.svg"></page-header> <main> - <quizzer :starting-prompts="startingPrompts" :starting-index="startingIndex" :settings="settings" + <quizzer :starting-prompts="prompts" :starting-index="index" :settings="settings" @new-prompt="updateProgress" @finished-prompts="$emit('back', referer);"> </quizzer> </main> diff --git a/js/reference.js b/js/reference.js @@ -27,20 +27,32 @@ const referenceTables = Vue.component("referenceTables", { * @returns {String} - The language code ("en", "es", etc.) */ getLang: function(label) { - if (label.toLowerCase().includes("spanish")) { - return "es"; + if (label.toLowerCase().includes("english") || label.toLowerCase().includes("type") || label.toLowerCase().includes("category")) { + return "en"; } else { - return "en"; + return "es"; } }, /** + * Read a peice of text. + * @param {String} text - The text to read. + * @param {String} label - The language of the text. + */ + Read: function(text, label) + { + var msg = new SpeechSynthesisUtterance(text); + msg.lang = this.getLang(label); + window.speechSynthesis.speak(msg); + }, + + /** * Handle a keyup event (implements keyboard shortcuts). * @param {object} e - The event args. */ keyup: function(e) { - if (this._inactive) return; + if (this._inactive || this.$refs.search === document.activeElement) return; if (e.key === "h" || e.key == "/") { try { this.$refs.search.focus(); diff --git a/tests/test.filtersPage.js b/tests/test.filtersPage.js @@ -11,12 +11,16 @@ describe("FilterInput", function() { expect(FilterInput.category).to.equal("verbs"); }); - it("VerbFilters should be empty", function() { - expect(FilterInput.verbFilters.length).to.equal(0); + it("VerbFilters should be correct", function() { + expect(FilterInput.verbFilters).to.deep.equal([ + {tense:"All Tenses", type:"All Types", subject:"All Subjects", direction:"Eng. → Conj."} + ]); }); - it("VocabFilters should be empty", function() { - expect(FilterInput.vocabFilters.length).to.equal(0); + it("VocabFilters should be correct", function() { + expect(FilterInput.vocabFilters).to.deep.equal([ + {category:"All Categories", type:"All Types", direction:"Eng. ↔ Esp."} + ]); }); }); @@ -70,6 +74,8 @@ describe("FilterInput", function() { it("Should add a verb filter if category is 'verbs'", function() { // Initialize variables FilterInput.category = "verbs"; + FilterInput.verbFilters = [] + FilterInput.vocabFilters = [] expect(FilterInput.verbFilters.length).to.equal(0); expect(FilterInput.vocabFilters.length).to.equal(0); @@ -86,6 +92,8 @@ describe("FilterInput", function() { it("Should add a vocab filter if category is 'vocab'", function() { // Initialize variables FilterInput.category = "vocab"; + FilterInput.verbFilters = [] + FilterInput.vocabFilters = [] expect(FilterInput.verbFilters.length).to.equal(0); expect(FilterInput.vocabFilters.length).to.equal(0); @@ -443,6 +451,7 @@ describe("SettingsInput", function() { }); + // filters-page component describe("FiltersPage", function() { let FiltersPage; @@ -459,12 +468,22 @@ describe("FiltersPage", function() { push_args = args; }}; + // Override $root.$data.data property + FiltersPage.$root = {$data: {data: {vocab: [ + ["English","Spanish","Type","Category"], + ["Hello","Hola","Type","Category"], + ]}}}; + // Initialize variables + FiltersPage.category = "vocab"; FiltersPage.settings = { promptType: "Text", // Required to prevent browser validation alerts inputType: "Text", // Required to prevent browser validation alerts testSetting: "testValue", }; + FiltersPage.filters = [ + {category:"All Categories", type:"All Types", direction:"Eng. → Esp."} + ]; // Call StartSession FiltersPage.StartSession([1, 2, 3], 0); @@ -473,7 +492,7 @@ describe("FiltersPage", function() { expect(push_args).to.deep.equal({ name: "quizzer", params: { - startingPrompts: [1, 2, 3], + startingPrompts: [["English", "Hello", "Spanish", "Hola"]], startingIndex: 0, settings: { promptType: "Text", // Required to prevent browser validation alerts diff --git a/tests/test.quizzer.js b/tests/test.quizzer.js @@ -554,19 +554,29 @@ describe("Quizzer", function() { }); describe("GetLang method", function() { - it("Should return English by default", function() { - expect(Quizzer.getLang("")).to.equal("en"); - expect(Quizzer.getLang("test")).to.equal("en"); + it("Should return Spanish by default", function() { + expect(Quizzer.getLang("")).to.equal("es"); + expect(Quizzer.getLang("test")).to.equal("es"); }); - it("Should return English for English labels", function() { + it("Should return English for labels containing 'english'", function() { expect(Quizzer.getLang("test english test")).to.equal("en"); expect(Quizzer.getLang("ENGLISH")).to.equal("en"); - }) + }); + + it("Should return English for labels containing 'type'", function() { + expect(Quizzer.getLang("test type test")).to.equal("en"); + expect(Quizzer.getLang("ENGLISH")).to.equal("en"); + }); - it("Should return Spanish for Spanish labels", function() { + it("Should return English for labels containing 'category'", function() { + expect(Quizzer.getLang("test category test")).to.equal("en"); + expect(Quizzer.getLang("ENGLISH")).to.equal("en"); + }); + + it("Should return Spanish for labels containing 'spanish'", function() { expect(Quizzer.getLang("test spanish test")).to.equal("es"); expect(Quizzer.getLang("SPANISH")).to.equal("es"); - }) + }); }); });