commit 2eb59833240b87418b355c5b4f787eda5f6172af
parent db6277fae72afd7da88c16637f31596030b35d73
Author: AsherMorgan <59518073+AsherMorgan@users.noreply.github.com>
Date: Sun, 21 Feb 2021 16:45:00 -0800
Merge pull request #26 from AsherMorgan/spa
Refactor into Single Page App
Diffstat:
25 files changed, 1482 insertions(+), 1447 deletions(-)
diff --git a/README.md b/README.md
@@ -6,7 +6,7 @@ Try it out [here](https://ashermorgan.github.io/Spanish-Quizzer/).
- Supports practice for all four language skills: reading, listening, writing, and speaking.
- Lots of filters for both vocabulary and verb conjugations.
- Easily type special characters (``` a` ``` → `á`, `n~` → `ñ`, `u~` → `ü`)
-- Spanish reference tables (available [here](https://ashermorgan.github.io/Spanish-Quizzer/reference.html))
+- Spanish reference tables
## Vocabulary Filters
The following filters can be used on vocabulary:
diff --git a/css/app.css b/css/app.css
@@ -1,5 +1,5 @@
/* Header */
-#home > h1 {
+.home main h1 {
font-size: 20px;
font-weight: bold;
margin-top: 15px;
@@ -7,7 +7,7 @@
}
/* Vocab & Conjugation buttons*/
-#home button {
+.home main button {
width: 150px;
height: 50px;
line-height: 50px;
@@ -16,47 +16,6 @@
}
/* Link to Reference Tables */
-#home a {
+.home main a {
margin-top: 10px;
}
-
-/* Filters and Settings */
-#settings {
- display: flex;
- flex-direction: column;
- align-items: center;
-}
-#settings[hidden] {
- display: none;
-}
-
-/* Start/Resume Buttons */
-.settingButtons {
- margin-top: 20px;
- margin-bottom: 10px;
-}
-.settingButtons button {
- width: 100px;
- height: 40px;
- font-size: 18px;
- margin: 0px 5px;
-}
-@media only screen and (max-width: 510px) {
- /* Expand Start/Resume buttons */
- .settingButtons {
- width: calc(100% - 10px);
- }
- .settingButtons button {
- width: calc(50% - 15px);
- height: 50px;
- }
-}
-
-/* Congrats message */
-#congrats p {
- margin: 20px;
-}
-#congrats button {
- width: 100px;
- height: 35px;
-}
diff --git a/css/filterInput.css b/css/filterInput.css
@@ -1,69 +0,0 @@
-/* FilterInput */
-.filtersInput h1 {
- font-size: 16px;
- font-weight: normal;
- margin-bottom: 10px;
-}
-.filtersInput h2 {
- font-size: 20px;
- margin-bottom: 5px;
-}
-.filtersInput div {
- margin-left: 10px;
- margin-right: 10px;
- margin-top: 10px;
-}
-.filtersInput div h2 button {
- padding: 3px;
-}
-
-/* Filter */
-.filter {
- width: max-content;
-
- background-color: var(--background-color);
- border: 1px solid var(--border-color);
- box-shadow: 0 0 15px var(--border-color);
-
- margin: 5px auto;
- padding: 5px;
- border-radius: 5px;
-}
-.filter button {
- border: none;
- background-color: var(--background-color);
- color: var(--foreground-color);
- cursor: pointer;
-}
-
-/* Conjugation settings on mobile devices */
-@media only screen and (max-width: 510px) {
- .verbSettings {
- width: calc(100% - 20px);
- }
- .verbSettings .filter {
- width: 100%;
- margin: 10px auto;
- }
- .verbSettings .filter select, .verbSettings .filter button {
- width: 100%;
- height: 30px;
- margin: 3px 0px;
- }
-}
-
-/* Vocab settings on mobile devices */
-@media only screen and (max-width: 350px) {
- .vocabSettings {
- width: calc(100% - 20px);
- }
- .vocabSettings .filter {
- width: 100%;
- margin: 10px auto;
- }
- .vocabSettings .filter select, .vocabSettings .filter button {
- width: 100%;
- height: 30px;
- margin: 3px 0px;
- }
-}
diff --git a/css/filtersPage.css b/css/filtersPage.css
@@ -0,0 +1,119 @@
+/******** filter-input ********/
+.filtersInput h1 {
+ font-size: 16px;
+ font-weight: normal;
+ margin-bottom: 10px;
+}
+.filtersInput h2 {
+ font-size: 20px;
+ margin-bottom: 5px;
+}
+.filtersInput div {
+ margin-left: 10px;
+ margin-right: 10px;
+ margin-top: 10px;
+}
+.filtersInput div h2 button {
+ padding: 3px;
+}
+
+/* Filter */
+.filter {
+ width: max-content;
+
+ background-color: var(--background-color);
+ border: 1px solid var(--border-color);
+ box-shadow: 0 0 15px var(--border-color);
+
+ margin: 5px auto;
+ padding: 5px;
+ border-radius: 5px;
+}
+.filter button {
+ border: none;
+ background-color: var(--background-color);
+ color: var(--foreground-color);
+ cursor: pointer;
+}
+
+/* Conjugation settings on mobile devices */
+@media only screen and (max-width: 510px) {
+ .verbSettings {
+ width: calc(100% - 20px);
+ }
+ .verbSettings .filter {
+ width: 100%;
+ margin: 10px auto;
+ }
+ .verbSettings .filter select, .verbSettings .filter button {
+ width: 100%;
+ height: 30px;
+ margin: 3px 0px;
+ }
+}
+
+/* Vocab settings on mobile devices */
+@media only screen and (max-width: 350px) {
+ .vocabSettings {
+ width: calc(100% - 20px);
+ }
+ .vocabSettings .filter {
+ width: 100%;
+ margin: 10px auto;
+ }
+ .vocabSettings .filter select, .vocabSettings .filter button {
+ width: 100%;
+ height: 30px;
+ margin: 3px 0px;
+ }
+}
+
+
+
+/******** settings-input ********/
+.settingsInput h2 {
+ font-size: 20px;
+ margin-bottom: 5px;
+}
+.settingsInput {
+ margin-left: 5px;
+ margin-right: 5px;
+ margin-top: 10px;
+}
+.settingsInput div {
+ text-align: left;
+}
+.settingsInput input[type=number] {
+ width: 50px;
+}
+
+
+
+/******** filters-page ********/
+.filtersPage main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+/* Start/Resume Buttons */
+.settingButtons {
+ margin-top: 20px;
+ margin-bottom: 10px;
+}
+.settingButtons button {
+ width: 100px;
+ height: 40px;
+ font-size: 18px;
+ margin: 0px 5px;
+}
+@media only screen and (max-width: 510px) {
+ /* Expand Start/Resume buttons */
+ .settingButtons {
+ width: calc(100% - 10px);
+ }
+ .settingButtons button {
+ width: calc(50% - 15px);
+ height: 50px;
+ }
+}
diff --git a/css/global.css b/css/global.css
@@ -30,7 +30,7 @@ body {
touch-action: manipulation;
}
-#app {
+#app>div {
width: 100%;
height: 100%;
@@ -92,8 +92,7 @@ header {
padding-top: 15px;
padding-bottom: 15px;
}
-
-#backArrow {
+header img {
position: absolute;
left: 10px;
height: 30px;
diff --git a/css/quizzer.css b/css/quizzer.css
@@ -1,6 +1,6 @@
/******** Quizzer styles ********/
/* Container */
-#quizzer {
+.quizzer {
margin: 10px;
padding: 10px;
width: fit-content;
@@ -11,7 +11,7 @@
}
/* Progress */
-#quizzerProgress {
+.quizzerProgress {
font-size: 18px;
font-weight: bold;
margin: 0px;
@@ -19,20 +19,20 @@
}
/* Prompt */
-#quizzer section {
+.quizzer section {
text-align: left;
margin-bottom: 15px;
}
-#quizzerPromptType, #quizzerInputType {
+.quizzerPromptType, .quizzerInputType {
font-weight: bold;
}
-#quizzerPrompt {
+.quizzerPrompt {
cursor: pointer;
font-size: 15px;
}
/* Responce*/
-#quizzerInput {
+.quizzerInput {
margin-top: 5px;
height: 30px;
padding: 5px;
@@ -41,16 +41,25 @@
}
/* Buttons */
-#quizzerButtons button {
+.quizzerButtons button {
width: 75px;
height: 25px;
}
/* Feedback */
-#quizzerFeedback {
+.quizzerFeedback {
margin-top: 15px;
}
-#quizzerFeedbackTerm {
+.quizzerFeedbackTerm {
text-decoration-line: underline;
cursor: pointer;
-}
-\ No newline at end of file
+}
+
+/* Congrats message */
+.congrats p {
+ margin: 20px;
+}
+.congrats button {
+ width: 100px;
+ height: 35px;
+}
diff --git a/css/reference.css b/css/reference.css
@@ -12,7 +12,7 @@ main > h1 {
}
/* Controls */
-#controls {
+.referenceTableControls {
margin-top: 10px;
margin-bottom: 15px;
}
@@ -20,13 +20,13 @@ main > h1 {
/******** Reference Table styles ********/
-#referenceTable {
+.referenceTable {
text-align: left;
overflow-x: auto;
}
/* Header cells */
-#referenceTable th {
+.referenceTable th {
background-color: var(--hover-color);
border: 1px solid var(--border-color);
position: sticky;
@@ -35,7 +35,7 @@ main > h1 {
}
/* All cells */
-#referenceTable td {
+.referenceTable td {
border: 1px solid var(--border-color);
cursor: pointer;
}
@@ -51,11 +51,11 @@ main > h1 {
--hover-color: #FFFFFF;
}
- header, h1, #controls, label, br {
+ header, h1, .referenceTableControls, label, br {
display: none;
}
-
- #referenceTable {
+
+ .referenceTable {
overflow: visible;
}
diff --git a/css/settingsInput.css b/css/settingsInput.css
@@ -1,15 +0,0 @@
-.settingsInput h2 {
- font-size: 20px;
- margin-bottom: 5px;
-}
-.settingsInput {
- margin-left: 5px;
- margin-right: 5px;
- margin-top: 10px;
-}
-.settingsInput div {
- text-align: left;
-}
-.settingsInput input[type=number] {
- width: 50px;
-}
diff --git a/images/x.svg b/images/x.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>
+\ No newline at end of file
diff --git a/index.html b/index.html
@@ -9,67 +9,34 @@
<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/app.css">
- <link rel="stylesheet" href="css/filterInput.css">
- <link rel="stylesheet" href="css/settingsInput.css">
+ <link rel="stylesheet" href="css/filtersPage.css">
<link rel="stylesheet" href="css/quizzer.css">
+ <link rel="stylesheet" href="css/reference.css">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>
+ <script src="https://cdn.jsdelivr.net/npm/vue-router@3.5.1"></script>
<script src="https://unpkg.com/papaparse@5.1.1/papaparse.min.js"></script>
<script src="js/global.js"></script>
- <script src="js/app.js"></script>
- <script src="js/filterInput.js"></script>
- <script src="js/settingsInput.js"></script>
<script src="js/filters.js"></script>
+ <script src="js/filtersPage.js"></script>
<script src="js/quizzer.js"></script>
+ <script src="js/reference.js"></script>
+ <script src="js/app.js"></script>
</head>
<body onload="Load();">
<div id="app">
- <header @click="Back">
- <img id="backArrow" v-show="state != 'home'" src="images/arrow-left.svg"/>
- Spanish-Quizzer
- </header>
-
- <main>
- <noscript>
- <div id="jsError">
- <h1>You must have JavaScript enabled to run Spanish-Quizzer.</h1>
- <p>Spanish-Quizzer uses JavaScript to load vocab, apply filters, and check your answers.</p>
- <p>You can still view the <a href="data/">data sets</a>, but you can't use the Quizzer or the Reference Tables.</p>
- </div>
- </noscript>
-
- <div id="home" v-show="state === 'home'" hidden>
- <h1>What do you want to study?</h1>
- <div>
- <button @click="category = 'verbs'; state = 'settings';">Conjugations</button>
- <button @click="category = 'vocab'; state = 'settings';">Vocab</button>
- </div>
- <div>
- <a href="reference.html">Or look something up in the Reference Tables</a>
- </div>
- </div>
-
- <div id="settings" v-show="state === 'settings'" hidden>
- <filter-input :category="category" v-model="filters"></filter-input>
-
- <settings-input v-model="settings"></settings-input>
-
- <div class="settingButtons">
- <button class="settingsStart" @click="CreateSession();">Start</button>
- <button class="settingsResume" @click="ResumeSession();">Resume</button>
- </div>
+ <noscript>
+ <header>Spanish-Quizzer</header>
+ <div id="jsError">
+ <h1>You must have JavaScript enabled to run Spanish-Quizzer.</h1>
+ <p>Spanish-Quizzer uses JavaScript to load vocab, apply filters, and check your answers.</p>
+ <p>You can still view the <a href="data/">data sets</a>, but you can't use the Quizzer or the Reference Tables.</p>
</div>
+ </noscript>
- <quizzer id="quizzer" v-if="state === 'quizzer'"
- :starting-index="promptIndex" :starting-prompts="prompts" :settings="settings"
- @new-prompt="updateProgress" @finished-prompts="state = 'congrats';">
- </quizzer>
-
- <div id="congrats" v-show="state === 'congrats'" hidden>
- <p>Congratulations, You finished all of the prompts!</p>
- <button @click="Back">Continue</button>
- </div>
- </main>
+ <keep-alive exclude="quizzerPage">
+ <router-view @back="Back"></router-view>
+ </keep-alive>
</div>
</body>
-</html>
-\ No newline at end of file
+</html>
diff --git a/js/app.js b/js/app.js
@@ -1,9 +1,78 @@
// Declare global variables
-let Data;
let app;
+// page-header component
+const pageHeader = Vue.component("pageHeader", {
+ props: {
+ image: {
+ type: String
+ },
+ title: {
+ type: String,
+ default: "Spanish-Quizzer",
+ }
+ },
+ template: `
+ <header @click="$emit('back');">
+ <img v-if="image" :src="image"/>
+ {{ title }}
+ </header>
+ `
+});
+
+
+
+// App pages
+const homePage = Vue.component("homePage", {
+ methods: {
+ /**
+ * Handle a keyup event (implements keyboard shortcuts).
+ * @param {object} e - The event args.
+ */
+ keyup: function(e) {
+ if (this._inactive) return;
+ if (e.key === "c") this.$router.push("verbs");
+ if (e.key === "v") this.$router.push("vocab");
+ if (e.key === "r") this.$router.push("reference");
+ },
+ },
+ created: function() {
+ // Add keyup handler
+ window.addEventListener("keyup", this.keyup);
+ },
+ destroyed: function() {
+ // Remove keyup handler
+ window.removeEventListener("keyup", this.keyup);
+ },
+ template: `
+ <div class="home">
+ <page-header></page-header>
+ <main>
+ <h1>What do you want to study?</h1>
+ <div>
+ <router-link tag="button" to="/verbs">Study Conjugations</router-link>
+ <router-link tag="button" to="/vocab">Study Vocab</router-link>
+ <router-link tag="button" to="/reference">Reference Tables</router-link>
+ </div>
+ </main>
+ </div>
+ `,
+});
+const referencePage = Vue.component("referencePage", {
+ template: `
+ <div>
+ <page-header @back="$emit('back');" image="images/arrow-left.svg"></page-header>
+ <main>
+ <reference-tables :data="this.$root.$data.data"></reference-tables>
+ </main>
+ </div>
+ `,
+});
+
+
+
/**
* Initialize the Vue app
*/
@@ -11,124 +80,56 @@ function loadVue() {
app = new Vue({
el: "#app", // Mount to app div
- data: {
- state: "home", // Can be either "home", "settings", or "quizzer"
- category: "verbs", // Can be either "verbs" or "vocab"
- filters: [],
- settings: getSettings(),
- prompts: [],
- promptIndex: 0,
- },
+ router: new VueRouter({
+ routes: [
+ { path: "/", redirect: "/home" },
+ { path: "/home", name: "home", component: homePage },
+ { path: "/verbs", name: "verbs", component: filtersPage, props: {category: "verbs"}},
+ { path: "/vocab", name: "vocab", component: filtersPage, props: {category: "vocab"}},
+ { path: "/quizzer", name: "quizzer", component: quizzerPage, props:true },
+ { path: "/reference", name: "reference", component: referencePage },
+ ],
+ }),
methods: {
/**
- * Return to the previous state.
+ * Go back to the previous page.
*/
Back: function() {
- switch (app.state) {
- case "quizzer":
- case "congrats":
- app.state = "settings";
- break;
- case "settings":
+ switch (this.$route.name) {
case "home":
- default:
- app.state = "home";
break;
+ case "verbs":
+ case "vocab":
+ case "reference":
+ this.$router.push("home");
+ break;
+ case "quizzer":
+ this.$router.push(this.$route.params.referer || "home");
}
},
/**
- * 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 = app.category === "verbs" ? "verb-" : "vocab-";
-
- // Save progress to local storage
- localStorage.setItem(prefix + "prompts", JSON.stringify(prompts));
- localStorage.setItem(prefix + "prompt", JSON.stringify(index));
- },
-
- /**
- * Start a new quizzer session
- */
- CreateSession: function() {
- // Get prompts
- if (this.category === "vocab") {
- this.prompts = Shuffle(ApplyFilters(Data.vocab, GetVocabFilters(this.filters), this.settings));
- }
- else if (this.category === "verbs") {
- // Get prompts
- this.prompts = Shuffle(ApplyFilters(Data.verbs, GetVerbFilters(this.filters), this.settings));
- }
-
- // Set progress
- this.promptIndex = 0;
-
- // Start quizzer
- this.StartSession();
- },
-
- /**
- * Resume the previous quizzer session.
+ * Handle a keyup event (implements keyboard shortcuts).
+ * @param {object} e - The event args.
*/
- ResumeSession: function() {
- // Get localStorage prefix
- let prefix;
- if (this.category === "vocab") {
- prefix = "vocab-"
- }
- else if (this.category === "verbs") {
- prefix = "verb-"
- }
-
- // Load prompts and progress
- this.prompts = JSON.parse(localStorage.getItem(prefix + "prompts"));
- this.promptIndex = parseInt(localStorage.getItem(prefix + "prompt"));
-
- // Start quizzer
- this.StartSession();
+ keyup: function(e) {
+ if (e.key === "Escape") this.Back();
},
+ },
- /**
- * Perform validation checks and then start the quizzer.
- */
- StartSession: function() {
- // Validate prompts and promptIndex
- if (!this.prompts) {
- alert("An error occured while resuming the previous session.");
- return;
- }
- else if (this.prompts.length === 0) {
- alert("You must have at least one filter.");
- return;
- }
- else if (isNaN(this.promptIndex) || this.promptIndex < 0 || this.promptIndex >= this.prompts.length) {
- alert("An error occured while resuming the previous session.");
- return;
- }
-
- // Validate browser for voice input
- if (this.settings.inputType !== "Text") {
- if ((window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.msSpeechRecognition) === undefined) {
- alert("Your browser does not support voice input.");
- return;
- }
- }
+ data: {
+ data: {}
+ },
- // Give iOS devices ringer warning for prompt audio
- if (this.settings.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.");
- }
- }
+ created: function() {
+ // Add keyup handler
+ window.addEventListener("keyup", this.keyup);
+ },
- // Show and hide elements (also enables the quizzer)
- this.state = "quizzer";
- },
+ destroyed: function() {
+ // Remove keyup handler
+ window.removeEventListener("keyup", this.keyup);
},
});
}
@@ -145,52 +146,6 @@ async 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("congrats").hidden = false;
-
- // Add event Listeners
- document.addEventListener("keydown", KeyDown);
-
// Load Spanish-Quizzer data
- Data = await loadData();
-}
-
-
-
-/**
- * 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.category = "verbs";
- app.state = "settings";
- }
- if (e.key === "v") {
- app.category = "vocab";
- app.state = "settings";
- }
- if (e.key === "r") {
- window.location = "reference.html";
- }
- }
-
- // Settings shortcuts
- if (app.state === "settings") {
- if (e.key === "s") {
- app.CreateSession();
- }
- if (e.key === "r") {
- app.ResumeSession();
- }
- }
+ app.data = await loadData();
}
diff --git a/js/filterInput.js b/js/filterInput.js
@@ -1,301 +0,0 @@
-let filterInput = Vue.component("filterInput", {
- props: {
- category: {
- type: String,
- default: "verbs",
- },
- },
-
- computed: {
- value: function() {
- if (this.category === "verbs") {
- return this.verbFilters;
- }
- else if (this.category === "vocab") {
- return this.vocabFilters;
- }
- }
- },
-
- data: function() {
- return {
- verbFilters: [],
- vocabFilters: [],
- };
- },
-
- watch: {
- value: {
- handler: function(value) {
- this.$emit("input", value);
- },
- deep: true,
- },
- },
-
- methods: {
- /**
- * Add a filter to the settings page.
- */
- AddFilter: function() {
- if (this.category === "verbs") {
- this.verbFilters.push({tense:"All Tenses", type:"All Types", subject:"All Subjects", direction:"Eng. → Conj."});
- }
- else if (this.category === "vocab") {
- this.vocabFilters.push({category:"All Categories", type:"All Types", direction:"Eng. ↔ Esp."});
- }
- },
-
- /**
- * Remove a filter from the settings page.
- * @param {Number} index - The index of the verb filter.
- */
- RemoveFilter: function(index) {
- if (this.category === "verbs") {
- this.verbFilters.splice(index, 1);
- }
- else if (this.category === "vocab") {
- 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;
- filters["Orthographic"] = false;
- break;
- case "Past Participles":
- filters["Reflexive"] = false;
- filters["Stem Changing"] = false;
- filters["Orthographic"] = false;
- break;
- case "Present Tense":
- filters["Orthographic"] = false;
- break;
- case "Preterite Tense":
- break;
- case "Imperfect Tense":
- filters["Stem Changing"] = false;
- filters["Orthographic"] = false;
- break;
- case "Simple Future Tense":
- filters["Stem Changing"] = false;
- filters["Orthographic"] = false;
- break;
- case "Present Subjunctive Tense":
- break;
- }
-
- // Reset type if needed
- if (!filters[this.verbFilters[index].type]) {
- this.verbFilters[index].type = "All Types";
- }
-
- // Return filters
- return filters;
- },
-
- /**
- * Get the subject filters available for a verb filter.
- * @param {Number} index - The index of the verb filter.
- * @returns {object} - An object with boolean properties for each subject filter.
- */
- getTenseSubjects: function(index) {
- // Set default filters
- let filters = {"All Subjects":true, "Type":true, "Yo":true, "Tú":true, "Él":true, "Nosotros":true, "Ellos":true}
-
- if (["Present Participles", "Past Participles"].includes(this.verbFilters[index].tense)) {
- // Override filters
- filters["Yo"] = false;
- filters["Tú"] = false;
- filters["Él"] = false;
- filters["Nosotros"] = false;
- filters["Ellos"] = false;
- }
-
- // Reset subject
- if (["Present Participles", "Past Participles"].includes(this.verbFilters[index].tense) && this.verbFilters[index].subject !== "Type") {
- this.verbFilters[index].subject = "All Subjects";
- }
-
- // Return filters
- return filters;
- },
-
- /**
- * Get the filters available for a vocab category.
- * @param {Number} index - The index of the vocab filter.
- * @returns {Array} - An array containing available filters.
- */
- getCategoryFilters: function(index) {
- // Get filter options
- let filters = {"All Types":true, "Adjectives":true, "Nouns":true, "Verbs":true}
- switch(this.vocabFilters[index].category)
- {
- case "Verbs":
- filters["Adjectives"] = false;
- filters["Nouns"] = false;
- filters["Verbs"] = false;
- break;
-
- case "Adjectives":
- filters["Nouns"] = false;
- filters["Verbs"] = false;
- break;
-
- case "Adverbs":
- filters["Adjectives"] = false;
- filters["Nouns"] = false;
- filters["Verbs"] = false;
- break;
-
- case "Prepositions":
- case "Transitions":
- case "Questions":
- filters["Adjectives"] = false;
- filters["Nouns"] = false;
- filters["Verbs"] = false;
- break;
-
- case "Colors":
- filters["Nouns"] = false;
- filters["Verbs"] = false;
- break;
-
- case "Days":
- case "Months":
- case "Numbers":
- filters["Adjectives"] = false;
- filters["Verbs"] = false;
- break;
-
- case "Weather":
- case "Professions":
- filters["Adjectives"] = false;
- break;
-
- case "Family":
- case "Clothes":
- filters["Verbs"] = false;
- break;
-
- case "Nature":
- case "House":
- case "Vacation":
- case "Childhood":
- case "Food":
- case "Health":
- break;
- }
-
- // Reset type if needed
- if (!filters[this.vocabFilters[index].type]) {
- this.vocabFilters[index].type = "All Types";
- }
-
- // Return filters
- return filters;
- }
- },
-
- template: `
- <div class="filtersInput" ref="container">
- <div class="verbSettings" v-show="category === 'verbs'">
- <h1>Choose your settings and then click start.</h1>
-
- <h2>
- Verb Filters
- <button @click="AddFilter();">Add Filter</button>
- </h2>
-
- <div v-for="(filter, index) in verbFilters" class="filter">
- <select v-model="filter.tense">
- <option>All Tenses</option>
- <option>Present Participles</option>
- <option>Past Participles</option>
- <option>Present Tense</option>
- <option>Preterite Tense</option>
- <option>Imperfect Tense</option>
- <option>Simple Future Tense</option>
- <option>Present Subjunctive Tense</option>
- </select>
- <select v-model="filter.type">
- <option v-for="(available, type) in getTenseTypes(index)" :disabled="!available">{{ type }}</option>
- </select>
- <select v-model="filter.subject">
- <option v-for="(available, subject) in getTenseSubjects(index)" :disabled="!available">{{ subject }}</option>
- </select>
- <select v-model="filter.direction">
- <option>Eng. → Conj.</option>
- <option>Esp. → Conj.</option>
- <option>Conj. → Eng.</option>
- <option>Conj. → Esp.</option>
- </select>
- <button class="itemRemove" @click="RemoveFilter(index);">╳</button>
- </div>
- </div>
-
-
- <div class="vocabSettings" v-show="category === 'vocab'">
- <h1>Choose your settings and then click start.</h1>
-
- <h2>
- Vocabulary Filters
- <button @click="AddFilter();">Add Filter</button>
- </h2>
-
- <div v-for="(filter, index) in vocabFilters" class="filter">
- <select class="vocabSetName" v-model="filter.category">
- <option>All Categories</option>
- <optgroup label="Common Words">
- <option>Adjectives</option>
- <option>Adverbs</option>
- <option>Prepositions</option>
- <option>Transitions</option>
- <option>Verbs</option>
- </optgroup>
- <optgroup label="Basic Words">
- <option>Colors</option>
- <option>Days</option>
- <option>Months</option>
- <option>Numbers</option>
- <option>Questions</option>
- </optgroup>
- <optgroup label="Advanced Words">
- <option>Childhood</option>
- <option>Clothes</option>
- <option>Family</option>
- <option>Food</option>
- <option>Health</option>
- <option>House</option>
- <option>Nature</option>
- <option>Professions</option>
- <option>Vacation</option>
- <option>Weather</option>
- </optgroup>
- </select>
- <select v-model="filter.type">
- <option v-for="(available, type) in getCategoryFilters(index)" :disabled="!available">{{ type }}</option>
- </select>
- <select v-model="filter.direction">
- <option>Eng. ↔ Esp.</option>
- <option>Eng. → Esp.</option>
- <option>Esp. → Eng.</option>
- </select>
- <button class="itemRemove" @click="RemoveFilter(index);">╳</button>
- </div>
- </div>
- </div>
- `,
-});
diff --git a/js/filtersPage.js b/js/filtersPage.js
@@ -0,0 +1,511 @@
+// filter-input component
+let filterInput = Vue.component("filterInput", {
+ props: {
+ category: {
+ type: String,
+ default: "verbs",
+ },
+ },
+
+ computed: {
+ value: function() {
+ if (this.category === "verbs") {
+ return this.verbFilters;
+ }
+ else if (this.category === "vocab") {
+ return this.vocabFilters;
+ }
+ }
+ },
+
+ data: function() {
+ return {
+ verbFilters: [],
+ vocabFilters: [],
+ };
+ },
+
+ watch: {
+ value: {
+ handler: function(value) {
+ this.$emit("input", value);
+ },
+ deep: true,
+ },
+ },
+
+ methods: {
+ /**
+ * Add a filter to the filters page.
+ */
+ AddFilter: function() {
+ if (this.category === "verbs") {
+ this.verbFilters.push({tense:"All Tenses", type:"All Types", subject:"All Subjects", direction:"Eng. → Conj."});
+ }
+ else if (this.category === "vocab") {
+ this.vocabFilters.push({category:"All Categories", type:"All Types", direction:"Eng. ↔ Esp."});
+ }
+ },
+
+ /**
+ * Remove a filter from the filters page.
+ * @param {Number} index - The index of the verb filter.
+ */
+ RemoveFilter: function(index) {
+ if (this.category === "verbs") {
+ this.verbFilters.splice(index, 1);
+ }
+ else if (this.category === "vocab") {
+ 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;
+ filters["Orthographic"] = false;
+ break;
+ case "Past Participles":
+ filters["Reflexive"] = false;
+ filters["Stem Changing"] = false;
+ filters["Orthographic"] = false;
+ break;
+ case "Present Tense":
+ filters["Orthographic"] = false;
+ break;
+ case "Preterite Tense":
+ break;
+ case "Imperfect Tense":
+ filters["Stem Changing"] = false;
+ filters["Orthographic"] = false;
+ break;
+ case "Simple Future Tense":
+ filters["Stem Changing"] = false;
+ filters["Orthographic"] = false;
+ break;
+ case "Present Subjunctive Tense":
+ break;
+ }
+
+ // Reset type if needed
+ if (!filters[this.verbFilters[index].type]) {
+ this.verbFilters[index].type = "All Types";
+ }
+
+ // Return filters
+ return filters;
+ },
+
+ /**
+ * Get the subject filters available for a verb filter.
+ * @param {Number} index - The index of the verb filter.
+ * @returns {object} - An object with boolean properties for each subject filter.
+ */
+ getTenseSubjects: function(index) {
+ // Set default filters
+ let filters = {"All Subjects":true, "Type":true, "Yo":true, "Tú":true, "Él":true, "Nosotros":true, "Ellos":true}
+
+ if (["Present Participles", "Past Participles"].includes(this.verbFilters[index].tense)) {
+ // Override filters
+ filters["Yo"] = false;
+ filters["Tú"] = false;
+ filters["Él"] = false;
+ filters["Nosotros"] = false;
+ filters["Ellos"] = false;
+ }
+
+ // Reset subject
+ if (["Present Participles", "Past Participles"].includes(this.verbFilters[index].tense) && this.verbFilters[index].subject !== "Type") {
+ this.verbFilters[index].subject = "All Subjects";
+ }
+
+ // Return filters
+ return filters;
+ },
+
+ /**
+ * Get the filters available for a vocab category.
+ * @param {Number} index - The index of the vocab filter.
+ * @returns {Array} - An array containing available filters.
+ */
+ getCategoryFilters: function(index) {
+ // Get filter options
+ let filters = {"All Types":true, "Adjectives":true, "Nouns":true, "Verbs":true}
+ switch(this.vocabFilters[index].category)
+ {
+ case "Verbs":
+ filters["Adjectives"] = false;
+ filters["Nouns"] = false;
+ filters["Verbs"] = false;
+ break;
+
+ case "Adjectives":
+ filters["Nouns"] = false;
+ filters["Verbs"] = false;
+ break;
+
+ case "Adverbs":
+ filters["Adjectives"] = false;
+ filters["Nouns"] = false;
+ filters["Verbs"] = false;
+ break;
+
+ case "Prepositions":
+ case "Transitions":
+ case "Questions":
+ filters["Adjectives"] = false;
+ filters["Nouns"] = false;
+ filters["Verbs"] = false;
+ break;
+
+ case "Colors":
+ filters["Nouns"] = false;
+ filters["Verbs"] = false;
+ break;
+
+ case "Days":
+ case "Months":
+ case "Numbers":
+ filters["Adjectives"] = false;
+ filters["Verbs"] = false;
+ break;
+
+ case "Weather":
+ case "Professions":
+ filters["Adjectives"] = false;
+ break;
+
+ case "Family":
+ case "Clothes":
+ filters["Verbs"] = false;
+ break;
+
+ case "Nature":
+ case "House":
+ case "Vacation":
+ case "Childhood":
+ case "Food":
+ case "Health":
+ break;
+ }
+
+ // Reset type if needed
+ if (!filters[this.vocabFilters[index].type]) {
+ this.vocabFilters[index].type = "All Types";
+ }
+
+ // Return filters
+ return filters;
+ }
+ },
+
+ template: `
+ <div class="filtersInput" ref="container">
+ <div class="verbSettings" v-show="category === 'verbs'">
+ <h1>Choose your settings and then click start.</h1>
+
+ <h2>
+ Verb Filters
+ <button @click="AddFilter();">Add Filter</button>
+ </h2>
+
+ <div v-for="(filter, index) in verbFilters" class="filter">
+ <select v-model="filter.tense">
+ <option>All Tenses</option>
+ <option>Present Participles</option>
+ <option>Past Participles</option>
+ <option>Present Tense</option>
+ <option>Preterite Tense</option>
+ <option>Imperfect Tense</option>
+ <option>Simple Future Tense</option>
+ <option>Present Subjunctive Tense</option>
+ </select>
+ <select v-model="filter.type">
+ <option v-for="(available, type) in getTenseTypes(index)" :disabled="!available">{{ type }}</option>
+ </select>
+ <select v-model="filter.subject">
+ <option v-for="(available, subject) in getTenseSubjects(index)" :disabled="!available">{{ subject }}</option>
+ </select>
+ <select v-model="filter.direction">
+ <option>Eng. → Conj.</option>
+ <option>Esp. → Conj.</option>
+ <option>Conj. → Eng.</option>
+ <option>Conj. → Esp.</option>
+ </select>
+ <button class="itemRemove" @click="RemoveFilter(index);">╳</button>
+ </div>
+ </div>
+
+
+ <div class="vocabSettings" v-show="category === 'vocab'">
+ <h1>Choose your settings and then click start.</h1>
+
+ <h2>
+ Vocabulary Filters
+ <button @click="AddFilter();">Add Filter</button>
+ </h2>
+
+ <div v-for="(filter, index) in vocabFilters" class="filter">
+ <select class="vocabSetName" v-model="filter.category">
+ <option>All Categories</option>
+ <optgroup label="Common Words">
+ <option>Adjectives</option>
+ <option>Adverbs</option>
+ <option>Prepositions</option>
+ <option>Transitions</option>
+ <option>Verbs</option>
+ </optgroup>
+ <optgroup label="Basic Words">
+ <option>Colors</option>
+ <option>Days</option>
+ <option>Months</option>
+ <option>Numbers</option>
+ <option>Questions</option>
+ </optgroup>
+ <optgroup label="Advanced Words">
+ <option>Childhood</option>
+ <option>Clothes</option>
+ <option>Family</option>
+ <option>Food</option>
+ <option>Health</option>
+ <option>House</option>
+ <option>Nature</option>
+ <option>Professions</option>
+ <option>Vacation</option>
+ <option>Weather</option>
+ </optgroup>
+ </select>
+ <select v-model="filter.type">
+ <option v-for="(available, type) in getCategoryFilters(index)" :disabled="!available">{{ type }}</option>
+ </select>
+ <select v-model="filter.direction">
+ <option>Eng. ↔ Esp.</option>
+ <option>Eng. → Esp.</option>
+ <option>Esp. → Eng.</option>
+ </select>
+ <button class="itemRemove" @click="RemoveFilter(index);">╳</button>
+ </div>
+ </div>
+ </div>
+ `,
+});
+
+
+
+// settings-input component
+let settingsInput = Vue.component("settingsInput", {
+ props: {
+ value: {
+ type: Object,
+ default: getSettings(),
+ },
+ },
+
+ watch: {
+ value: {
+ handler: function(value) {
+ setSettings(value);
+
+ this.$emit("input", value);
+ },
+ deep: true,
+ },
+ },
+
+ template: `
+ <div class="settingsInput" ref="container">
+ <h2>Quizzer Settings</h2>
+ <div>
+ <input type="checkbox" id="settingsDarkTheme" v-model="value.darkTheme">
+ <label for="settingsDarkTheme">Dark Mode</label>
+ </div>
+ <div>
+ <label for="settingsPromptType">Prompt type</label>
+ <select id="settingsPromptType" v-model="value.promptType">
+ <option>Text</option>
+ <option>Audio</option>
+ <option>Both</option>
+ </select>
+ </div>
+ <div>
+ <label for="settingsInputType">Input type</label>
+ <select id="settingsInputType" v-model="value.inputType">
+ <option>Text</option>
+ <option>Voice</option>
+ <option>Either</option>
+ </select>
+ </div>
+ <div>
+ <label for="settingsOnMissedPrompt">When I miss a prompt</label>
+ <select id="settingsOnMissedPrompt" v-model="value.onMissedPrompt">
+ <option>Correct me</option>
+ <option>Tell me</option>
+ <option>Ignore it</option>
+ </select>
+ </div>
+ <div>
+ <label for="settingsRepeatPrompts">Repeat missed prompts</label>
+ <select id="settingsRepeatPrompts" v-model="value.repeatPrompts">
+ <option>Never</option>
+ <option>Immediately</option>
+ <option>5 prompts later</option>
+ <option>5 & 10 prompts later</option>
+ <option>At the end</option>
+ </select>
+ </div>
+ <div>
+ <label for="settingsMultiplePrompts">Multiple prompts</label>
+ <select id="settingsMultiplePrompts" v-model="value.multiplePrompts">
+ <option>Show together</option>
+ <option>Show separately</option>
+ <option>Show one</option>
+ </select>
+ </div>
+ <div>
+ <label for="settingsMultipleAnswers">Multiple answers</label>
+ <select id="settingsMultipleAnswers" v-model="value.multipleAnswers">
+ <option>Require all</option>
+ <option>Require any</option>
+ </select>
+ </div>
+ <div>
+ <input type="checkbox" id="settingsRemoveDuplicates" v-model="value.removeDuplicates">
+ <label for="settingsRemoveDuplicates">Remove duplicate prompts</label>
+ </div>
+ </div>
+ `,
+});
+
+
+
+// filters-page component
+let filtersPage = Vue.component("filtersPage", {
+ props: {
+ category: {
+ type: String,
+ default: "verbs",
+ }
+ },
+
+ data: function() {
+ return {
+ filters: [],
+ settings: getSettings(),
+ };
+ },
+
+ methods: {
+ /**
+ * Start a new quizzer session
+ */
+ CreateSession: function() {
+ // Get prompts
+ if (this.category === "vocab") {
+ prompts = Shuffle(ApplyFilters(this.$root.$data.data.vocab, GetVocabFilters(this.filters), this.settings));
+ }
+ else if (this.category === "verbs") {
+ // Get prompts
+ prompts = Shuffle(ApplyFilters(this.$root.$data.data.verbs, GetVerbFilters(this.filters), this.settings));
+ }
+
+ // Set progress
+ promptIndex = 0;
+
+ // Start quizzer
+ this.StartSession(prompts, promptIndex);
+ },
+
+ /**
+ * Resume the previous quizzer session.
+ */
+ ResumeSession: function() {
+ // Load prompts and progress
+ let { prompts, index } = JSON.parse(localStorage.getItem("last-session"));
+
+ // Start quizzer
+ this.StartSession(prompts, index);
+ },
+
+ /**
+ * Perform validation checks and then start the quizzer.
+ */
+ StartSession: function(prompts, promptIndex) {
+ // Validate prompts and promptIndex
+ if (!prompts) {
+ alert("An error occured while resuming the previous session.");
+ return;
+ }
+ else if (prompts.length === 0) {
+ alert("You must have at least one filter.");
+ return;
+ }
+ else if (isNaN(promptIndex) || promptIndex < 0 || promptIndex >= prompts.length) {
+ alert("An error occured while resuming the previous session.");
+ return;
+ }
+
+ // Validate browser for voice input
+ if (this.settings.inputType !== "Text") {
+ if ((window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.msSpeechRecognition) === undefined) {
+ alert("Your browser does not support voice input.");
+ return;
+ }
+ }
+
+ // Give iOS devices ringer warning for prompt audio
+ if (this.settings.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.");
+ }
+ }
+
+ // Start quizzer
+ this.$router.push({name:"quizzer", params:{startingPrompts:prompts, startingIndex:promptIndex, settings:this.settings, referer:this.category}});
+ },
+
+ /**
+ * Handle a keyup event (implements some keyboard shortcuts).
+ * @param {object} e - The event args.
+ */
+ keyup: function(e) {
+ if (this._inactive) return;
+ if (e.key === "s") this.CreateSession();
+ if (e.key === "r") this.ResumeSession();
+ }
+ },
+
+ created: function() {
+ // Add keyup handler
+ window.addEventListener("keyup", this.keyup);
+ },
+
+ destroyed: function() {
+ // Remove keyup handler
+ window.removeEventListener("keyup", this.keyup);
+ },
+
+ template: `
+ <div class="filtersPage">
+ <page-header @back="$emit('back');" image="images/arrow-left.svg"></page-header>
+ <main>
+ <filter-input :category="category" v-model="filters"></filter-input>
+ <settings-input v-model="settings"></settings-input>
+ <div class="settingButtons">
+ <button class="settingsStart" @click="CreateSession();">Start</button>
+ <button class="settingsResume" @click="ResumeSession();">Resume</button>
+ </div>
+ </main>
+ </div>
+ `,
+});
diff --git a/js/global.js b/js/global.js
@@ -23,22 +23,6 @@ function SetTheme(darkTheme = null) {
/**
- * Get the language code that matches a label.
- * @param {String} label - The label.
- * @returns {String} - The language code ("en", "es", etc.)
- */
-function getLang(label) {
- if (label.toLowerCase().includes("spanish")) {
- return "es";
- }
- else {
- return "en";
- }
-}
-
-
-
-/**
* Read a peice of text.
* @param {String} text - The text to read.
* @param {String} label - The language of the text.
diff --git a/js/quizzer.js b/js/quizzer.js
@@ -60,6 +60,12 @@ let quizzer = Vue.component("quizzer", {
* Give the user the next prompt and reset the quizzer.
*/
Reset: function() {
+ // Get new prompt
+ this.index++;
+ if (this.index >= this.prompts.length) {
+ return;
+ }
+
// Show and hide elements
this.responceActive = true;
try {
@@ -68,14 +74,6 @@ let quizzer = Vue.component("quizzer", {
}
catch { }
- // Get new prompt
- this.index++;
- if (this.index === this.prompts.length) {
- // The user just finished
- this.$emit("finished-prompts");
- return;
- }
-
// Emit new-prompt event
this.$emit("new-prompt", this.prompts, this.index);
@@ -224,6 +222,20 @@ let quizzer = Vue.component("quizzer", {
this.Continue();
}
},
+
+ /**
+ * 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";
+ }
+ },
},
computed: {
@@ -244,7 +256,7 @@ let quizzer = Vue.component("quizzer", {
created: function() {
// Add keyup handler
window.addEventListener("keyup", this.keyup);
-
+
// Update prompts
this.prompts = this.startingPrompts;
this.index = this.startingIndex - 1;
@@ -260,34 +272,91 @@ let quizzer = Vue.component("quizzer", {
template: `
<div>
- <p id="quizzerProgress">{{ index }} / {{ prompts.length }}</p>
-
- <section>
- <label id="quizzerPromptType" for="quizzerPrompt">{{ prompt[0] }}</label>
- <span id="quizzerPrompt" :lang="getLang(prompt[0])" @click="Read(prompt[1], prompt[0]);">{{ settings.promptType === "Audio" ? "Click to hear again" : prompt[1] }}</span>
- </section>
-
- <section>
- <label id="quizzerInputType" for="quizzerInput">{{ prompt[2] }}</label>
- <input id="quizzerInput" ref="input" type="text" v-model="responce" :readonly="!responceActive || settings.inputType === 'Voice'"
- :lang="getLang(prompt[2])" autocomplete="off" spellcheck="false" autocorrect="off" placeholder="Type the answer">
- </section>
-
- <div id="quizzerButtons">
- <button v-if="responceActive" :disabled="settings.inputType === 'Voice'" @click="Submit();">Submit</button>
- <button v-else @click="Continue();">Continue</button>
- <button @click="Reset();">Skip</button>
+ <div class="quizzer" v-show="index < prompts.length">
+ <p class="quizzerProgress">{{ index }} / {{ prompts.length }}</p>
+
+ <section>
+ <label class="quizzerPromptType" for="quizzerPrompt">{{ prompt[0] }}</label>
+ <span class="quizzerPrompt" :lang="getLang(prompt[0])" @click="Read(prompt[1], prompt[0]);">{{ settings.promptType === "Audio" ? "Click to hear again" : prompt[1] }}</span>
+ </section>
+
+ <section>
+ <label class="quizzerInputType" for="quizzerInput">{{ prompt[2] }}</label>
+ <input class="quizzerInput" ref="input" type="text" v-model="responce" :readonly="!responceActive || settings.inputType === 'Voice'"
+ :lang="getLang(prompt[2])" autocomplete="off" spellcheck="false" autocorrect="off" placeholder="Type the answer">
+ </section>
+
+ <div class="quizzerButtons">
+ <button v-if="responceActive" :disabled="settings.inputType === 'Voice'" @click="Submit();">Submit</button>
+ <button v-else @click="Continue();">Continue</button>
+ <button @click="Reset();">Skip</button>
+ </div>
+
+ <div class="quizzerFeedback bad" ref="feedback" v-show="!responceActive">
+ <span v-if="settings.onMissedPrompt === 'Correct me'">
+ The correct answer is
+ <span class="quizzerFeedbackTerm" @click="Read(prompt[3], prompt[2]);">{{ prompt[3].toLowerCase() }}</span>.
+ </span>
+ <span v-if="settings.onMissedPrompt === 'Tell me'">
+ Incorrect.
+ </span>
+ </div>
</div>
- <div id="quizzerFeedback" ref="feedback" v-show="!responceActive" class="bad">
- <span v-if="settings.onMissedPrompt === 'Correct me'">
- The correct answer is
- <span id="quizzerFeedbackTerm" @click="Read(prompt[3], prompt[2]);">{{ prompt[3].toLowerCase() }}</span>.
- </span>
- <span v-if="settings.onMissedPrompt === 'Tell me'">
- Incorrect.
- </span>
+ <div class="congrats" v-show="index >= prompts.length">
+ <p>Congratulations, You finished all of the prompts!</p>
+ <button @click="$emit('finished-prompts')">Continue</button>
</div>
</div>
`,
});
+
+
+
+// quizzer-page component
+const quizzerPage = Vue.component("quizzerPage", {
+ props: {
+ "referer": {
+ type: String,
+ default: "home",
+ },
+ "startingPrompts": {
+ type: Array
+ },
+ "startingIndex": {
+ type: Number
+ },
+ "settings": {
+ type: Object
+ }
+ },
+
+ methods: {
+ /**
+ * 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) {
+ // Save progress
+ localStorage.setItem("last-session", JSON.stringify({ prompts: prompts, index: index }));
+ }
+ },
+
+ mounted: function() {
+ if (this.startingPrompts == null || this.startingIndex == null || this.settings == null) {
+ this.$router.replace({name:this.referer});
+ }
+ },
+
+ template: `
+ <div class="quizzer-page">
+ <page-header @back="$emit('back', referer);" image="images/x.svg"></page-header>
+ <main>
+ <quizzer :starting-prompts="startingPrompts" :starting-index="startingIndex" :settings="settings"
+ @new-prompt="updateProgress" @finished-prompts="$emit('back', referer);">
+ </quizzer>
+ </main>
+ </div>
+ `,
+});
diff --git a/js/reference.js b/js/reference.js
@@ -1,54 +1,91 @@
-// Declare global variables
-let app;
-
-
-
-/**
- * Initializes the Vue app
- */
-function loadVue() {
- app = new Vue({
- el: "#app", // Mount to app div
-
+let referenceTables = Vue.component("referenceTables", {
+ props: {
data: {
+ type: Object,
+ default: {},
+ }
+ },
+ data: function() {
+ return {
category: "Choose a category",
- data: {"Choose a category":[]},
- query: ""
+ tableData: {...{"Choose a category":[]}, ...this.data},
+ query: "",
}
- });
-}
-
-
-
-/**
- * Load the document
- */
-async function Load() {
- // Update theme
- SetTheme(null);
-
- // Initialize the Vue
- loadVue();
+ },
+ methods: {
+ /**
+ * Set the table height.
+ */
+ setTableHeight: function() {
+ this.$refs.referenceTable.style.height = `${window.innerHeight - this.$refs.referenceTable.offsetTop - 10}px`;
+ },
- // 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;
+ /**
+ * 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";
+ }
+ },
- // Set table height
- setTableHeight();
+ /**
+ * Handle a keyup event (implements keyboard shortcuts).
+ * @param {object} e - The event args.
+ */
+ keyup: function(e) {
+ if (this._inactive) return;
+ if (e.key === "h" || e.key == "/") {
+ try {
+ this.$refs.search.focus();
+ } catch {}
+ }
+ if (e.key === "c") this.category = "verbs"
+ if (e.key === "v") this.category = "vocab";
+ },
+ },
+ mounted: function() {
+ // Set table height
+ this.setTableHeight();
- // Load Spanish-Quizzer data
- app.data = {...app.data, ...await loadData()};
-}
+ // Add onresize handler
+ window.addEventListener("resize", this.setTableHeight);
+ // Add keyup handler
+ window.addEventListener("keyup", this.keyup);
+ },
+ destroyed: function() {
+ // Remove onresize handler
+ window.removeEventListener("resize", this.setTableHeight);
+ // Remove keyup handler
+ window.removeEventListener("keyup", this.keyup);
+ },
+ template: `
+ <div>
+ <div class="referenceTableControls">
+ <select aria-label="Vocab Set" v-model="category">
+ <option>Choose a category</option>
+ <option value="verbs">Conjugations</option>
+ <option value="vocab">Vocab</option>
+ </select>
+ <input type="text" aria-label="Search" v-model="query" placeholder="Search"
+ autocomplete="off" autocorrect="off" ref="search">
+ </div>
-/**
- * Set the table height.
- */
-function setTableHeight() {
- var tableY = document.getElementById("referenceTable").offsetTop;
- document.getElementById("referenceTable").style.height = `${window.innerHeight - tableY - 10}px`;
-}
+ <div class="referenceTable" ref="referenceTable">
+ <table>
+ <tr v-for="(row, rowIndex) in data[category]" 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(column, data[category][0][columnIndex])" :lang="getLang(data[category][0][columnIndex])">{{ column }}</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ `
+});
diff --git a/js/settingsInput.js b/js/settingsInput.js
@@ -1,82 +0,0 @@
-let settingsInput = Vue.component("settingsInput", {
- props: {
- value: {
- type: Object,
- default: getSettings(),
- },
- },
-
- watch: {
- value: {
- handler: function(value) {
- setSettings(value);
-
- this.$emit("input", value);
- },
- deep: true,
- },
- },
-
- template: `
- <div class="settingsInput" ref="container">
- <h2>Quizzer Settings</h2>
- <div>
- <input type="checkbox" id="settingsDarkTheme" v-model="value.darkTheme">
- <label for="settingsDarkTheme">Dark Mode</label>
- </div>
- <div>
- <label for="settingsPromptType">Prompt type</label>
- <select id="settingsPromptType" v-model="value.promptType">
- <option>Text</option>
- <option>Audio</option>
- <option>Both</option>
- </select>
- </div>
- <div>
- <label for="settingsInputType">Input type</label>
- <select id="settingsInputType" v-model="value.inputType">
- <option>Text</option>
- <option>Voice</option>
- <option>Either</option>
- </select>
- </div>
- <div>
- <label for="settingsOnMissedPrompt">When I miss a prompt</label>
- <select id="settingsOnMissedPrompt" v-model="value.onMissedPrompt">
- <option>Correct me</option>
- <option>Tell me</option>
- <option>Ignore it</option>
- </select>
- </div>
- <div>
- <label for="settingsRepeatPrompts">Repeat missed prompts</label>
- <select id="settingsRepeatPrompts" v-model="value.repeatPrompts">
- <option>Never</option>
- <option>Immediately</option>
- <option>5 prompts later</option>
- <option>5 & 10 prompts later</option>
- <option>At the end</option>
- </select>
- </div>
- <div>
- <label for="settingsMultiplePrompts">Multiple prompts</label>
- <select id="settingsMultiplePrompts" v-model="value.multiplePrompts">
- <option>Show together</option>
- <option>Show separately</option>
- <option>Show one</option>
- </select>
- </div>
- <div>
- <label for="settingsMultipleAnswers">Multiple answers</label>
- <select id="settingsMultipleAnswers" v-model="value.multipleAnswers">
- <option>Require all</option>
- <option>Require any</option>
- </select>
- </div>
- <div>
- <input type="checkbox" id="settingsRemoveDuplicates" v-model="value.removeDuplicates">
- <label for="settingsRemoveDuplicates">Remove duplicate prompts</label>
- </div>
- </div>
- `,
-});
diff --git a/reference.html b/reference.html
@@ -1,57 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="utf-8"/>
- <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="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="js/global.js"></script>
- <script src="js/reference.js"></script>
- </head>
-
- <body onload="Load()" onresize="setTableHeight()">
- <div id="app">
- <header onclick="window.location='./'">
- <img id="backArrow" src="images/arrow-left.svg"/>
- Spanish-Quizzer
- </header>
-
- <main>
- <noscript>
- <div id="jsError">
- <h1>You must have JavaScript enabled to run Spanish-Quizzer.</h1>
- <p>Spanish-Quizzer uses JavaScript to load vocab, apply filters, and check your answers.</p>
- <p>You can still view the <a href="data/">data sets</a>, but you can't use the Quizzer or the Reference Tables.</p>
- </div>
- </noscript>
-
- <h1 hidden>Reference Tables</h1>
-
- <div id="controls" hidden>
- <select aria-label="Vocab Set" v-model="category">
- <option>Choose a category</option>
- <option value="verbs">Conjugations</option>
- <option value="vocab">Vocab</option>
- </select>
- <input type="text" aria-label="Search" v-model="query" placeholder="Search"
- autocomplete="off" autocorrect="off">
- </div>
-
- <div id="referenceTable" hidden>
- <table>
- <tr v-for="(row, rowIndex) in data[category]" 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(column, data[category][0][columnIndex])" :lang="getLang(data[category][0][columnIndex])">{{ column }}</td>
- </tr>
- </table>
- </div>
- </main>
- </div>
- </body>
-</html>
-\ No newline at end of file
diff --git a/tests/index.html b/tests/index.html
@@ -17,9 +17,7 @@
<!-- Scripts being tested -->
<script src="../js/global.js"></script>
- <script src="../js/app.js"></script>
- <script src="../js/filterInput.js"></script>
- <script src="../js/settingsInput.js"></script>
+ <script src="../js/filtersPage.js"></script>
<script src="../js/filters.js"></script>
<script src="../js/quizzer.js"></script>
@@ -32,9 +30,7 @@
<!-- Tests -->
<script src="test.global.js"></script>
- <script src="test.app.js"></script>
- <script src="test.filterInput.js"></script>
- <script src="test.settingsInput.js"></script>
+ <script src="test.filtersPage.js"></script>
<script src="test.filters.js"></script>
<script src="test.quizzer.js"></script>
diff --git a/tests/test.app.js b/tests/test.app.js
@@ -1,81 +0,0 @@
-describe("App", function() {
- beforeEach(function() {
- // Initialize Vue
- loadVue();
- });
-
- describe("Created lifecycle hook", function() {
- it("State should be 'home'", function() {
- expect(app.state).to.equal("home");
- });
-
- it("Category should be 'verbs'", function() {
- expect(app.category).to.equal("verbs");
- });
-
- it("Settings should be correctly loaded", function() {
- expect(app.settings).to.deep.equal(getSettings());
- });
-
- it("Prompts should be empty", function() {
- expect(app.prompts.length).to.equal(0);
- });
-
- it("PromptIndex should be 0", function() {
- expect(app.promptIndex).to.equal(0);
- });
- });
-
- describe("Back method", function() {
- it("Should go to home by default", function() {
- // Set state and test Back method
- app.state = "home";
- app.Back();
- expect(app.state).to.equal("home");
-
- // Set state and test Back method again
- app.state = "";
- app.Back();
- expect(app.state).to.equal("home");
-
- // Set state and test Back method again
- app.state = "test";
- app.Back();
- expect(app.state).to.equal("home");
- });
-
- it("Should go to home when on settings", function() {
- // Set state and test Back method
- app.state = "settings";
- app.Back();
- expect(app.state).to.equal("home");
- });
-
- it("Should go to settings when on quizzer", function() {
- // Set state and test Back method
- app.state = "quizzer";
- app.Back();
- expect(app.state).to.equal("settings");
- });
- });
-
- describe("StartSession method", function() {
- it("Should set state to 'quizzer'", function() {
- // Initialize settings
- app.state = "settings";
- app.settings.inputType = "Text";
- app.settings.promptType = "Text";
- app.prompts = ["prompt1", "prompt2", "prompt3"];
-
- // Call StartSession
- app.StartSession([1, 2, 3], 4, {
- promptType: "Text", // Required to prevent browser validation alerts
- inputType: "Text", // Required to prevent browser validation alerts
- testSetting: "testValue",
- });
-
- // Assert parameters imported
- expect(app.state).to.equal("quizzer");
- });
- });
-});
diff --git a/tests/test.filterInput.js b/tests/test.filterInput.js
@@ -1,378 +0,0 @@
-describe("FilterInput", function() {
- let FilterInput;
- beforeEach(function() {
- // Create filtersInput component
- FilterInput = new filterInput();
- });
-
- describe("Created lifecycle hook", function() {
- it("Category should be 'verbs'", function() {
- expect(FilterInput.category).to.equal("verbs");
- });
-
- it("VerbFilters should be empty", function() {
- expect(FilterInput.verbFilters.length).to.equal(0);
- });
-
- it("VocabFilters should be empty", function() {
- expect(FilterInput.vocabFilters.length).to.equal(0);
- });
- });
-
- describe("Value computed property", function() {
- it("Should return verb filters if category is 'verbs'", function() {
- // Initialize variables
- FilterInput.category = "verbs";
- FilterInput.verbFilters = "verb-filters";
- FilterInput.vocabFilters = "vocab-filters";
-
- // Assert value returns verb filters
- expect(FilterInput.value, "verb-filters");
- });
-
- it("Should return vocab filters if category is 'vocab'", function() {
- // Initialize variables
- FilterInput.category = "vocab";
- FilterInput.verbFilters = "verb-filters";
- FilterInput.vocabFilters = "vocab-filters";
-
- // Assert value returns vocab filters
- expect(FilterInput.value, "vocab-filters");
- });
- });
-
- describe("Value watch", function() {
- it("Should emit input event", async function() {
- // Initialize variables
- FilterInput.category = "verbs";
- FilterInput.verbFilters = ["filter1"];
-
- // Override $emit method
- let event = "";
- let event_args;
- FilterInput.$emit = function(name, value) {
- event = name;
- event_args = value;
- };
-
- // Edit verb filters
- FilterInput.verbFilters.push("filter2");
- await FilterInput.$nextTick();
-
- // Assert event emited
- expect(event).to.equal("input");
- expect(event_args).to.have.deep.members(["filter1", "filter2"]);
- });
- });
-
- describe("AddFilter method", function() {
- it("Should add a verb filter if category is 'verbs'", function() {
- // Initialize variables
- FilterInput.category = "verbs";
- expect(FilterInput.verbFilters.length).to.equal(0);
- expect(FilterInput.vocabFilters.length).to.equal(0);
-
- // Add filter
- FilterInput.AddFilter();
-
- // Assert filter added
- expect(FilterInput.verbFilters).to.have.deep.members([
- {tense:"All Tenses", type:"All Types", subject:"All Subjects", direction:"Eng. → Conj."},
- ]);
- expect(FilterInput.vocabFilters).to.have.deep.members([]);
- });
-
- it("Should add a vocab filter if category is 'vocab'", function() {
- // Initialize variables
- FilterInput.category = "vocab";
- expect(FilterInput.verbFilters.length).to.equal(0);
- expect(FilterInput.vocabFilters.length).to.equal(0);
-
- // Add filter
- FilterInput.AddFilter();
-
- // Assert filter added
- expect(FilterInput.vocabFilters).to.have.deep.members([
- {category:"All Categories", type:"All Types", direction:"Eng. ↔ Esp."},
- ]);
- expect(FilterInput.verbFilters).to.have.deep.members([]);
- });
- });
-
- describe("RemoveFilter method", function() {
- it("Should remove the specified verb filter", function() {
- // Initialize filters
- FilterInput.category = "verbs";
- FilterInput.verbFilters = [
- "verb1",
- "verb2",
- "verb3",
- ];
- FilterInput.vocabFilters = [
- "vocab1",
- "vocab2",
- "vocab3",
- ];
-
- // Remove filter
- FilterInput.RemoveFilter(1);
-
- // Assert filter removed
- expect(FilterInput.verbFilters.length).to.equal(2);
- expect(FilterInput.verbFilters[0]).to.equal("verb1");
- expect(FilterInput.verbFilters[1]).to.equal("verb3");
- expect(FilterInput.vocabFilters.length).to.equal(3);
- });
-
- it("Should remove the specified vocab filter", function() {
- // Initialize filters
- FilterInput.category = "vocab";
- FilterInput.verbFilters = [
- "verb1",
- "verb2",
- "verb3",
- ];
- FilterInput.vocabFilters = [
- "vocab1",
- "vocab2",
- "vocab3",
- ];
-
- // Remove filter
- FilterInput.RemoveFilter(1);
-
- // Assert filter removed
- expect(FilterInput.verbFilters.length).to.equal(3);
- expect(FilterInput.vocabFilters.length).to.equal(2);
- expect(FilterInput.vocabFilters[0]).to.equal("vocab1");
- expect(FilterInput.vocabFilters[1]).to.equal("vocab3");
- });
- });
-
- describe("GetTenseTypes method", function() {
- it("Should be correct for All Tenses", function() {
- // Initialize filters
- FilterInput.verbFilters = [
- {tense:"All Types", type:"All Types"}
- ]
-
- // Get filters
- let filters = FilterInput.getTenseTypes(0);
-
- // Assert filters are correct
- expect(filters["All Types"]).to.equal(true);
- expect(filters["Reflexive"]).to.equal(true);
- expect(filters["Regular"]).to.equal(true);
- expect(filters["Nonregular"]).to.equal(true);
- expect(filters["Stem Changing"]).to.equal(true);
- expect(filters["Orthographic"]).to.equal(true);
- expect(filters["Irregular"]).to.equal(true);
- });
-
- it("Should be correct for Present Tense", function() {
- // Initialize filters
- FilterInput.verbFilters = [
- {tense:"Present Tense", type:"All Types"}
- ]
-
- // Get filters
- let filters = FilterInput.getTenseTypes(0);
-
- // Assert filters are correct
- expect(filters["All Types"]).to.equal(true);
- expect(filters["Reflexive"]).to.equal(true);
- expect(filters["Regular"]).to.equal(true);
- expect(filters["Nonregular"]).to.equal(true);
- expect(filters["Stem Changing"]).to.equal(true);
- expect(filters["Orthographic"]).to.equal(false);
- expect(filters["Irregular"]).to.equal(true);
- });
-
- it("Should change selection if not available", function() {
- // Initialize filters
- FilterInput.verbFilters = [
- {tense:"Present Tense", type:"Orthographic"}
- ]
-
- // Get filters
- let filters = FilterInput.getTenseTypes(0);
-
- // Assert filters are correct
- expect(filters["All Types"]).to.equal(true);
- expect(filters["Reflexive"]).to.equal(true);
- expect(filters["Regular"]).to.equal(true);
- expect(filters["Nonregular"]).to.equal(true);
- expect(filters["Stem Changing"]).to.equal(true);
- expect(filters["Orthographic"]).to.equal(false);
- expect(filters["Irregular"]).to.equal(true);
-
- // Assert selection changed
- expect(FilterInput.verbFilters[0]["type"]).to.equal("All Types");
- });
-
- it("Should not change selection if available", function() {
- // Initialize filters
- FilterInput.verbFilters = [
- {tense:"Preterite Tense", type:"Orthographic"}
- ]
-
- // Get filters
- let filters = FilterInput.getTenseTypes(0);
-
- // Assert filters are correct
- expect(filters["All Types"]).to.equal(true);
- expect(filters["Reflexive"]).to.equal(true);
- expect(filters["Regular"]).to.equal(true);
- expect(filters["Nonregular"]).to.equal(true);
- expect(filters["Stem Changing"]).to.equal(true);
- expect(filters["Orthographic"]).to.equal(true);
- expect(filters["Irregular"]).to.equal(true);
-
- // Assert selection not changed
- expect(FilterInput.verbFilters[0]["type"]).to.equal("Orthographic");
- });
- });
-
- describe("GetTenseSubjects method", function() {
- it("Should be correct for All Tenses", function() {
- // Initialize filters
- FilterInput.verbFilters = [
- {tense:"All Types", type:"All Types"}
- ]
-
- // Get filters
- let filters = FilterInput.getTenseSubjects(0);
-
- // Assert filters are correct
- expect(filters["All Subjects"]).to.equal(true);
- expect(filters["Yo"]).to.equal(true);
- expect(filters["Tú"]).to.equal(true);
- expect(filters["Él"]).to.equal(true);
- expect(filters["Nosotros"]).to.equal(true);
- expect(filters["Ellos"]).to.equal(true);
- });
-
- it("Should be correct for Present Participles", function() {
- // Initialize filters
- FilterInput.verbFilters = [
- {tense:"Present Participles", subject:"All Subjects", type:"All Types"}
- ]
-
- // Get filters
- let filters = FilterInput.getTenseSubjects(0);
-
- // Assert filters are correct
- expect(filters["All Subjects"]).to.equal(true);
- expect(filters["Yo"]).to.equal(false);
- expect(filters["Tú"]).to.equal(false);
- expect(filters["Él"]).to.equal(false);
- expect(filters["Nosotros"]).to.equal(false);
- expect(filters["Ellos"]).to.equal(false);
- });
-
- it("Should change selection if not available", function() {
- // Initialize filters
- FilterInput.verbFilters = [
- {tense:"Present Participles", subject:"Yo", type:"All Types"}
- ]
-
- // Get filters
- let filters = FilterInput.getTenseSubjects(0);
-
- // Assert filters are correct
- expect(filters["All Subjects"]).to.equal(true);
- expect(filters["Yo"]).to.equal(false);
- expect(filters["Tú"]).to.equal(false);
- expect(filters["Él"]).to.equal(false);
- expect(filters["Nosotros"]).to.equal(false);
- expect(filters["Ellos"]).to.equal(false);
-
- // Assert selection changed
- expect(FilterInput.verbFilters[0]["subject"]).to.equal("All Subjects");
- });
-
- it("Should not change selection if available", function() {
- // Initialize filters
- FilterInput.verbFilters = [
- {tense:"Present Participles", subject:"Type", type:"All Types"},
- {tense:"Preterite Tense", subject:"Yo", type:"All Types"},
- ]
-
- // Get filters
- FilterInput.getTenseSubjects(0);
- FilterInput.getTenseSubjects(1);
-
- // Assert selection not changed
- expect(FilterInput.verbFilters[0].subject).to.equal("Type");
- expect(FilterInput.verbFilters[1].subject).to.equal("Yo");
- });
- });
-
- describe("GetCategoryFilters method", function() {
- it("Should be correct for Verbs", function() {
- // Initialize filters
- FilterInput.vocabFilters = [
- {category:"Verbs", type:"All Definitions"}
- ]
-
- // Get filters
- let filters = FilterInput.getCategoryFilters(0);
-
- // Assert filters are correct
- expect(filters["All Types"]).to.equal(true);
- expect(filters["Adjectives"]).to.equal(false);
- expect(filters["Nouns"]).to.equal(false);
- expect(filters["Verbs"]).to.equal(false);
- });
-
- it("Should be correct for sets with 1 type", function() {
- // Initialize filters
- FilterInput.vocabFilters = [
- {category:"Colors", type:"All Definitions"}
- ]
-
- // Get filters
- let filters = FilterInput.getCategoryFilters(0);
-
- // Assert filters are correct
- expect(filters["All Types"]).to.equal(true);
- expect(filters["Adjectives"]).to.equal(true);
- expect(filters["Nouns"]).to.equal(false);
- expect(filters["Verbs"]).to.equal(false);
- });
-
- it("Should change selection if not available", function() {
- // Initialize filters
- FilterInput.vocabFilters = [
- {category:"Colors", type:"Verbs"}
- ]
-
- // Get filters
- let filters = FilterInput.getCategoryFilters(0);
-
- // Assert selection changed
- expect(filters["All Types"]).to.equal(true);
- expect(filters["Adjectives"]).to.equal(true);
- expect(filters["Nouns"]).to.equal(false);
- expect(filters["Verbs"]).to.equal(false);
- expect(FilterInput.vocabFilters[0]["type"]).to.equal("All Types");
- });
-
- it("Should not change selection if available", function() {
- // Initialize filters
- FilterInput.vocabFilters = [
- {category:"Professions", type:"Verbs"}
- ]
-
- // Get filters
- let filters = FilterInput.getCategoryFilters(0);
-
- // Assert selection not changed
- expect(filters["All Types"]).to.equal(true);
- expect(filters["Adjectives"]).to.equal(false);
- expect(filters["Nouns"]).to.equal(true);
- expect(filters["Verbs"]).to.equal(true);
- expect(FilterInput.vocabFilters[0]["type"]).to.equal("Verbs");
- });
- });
-});
diff --git a/tests/test.filtersPage.js b/tests/test.filtersPage.js
@@ -0,0 +1,488 @@
+// filter-input component
+describe("FilterInput", function() {
+ let FilterInput;
+ beforeEach(function() {
+ // Create filtersInput component
+ FilterInput = new filterInput();
+ });
+
+ describe("Created lifecycle hook", function() {
+ it("Category should be 'verbs'", function() {
+ expect(FilterInput.category).to.equal("verbs");
+ });
+
+ it("VerbFilters should be empty", function() {
+ expect(FilterInput.verbFilters.length).to.equal(0);
+ });
+
+ it("VocabFilters should be empty", function() {
+ expect(FilterInput.vocabFilters.length).to.equal(0);
+ });
+ });
+
+ describe("Value computed property", function() {
+ it("Should return verb filters if category is 'verbs'", function() {
+ // Initialize variables
+ FilterInput.category = "verbs";
+ FilterInput.verbFilters = "verb-filters";
+ FilterInput.vocabFilters = "vocab-filters";
+
+ // Assert value returns verb filters
+ expect(FilterInput.value, "verb-filters");
+ });
+
+ it("Should return vocab filters if category is 'vocab'", function() {
+ // Initialize variables
+ FilterInput.category = "vocab";
+ FilterInput.verbFilters = "verb-filters";
+ FilterInput.vocabFilters = "vocab-filters";
+
+ // Assert value returns vocab filters
+ expect(FilterInput.value, "vocab-filters");
+ });
+ });
+
+ describe("Value watch", function() {
+ it("Should emit input event", async function() {
+ // Initialize variables
+ FilterInput.category = "verbs";
+ FilterInput.verbFilters = ["filter1"];
+
+ // Override $emit method
+ let event = "";
+ let event_args;
+ FilterInput.$emit = function(name, value) {
+ event = name;
+ event_args = value;
+ };
+
+ // Edit verb filters
+ FilterInput.verbFilters.push("filter2");
+ await FilterInput.$nextTick();
+
+ // Assert event emited
+ expect(event).to.equal("input");
+ expect(event_args).to.have.deep.members(["filter1", "filter2"]);
+ });
+ });
+
+ describe("AddFilter method", function() {
+ it("Should add a verb filter if category is 'verbs'", function() {
+ // Initialize variables
+ FilterInput.category = "verbs";
+ expect(FilterInput.verbFilters.length).to.equal(0);
+ expect(FilterInput.vocabFilters.length).to.equal(0);
+
+ // Add filter
+ FilterInput.AddFilter();
+
+ // Assert filter added
+ expect(FilterInput.verbFilters).to.have.deep.members([
+ {tense:"All Tenses", type:"All Types", subject:"All Subjects", direction:"Eng. → Conj."},
+ ]);
+ expect(FilterInput.vocabFilters).to.have.deep.members([]);
+ });
+
+ it("Should add a vocab filter if category is 'vocab'", function() {
+ // Initialize variables
+ FilterInput.category = "vocab";
+ expect(FilterInput.verbFilters.length).to.equal(0);
+ expect(FilterInput.vocabFilters.length).to.equal(0);
+
+ // Add filter
+ FilterInput.AddFilter();
+
+ // Assert filter added
+ expect(FilterInput.vocabFilters).to.have.deep.members([
+ {category:"All Categories", type:"All Types", direction:"Eng. ↔ Esp."},
+ ]);
+ expect(FilterInput.verbFilters).to.have.deep.members([]);
+ });
+ });
+
+ describe("RemoveFilter method", function() {
+ it("Should remove the specified verb filter", function() {
+ // Initialize filters
+ FilterInput.category = "verbs";
+ FilterInput.verbFilters = [
+ "verb1",
+ "verb2",
+ "verb3",
+ ];
+ FilterInput.vocabFilters = [
+ "vocab1",
+ "vocab2",
+ "vocab3",
+ ];
+
+ // Remove filter
+ FilterInput.RemoveFilter(1);
+
+ // Assert filter removed
+ expect(FilterInput.verbFilters.length).to.equal(2);
+ expect(FilterInput.verbFilters[0]).to.equal("verb1");
+ expect(FilterInput.verbFilters[1]).to.equal("verb3");
+ expect(FilterInput.vocabFilters.length).to.equal(3);
+ });
+
+ it("Should remove the specified vocab filter", function() {
+ // Initialize filters
+ FilterInput.category = "vocab";
+ FilterInput.verbFilters = [
+ "verb1",
+ "verb2",
+ "verb3",
+ ];
+ FilterInput.vocabFilters = [
+ "vocab1",
+ "vocab2",
+ "vocab3",
+ ];
+
+ // Remove filter
+ FilterInput.RemoveFilter(1);
+
+ // Assert filter removed
+ expect(FilterInput.verbFilters.length).to.equal(3);
+ expect(FilterInput.vocabFilters.length).to.equal(2);
+ expect(FilterInput.vocabFilters[0]).to.equal("vocab1");
+ expect(FilterInput.vocabFilters[1]).to.equal("vocab3");
+ });
+ });
+
+ describe("GetTenseTypes method", function() {
+ it("Should be correct for All Tenses", function() {
+ // Initialize filters
+ FilterInput.verbFilters = [
+ {tense:"All Types", type:"All Types"}
+ ]
+
+ // Get filters
+ let filters = FilterInput.getTenseTypes(0);
+
+ // Assert filters are correct
+ expect(filters["All Types"]).to.equal(true);
+ expect(filters["Reflexive"]).to.equal(true);
+ expect(filters["Regular"]).to.equal(true);
+ expect(filters["Nonregular"]).to.equal(true);
+ expect(filters["Stem Changing"]).to.equal(true);
+ expect(filters["Orthographic"]).to.equal(true);
+ expect(filters["Irregular"]).to.equal(true);
+ });
+
+ it("Should be correct for Present Tense", function() {
+ // Initialize filters
+ FilterInput.verbFilters = [
+ {tense:"Present Tense", type:"All Types"}
+ ]
+
+ // Get filters
+ let filters = FilterInput.getTenseTypes(0);
+
+ // Assert filters are correct
+ expect(filters["All Types"]).to.equal(true);
+ expect(filters["Reflexive"]).to.equal(true);
+ expect(filters["Regular"]).to.equal(true);
+ expect(filters["Nonregular"]).to.equal(true);
+ expect(filters["Stem Changing"]).to.equal(true);
+ expect(filters["Orthographic"]).to.equal(false);
+ expect(filters["Irregular"]).to.equal(true);
+ });
+
+ it("Should change selection if not available", function() {
+ // Initialize filters
+ FilterInput.verbFilters = [
+ {tense:"Present Tense", type:"Orthographic"}
+ ]
+
+ // Get filters
+ let filters = FilterInput.getTenseTypes(0);
+
+ // Assert filters are correct
+ expect(filters["All Types"]).to.equal(true);
+ expect(filters["Reflexive"]).to.equal(true);
+ expect(filters["Regular"]).to.equal(true);
+ expect(filters["Nonregular"]).to.equal(true);
+ expect(filters["Stem Changing"]).to.equal(true);
+ expect(filters["Orthographic"]).to.equal(false);
+ expect(filters["Irregular"]).to.equal(true);
+
+ // Assert selection changed
+ expect(FilterInput.verbFilters[0]["type"]).to.equal("All Types");
+ });
+
+ it("Should not change selection if available", function() {
+ // Initialize filters
+ FilterInput.verbFilters = [
+ {tense:"Preterite Tense", type:"Orthographic"}
+ ]
+
+ // Get filters
+ let filters = FilterInput.getTenseTypes(0);
+
+ // Assert filters are correct
+ expect(filters["All Types"]).to.equal(true);
+ expect(filters["Reflexive"]).to.equal(true);
+ expect(filters["Regular"]).to.equal(true);
+ expect(filters["Nonregular"]).to.equal(true);
+ expect(filters["Stem Changing"]).to.equal(true);
+ expect(filters["Orthographic"]).to.equal(true);
+ expect(filters["Irregular"]).to.equal(true);
+
+ // Assert selection not changed
+ expect(FilterInput.verbFilters[0]["type"]).to.equal("Orthographic");
+ });
+ });
+
+ describe("GetTenseSubjects method", function() {
+ it("Should be correct for All Tenses", function() {
+ // Initialize filters
+ FilterInput.verbFilters = [
+ {tense:"All Types", type:"All Types"}
+ ]
+
+ // Get filters
+ let filters = FilterInput.getTenseSubjects(0);
+
+ // Assert filters are correct
+ expect(filters["All Subjects"]).to.equal(true);
+ expect(filters["Yo"]).to.equal(true);
+ expect(filters["Tú"]).to.equal(true);
+ expect(filters["Él"]).to.equal(true);
+ expect(filters["Nosotros"]).to.equal(true);
+ expect(filters["Ellos"]).to.equal(true);
+ });
+
+ it("Should be correct for Present Participles", function() {
+ // Initialize filters
+ FilterInput.verbFilters = [
+ {tense:"Present Participles", subject:"All Subjects", type:"All Types"}
+ ]
+
+ // Get filters
+ let filters = FilterInput.getTenseSubjects(0);
+
+ // Assert filters are correct
+ expect(filters["All Subjects"]).to.equal(true);
+ expect(filters["Yo"]).to.equal(false);
+ expect(filters["Tú"]).to.equal(false);
+ expect(filters["Él"]).to.equal(false);
+ expect(filters["Nosotros"]).to.equal(false);
+ expect(filters["Ellos"]).to.equal(false);
+ });
+
+ it("Should change selection if not available", function() {
+ // Initialize filters
+ FilterInput.verbFilters = [
+ {tense:"Present Participles", subject:"Yo", type:"All Types"}
+ ]
+
+ // Get filters
+ let filters = FilterInput.getTenseSubjects(0);
+
+ // Assert filters are correct
+ expect(filters["All Subjects"]).to.equal(true);
+ expect(filters["Yo"]).to.equal(false);
+ expect(filters["Tú"]).to.equal(false);
+ expect(filters["Él"]).to.equal(false);
+ expect(filters["Nosotros"]).to.equal(false);
+ expect(filters["Ellos"]).to.equal(false);
+
+ // Assert selection changed
+ expect(FilterInput.verbFilters[0]["subject"]).to.equal("All Subjects");
+ });
+
+ it("Should not change selection if available", function() {
+ // Initialize filters
+ FilterInput.verbFilters = [
+ {tense:"Present Participles", subject:"Type", type:"All Types"},
+ {tense:"Preterite Tense", subject:"Yo", type:"All Types"},
+ ]
+
+ // Get filters
+ FilterInput.getTenseSubjects(0);
+ FilterInput.getTenseSubjects(1);
+
+ // Assert selection not changed
+ expect(FilterInput.verbFilters[0].subject).to.equal("Type");
+ expect(FilterInput.verbFilters[1].subject).to.equal("Yo");
+ });
+ });
+
+ describe("GetCategoryFilters method", function() {
+ it("Should be correct for Verbs", function() {
+ // Initialize filters
+ FilterInput.vocabFilters = [
+ {category:"Verbs", type:"All Definitions"}
+ ]
+
+ // Get filters
+ let filters = FilterInput.getCategoryFilters(0);
+
+ // Assert filters are correct
+ expect(filters["All Types"]).to.equal(true);
+ expect(filters["Adjectives"]).to.equal(false);
+ expect(filters["Nouns"]).to.equal(false);
+ expect(filters["Verbs"]).to.equal(false);
+ });
+
+ it("Should be correct for sets with 1 type", function() {
+ // Initialize filters
+ FilterInput.vocabFilters = [
+ {category:"Colors", type:"All Definitions"}
+ ]
+
+ // Get filters
+ let filters = FilterInput.getCategoryFilters(0);
+
+ // Assert filters are correct
+ expect(filters["All Types"]).to.equal(true);
+ expect(filters["Adjectives"]).to.equal(true);
+ expect(filters["Nouns"]).to.equal(false);
+ expect(filters["Verbs"]).to.equal(false);
+ });
+
+ it("Should change selection if not available", function() {
+ // Initialize filters
+ FilterInput.vocabFilters = [
+ {category:"Colors", type:"Verbs"}
+ ]
+
+ // Get filters
+ let filters = FilterInput.getCategoryFilters(0);
+
+ // Assert selection changed
+ expect(filters["All Types"]).to.equal(true);
+ expect(filters["Adjectives"]).to.equal(true);
+ expect(filters["Nouns"]).to.equal(false);
+ expect(filters["Verbs"]).to.equal(false);
+ expect(FilterInput.vocabFilters[0]["type"]).to.equal("All Types");
+ });
+
+ it("Should not change selection if available", function() {
+ // Initialize filters
+ FilterInput.vocabFilters = [
+ {category:"Professions", type:"Verbs"}
+ ]
+
+ // Get filters
+ let filters = FilterInput.getCategoryFilters(0);
+
+ // Assert selection not changed
+ expect(filters["All Types"]).to.equal(true);
+ expect(filters["Adjectives"]).to.equal(false);
+ expect(filters["Nouns"]).to.equal(true);
+ expect(filters["Verbs"]).to.equal(true);
+ expect(FilterInput.vocabFilters[0]["type"]).to.equal("Verbs");
+ });
+ });
+});
+
+
+
+// settings-input component
+describe("SettingsInput", function() {
+ let SettingsInput;
+ beforeEach(function() {
+ // Create settingsInput component
+ SettingsInput = new settingsInput();
+ });
+
+ describe("Value watch", function() {
+ it("Should emit input event", async function() {
+ // Override $emit method
+ let event = "";
+ let event_args;
+ SettingsInput.$emit = function(name, value) {
+ event = name;
+ event_args = value;
+ };
+
+ // Override setSettings method
+ let old_setSettings = setSettings;
+ setSettings = function() {};
+
+ // Edit setting
+ SettingsInput.value.promptType = "test-prompt-type";
+ await SettingsInput.$nextTick();
+
+ // Assert event emited
+ expect(event).to.equal("input");
+ expect(event_args.promptType).to.equal("test-prompt-type");
+
+ // Restore setSettings method
+ setSettings = old_setSettings;
+ });
+
+ it("Should call setSettings", async function() {
+ // Override $emit method
+ let event = "";
+ let event_args;
+ SettingsInput.$emit = function(name, value) {
+ event = name;
+ event_args = value;
+ };
+
+ // Override setSettings method
+ let old_setSettings = setSettings;
+ let args = null;
+ setSettings = function(value) {
+ args = value;
+ };
+
+ // Edit setting
+ SettingsInput.value.inputType = "test-input-type";
+ await SettingsInput.$nextTick();
+ // Assert setSettings called
+ expect(args.inputType).to.equal("test-input-type");
+
+ // Restore setSettings method
+ setSettings = old_setSettings;
+ });
+ });
+});
+
+
+// filters-page component
+describe("FiltersPage", function() {
+ let FiltersPage;
+ beforeEach(function() {
+ // Create filtersPage component
+ FiltersPage = new filtersPage();
+ });
+
+ describe("StartSession method", function() {
+ it("Should push quizzer page", function() {
+ // Override $router.push method
+ let push_args;
+ FiltersPage.$router = {push: function(args) {
+ push_args = args;
+ }};
+
+ // Initialize variables
+ FiltersPage.settings = {
+ promptType: "Text", // Required to prevent browser validation alerts
+ inputType: "Text", // Required to prevent browser validation alerts
+ testSetting: "testValue",
+ };
+
+ // Call StartSession
+ FiltersPage.StartSession([1, 2, 3], 0);
+
+ // Assert event emited
+ expect(push_args).to.deep.equal({
+ name: "quizzer",
+ params: {
+ startingPrompts: [1, 2, 3],
+ startingIndex: 0,
+ settings: {
+ promptType: "Text", // Required to prevent browser validation alerts
+ inputType: "Text", // Required to prevent browser validation alerts
+ testSetting: "testValue",
+ },
+ referer: FiltersPage.category,
+ }
+ });
+ });
+ });
+});
diff --git a/tests/test.global.js b/tests/test.global.js
@@ -1,21 +1,4 @@
describe("Global.js", function() {
- describe("GetLang method", function() {
- it("Should return English by default", function() {
- expect(getLang("")).to.equal("en");
- expect(getLang("test")).to.equal("en");
- });
-
- it("Should return English for English labels", function() {
- expect(getLang("test english test")).to.equal("en");
- expect(getLang("ENGLISH")).to.equal("en");
- })
-
- it("Should return Spanish for Spanish labels", function() {
- expect(getLang("test spanish test")).to.equal("es");
- expect(getLang("SPANISH")).to.equal("es");
- })
- });
-
describe("GetSettings method", function() {
it("Settings should be loaded", async function() {
// Save original setting from localStorage
diff --git a/tests/test.quizzer.js b/tests/test.quizzer.js
@@ -42,6 +42,8 @@ describe("Quizzer", function() {
describe("Reset method", function() {
it("Should reset responce", function() {
// Initialize variables
+ Quizzer.prompts = ["prompt 1", "prompt 2"];
+ Quizzer.index = 0;
Quizzer.responce = "test";
expect(Quizzer.responce).to.equal("test");
@@ -54,6 +56,8 @@ describe("Quizzer", function() {
it("Should set responceActive to true", function() {
// Initialize variables
+ Quizzer.prompts = ["prompt 1", "prompt 2"];
+ Quizzer.index = 0;
Quizzer.responceActive = false;
// Run reset
@@ -98,24 +102,6 @@ describe("Quizzer", function() {
// Assert event emited
expect(event).to.equal("new-prompt");
});
-
- it("Should emit 'finished-prompts' event if on last term", function() {
- // Initialize variables
- Quizzer.prompts = ["prompt 1", "prompt 2"];
- Quizzer.index = 1;
-
- // Override $emit method
- let event = "";
- Quizzer.$emit = function(name) {
- event = name;
- };
-
- // Run reset
- Quizzer.Reset();
-
- // Assert event emited
- expect(event).to.equal("finished-prompts");
- });
});
describe("Submit method", function() {
@@ -566,4 +552,21 @@ describe("Quizzer", function() {
expect(Quizzer.prompt[3]).to.equal("d2");
});
});
+
+ describe("GetLang method", function() {
+ it("Should return English by default", function() {
+ expect(Quizzer.getLang("")).to.equal("en");
+ expect(Quizzer.getLang("test")).to.equal("en");
+ });
+
+ it("Should return English for English labels", function() {
+ expect(Quizzer.getLang("test english test")).to.equal("en");
+ expect(Quizzer.getLang("ENGLISH")).to.equal("en");
+ })
+
+ it("Should return Spanish for Spanish labels", function() {
+ expect(Quizzer.getLang("test spanish test")).to.equal("es");
+ expect(Quizzer.getLang("SPANISH")).to.equal("es");
+ })
+ });
});
diff --git a/tests/test.settingsInput.js b/tests/test.settingsInput.js
@@ -1,60 +0,0 @@
-describe("SettingsInput", function() {
- let SettingsInput;
- beforeEach(function() {
- // Create settingsInput component
- SettingsInput = new settingsInput();
- });
-
- describe("Value watch", function() {
- it("Should emit input event", async function() {
- // Override $emit method
- let event = "";
- let event_args;
- SettingsInput.$emit = function(name, value) {
- event = name;
- event_args = value;
- };
-
- // Override setSettings method
- let old_setSettings = setSettings;
- setSettings = function() {};
-
- // Edit setting
- SettingsInput.value.promptType = "test-prompt-type";
- await SettingsInput.$nextTick();
-
- // Assert event emited
- expect(event).to.equal("input");
- expect(event_args.promptType).to.equal("test-prompt-type");
-
- // Restore setSettings method
- setSettings = old_setSettings;
- });
-
- it("Should call setSettings", async function() {
- // Override $emit method
- let event = "";
- let event_args;
- SettingsInput.$emit = function(name, value) {
- event = name;
- event_args = value;
- };
-
- // Override setSettings method
- let old_setSettings = setSettings;
- let args = null;
- setSettings = function(value) {
- args = value;
- };
-
- // Edit setting
- SettingsInput.value.inputType = "test-input-type";
- await SettingsInput.$nextTick();
- // Assert setSettings called
- expect(args.inputType).to.equal("test-input-type");
-
- // Restore setSettings method
- setSettings = old_setSettings;
- });
- });
-});