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 859e3cddd69a95da56e49e4117a3eead5dffef9b
parent 12e29627985d85f38b831686c20ec45244618204
Author: AsherMorgan <59518073+AsherMorgan@users.noreply.github.com>
Date:   Fri,  4 Sep 2020 19:34:34 -0700

Merge pull request #17 from AsherMorgan/vue

Refactor Spanish-Quizzer to use Vue.js.
Diffstat:
MOffline/index.html | 84+++++++++++++++++++++++++++++++++++++++++--------------------------------------
MScripts/Home.js | 290+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
AScripts/Offline.js | 49+++++++++++++++++++++++++++++++++++++++++++++++++
MScripts/Quizzer.js | 123++++++++++++++++++++++++++++++++-----------------------------------------------
MScripts/Reference.js | 131+++++++++++++++++++++++++++++++------------------------------------------------
MScripts/Settings.js | 346++++++++++++++++++-------------------------------------------------------------
MStyles/Global.css | 5+++++
MStyles/Reference.css | 2+-
Mindex.html | 326++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mreference.html | 120++++++++++++++++++++++++++++++++++++++++++-------------------------------------
10 files changed, 695 insertions(+), 781 deletions(-)

diff --git a/Offline/index.html b/Offline/index.html @@ -9,48 +9,52 @@ <link rel="apple-touch-icon" sizes="180x180" href="../Images/apple-touch-icon.png"> <link rel="stylesheet" href="../Styles/Global.css"> <link rel="stylesheet" href="../Styles/Offline.css"> + <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script> + <script src="../Scripts/Offline.js"></script> </head> - <body> - <header onclick="window.location='../'">Spanish-Quizzer</header> + <body onload="Load();"> + <div id="app"> + <header onclick="window.location='../'">Spanish-Quizzer</header> - <main> - <h1>How to Study Offline</h1> - - <div id="instructions"> - <h2>Safari (iOS)</h2> - <ol> - <li>Go to the <a href="../">Spanish-Quizzer homepage</a>.</li> - <li>Tap on either Conjugations, Vocab, or the Reference Tables.</li> - <li>Tap the share icon and select "Add to Reading List".</li> - <li>Open the Settings app and select "Safari".</li> - <li>Scroll down to the "Reading List" section and enable "Automatically Save Offline".</li> - <li>When you are offline, open Safari, go to your Reading List, and tap on Spanish-Quizzer.</li> - </ol> - <h2>Firefox</h2> - <ol> - <li>Go to the <a href="../">Spanish-Quizzer homepage</a>.</li> - <li>Click on either Conjugations, Vocab, or the Reference Tables.</li> - <li>When you are offline, click on the hamburger menu in the top right corner, click on "Web Developer", and enable "Work Offline".</li> - <li>Return to the page you clicked on to study offline.</li> - <li>When you get back online, you must disable "Work Offline" in order to browse the internet again.</li> - </ol> - <h2>Chrome / Edge</h2> - <p>Offline studying is not supported on Google Chrome or Microsoft Edge. Spanish-Quizzer will not work if it is saved as an HTML document.</p> - </div> - </main> - - <footer> - <div id="share" hidden> - <a href="mailto:?Subject=Spanish-Quizzer&amp;Body=https://ashermorgan.github.io/Spanish-Quizzer">Email</a>&nbsp; - <a href="http://www.facebook.com/sharer.php?u=https://ashermorgan.github.io/Spanish-Quizzer" target="_blank">Facebook</a>&nbsp; - <a href="https://twitter.com/share?url=https://ashermorgan.github.io/Spanish-Quizzer&amp;text=Spanish-Quizzer" target="_blank">Twitter</a>&nbsp; - <a href="http://reddit.com/submit?url=https://ashermorgan.github.io/Spanish-Quizzer&amp;title=Spanish-Quizzer" target="_blank">Reddit</a>&nbsp; - <a id="shareSMS" href="sms:&body=https://ashermorgan.github.io/Spanish-Quizzer" hidden>SMS</a>&nbsp; - </div> - <a href="../">Home</a>&nbsp; - <a href="../reference.html">Reference Tables</a>&nbsp; - <a href="javascript:document.getElementById('share').hidden = false;">Share</a>&nbsp; - </footer> + <main> + <h1>How to Study Offline</h1> + + <div id="instructions"> + <h2>Safari (iOS)</h2> + <ol> + <li>Go to the <a href="../">Spanish-Quizzer homepage</a>.</li> + <li>Tap on either Conjugations, Vocab, or the Reference Tables.</li> + <li>Tap the share icon and select "Add to Reading List".</li> + <li>Open the Settings app and select "Safari".</li> + <li>Scroll down to the "Reading List" section and enable "Automatically Save Offline".</li> + <li>When you are offline, open Safari, go to your Reading List, and tap on Spanish-Quizzer.</li> + </ol> + <h2>Firefox</h2> + <ol> + <li>Go to the <a href="../">Spanish-Quizzer homepage</a>.</li> + <li>Click on either Conjugations, Vocab, or the Reference Tables.</li> + <li>When you are offline, click on the hamburger menu in the top right corner, click on "Web Developer", and enable "Work Offline".</li> + <li>Return to the page you clicked on to study offline.</li> + <li>When you get back online, you must disable "Work Offline" in order to browse the internet again.</li> + </ol> + <h2>Chrome / Edge</h2> + <p>Offline studying is not supported on Google Chrome or Microsoft Edge. Spanish-Quizzer will not work if it is saved as an HTML document.</p> + </div> + </main> + + <footer> + <div id="share" hidden> + <a href="mailto:?Subject=Spanish-Quizzer&amp;Body=https://ashermorgan.github.io/Spanish-Quizzer">Email</a>&nbsp; + <a href="http://www.facebook.com/sharer.php?u=https://ashermorgan.github.io/Spanish-Quizzer" target="_blank">Facebook</a>&nbsp; + <a href="https://twitter.com/share?url=https://ashermorgan.github.io/Spanish-Quizzer&amp;text=Spanish-Quizzer" target="_blank">Twitter</a>&nbsp; + <a href="http://reddit.com/submit?url=https://ashermorgan.github.io/Spanish-Quizzer&amp;title=Spanish-Quizzer" target="_blank">Reddit</a>&nbsp; + <a id="shareSMS" href="sms:&body=https://ashermorgan.github.io/Spanish-Quizzer" hidden>SMS</a>&nbsp; + </div> + <a href="../">Home</a>&nbsp; + <a href="../reference.html">Reference Tables</a>&nbsp; + <a href="javascript:document.getElementById('share').hidden = false; void 0;">Share</a>&nbsp; + </footer> + </div> </body> </html> diff --git a/Scripts/Home.js b/Scripts/Home.js @@ -1,54 +1,206 @@ // Declare global variables let Sets; // List of parsed sets let quizzerType = null; // Type of quizzer +let app; // Load the document function Load() { + // Initialize Vue + app = new Vue({ + el: "#app", // Mount to app div + + data: { + state: "home", + darkTheme: false, + verbFilters: [], + vocabFilters: [], + promptType: localStorage.getItem("promptType") || "Text", + inputType: localStorage.getItem("inputType") || "Text", + repeatPrompts: localStorage.getItem("repeatPrompts") || "Never", + + prompts: [], + promptIndex: 0, + responce: "", + responceActive: true, + }, + + methods: { + Back: function() { + switch (app.state) { + case "verbQuizzer": + app.state = "verbSettings"; + break; + case "vocabQuizzer": + app.state = "vocabSettings"; + break; + case "verbSettings": + case "vocabSettings": + case "home": + default: + app.state = "home"; + break; + } + }, + AddVerbFilter: function() { + this.verbFilters.push({"tense":"All Tenses", "type":"All Types"}); + }, + RemoveVerbFilter: function(index) { + // Remove filter + this.verbFilters.splice(index, 1); + }, + AddVocabFilter: function() { + this.vocabFilters.push({"set":"Verbs", "type":"All Definitions"}); + }, + RemoveVocabFilter: function(index) { + // Remove filter + this.vocabFilters.splice(index, 1); + }, + getTenseTypes: function(index) { + // Get filter options + let filters = {"All Types":true, "Reflexive":true, "Regular":true, "Nonregular":true, "Stem Changing":true, "Orthographic":true, "Irregular":true} + switch(this.verbFilters[index].tense) + { + case "All Tenses": + break; + case "Present Participles": + filters["Reflexive"] = false; // Reflexive + filters["Orthographic"] = false; // Orthographic + break; + case "Present Tense": + filters["Orthographic"] = false; // Orthographic + break; + case "Preterite Tense": + break; + case "Imperfect Tense": + filters["Stem Changing"] = false; // Stem Changing + filters["Orthographic"] = false; // Orthographic + break; + } + + // Reset type if needed + if (!filters[this.verbFilters[index].type]) { + this.verbFilters[index].type = "All Types"; + } + + // Return filters + return filters; + }, + getSetFilters: function(index) { + // Get filter options + var filters = []; + switch(this.vocabFilters[index].set) + { + case "Verbs": + filters = ["All Definitions", "Spanish Infinitives", "English Infinitives", "Reverse Conjugations"]; + break; + + case "Adjectives": + case "Adverbs": + case "Prepositions": + case "Transitions": + case "Colors": + case "Days": + case "Months": + case "Questions": + filters = ["All Definitions", "English to Spanish", "Spanish to English"]; + break; + + case "Weather": + case "Professions": + filters = ["All Definitions", "English to Spanish", "Spanish to English", + "Nouns", "Verbs"]; + break; + + case "Family": + case "Clothes": + filters = ["All Definitions", "English to Spanish", "Spanish to English", + "Nouns", "Adjectives"]; + break; + + case "Nature": + case "House": + case "Vacation": + case "Childhood": + case "Health": + filters = ["All Definitions", "English to Spanish", "Spanish to English", + "Nouns", "Verbs", "Adjectives"]; + break; + } + + // Reset type if needed + if (!filters.includes(this.vocabFilters[index].type)) { + this.vocabFilters[index].type = filters[0]; + } + + // Return filters + return filters; + } + }, + + watch: { + darkTheme: function() { + // Get theme from localStorage if null + if (this.darkTheme === null) { + this.darkTheme = JSON.parse(localStorage.getItem("darkTheme")); + } + + // Detect preferred color scheme if null + if (this.darkTheme === null) { + this.darkTheme = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) + } + + // Apply theme + if (this.darkTheme) { + document.body.classList.add("dark"); + } + else { + document.body.classList.remove("dark"); + } + + // Save theme + localStorage.setItem("darkTheme", this.darkTheme); + }, + promptType: function(value) { + localStorage.setItem("promptType", value); + }, + inputType: function(value) { + localStorage.setItem("inputType", value); + }, + repeatPrompts: function(value) { + localStorage.setItem("repeatPrompts", value); + } + }, + + computed: { + prompt: function() { + if (this.promptIndex < this.prompts.length) { + return this.prompts[this.promptIndex]; + } + else { + return ["", "", "", ""]; + } + } + } + }); + + // Unhide hidden divs + // Divs were hidden to improve interface for users with JS blocked + document.getElementById("home").hidden = false; + document.getElementById("settings").hidden = false; + document.getElementById("quizzer").hidden = false; + document.querySelector("footer").hidden = false; + + // Load settings - if (localStorage.getItem("darkMode") == "true") { - document.body.classList.toggle("dark"); - document.getElementById("settingsDarkMode").checked = true; - } - if (localStorage.getItem("PromptType")) { - document.getElementById("settingsPromptType").value = localStorage.getItem("PromptType"); - } - if (localStorage.getItem("InputType")) { - document.getElementById("settingsInputType").value = localStorage.getItem("InputType"); - } - if (localStorage.getItem("repeatPrompt")) { - document.getElementById("settingsRepeatPrompts").value = localStorage.getItem("repeatPrompt"); - } + app.darkTheme = null; // Force theme to update // Add event Listeners document.addEventListener("click", function (e) { document.getElementById('share').hidden = true; }); document.addEventListener("keydown", KeyDown); - document.getElementById("quizzerInput").addEventListener("keydown", function (e) { - if (e.ctrlKey && e.keyCode === 13) { - // Key was Ctrl+Enter - Reset(); // Skip prompt - } - else if (e.keyCode === 13) { - // Key was Enter - if (document.getElementById("quizzerInput").readOnly) { - Continue(); - } - else { - Submit(); - } - } - }); - document.getElementById("quizzerEnter").addEventListener("click", function (e) { - if (document.getElementById("quizzerInput").readOnly) { - Continue(); - } - else { - Submit(); - } - }); // Load CSVs Sets = []; @@ -68,73 +220,19 @@ function Load() { -// Shows specific groups of elements -function Show(div) { - // Hide all elements - document.getElementById("home").hidden = true; - document.getElementById("settings").hidden = true; - document.getElementById("verbSettings").hidden = true; - document.getElementById("vocabSettings").hidden = true; - document.getElementById("quizzerSettings").hidden = true; - document.getElementById("quizzer").hidden = true; - - // Reset settings error message - document.getElementById("settingsError").textContent = ""; - - // Show elements - switch (div) { - default: - case "home": - document.getElementById("home").hidden = false; - quizzerType = null; - break; - case "vocab": - document.getElementById("settings").hidden = false; - document.getElementById("vocabSettings").hidden = false; - document.getElementById("quizzerSettings").hidden = false; - quizzerType = "vocab"; - break; - case "verbs": - document.getElementById("settings").hidden = false; - document.getElementById("verbSettings").hidden = false; - document.getElementById("quizzerSettings").hidden = false; - quizzerType = "verbs"; - break; - case "quizzer": - document.getElementById("quizzer").hidden = false; - break; - } -} - - - -// Controls navigation when user clicks on title -function TitleClicked() { - if (!document.getElementById("quizzer").hidden) { - // Go to settings screen - Show(quizzerType); - } - else { - // Go to home screen - Show("home"); - } -} - - - -// Handles keyDown events (implements keyboard shortcuts) +// Handles keyDown events (implements some keyboard shortcuts) function KeyDown(e) { if (e.key === "Escape") { - TitleClicked(); + app.Back(); } // Home shortcuts - if (document.getElementById("home").hidden == false) { + if (app.state === "home") { if (e.key === "c") { - Show("verbs"); + app.state = "verbSettings"; } if (e.key === "v") { - Show("vocab"); + app.state = "vocabSettings"; } if (e.key === "r") { window.location = "/reference.html"; @@ -142,7 +240,7 @@ function KeyDown(e) { } // Settings shortcuts - if (document.getElementById("settings").hidden == false) { + if (app.state === "verbSettings" || app.state === "vocabSettings") { if (e.key === "s") { CreateSession(); } diff --git a/Scripts/Offline.js b/Scripts/Offline.js @@ -0,0 +1,49 @@ +// Declare global variables +let app; + + + +// Load the document +function Load() { + // Initialize Vue + app = new Vue({ + el: "#app", // Mount to app div + + data: { + darkTheme: false + }, + + watch: { + darkTheme: function() { + // Get theme from localStorage if null + if (this.darkTheme === null) { + this.darkTheme = JSON.parse(localStorage.getItem("darkTheme")); + } + + // Detect preferred color scheme if null + if (this.darkTheme === null) { + this.darkTheme = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) + } + + // Apply theme + if (this.darkTheme) { + document.body.classList.add("dark"); + } + else { + document.body.classList.remove("dark"); + } + + // Save theme + localStorage.setItem("darkTheme", this.darkTheme); + } + } + }); + + // Load settings + app.darkTheme = null; // Force theme to update + + // Add event Listeners + document.addEventListener("click", function (e) { + document.getElementById('share').hidden = true; + }); +} diff --git a/Scripts/Quizzer.js b/Scripts/Quizzer.js @@ -1,7 +1,5 @@ // Declare global variables -let Terms; // List of prompts -let Term; // Index of current prompt -let Settings = {}; // Dictionary of quizzer settings +let Prefix; // Dictionary of quizzer settings @@ -46,25 +44,21 @@ function Shuffle(items) { // Starts the quizzer -function StartQuizzer(terms, term, prefix, inputType, promptType, repeatPrompts) { +function StartQuizzer(prefix) { // Set variables and settings - Terms = terms; - Term = term - 1; - Settings["Prefix"] = prefix; - Settings["InputType"] = inputType; - Settings["PromptType"] = promptType; - Settings["RepeatPrompts"] = repeatPrompts; + app.promptIndex--; + Prefix = prefix; // Validate Terms - if (!Terms || isNaN(Term) || Term < -1 || Term > Terms.length) { + if (!app.prompts || isNaN(app.promptIndex) || app.promptIndex < -1 || app.promptIndex > app.prompts.length) { throw "Bad arguments."; } - else if (Terms.length == 0) { + else if (app.prompts.length == 0) { throw "Terms is empty."; } // Validate browser for voice input - if (Settings["InputType"] != "Text") { + if (app.inputType != "Text") { if (typeof InstallTrigger !== "undefined") { // Browser is Firefox alert("You must enable speech recognition in about:config."); @@ -77,10 +71,10 @@ function StartQuizzer(terms, term, prefix, inputType, promptType, repeatPrompts) } // Save terms to local storage - localStorage.setItem(Settings["Prefix"] + "terms", JSON.stringify(Terms)); + localStorage.setItem(Prefix + "prompts", JSON.stringify(app.prompts)); // Give iOS devices ringer warning for prompt audio - if (Settings["PromptType"] != "Text") { + if (app.promptType != "Text") { if (!!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform)) { alert("Please make sure your ringer is on in order to hear audio prompts."); } @@ -95,65 +89,40 @@ function StartQuizzer(terms, term, prefix, inputType, promptType, repeatPrompts) // Give the user a new prompt function Reset() { // Show and hide elements - document.getElementById("quizzerEnter").textContent = "Submit"; - document.getElementById("quizzerEnter").disabled = false; - document.getElementById("quizzerFeedback").hidden = true; document.getElementById("quizzerCongrats").hidden = true; - document.getElementById("quizzerInput").readOnly = false; document.getElementById("quizzerInput").focus(); + app.responceActive = true; - // Get prompt - Term++; - if (Term == Terms.length) { + // Get new prompt + app.promptIndex++; + if (app.promptIndex == app.prompts.length) { // The user just finished - Terms = Shuffle(Terms); - Term = 0; - - // Congradulate user - document.getElementById("quizzerCongrats").textContent = "Congratulations! You made it back to the beginning!"; + app.prompts = Shuffle(app.prompts); + app.promptIndex = 0; document.getElementById("quizzerCongrats").hidden = false; } // Save progress to local storage - localStorage.setItem(Settings["Prefix"] + "term", Term); - - // Update progress - document.getElementById("quizzerProgress").textContent = `${Term} / ${Terms.length}`; - - // Set prompt - document.getElementById("quizzerPromptType").textContent = `${Terms[Term][0]}: `; - if (Settings["PromptType"] != "Audio") { - document.getElementById("quizzerPrompt").textContent = Terms[Term][1]; - } - else { - document.getElementById("quizzerPrompt").textContent = "Click to hear again"; - } - document.getElementById("quizzerInputType").textContent = `${Terms[Term][2]}: `; + localStorage.setItem(Prefix + "prompt", app.promptIndex); // Reset responce - document.getElementById("quizzerInput").value = ""; + app.responce = ""; // Read prompt - if (Settings["PromptType"] != "Text") { - Read(Terms[Term][1], Terms[Term][0]); - } - - // Disable textbox and submit button - if (Settings["InputType"] == "Voice") { - document.getElementById("quizzerInput").readOnly = true; - document.getElementById("quizzerEnter").disabled = true; + if (app.promptType != "Text") { + Read(app.prompt[1], app.prompt[0]); } // Get voice input - if (Settings["InputType"] != "Text") { + if (app.inputType != "Text") { // Create recognition object var recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.msSpeechRecognition)(); // Set language - if (Terms[Term][2].toLowerCase().includes("english")) { + if (app.prompt[2].toLowerCase().includes("english")) { recognition.lang = 'en-US'; } - else if (Terms[Term][2].toLowerCase().includes("spanish")) { + else if (app.prompt[2].toLowerCase().includes("spanish")) { recognition.lang = 'es-mx'; } @@ -170,7 +139,7 @@ function Reset() { responce += `${result.transcript}, `; responce += `${result.transcript.split(" or ").join(", ")}, `; } - document.getElementById("quizzerInput").value = responce; + app.responce = responce; Submit() }; } @@ -181,7 +150,7 @@ function Reset() { // Processes a user's submitted responce function Submit() { // Parse responce - var responce = document.getElementById("quizzerInput").value.toLowerCase(); // Make responce lowercase + var responce = app.responce.toLowerCase(); // Make responce lowercase responce = responce.replace(/a`/g, "á"); // Apply accented a shortcut responce = responce.replace(/e`/g, "é"); // Apply accented e shortcut responce = responce.replace(/i`/g, "í"); // Apply accented i shortcut @@ -196,7 +165,7 @@ function Submit() { } // Parse answer - answers = Terms[Term][3].toLowerCase().split(","); // Split string by commas + answers = app.prompt[3].toLowerCase().split(","); // Split string by commas for (var i = 0; i < answers.length; i++) { answers[i] = answers[i].trim(); // Trim whitespace } @@ -211,17 +180,11 @@ function Submit() { // Give user feedback if (!correct) { - // Responce was incorrect - document.getElementById("quizzerFeedbackTerm").textContent = Terms[Term][3].toLowerCase(); - // Show and hide elements - document.getElementById("quizzerInput").readOnly = true; - document.getElementById("quizzerEnter").textContent = "Continue"; - document.getElementById("quizzerEnter").disabled = false; - document.getElementById("quizzerFeedback").hidden = false; document.getElementById("quizzerCongrats").hidden = true; document.getElementById("quizzerFeedback").scrollIntoView(false); document.getElementById("quizzerInput").focus(); + app.responceActive = false; } else { // Responce was correct @@ -234,34 +197,46 @@ function Submit() { // Processes an incorrect responce and then resets the quizzer function Continue() { // Repeat prompt - switch (Settings["RepeatPrompts"]) + switch (app.repeatPrompts) { case "Never": // Don't repeat break; case "Immediately": // Repeat imitiately - Term--; + app.promptIndex--; break; case "5 prompts later": // Repeat 5 prompts later - var temp = Terms[Term]; - Terms.splice(Term, 1); - Terms.splice(Term + 5, 0, temp); - Term--; + var temp = app.prompt; + app.prompts.splice(app.promptIndex, 1); + app.prompts.splice(app.promptIndex + 5, 0, temp); + app.promptIndex--; break; case "At the end": // Repeat at end of Terms - var temp = Terms[Term]; - Terms.splice(Term, 1); - Terms.push(temp); - Term--; + var temp = app.prompt; + app.prompts.splice(app.promptIndex, 1); + app.prompts.push(temp); + app.promptIndex--; break; } // Save terms to local storage - localStorage.setItem(Settings["Prefix"] + "terms", JSON.stringify(Terms)); + localStorage.setItem(Prefix + "terms", JSON.stringify(app.prompts)); // Reset quizzer Reset(); } + + + +// Called when the user hits enter or presses the enter button +function Enter() { + if (app.responceActive) { + Continue(); + } + else { + Submit(); + } +} diff --git a/Scripts/Reference.js b/Scripts/Reference.js @@ -1,14 +1,57 @@ // Declare global variables -var Sets; // List of parsed sets +let app; // Load the document function Load() { - // Apply dark mode - if (localStorage.getItem("darkMode") == "true") { - document.body.classList.toggle("dark"); - } + // Initialize Vue + app = new Vue({ + el: "#app", // Mount to app div + + data: { + darkTheme: false, + set: "Choose a vocab set", + sets: {"Choose a vocab set":[]}, + query: "" + }, + + watch: { + darkTheme: function() { + // Get theme from localStorage if null + if (this.darkTheme === null) { + this.darkTheme = JSON.parse(localStorage.getItem("darkTheme")); + } + + // Detect preferred color scheme if null + if (this.darkTheme === null) { + this.darkTheme = (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) + } + + // Apply theme + if (this.darkTheme) { + document.body.classList.add("dark"); + } + else { + document.body.classList.remove("dark"); + } + + // Save theme + localStorage.setItem("darkTheme", this.darkTheme); + } + } + }); + + // Unhide hidden divs + // Divs were hidden to improve interface for users with JS blocked + document.querySelector("h1").hidden = false; + document.getElementById("controls").hidden = false; + document.getElementById("referenceTable").hidden = false; + document.querySelector("footer").hidden = false; + + + // Load settings + app.darkTheme = null; // Force theme to update // Set table height setTableHeight(); @@ -19,7 +62,6 @@ function Load() { }); // Load CSVs - Sets = []; let setNames = ["Verbs", "Adjectives", "Adverbs", "Prepositions", "Transitions", "Colors", "Days", "Months", "Questions", "Weather", "Family", "Clothes", "Nature", "House", "Vacation", "Childhood", "Professions", "Health"]; @@ -28,7 +70,7 @@ function Load() { download: true, complete: function(results) { // Set verbs - Sets[setName] = results.data; + app.sets[setName] = results.data; } }); } @@ -44,83 +86,15 @@ function setTableHeight() { -// Change the vocab set -function referenceSetChanged() { - // Clear table - if (document.getElementById("referenceSet").value == "Choose a vocab set") { - document.getElementById("referenceTableInner").innerHTML = ""; - return; - } - - // Get headers - var head = '<tr>'; - for (column of Sets[document.getElementById("referenceSet").value][0]) { - head += `<th>${column}</th>`; - } - head += "</tr>"; - - // Get body - var body = ""; - rows = Sets[document.getElementById("referenceSet").value].slice(1); - for (var row = 0; row < rows.length; row++) { - body += '<tr>'; - columns = rows[row]; - for (var column = 0; column < columns.length; column++) { - body += `<td onclick="Read(${row + 1}, ${column})">${columns[column]}</td>`; - } - body += "</tr>"; - } - - // Add html - document.getElementById("referenceTableInner").innerHTML = head + body; -} - - - // Reads a vocab word function Read(row, column) { - var msg = new SpeechSynthesisUtterance(Sets[document.getElementById("referenceSet").value][row][column]); - if (Sets[document.getElementById("referenceSet").value][0][column].toLowerCase().includes("english")) { + var msg = new SpeechSynthesisUtterance(app.sets[app.set][row][column]); + if (app.sets[app.set][0][column].toLowerCase().includes("english")) { msg.lang = 'en'; } - else if (Sets[document.getElementById("referenceSet").value][0][column].toLowerCase().includes("spanish")){ + else if (app.sets[app.set][0][column].toLowerCase().includes("spanish")){ msg.lang = 'es'; } window.speechSynthesis.speak(msg); } - - - -// Filter the table -function referenceFilterChanged() { - // Declare variables - var match, txtValue - var filter = document.getElementById("referenceFilter").value.toLowerCase(); - var rows = document.getElementById("referenceTableInner").getElementsByTagName("tr"); - - // Loop through rows - for (row of rows) - { - // Loop through columns - match = false; - row.style.display = "none"; - for (column of row.children) - { - // If a match hasn't already been found - if (!match) { - // Get text - txtValue = column.textContent || column.innerText; - - // Look for match - if (txtValue.toLowerCase().indexOf(filter) != -1) { - row.style.display = ""; - match = true; - } - } - } - } - - // Make first row visible - rows[0].style.display = ""; -} -\ No newline at end of file diff --git a/Scripts/Settings.js b/Scripts/Settings.js @@ -1,232 +1,45 @@ -// Declare global variables -let setId = 0; // Next valid vocab set id number - - - -// Update local storage -function UpdateLocalStorage() { - localStorage.setItem("darkMode", document.getElementById("settingsDarkMode").checked); - localStorage.setItem("PromptType", document.getElementById("settingsPromptType").value); - localStorage.setItem("InputType", document.getElementById("settingsInputType").value); - localStorage.setItem("repeatPrompt", document.getElementById("settingsRepeatPrompts").value); -} - - - -// Add a filtered set -function AddVocabSet() { - // Create row - var clone = document.getElementById("vocabSetTemplate").content.cloneNode(true); - - // Set row ids - clone.children[0].setAttribute("id", `vocabSet-${setId}`); - clone.getElementById("vocabSetName").setAttribute("id", `vocabSetName-${setId}`); - clone.getElementById("vocabSetFilter").setAttribute("id", `vocabSetFilter-${setId}`); - - // Add remove button onclick attribute - clone.getElementById("vocabSetRemove").setAttribute("onclick", `var element = document.getElementById('vocabSet-${setId}'); element.parentNode.removeChild(element);`); - - // Add row - document.getElementById("vocabSetsInner").appendChild(clone); - - // Add filters - VocabSetChanged(document.getElementById(`vocabSetName-${setId}`)); - - // Increment setId - setId++; // increment fileId to get a unique ID for the new element -} - - - -// Add a verb filter -function AddVerbFilter() { - // Create row - var clone = document.getElementById("verbFilterTemplate").content.cloneNode(true); - - // Set row ids - clone.children[0].setAttribute("id", `verbFilter-${setId}`); - clone.getElementById("verbFilterTense").setAttribute("id", `verbFilterTense-${setId}`); - clone.getElementById("verbFilterType").setAttribute("id", `verbFilterType-${setId}`); - - // Add remove button onclick attribute - clone.getElementById("verbFilterRemove").setAttribute("onclick", `var element = document.getElementById('verbFilter-${setId}'); element.parentNode.removeChild(element);`); - - // Add row - document.getElementById("verbFiltersInner").appendChild(clone); - - // Increment setId - setId++; // increment fileId to get a unique ID for the new element -} - - - -// Update the filter option -function VocabSetChanged(setName) { - // Get filter options - var items = []; - switch(setName.value) - { - case "Verbs": - items = ["All Definitions", "Spanish Infinitives", "English Infinitives", "Reverse Conjugations"]; - break; - - case "Adjectives": - case "Adverbs": - case "Prepositions": - case "Transitions": - case "Colors": - case "Days": - case "Months": - case "Questions": - items = ["All Definitions", "English to Spanish", "Spanish to English"]; - break; - - case "Weather": - case "Professions": - items = ["All Definitions", "English to Spanish", "Spanish to English", - "Nouns", "Verbs"]; - break; - - case "Family": - case "Clothes": - items = ["All Definitions", "English to Spanish", "Spanish to English", - "Nouns", "Adjectives"]; - break; - - case "Nature": - case "House": - case "Vacation": - case "Childhood": - case "Health": - items = ["All Definitions", "English to Spanish", "Spanish to English", - "Nouns", "Verbs", "Adjectives"]; - break; - } - - // Create html - var html = "" - for (var item of items) { - html += "<option>" + item + "</option>" - } - - // Set html - filterId = setName.id.replace("vocabSetName", "vocabSetFilter"); - document.getElementById(filterId).innerHTML = html; -} - - - -// Update the type filter options -function VerbTenseChanged(filter) { - // Get type filter element - let types = document.getElementById(filter.id.replace("verbFilterTense", "verbFilterType")); - - // Enable all types - types[0].disabled = false; - types[1].disabled = false; - types[2].disabled = false; - types[3].disabled = false; - types[4].disabled = false; - types[5].disabled = false; - types[6].disabled = false; - - // Disable unavailable types - switch(filter.value) - { - case "All Tenses": - break; - case "Present Participles": - types[1].disabled = true; // Reflexive - types[5].disabled = true; // Orthographic - if (types.selectedIndex === 1 || types.selectedIndex === 5) { - // Deselect unavailable types - types.selectedIndex = 0 - } - break; - case "Present Tense": - types[5].disabled = true; // Orthographic - if (types.selectedIndex === 5) { - // Deselect unavailable types - types.selectedIndex = 0 - } - break; - case "Preterite Tense": - break; - case "Imperfect Tense": - types[4].disabled = true; // Stem Changing - types[5].disabled = true; // Orthographic - if (types.selectedIndex === 4 || types.selectedIndex === 5) { - // Deselect unavailable types - types.selectedIndex = 0 - } - break; - } -} - - - // Start a new session function CreateSession() { - // Get terms and localStorage prefix - let terms; + // Get prompts and localStorage prefix let prefix; - if (!document.getElementById("vocabSettings").hidden) { - // Filter and load Sets into Terms - terms = []; - for (var i = 0; i < setId; i++) + if (app.state == "vocabSettings") { + // Filter and load Sets into prompts + app.prompts = []; + for (let filter of app.vocabFilters) { - if (document.getElementById(`vocabSet-${i}`)) - { - // Get filter information - var set = document.getElementById(`vocabSetName-${i}`).value; - var filter = document.getElementById(`vocabSetFilter-${i}`).value; - - // Add filtered set - terms.push(...ApplyVocabFilter(Sets[set], filter)); - } + // Add filtered set + app.prompts.push(...ApplyVocabFilter(Sets[filter.set], filter.type)); } - // Shuffle terms - terms = Shuffle(terms); + // Shuffle prompts + app.prompts = Shuffle(app.prompts); // Set prefix prefix = "vocab-" } - else if (!document.getElementById("verbSettings").hidden) { - // Get filters - let filters = []; - for (let i = 0; i < setId; i++) - { - if (document.getElementById(`verbFilter-${i}`)) - { - // Get filter information - let tense = document.getElementById(`verbFilterTense-${i}`).value; - let type = document.getElementById(`verbFilterType-${i}`).value; - - // Add filter - filters.push({tense: tense, regularity: type}); - } - } - - // Get terms - terms = Shuffle(ApplyVerbFilter(Sets["Verbs"], filters)); + else if (app.state == "verbSettings") { + // Get prompts + app.prompts = Shuffle(ApplyVerbFilter(Sets["Verbs"], app.verbFilters)); // Set prefix prefix = "verb-" } - - // Get quizzer settings - inputType = document.getElementById("settingsInputType").value; - promptType = document.getElementById("settingsPromptType").value; - repeatPrompts = document.getElementById("settingsRepeatPrompts").value; + + // Set progress + app.promptIndex = 0; // Start quizzer try { // Start quizzer - StartQuizzer(terms, 0, prefix, inputType, promptType, repeatPrompts); + StartQuizzer(prefix); // Show and hide elements - Show("quizzer"); + if (app.state == "verbSettings") { + app.state = "verbQuizzer"; + } + if (app.state == "vocabSettings") { + app.state = "vocabQuizzer"; + } } catch (e) { switch (e) { @@ -248,29 +61,28 @@ function CreateSession() { function ResumeSession() { // Get localStorage prefix let prefix; - if (!document.getElementById("vocabSettings").hidden) { + if (app.state == "vocabSettings") { prefix = "vocab-" } - else if (!document.getElementById("verbSettings").hidden) { + else if (app.state == "verbSettings") { prefix = "verb-" } - // Load terms and progress - let terms = JSON.parse(localStorage.getItem(prefix + "terms")); - let term = parseInt(localStorage.getItem(prefix + "term")); - - // Get quizzer settings - inputType = document.getElementById("settingsInputType").value; - promptType = document.getElementById("settingsPromptType").value; - repeatPrompts = document.getElementById("settingsRepeatPrompts").value; + // Load prompts and progress + app.prompts = JSON.parse(localStorage.getItem(prefix + "prompts")); + app.promptIndex = parseInt(localStorage.getItem(prefix + "prompt")); // Start quizzer try { - StartQuizzer(terms, term, prefix, inputType, promptType, repeatPrompts); + StartQuizzer(prefix); // Show and hide elements - document.getElementById("settings").hidden = true; - document.getElementById("quizzer").hidden = false; + if (app.state == "verbSettings") { + app.state = "verbQuizzer"; + } + if (app.state == "vocabSettings") { + app.state = "vocabQuizzer"; + } } catch (e) { switch (e) { @@ -360,89 +172,89 @@ function ApplyVocabFilter(vocabSet, name) { // Filters verbs set given the filter information function ApplyVerbFilter(terms, filterInfo) { - // Change regularity strings into regex + // Create filters + let filters = []; // Format: [{outputIndex:0, inputIndex:0, filterIndex:0, filterValue:"regex"}] for (config of filterInfo) { - switch (config.regularity.toLowerCase()) { + // Get regularity + let regularity; + switch (config.type.toLowerCase()) { case "regular": - config.regularity = "Regular"; + regularity = "Regular"; break; case "reflexive": - config.regularity = "Reflexive"; + regularity = "Reflexive"; break; case "irregular": - config.regularity = "Irregular"; + regularity = "Irregular"; break; case "stem-changing": case "stem changing": - config.regularity = "Stem.?Changing"; + regularity = "Stem.?Changing"; break; case "orthographic": - config.regularity = "Orthographic"; + regularity = "Orthographic"; break; case "non-regular": case "non regular": case "nonregular": - config.regularity = "Irregular|Stem.?Changing|Orthographic"; + regularity = "Irregular|Stem.?Changing|Orthographic"; break; default: case "all": - config.regularity = ".*"; + regularity = ".*"; } - } - // Create filters - let filters = []; // Format: [{outputIndex:0, inputIndex:0, filterIndex:0, filterValue:"regex"}] - for (config of filterInfo) { + // Create filter switch (config.tense.toLowerCase()) { case "present participle": case "present participles": - filters.push({outputIndex:0, inputIndex:3, filterIndex:2, filterValue:config.regularity}); + filters.push({outputIndex:0, inputIndex:3, filterIndex:2, filterValue:regularity}); break; case "present": case "present tense": - filters.push({outputIndex:0, inputIndex:5, filterIndex:4, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:6, filterIndex:4, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:7, filterIndex:4, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:8, filterIndex:4, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:9, filterIndex:4, filterValue:config.regularity}); + filters.push({outputIndex:0, inputIndex:5, filterIndex:4, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:6, filterIndex:4, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:7, filterIndex:4, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:8, filterIndex:4, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:9, filterIndex:4, filterValue:regularity}); break; case "preterite": case "preterite tense": - filters.push({outputIndex:0, inputIndex:11, filterIndex:10, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:12, filterIndex:10, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:13, filterIndex:10, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:14, filterIndex:10, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:15, filterIndex:10, filterValue:config.regularity}); + filters.push({outputIndex:0, inputIndex:11, filterIndex:10, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:12, filterIndex:10, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:13, filterIndex:10, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:14, filterIndex:10, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:15, filterIndex:10, filterValue:regularity}); break; case "imperfect": case "imperfect tense": - filters.push({outputIndex:0, inputIndex:17, filterIndex:16, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:18, filterIndex:16, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:19, filterIndex:16, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:20, filterIndex:16, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:21, filterIndex:16, filterValue:config.regularity}); + filters.push({outputIndex:0, inputIndex:17, filterIndex:16, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:18, filterIndex:16, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:19, filterIndex:16, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:20, filterIndex:16, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:21, filterIndex:16, filterValue:regularity}); break; default: case "all": - filters.push({outputIndex:0, inputIndex:3, filterIndex:2, filterValue:config.regularity}); + filters.push({outputIndex:0, inputIndex:3, filterIndex:2, filterValue:regularity}); - filters.push({outputIndex:0, inputIndex:5, filterIndex:4, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:6, filterIndex:4, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:7, filterIndex:4, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:8, filterIndex:4, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:9, filterIndex:4, filterValue:config.regularity}); + filters.push({outputIndex:0, inputIndex:5, filterIndex:4, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:6, filterIndex:4, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:7, filterIndex:4, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:8, filterIndex:4, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:9, filterIndex:4, filterValue:regularity}); - filters.push({outputIndex:0, inputIndex:11, filterIndex:10, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:12, filterIndex:10, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:13, filterIndex:10, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:14, filterIndex:10, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:15, filterIndex:10, filterValue:config.regularity}); + filters.push({outputIndex:0, inputIndex:11, filterIndex:10, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:12, filterIndex:10, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:13, filterIndex:10, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:14, filterIndex:10, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:15, filterIndex:10, filterValue:regularity}); - filters.push({outputIndex:0, inputIndex:17, filterIndex:16, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:18, filterIndex:16, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:19, filterIndex:16, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:20, filterIndex:16, filterValue:config.regularity}); - filters.push({outputIndex:0, inputIndex:21, filterIndex:16, filterValue:config.regularity}); + filters.push({outputIndex:0, inputIndex:17, filterIndex:16, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:18, filterIndex:16, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:19, filterIndex:16, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:20, filterIndex:16, filterValue:regularity}); + filters.push({outputIndex:0, inputIndex:21, filterIndex:16, filterValue:regularity}); break; } } diff --git a/Styles/Global.css b/Styles/Global.css @@ -27,6 +27,11 @@ body { margin: 0px; touch-action: manipulation; +} + +#app { + width: 100%; + height: 100%; display: grid; grid-template-rows: auto auto 1fr auto; diff --git a/Styles/Reference.css b/Styles/Reference.css @@ -50,7 +50,7 @@ h1 { --hover-color: #FFFFFF; } - header, h1, #referenceFilter, #referenceSet, footer, label, br { + header, h1, #controls, footer, label, br { display: none; } diff --git a/index.html b/index.html @@ -11,6 +11,7 @@ <link rel="stylesheet" href="Styles/Home.css"> <link rel="stylesheet" href="Styles/Settings.css"> <link rel="stylesheet" href="Styles/Quizzer.css"> + <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script> <script src="https://unpkg.com/papaparse@5.1.1/papaparse.min.js"></script> <script src="Scripts/Home.js"></script> <script src="Scripts/Settings.js"></script> @@ -18,195 +19,184 @@ </head> <body onload="Load();"> - <header onclick="TitleClicked();">Spanish-Quizzer</header> + <div id="app"> + <header @click="Back();">Spanish-Quizzer</header> - <main> - <noscript> - <h1 id="jsError">You must have JavaScript enabled to run Spanish-Quizzer.</h1> - </noscript> - - <div id="home"> - <h1>What do you want to study?</h1> + <main> + <noscript> + <h1 id="jsError">You must have JavaScript enabled to run Spanish-Quizzer.</h1> + </noscript> - <div> - <button onclick="Show('verbs');">Conjugations</button> - <button onclick="Show('vocab');">Vocab</button> - </div> + <div id="home" v-show="state == 'home'" hidden> + <h1>What do you want to study?</h1> - <div> - <a href="reference.html">Or look something up in the Reference Tables</a> + <div> + <button @click="state = 'verbSettings';">Conjugations</button> + <button @click="state = 'vocabSettings';">Vocab</button> + </div> + + <div> + <a href="reference.html">Or look something up in the Reference Tables</a> + </div> </div> - </div> - - - <div id="settings" hidden> - <div id="verbSettings" hidden> - <template id="verbFilterTemplate"> - <div class="verbFilter"> - <select id="verbFilterTense" onchange="VerbTenseChanged(this);"> - <option>All Tenses</option> - <option>Present Participles</option> - <option>Present Tense</option> - <option>Preterite Tense</option> - <option>Imperfect Tense</option> - </select> - <select id="verbFilterType"> - <option>All Types</option> - <option>Reflexive</option> - <option>Regular</option> - <option>Nonregular</option> - <option>Stem Changing</option> - <option>Orthographic</option> - <option>Irregular</option> - </select> - <button id="verbFilterRemove" class="itemRemove">╳</button> + + + <div id="settings" v-show="state == 'verbSettings' || state == 'vocabSettings'" hidden> + <div id="verbSettings" v-show="state == 'verbSettings'"> + <h1>Choose your settings and then click start.</h1> + + <h2> + Verb Filters + <button @click="AddVerbFilter();">Add Filter</button> + </h2> + + <div id="verbFilters"> + <div v-for="(filter, index) in verbFilters" class="verbFilter"> + <select v-model="filter.tense"> + <option>All Tenses</option> + <option>Present Participles</option> + <option>Present Tense</option> + <option>Preterite Tense</option> + <option>Imperfect Tense</option> + </select> + <select v-model="filter.type"> + <option v-for="(available, type) in getTenseTypes(index)" :disabled="!available">{{ type }}</option> + </select> + <button class="itemRemove" @click="RemoveVerbFilter(index);">╳</button> + </div> </div> - </template> - - <h1>Choose your settings and then click start.</h1> + </div> - <h2> - Verb Filters - <button id="verbAddFilter" onclick="AddVerbFilter();">Add Filter</button> - </h2> - <div id="verbFilters"> - <div id="verbFiltersInner"> + <div id="vocabSettings" v-show="state == 'vocabSettings'"> + <h1>Choose your settings and then click start.</h1> + + <h2> + Vocabulary Sets + <button @click="AddVocabFilter();">Add set</button> + </h2> + + <div id="vocabSets"> + <div v-for="(filter, index) in vocabFilters" class="vocabSet"> + <select class="vocabSetName" v-model="filter.set"> + <optgroup label="Common Words"> + <option>Verbs</option> + <option>Adjectives</option> + <option>Adverbs</option> + <option>Prepositions</option> + <option>Transitions</option> + </optgroup> + <optgroup label="Spanish 1"> + <option>Colors</option> + <option>Days</option> + <option>Months</option> + <option>Questions</option> + <option>Weather</option> + <option>Family</option> + <option>Clothes</option> + </optgroup> + <optgroup label="Spanish 2"> + <option>Nature</option> + <option>House</option> + <option>Vacation</option> + <option>Childhood</option> + <option>Professions</option> + <option>Health</option> + </optgroup> + </select> + <select class="vocabSetFilter" v-model="filter.type"> + <option v-for="type in getSetFilters(index)" >{{ type }}</option> + </select> + <button class="itemRemove" @click="RemoveVocabFilter(index);">╳</button> + </div> </div> </div> - </div> - - - <div id="vocabSettings" hidden> - <template id="vocabSetTemplate"> - <div class="vocabSet"> - <select id="vocabSetName" class="vocabSetName" onchange="VocabSetChanged(this);"> - <optgroup label="Common Words"> - <option>Verbs</option> - <option>Adjectives</option> - <option>Adverbs</option> - <option>Prepositions</option> - <option>Transitions</option> - </optgroup> - <optgroup label="Spanish 1"> - <option>Colors</option> - <option>Days</option> - <option>Months</option> - <option>Questions</option> - <option>Weather</option> - <option>Family</option> - <option>Clothes</option> - </optgroup> - <optgroup label="Spanish 2"> - <option>Nature</option> - <option>House</option> - <option>Vacation</option> - <option>Childhood</option> - <option>Professions</option> - <option>Health</option> - </optgroup> + + + <div id="quizzerSettings"> + <h2>Quizzer Settings</h2> + + <div> + <input type="checkbox" id="settingsDarkTheme" v-model="darkTheme"> + <label for="settingsDarkTheme">Dark Mode</label> + </div> + <div> + <label for="settingsPromptType">Prompt type</label> + <select id="settingsPromptType" v-model="promptType"> + <option>Text</option> + <option>Audio</option> + <option>Both</option> </select> - <select id="vocabSetFilter" class="vocabSetFilter"> - <!-- Generated by VocabSetChanged() --> + </div> + <div> + <label for="settingsInputType">Input type</label> + <select id="settingsInputType" v-model="inputType"> + <option>Text</option> + <option>Voice</option> + <option>Either</option> + </select> + </div> + <div> + <label for="settingsRepeatPrompts">Repeat missed prompts</label> + <select id="settingsRepeatPrompts" v-model="repeatPrompts"> + <option>Never</option> + <option>Immediately</option> + <option>5 prompts later</option> + <option>At the end</option> </select> - <button id="vocabSetRemove" class="itemRemove">╳</button> </div> - </template> - - <h1>Choose your settings and then click start.</h1> - - <h2> - Vocabulary Sets - <button id="vocabAddSet" onclick="AddVocabSet();">Add set</button> - </h2> - <div id="vocabSets"> - <div id="vocabSetsInner"> + <div id="settingButtons"> + <button id="settingsStart" onclick="CreateSession();">Start</button> + <button id="settingsResume" onclick="ResumeSession();">Resume</button> </div> + + <div id="settingsError" class="centered bad"></div> </div> </div> - - - <div id="quizzerSettings"> - <h2>Quizzer Settings</h2> - - <div> - <input type="checkbox" id="settingsDarkMode" onchange="document.body.classList.toggle('dark'); UpdateLocalStorage();"> - <label for="settingsDarkMode">Dark Mode</label> - </div> - <div> - <label for="settingsPromptType">Prompt type</label> - <select id="settingsPromptType" onchange="UpdateLocalStorage();"> - <option>Text</option> - <option>Audio</option> - <option>Both</option> - </select> - </div> - <div> - <label for="settingsInputType">Input type</label> - <select id="settingsInputType" onchange="UpdateLocalStorage();"> - <option>Text</option> - <option>Voice</option> - <option>Either</option> - </select> - </div> - <div> - <label for="settingsRepeatPrompts">Repeat missed prompts</label> - <select id="settingsRepeatPrompts" onchange="UpdateLocalStorage();"> - <option>Never</option> - <option>Immediately</option> - <option>5 prompts later</option> - <option>At the end</option> - </select> - </div> - - <div id="settingButtons"> - <button id="settingsStart" onclick="CreateSession();">Start</button> - <button id="settingsResume" onclick="ResumeSession();">Resume</button> + + + <div id="quizzer" v-show="state == 'verbQuizzer' || state == 'vocabQuizzer'" hidden> + <p id="quizzerProgress">{{ promptIndex }} / {{ prompts.length}}</p> + + <section> + <label id="quizzerPromptType" for="quizzerPrompt">{{ prompt[0] }}</label> + <span id="quizzerPrompt" @click="Read(prompt[1], prompt[0]);">{{ prompt[1] }}</span> + </section> + + <section> + <label id="quizzerInputType" for="quizzerInput">{{ prompt[2] }}</label> + <input id="quizzerInput" type="text" v-model="responce" :readonly="!responceActive || inputType == 'Voice'" + @keyup.ctrl.enter.exact="Reset();" @keyup.enter.exact="Enter();" + autocomplete="off" spellcheck="false" autocorrect="off" placeholder="Type the answer"> + </section> + + <div id="quizzerButtons"> + <button v-if="responceActive" :disabled="inputType == 'Voice'" @click="Submit();">Submit</button> + <button v-else @click="Continue();">Continue</button> + <button @click="Reset();">Skip</button> </div> - <div id="settingsError" class="centered bad"></div> + <div id="quizzerFeedback" v-show="!responceActive" class="bad"> + The correct answer is + <span id="quizzerFeedbackTerm" @click="Read(prompt[3], prompt[2]);">{{ prompt[3].toLowerCase() }}</span>. + </div> + <div id="quizzerCongrats" class="good">Congratulations! You made it back to the beginning!</div> </div> - </div> - - - <div id="quizzer" hidden> - <p id="quizzerProgress"></p> + </main> - <section> - <label id="quizzerPromptType" for="quizzerPrompt"></label> - <span id="quizzerPrompt" onclick="Read(Terms[Term][1], Terms[Term][0]);"></span> - </section> - - <section> - <label id="quizzerInputType" for="quizzerInput"></label> - <input id="quizzerInput" type="text" autocomplete="off" spellcheck="false" autocorrect="off" placeholder="Type the answer"> - </section> - <div id="quizzerButtons"> - <button id="quizzerEnter">Submit</button> - <button id="quizzerSkip" onclick="Reset();">Skip</button> + <footer hidden> + <div id="share" hidden> + <a href="mailto:?Subject=Spanish-Quizzer&amp;Body=https://ashermorgan.github.io/Spanish-Quizzer">Email</a>&nbsp; + <a rel="noopener" href="http://www.facebook.com/sharer.php?u=https://ashermorgan.github.io/Spanish-Quizzer" target="_blank">Facebook</a>&nbsp; + <a rel="noopener" href="https://twitter.com/share?url=https://ashermorgan.github.io/Spanish-Quizzer&amp;text=Spanish-Quizzer" target="_blank">Twitter</a>&nbsp; + <a rel="noopener" href="http://reddit.com/submit?url=https://ashermorgan.github.io/Spanish-Quizzer&amp;title=Spanish-Quizzer" target="_blank">Reddit</a>&nbsp; + <a id="shareSMS" href="sms:&body=https://ashermorgan.github.io/Spanish-Quizzer" hidden>SMS</a>&nbsp; </div> - - <div id="quizzerFeedback" class="bad"> - The correct answer is - <span id="quizzerFeedbackTerm" onclick="Read(Terms[Term][3], Terms[Term][2]);"></span>. - </div> - <div id="quizzerCongrats" class="good"></div> - </div> - </main> - - - <footer> - <div id="share" hidden> - <a href="mailto:?Subject=Spanish-Quizzer&amp;Body=https://ashermorgan.github.io/Spanish-Quizzer">Email</a>&nbsp; - <a rel="noopener" href="http://www.facebook.com/sharer.php?u=https://ashermorgan.github.io/Spanish-Quizzer" target="_blank">Facebook</a>&nbsp; - <a rel="noopener" href="https://twitter.com/share?url=https://ashermorgan.github.io/Spanish-Quizzer&amp;text=Spanish-Quizzer" target="_blank">Twitter</a>&nbsp; - <a rel="noopener" href="http://reddit.com/submit?url=https://ashermorgan.github.io/Spanish-Quizzer&amp;title=Spanish-Quizzer" target="_blank">Reddit</a>&nbsp; - <a id="shareSMS" href="sms:&body=https://ashermorgan.github.io/Spanish-Quizzer" hidden>SMS</a>&nbsp; - </div> - <a href="Offline">Study Offline</a>&nbsp; - <a href="javascript:document.getElementById('share').hidden = false; void 0;">Share</a>&nbsp; - </footer> + <a href="Offline">Study Offline</a>&nbsp; + <a href="javascript:document.getElementById('share').hidden = false; void 0;">Share</a>&nbsp; + </footer> + </div> </body> </html> \ No newline at end of file diff --git a/reference.html b/reference.html @@ -9,67 +9,75 @@ <link rel="apple-touch-icon" sizes="180x180" href="Images/apple-touch-icon.png"> <link rel="stylesheet" href="Styles/Global.css"> <link rel="stylesheet" href="Styles/Reference.css"> + <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script> <script src="https://unpkg.com/papaparse@5.1.1/papaparse.min.js"></script> <script src="Scripts/Reference.js"></script> </head> <body onload="Load()" onresize="setTableHeight()"> - <header onclick="window.location='./'">Spanish-Quizzer</header> + <div id="app"> + <header onclick="window.location='./'">Spanish-Quizzer</header> - <main> - <noscript> - <h1 id="jsError">You must have JavaScript enabled to run Spanish-Quizzer.</h1> - </noscript> - - <h1>Reference Tables</h1> - - <div id="controls"> - <select id="referenceSet" aria-label="Vocab Set" onchange="referenceSetChanged()"> - <option>Choose a vocab set</option> - <optgroup label="Common Words"> - <option>Verbs</option> - <option>Adjectives</option> - <option>Adverbs</option> - <option>Prepositions</option> - <option>Transitions</option> - </optgroup> - <optgroup label="Spanish 1"> - <option>Colors</option> - <option>Days</option> - <option>Months</option> - <option>Questions</option> - <option>Weather</option> - <option>Family</option> - <option>Clothes</option> - </optgroup> - <optgroup label="Spanish 2"> - <option>Nature</option> - <option>House</option> - <option>Vacation</option> - <option>Childhood</option> - <option>Professions</option> - <option>Health</option> - </optgroup> - </select> - <input type="text" aria-label="Search" id="referenceFilter" onkeyup="referenceFilterChanged()" placeholder="Search"> - </div> - - <div id="referenceTable"> - <table id="referenceTableInner"></table> - </div> - </main> - - <footer> - <div id="share" hidden> - <a href="mailto:?Subject=Spanish-Quizzer&amp;Body=https://ashermorgan.github.io/Spanish-Quizzer/Reference">Email</a>&nbsp; - <a rel="noopener" href="http://www.facebook.com/sharer.php?u=https://ashermorgan.github.io/Spanish-Quizzer/Reference" target="_blank">Facebook</a>&nbsp; - <a rel="noopener" href="https://twitter.com/share?url=https://ashermorgan.github.io/Spanish-Quizzer/Reference&amp;text=Spanish-Quizzer" target="_blank">Twitter</a>&nbsp; - <a rel="noopener" href="http://reddit.com/submit?url=https://ashermorgan.github.io/Spanish-Quizzer/Reference&amp;title=Spanish-Quizzer" target="_blank">Reddit</a>&nbsp; - <a id="shareSMS" href="sms:&body=https://ashermorgan.github.io/Spanish-Quizzer/Reference" hidden>SMS</a>&nbsp; - </div> - <a href="./">Home</a>&nbsp; - <a href="Offline">Study Offline</a>&nbsp; - <a href="javascript:document.getElementById('share').hidden = false; void 0;">Share</a>&nbsp; - </footer> + <main> + <noscript> + <h1 id="jsError">You must have JavaScript enabled to run Spanish-Quizzer.</h1> + </noscript> + + <h1 hidden>Reference Tables</h1> + + <div id="controls" hidden> + <select aria-label="Vocab Set" v-model="set"> + <option>Choose a vocab set</option> + <optgroup label="Common Words"> + <option>Verbs</option> + <option>Adjectives</option> + <option>Adverbs</option> + <option>Prepositions</option> + <option>Transitions</option> + </optgroup> + <optgroup label="Spanish 1"> + <option>Colors</option> + <option>Days</option> + <option>Months</option> + <option>Questions</option> + <option>Weather</option> + <option>Family</option> + <option>Clothes</option> + </optgroup> + <optgroup label="Spanish 2"> + <option>Nature</option> + <option>House</option> + <option>Vacation</option> + <option>Childhood</option> + <option>Professions</option> + <option>Health</option> + </optgroup> + </select> + <input type="text" aria-label="Search" v-model="query" placeholder="Search"> + </div> + + <div id="referenceTable" hidden> + <table> + <tr v-for="(row, rowIndex) in sets[set]" v-show="rowIndex == 0 || row.join(',').toLowerCase().includes(query.toLowerCase())"> + <th v-if="rowIndex == 0" v-for="column in row">{{ column }}</th> + <td v-if="rowIndex != 0" v-for="(column, columnIndex) in row" @click="Read(rowIndex, columnIndex)">{{ column }}</td> + </tr> + </table> + </div> + </main> + + <footer hidden> + <div id="share" hidden> + <a href="mailto:?Subject=Spanish-Quizzer&amp;Body=https://ashermorgan.github.io/Spanish-Quizzer/Reference">Email</a>&nbsp; + <a rel="noopener" href="http://www.facebook.com/sharer.php?u=https://ashermorgan.github.io/Spanish-Quizzer/Reference" target="_blank">Facebook</a>&nbsp; + <a rel="noopener" href="https://twitter.com/share?url=https://ashermorgan.github.io/Spanish-Quizzer/Reference&amp;text=Spanish-Quizzer" target="_blank">Twitter</a>&nbsp; + <a rel="noopener" href="http://reddit.com/submit?url=https://ashermorgan.github.io/Spanish-Quizzer/Reference&amp;title=Spanish-Quizzer" target="_blank">Reddit</a>&nbsp; + <a id="shareSMS" href="sms:&body=https://ashermorgan.github.io/Spanish-Quizzer/Reference" hidden>SMS</a>&nbsp; + </div> + <a href="./">Home</a>&nbsp; + <a href="Offline">Study Offline</a>&nbsp; + <a href="javascript:document.getElementById('share').hidden = false; void 0;">Share</a>&nbsp; + </footer> + </div> </body> </html> \ No newline at end of file