commit 62afbdbc695dd89283c66aee2b8b56ce0ad663f4 parent 792b77fb7d136415134d84583643dce19b3f01d2 Author: AsherMorgan <59518073+AsherMorgan@users.noreply.github.com> Date: Fri, 25 Sep 2020 18:20:17 -0700 Rearrange files. Diffstat:
43 files changed, 763 insertions(+), 763 deletions(-)
diff --git a/Images/apple-touch-icon.png b/Images2/apple-touch-icon.png Binary files differ. diff --git a/Images/favicon-32.png b/Images2/favicon-32.png Binary files differ. diff --git a/Scripts/Home.js b/Scripts/Home.js @@ -1,349 +0,0 @@ -// Declare global variables -let Sets; // List of parsed sets -let app; - - - -/** - * Initialize the Vue app - */ -function loadVue() { - app = new Vue({ - el: "#app", // Mount to app div - - data: { - state: "home", - darkTheme: false, - verbFilters: [], - vocabFilters: [], - errorMsg: "", - promptType: localStorage.getItem("promptType") || "Text", - inputType: localStorage.getItem("inputType") || "Text", - repeatPrompts: localStorage.getItem("repeatPrompts") || "Never", - - prompts: [], - promptIndex: 0, - }, - - methods: { - /** - * Return to the previous state. - */ - 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; - } - }, - - /** - * Add a verb filter on the settings page. - */ - AddVerbFilter: function() { - this.verbFilters.push({"tense":"All Tenses", "type":"All Types"}); - }, - - /** - * Remove a verb filter from the settings page. - * @param {Number} index - The index of the verb filter. - */ - RemoveVerbFilter: function(index) { - // Remove filter - this.verbFilters.splice(index, 1); - }, - - /** - * Add a vocab filter on the settings page. - */ - AddVocabFilter: function() { - this.vocabFilters.push({"set":"Verbs", "type":"All Definitions"}); - }, - - /** - * Remove a vocab filter from the settings page. - * @param {Number} index - The index of the vocab filter. - */ - RemoveVocabFilter: function(index) { - // Remove filter - this.vocabFilters.splice(index, 1); - }, - - /** - * Get the regularity filters available for a verb filter. - * @param {Number} index - The index of the verb filter. - * @returns {object} - An object with boolean properties for each regularity filter. - */ - 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; - }, - - /** - * Get the filters available for a vocab Set. - * @param {Number} index - The index of the vocab filter. - * @returns {Array} - An array containing available 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; - }, - - /** - * Get the language code that matches a label. - * @param {String} label - The label. - * @returns {String} - The language code ("en", "es", etc.) - */ - getLang: function(label) { - if (label.toLowerCase().includes("spanish")) { - return "es"; - } - else { - return "en"; - } - }, - - /** - * Update the user's progress in localStorage. - * @param {Array} prompts - The list of prompts. - * @param {Number} index - The index of the current prompt. - */ - updateProgress: function(prompts, index) { - // Get localStorage prefix - let prefix; - if (app.state === "vocabSettings" || app.state === "vocabQuizzer") { - prefix = "vocab-" - } - else if (app.state === "verbSettings" || app.state === "verbQuizzer") { - prefix = "verb-" - } - else { - return; - } - - // Save progress to local storage - localStorage.setItem(prefix + "prompts", JSON.stringify(prompts)); - localStorage.setItem(prefix + "prompt", JSON.stringify(index)); - } - }, - - watch: { - /** - * Update the app theme. - */ - 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); - }, - - /** - * Update the promptType setting in localStorage. - * @param {String} value - The prompt type. - */ - promptType: function(value) { - localStorage.setItem("promptType", value); - }, - - /** - * Update the inputType setting in localStorage. - * @param {String} value - The input type. - */ - inputType: function(value) { - localStorage.setItem("inputType", value); - }, - - /** - * Update the repeatPrompts setting in localStorage. - * @param {String} value - The repeat prompts setting value. - */ - repeatPrompts: function(value) { - localStorage.setItem("repeatPrompts", value); - }, - - /** - * Clear the error message when the state changes. - */ - state: function() { - // Reset error message - app.errorMsg = ""; - } - }, - - created: function() { - // Force theme to update - this.darkTheme = null; - }, - }); -} - - - -/** - * Load the document. - */ -function Load() { - // Initialize the Vue app - loadVue(); - - // 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; - - // Add event Listeners - document.addEventListener("click", function (e) { - document.getElementById('share').hidden = true; - }); - document.addEventListener("keydown", KeyDown); - - // Load CSVs - Sets = []; - let setNames = ["Verbs", "Adjectives", "Adverbs", "Prepositions", "Transitions", - "Colors", "Days", "Months", "Questions", "Weather", "Family", "Clothes", - "Nature", "House", "Vacation", "Childhood", "Professions", "Health"]; - for (let setName of setNames) { - Papa.parse(`Vocab/${setName}.csv`, { - download: true, - complete: function(results) { - // Set verbs - Sets[setName] = results.data; - } - }); - } -} - - - -/** - * Handle a keyDown event (implements some keyboard shortcuts). - * @param {object} e - The event args. - */ -function KeyDown(e) { - if (e.key === "Escape") { - app.Back(); - } - - // Home shortcuts - if (app.state === "home") { - if (e.key === "c") { - app.state = "verbSettings"; - } - if (e.key === "v") { - app.state = "vocabSettings"; - } - if (e.key === "r") { - window.location = "reference.html"; - } - } - - // Settings shortcuts - if (app.state === "verbSettings" || app.state === "vocabSettings") { - if (e.key === "s") { - CreateSession(); - } - if (e.key === "r") { - ResumeSession(); - } - } -} diff --git a/Scripts/Reference.js b/Scripts/Reference.js @@ -1,136 +0,0 @@ -// Declare global variables -let app; - - - -/** - * Initializes the Vue app - */ -function loadVue() { - app = new Vue({ - el: "#app", // Mount to app div - - data: { - darkTheme: false, - set: "Choose a vocab set", - sets: {"Choose a vocab set":[]}, - query: "" - }, - - methods: { - /** - * Get the language code that matches a label. - * @param {String} label - The label. - * @returns {String} - The language code ("en", "es", etc.) - */ - getLang: function(label) { - if (label.toLowerCase().includes("spanish")) { - return "es"; - } - else { - return "en"; - } - } - }, - - watch: { - /** - * Update the app theme. - */ - 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 the document - */ -function Load() { - // Initialize the Vue - loadVue(); - - // 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(); - - // Add event Listeners - document.addEventListener("click", function (e) { - document.getElementById('share').hidden = true; - }); - - // Load CSVs - let setNames = ["Verbs", "Adjectives", "Adverbs", "Prepositions", "Transitions", - "Colors", "Days", "Months", "Questions", "Weather", "Family", "Clothes", - "Nature", "House", "Vacation", "Childhood", "Professions", "Health"]; - for (let setName of setNames) { - Papa.parse(`Vocab/${setName}.csv`, { - download: true, - complete: function(results) { - // Set verbs - app.sets[setName] = results.data; - } - }); - } -} - - - -/** - * Set the table height. - */ -function setTableHeight() { - var tableY = document.getElementById("referenceTable").offsetTop; - document.getElementById("referenceTable").style.height = `${window.innerHeight - tableY - 50}px`; -} - - - -/** - * Read a term. - * @param {Number} row - The row of the term. - * @param {Number} column - The column of the term. - */ -function Read(row, column) -{ - 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 (app.sets[app.set][0][column].toLowerCase().includes("spanish")){ - msg.lang = 'es'; - } - window.speechSynthesis.speak(msg); -} diff --git a/Styles/Settings.css b/Styles/Settings.css @@ -1,62 +0,0 @@ -/******** Settings styles ********/ -#settings { - margin: auto; - text-align: left; - max-width: 300px; -} - - - -/******** Vocab/Verb Settings styles ********/ -/* Instructions */ -#settings h1 { - text-align: center; - font-size: 16px; - font-weight: normal; -} - -/* Headings */ -h2 { - font-size: 18px; - margin-top: 15px; - margin-bottom: 5px; -} -h2 button { - padding: 2px 5px 2px 5px; -} - -/* List item */ -.vocabSet, .verbFilter { - padding-top: 5px; -} - -/* Vocab list item parts */ -.vocabSetName { - width: 115px; -} -.vocabSetFilter { - width: 160px; -} - -/* List item remove */ -.itemRemove { - border: none; - padding: 0px; - margin: 0px; - cursor: pointer; - color: var(--foreground-color); - background-color: var(--background-color); -} - - - -/******** Settings Button styles ********/ -#settingButtons { - text-align: center; -} -#settingButtons button { - margin-top: 15px; - margin-bottom: 5px; - width: 75px; - height: 25px; -} diff --git a/Tests/index.html b/Tests/index.html @@ -1,41 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="utf-8" /> - <title>Mocha Tests</title> - <meta name="viewport" content="width=device-width, initial-scale=1.0"/> - <link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css"/> - <script src="https://unpkg.com/chai/chai.js"></script> - <script src="https://unpkg.com/mocha/mocha.js"></script> - <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> - </head> - - <body> - <div id="mocha"></div> - - <!-- To prevent document.getElementById errors --> - <div id="app" hidden></div> - - <!-- Scripts being tested --> - <script src="../Scripts/Home.js"></script> - <script src="../Scripts/Quizzer.js"></script> - <script src="../Scripts/Settings.js"></script> - - <!-- Setup tests --> - <script class="mocha-init"> - mocha.setup('bdd'); - mocha.checkLeaks(); - let expect = chai.expect; - </script> - - <!-- Tests --> - <script src="test.app.js"></script> - <script src="test.quizzer.js"></script> - <script src="test.settings.js"></script> - - <!-- Run tests --> - <script class="mocha-exec"> - mocha.run(); - </script> - </body> -</html> diff --git a/Tests2/index.html b/Tests2/index.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <title>Mocha Tests</title> + <meta name="viewport" content="width=device-width, initial-scale=1.0"/> + <link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css"/> + <script src="https://unpkg.com/chai/chai.js"></script> + <script src="https://unpkg.com/mocha/mocha.js"></script> + <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> + </head> + + <body> + <div id="mocha"></div> + + <!-- To prevent document.getElementById errors --> + <div id="app" hidden></div> + + <!-- Scripts being tested --> + <script src="../js/home.js"></script> + <script src="../js/quizzer.js"></script> + <script src="../js/settings.js"></script> + + <!-- Setup tests --> + <script class="mocha-init"> + mocha.setup('bdd'); + mocha.checkLeaks(); + let expect = chai.expect; + </script> + + <!-- Tests --> + <script src="test.app.js"></script> + <script src="test.quizzer.js"></script> + <script src="test.settings.js"></script> + + <!-- Run tests --> + <script class="mocha-exec"> + mocha.run(); + </script> + </body> +</html> diff --git a/Tests/test.app.js b/Tests2/test.app.js diff --git a/Tests/test.quizzer.js b/Tests2/test.quizzer.js diff --git a/Tests/test.settings.js b/Tests2/test.settings.js diff --git a/Vocab/Adjectives.csv b/Vocab2/Adjectives.csv diff --git a/Vocab/Adverbs.csv b/Vocab2/Adverbs.csv diff --git a/Vocab/Childhood.csv b/Vocab2/Childhood.csv diff --git a/Vocab/Clothes.csv b/Vocab2/Clothes.csv diff --git a/Vocab/Colors.csv b/Vocab2/Colors.csv diff --git a/Vocab/Days.csv b/Vocab2/Days.csv diff --git a/Vocab/Family.csv b/Vocab2/Family.csv diff --git a/Vocab/Health.csv b/Vocab2/Health.csv diff --git a/Vocab/House.csv b/Vocab2/House.csv diff --git a/Vocab/Months.csv b/Vocab2/Months.csv diff --git a/Vocab/Nature.csv b/Vocab2/Nature.csv diff --git a/Vocab/Prepositions.csv b/Vocab2/Prepositions.csv diff --git a/Vocab/Professions.csv b/Vocab2/Professions.csv diff --git a/Vocab/Questions.csv b/Vocab2/Questions.csv diff --git a/Vocab/Transitions.csv b/Vocab2/Transitions.csv diff --git a/Vocab/Vacation.csv b/Vocab2/Vacation.csv diff --git a/Vocab/Verbs.csv b/Vocab2/Verbs.csv diff --git a/Vocab/Weather.csv b/Vocab2/Weather.csv diff --git a/Styles/Global.css b/css/global.css diff --git a/Styles/Home.css b/css/home.css diff --git a/Styles/Quizzer.css b/css/quizzer.css diff --git a/Styles/Reference.css b/css/reference.css diff --git a/css/settings.css b/css/settings.css @@ -0,0 +1,62 @@ +/******** Settings styles ********/ +#settings { + margin: auto; + text-align: left; + max-width: 300px; +} + + + +/******** vocab/Verb Settings styles ********/ +/* Instructions */ +#settings h1 { + text-align: center; + font-size: 16px; + font-weight: normal; +} + +/* Headings */ +h2 { + font-size: 18px; + margin-top: 15px; + margin-bottom: 5px; +} +h2 button { + padding: 2px 5px 2px 5px; +} + +/* List item */ +.vocabSet, .verbFilter { + padding-top: 5px; +} + +/* Vocab list item parts */ +.vocabSetName { + width: 115px; +} +.vocabSetFilter { + width: 160px; +} + +/* List item remove */ +.itemRemove { + border: none; + padding: 0px; + margin: 0px; + cursor: pointer; + color: var(--foreground-color); + background-color: var(--background-color); +} + + + +/******** Settings Button styles ********/ +#settingButtons { + text-align: center; +} +#settingButtons button { + margin-top: 15px; + margin-bottom: 5px; + width: 75px; + height: 25px; +} diff --git a/index.html b/index.html @@ -5,17 +5,17 @@ <title>Spanish-Quizzer</title> <meta name="description" content="Quiz yourself on Spanish vocabulary and conjugations through a free and highly customizable web interface."> <meta name="viewport" content="width=device-width, user-scalable=no"/> - <link rel="icon" type="image/png" href="Images/favicon-32.png"> - <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/Home.css"> - <link rel="stylesheet" href="Styles/Settings.css"> - <link rel="stylesheet" href="Styles/Quizzer.css"> + <link rel="icon" type="image/png" href="images/favicon-32.png"> + <link rel="apple-touch-icon" sizes="180x180" href="images/apple-touch-icon.png"> + <link rel="stylesheet" href="css/global.css"> + <link rel="stylesheet" href="css/home.css"> + <link rel="stylesheet" href="css/settings.css"> + <link rel="stylesheet" href="css/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> - <script src="Scripts/Quizzer.js"></script> + <script src="js/home.js"></script> + <script src="js/settings.js"></script> + <script src="js/quizzer.js"></script> </head> <body onload="Load();"> diff --git a/js/home.js b/js/home.js @@ -0,0 +1,349 @@ +// Declare global variables +let Sets; // List of parsed sets +let app; + + + +/** + * Initialize the Vue app + */ +function loadVue() { + app = new Vue({ + el: "#app", // Mount to app div + + data: { + state: "home", + darkTheme: false, + verbFilters: [], + vocabFilters: [], + errorMsg: "", + promptType: localStorage.getItem("promptType") || "Text", + inputType: localStorage.getItem("inputType") || "Text", + repeatPrompts: localStorage.getItem("repeatPrompts") || "Never", + + prompts: [], + promptIndex: 0, + }, + + methods: { + /** + * Return to the previous state. + */ + 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; + } + }, + + /** + * Add a verb filter on the settings page. + */ + AddVerbFilter: function() { + this.verbFilters.push({"tense":"All Tenses", "type":"All Types"}); + }, + + /** + * Remove a verb filter from the settings page. + * @param {Number} index - The index of the verb filter. + */ + RemoveVerbFilter: function(index) { + // Remove filter + this.verbFilters.splice(index, 1); + }, + + /** + * Add a vocab filter on the settings page. + */ + AddVocabFilter: function() { + this.vocabFilters.push({"set":"Verbs", "type":"All Definitions"}); + }, + + /** + * Remove a vocab filter from the settings page. + * @param {Number} index - The index of the vocab filter. + */ + RemoveVocabFilter: function(index) { + // Remove filter + this.vocabFilters.splice(index, 1); + }, + + /** + * Get the regularity filters available for a verb filter. + * @param {Number} index - The index of the verb filter. + * @returns {object} - An object with boolean properties for each regularity filter. + */ + 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; + }, + + /** + * Get the filters available for a vocab Set. + * @param {Number} index - The index of the vocab filter. + * @returns {Array} - An array containing available 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; + }, + + /** + * Get the language code that matches a label. + * @param {String} label - The label. + * @returns {String} - The language code ("en", "es", etc.) + */ + getLang: function(label) { + if (label.toLowerCase().includes("spanish")) { + return "es"; + } + else { + return "en"; + } + }, + + /** + * Update the user's progress in localStorage. + * @param {Array} prompts - The list of prompts. + * @param {Number} index - The index of the current prompt. + */ + updateProgress: function(prompts, index) { + // Get localStorage prefix + let prefix; + if (app.state === "vocabSettings" || app.state === "vocabQuizzer") { + prefix = "vocab-" + } + else if (app.state === "verbSettings" || app.state === "verbQuizzer") { + prefix = "verb-" + } + else { + return; + } + + // Save progress to local storage + localStorage.setItem(prefix + "prompts", JSON.stringify(prompts)); + localStorage.setItem(prefix + "prompt", JSON.stringify(index)); + } + }, + + watch: { + /** + * Update the app theme. + */ + 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); + }, + + /** + * Update the promptType setting in localStorage. + * @param {String} value - The prompt type. + */ + promptType: function(value) { + localStorage.setItem("promptType", value); + }, + + /** + * Update the inputType setting in localStorage. + * @param {String} value - The input type. + */ + inputType: function(value) { + localStorage.setItem("inputType", value); + }, + + /** + * Update the repeatPrompts setting in localStorage. + * @param {String} value - The repeat prompts setting value. + */ + repeatPrompts: function(value) { + localStorage.setItem("repeatPrompts", value); + }, + + /** + * Clear the error message when the state changes. + */ + state: function() { + // Reset error message + app.errorMsg = ""; + } + }, + + created: function() { + // Force theme to update + this.darkTheme = null; + }, + }); +} + + + +/** + * Load the document. + */ +function Load() { + // Initialize the Vue app + loadVue(); + + // 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; + + // Add event Listeners + document.addEventListener("click", function (e) { + document.getElementById('share').hidden = true; + }); + document.addEventListener("keydown", KeyDown); + + // Load CSVs + Sets = []; + let setNames = ["Verbs", "Adjectives", "Adverbs", "Prepositions", "Transitions", + "Colors", "Days", "Months", "Questions", "Weather", "Family", "Clothes", + "Nature", "House", "Vacation", "Childhood", "Professions", "Health"]; + for (let setName of setNames) { + Papa.parse(`vocab/${setName}.csv`, { + download: true, + complete: function(results) { + // Set verbs + Sets[setName] = results.data; + } + }); + } +} + + + +/** + * Handle a keyDown event (implements some keyboard shortcuts). + * @param {object} e - The event args. + */ +function KeyDown(e) { + if (e.key === "Escape") { + app.Back(); + } + + // Home shortcuts + if (app.state === "home") { + if (e.key === "c") { + app.state = "verbSettings"; + } + if (e.key === "v") { + app.state = "vocabSettings"; + } + if (e.key === "r") { + window.location = "reference.html"; + } + } + + // Settings shortcuts + if (app.state === "verbSettings" || app.state === "vocabSettings") { + if (e.key === "s") { + CreateSession(); + } + if (e.key === "r") { + ResumeSession(); + } + } +} diff --git a/Scripts/Quizzer.js b/js/quizzer.js diff --git a/js/reference.js b/js/reference.js @@ -0,0 +1,136 @@ +// Declare global variables +let app; + + + +/** + * Initializes the Vue app + */ +function loadVue() { + app = new Vue({ + el: "#app", // Mount to app div + + data: { + darkTheme: false, + set: "Choose a vocab set", + sets: {"Choose a vocab set":[]}, + query: "" + }, + + methods: { + /** + * Get the language code that matches a label. + * @param {String} label - The label. + * @returns {String} - The language code ("en", "es", etc.) + */ + getLang: function(label) { + if (label.toLowerCase().includes("spanish")) { + return "es"; + } + else { + return "en"; + } + } + }, + + watch: { + /** + * Update the app theme. + */ + 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 the document + */ +function Load() { + // Initialize the Vue + loadVue(); + + // 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(); + + // Add event Listeners + document.addEventListener("click", function (e) { + document.getElementById('share').hidden = true; + }); + + // Load CSVs + let setNames = ["Verbs", "Adjectives", "Adverbs", "Prepositions", "Transitions", + "Colors", "Days", "Months", "Questions", "Weather", "Family", "Clothes", + "Nature", "House", "Vacation", "Childhood", "Professions", "Health"]; + for (let setName of setNames) { + Papa.parse(`vocab/${setName}.csv`, { + download: true, + complete: function(results) { + // Set verbs + app.sets[setName] = results.data; + } + }); + } +} + + + +/** + * Set the table height. + */ +function setTableHeight() { + var tableY = document.getElementById("referenceTable").offsetTop; + document.getElementById("referenceTable").style.height = `${window.innerHeight - tableY - 50}px`; +} + + + +/** + * Read a term. + * @param {Number} row - The row of the term. + * @param {Number} column - The column of the term. + */ +function Read(row, column) +{ + 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 (app.sets[app.set][0][column].toLowerCase().includes("spanish")){ + msg.lang = 'es'; + } + window.speechSynthesis.speak(msg); +} diff --git a/Scripts/Settings.js b/js/settings.js diff --git a/reference.html b/reference.html @@ -5,13 +5,13 @@ <title>Reference Tables - Spanish-Quizzer</title> <meta name="description" content="Look up Spanish vocabulary and conjugations in free and printable reference tables."> <meta name="viewport" content="width=device-width, user-scalable=no"/> - <link rel="icon" type="image/png" href="Images/favicon-32.png"> - <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"> + <link rel="icon" type="image/png" href="images/favicon-32.png"> + <link rel="apple-touch-icon" sizes="180x180" href="images/apple-touch-icon.png"> + <link rel="stylesheet" href="css/global.css"> + <link rel="stylesheet" href="css/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> + <script src="js/reference.js"></script> </head> <body onload="Load()" onresize="setTableHeight()"> diff --git a/scraper.py b/scraper.py @@ -1,73 +0,0 @@ -# Import dependencies -from bs4 import BeautifulSoup -import csv -import requests - - - -# Gets the conjugations of a verb from the Spanish Infinative -def getConjugations(verb): - # Convert to lowercase - verb = verb.lower().replace(" ", "") - - # Get page - page = requests.get("https://www.spanishdict.com/conjugate/{0}".format(verb)) - soup = BeautifulSoup(page.text, "html.parser") - - # Get English infinative - english = soup.find("div", class_="CMxOwuaP _1v-p9pvd").text - - # Get present participle - presentParticiple = soup.find("div", class_="_2xfncFkp").text - - # Get Indicative conjugations - conjugations = [] - rows = soup.find("table", class_="_2qmJM3i9").find_all('tr') - for row in rows: - cols = row.find_all('td') - conjugations += [[col.text for col in cols]] - - # Return verb info - result = [english,verb, # Infinatives - None, presentParticiple, # Present participle - None, conjugations[1][1], conjugations[2][1], conjugations[3][1], conjugations[4][1], conjugations[6][1], # Present conjugations - None, conjugations[1][2], conjugations[2][2], conjugations[3][2], conjugations[4][2], conjugations[6][2], # Preterite conjugations - None, conjugations[1][3], conjugations[2][3], conjugations[3][3], conjugations[4][3], conjugations[6][3]] # Imperfect conjugations - return result - - - -# Corrects the conjugations in a CSV file -def correctConjugations(filepath): - # Load csv - rows = [] - with open(filepath, encoding="utf-8") as f: - csvreader = csv.reader(f) - fields = next(csvreader) - for row in csv.reader(f): - rows.append(row) - - # Iterate over rows - for row in rows[1:]: - try: - # Get correct conjugations - temp = getConjugations(row[1]) - - # Compare and correct conjugations - for i in range(len(row)): - if (temp[i] != None and temp[i].lower() != row[i].lower()): - row[i] = temp[i].capitalize() - except: - print("Exception during {0}".format(row[1])) - - # Save csv - with open(filepath, "w", newline="", encoding="utf-8") as f: - csvwriter = csv.writer(f) - csvwriter.writerow(fields) - csvwriter.writerows(rows) - - - -# Correct conjugations in Verbs.csv -if (__name__ == "__main__"): - correctConjugations("Vocab/Verbs.csv") diff --git a/scripts/scraper.py b/scripts/scraper.py @@ -0,0 +1,73 @@ +# Import dependencies +from bs4 import BeautifulSoup +import csv +import requests + + + +# Gets the conjugations of a verb from the Spanish Infinative +def getConjugations(verb): + # Convert to lowercase + verb = verb.lower().replace(" ", "") + + # Get page + page = requests.get("https://www.spanishdict.com/conjugate/{0}".format(verb)) + soup = BeautifulSoup(page.text, "html.parser") + + # Get English infinative + english = soup.find("div", class_="CMxOwuaP _1v-p9pvd").text + + # Get present participle + presentParticiple = soup.find("div", class_="_2xfncFkp").text + + # Get Indicative conjugations + conjugations = [] + rows = soup.find("table", class_="_2qmJM3i9").find_all('tr') + for row in rows: + cols = row.find_all('td') + conjugations += [[col.text for col in cols]] + + # Return verb info + result = [english,verb, # Infinatives + None, presentParticiple, # Present participle + None, conjugations[1][1], conjugations[2][1], conjugations[3][1], conjugations[4][1], conjugations[6][1], # Present conjugations + None, conjugations[1][2], conjugations[2][2], conjugations[3][2], conjugations[4][2], conjugations[6][2], # Preterite conjugations + None, conjugations[1][3], conjugations[2][3], conjugations[3][3], conjugations[4][3], conjugations[6][3]] # Imperfect conjugations + return result + + + +# Corrects the conjugations in a CSV file +def correctConjugations(filepath): + # Load csv + rows = [] + with open(filepath, encoding="utf-8") as f: + csvreader = csv.reader(f) + fields = next(csvreader) + for row in csv.reader(f): + rows.append(row) + + # Iterate over rows + for row in rows[1:]: + try: + # Get correct conjugations + temp = getConjugations(row[1]) + + # Compare and correct conjugations + for i in range(len(row)): + if (temp[i] != None and temp[i].lower() != row[i].lower()): + row[i] = temp[i].capitalize() + except: + print("Exception during {0}".format(row[1])) + + # Save csv + with open(filepath, "w", newline="", encoding="utf-8") as f: + csvwriter = csv.writer(f) + csvwriter.writerow(fields) + csvwriter.writerows(rows) + + + +# Correct conjugations in Verbs.csv +if (__name__ == "__main__"): + correctConjugations("../vocab/Verbs.csv") diff --git a/scripts/xlsx-builder.py b/scripts/xlsx-builder.py @@ -0,0 +1,88 @@ +# Import dependencies +import csv +import openpyxl +from openpyxl.styles.borders import Border, Side + + + +# Converts a csv of verbs to a spreadsheet +def createXlsx(csvPath, xlsxPath): + # Load csv + rows = [] + with open(csvPath, encoding="utf-8") as f: + for row in csv.reader(f): + rows.append(row) + + # Rearrange csv data + data = [["English", "Infinitive", "Yo", "Tú", "Él", "Nosotros", "Ellos"]] + for row in rows[1:]: + data += [["", "", row[5], row[6], row[7], row[8], row[9]]] + data += [[row[0], row[1], row[11], row[12], row[13], row[14], row[15]]] + data += [["", "", row[17], row[18], row[19], row[20], row[21]]] + + # Create spreadsheet + vk = openpyxl.Workbook() + + # Get border styles + thick = Side(border_style='thick', color="FF000000") + thin = Side(border_style='thin', color="FF000000") + + # Set data + sh = vk.active + sh.page_setup.fitToHeight = False + for row in range(len(data)): + for column in range(len(data[row])): + # Get cell + cell = sh.cell(row=row + 1, column=column + 1) + + # Set cell value + cell.value = data[row][column] + + # Get cell borders + border = Border( + left=cell.border.left, + right=cell.border.right, + top=cell.border.top, + bottom=cell.border.bottom + ) + + # Set inner borders + if column > 1: + # Conjugation columns only + border.top = thin + border.bottom = thin + if row % 3 == 1: + # Present tense rows only + border.top = thick + border.left = thin + border.right = thin + + # Set outside borders + if column == 0: + border.left = thick + if column == len(data[row]) - 1: + border.right = thick + if row == 0: + border.top = thick + if row == len(data) - 1: + border.bottom = thick + + # Update cell borders + cell.border = border + + # Set page margins + sh.page_margins.left = 0.25 + sh.page_margins.right = 0.25 + sh.page_margins.top = 0.75 + sh.page_margins.bottom = 0.25 + sh.page_margins.header = 0.3 + sh.page_margins.footer = 0 + + # Save spreadsheet + vk.save(xlsxPath) + + + +# Create spreadsheet from Verbs.csv +if (__name__ == "__main__"): + createXlsx("../vocab/Verbs.csv", "../vocab/Verbs.xlsx") diff --git a/xlsx-builder.py b/xlsx-builder.py @@ -1,88 +0,0 @@ -# Import dependencies -import csv -import openpyxl -from openpyxl.styles.borders import Border, Side - - - -# Converts a csv of verbs to a spreadsheet -def createXlsx(csvPath, xlsxPath): - # Load csv - rows = [] - with open(csvPath, encoding="utf-8") as f: - for row in csv.reader(f): - rows.append(row) - - # Rearrange csv data - data = [["English", "Infinitive", "Yo", "Tú", "Él", "Nosotros", "Ellos"]] - for row in rows[1:]: - data += [["", "", row[5], row[6], row[7], row[8], row[9]]] - data += [[row[0], row[1], row[11], row[12], row[13], row[14], row[15]]] - data += [["", "", row[17], row[18], row[19], row[20], row[21]]] - - # Create spreadsheet - vk = openpyxl.Workbook() - - # Get border styles - thick = Side(border_style='thick', color="FF000000") - thin = Side(border_style='thin', color="FF000000") - - # Set data - sh = vk.active - sh.page_setup.fitToHeight = False - for row in range(len(data)): - for column in range(len(data[row])): - # Get cell - cell = sh.cell(row=row + 1, column=column + 1) - - # Set cell value - cell.value = data[row][column] - - # Get cell borders - border = Border( - left=cell.border.left, - right=cell.border.right, - top=cell.border.top, - bottom=cell.border.bottom - ) - - # Set inner borders - if column > 1: - # Conjugation columns only - border.top = thin - border.bottom = thin - if row % 3 == 1: - # Present tense rows only - border.top = thick - border.left = thin - border.right = thin - - # Set outside borders - if column == 0: - border.left = thick - if column == len(data[row]) - 1: - border.right = thick - if row == 0: - border.top = thick - if row == len(data) - 1: - border.bottom = thick - - # Update cell borders - cell.border = border - - # Set page margins - sh.page_margins.left = 0.25 - sh.page_margins.right = 0.25 - sh.page_margins.top = 0.75 - sh.page_margins.bottom = 0.25 - sh.page_margins.header = 0.3 - sh.page_margins.footer = 0 - - # Save spreadsheet - vk.save(xlsxPath) - - - -# Create spreadsheet from Verbs.csv -if (__name__ == "__main__"): - createXlsx("Vocab/Verbs.csv", "Vocab/Verbs.xlsx")