commit 1f9d631258cbeb0b6fbb309ea588892fe0c5b364
parent 93563039001002d00033e9062817c594f8c83ae0
Author: AsherMorgan <59518073+AsherMorgan@users.noreply.github.com>
Date: Fri, 12 Mar 2021 18:14:10 -0800
Create /settings page
Diffstat:
14 files changed, 286 insertions(+), 195 deletions(-)
diff --git a/css/filtersPage.css b/css/filtersPage.css
@@ -47,25 +47,6 @@
-/******** settings-input component ********/
-.settingsInput h2 {
- text-align: center;
- margin-bottom: 5px;
-}
-.settingsInput div, .settingsInput h3 {
- text-align: left;
- margin-bottom: 5px;
-}
-.settingsInput h3 {
- margin-top: 10px;
- font-size: 16px;
-}
-.settingsInput input[type=number] {
- width: 50px;
-}
-
-
-
/******** filters-page component ********/
.filtersPage main {
display: flex;
@@ -96,3 +77,15 @@
height: 50px;
}
}
+@media only screen and (max-width: 800px) {
+ /* Hide settings */
+ .filtersPage main .settingsInput {
+ display: none;
+ }
+}
+@media only screen and (min-width: 801px) {
+ /* Hide settings icon */
+ .filtersPage header img:last-child {
+ display: none;
+ }
+}
diff --git a/css/global.css b/css/global.css
@@ -36,19 +36,26 @@ header {
background-color: var(--theme-color);
font-size: 25px;
font-weight: bold;
- cursor: pointer;
color: #000000;
text-decoration: none;
margin: 0px;
padding-top: 15px;
padding-bottom: 15px;
}
-header img {
+header>* {
+ cursor: pointer;
+}
+header>img {
position: absolute;
- left: 10px;
height: 30px;
background-color: var(--theme-color);
}
+header>img:first-child {
+ left: 10px;
+}
+header>img:last-child {
+ right: 10px;
+}
@@ -66,7 +73,7 @@ header img {
/******** Other elements ********/
-h1, h2 {
+h1 {
font-size: 20px;
}
button:not(.icon) {
diff --git a/css/settingsPage.css b/css/settingsPage.css
@@ -0,0 +1,26 @@
+/******** settings-input component ********/
+.settingsInput h1 {
+ text-align: center;
+ margin-bottom: 5px;
+}
+.settingsInput div, .settingsInput h2 {
+ text-align: left;
+ margin-bottom: 5px;
+}
+.settingsInput h2 {
+ margin-top: 10px;
+ font-size: 16px;
+}
+.settingsInput input[type=number] {
+ width: 50px;
+}
+
+
+
+/******** settings-page component ********/
+.settingsPage main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 10px;
+}
diff --git a/images/settings.svg b/images/settings.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" class="feather feather-settings"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
+\ No newline at end of file
diff --git a/index.html b/index.html
@@ -12,6 +12,7 @@
<link rel="stylesheet" href="css/global.css">
<link rel="stylesheet" href="css/app.css">
<link rel="stylesheet" href="css/filtersPage.css">
+ <link rel="stylesheet" href="css/settingsPage.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>
@@ -20,6 +21,7 @@
<script src="js/global.js"></script>
<script src="js/filters.js"></script>
<script src="js/filtersPage.js"></script>
+ <script src="js/settingsPage.js"></script>
<script src="js/quizzer.js"></script>
<script src="js/reference.js"></script>
<script src="js/app.js"></script>
diff --git a/js/app.js b/js/app.js
@@ -6,7 +6,10 @@ let app;
// page-header component
const pageHeader = Vue.component("pageHeader", {
props: {
- image: {
+ icon1: {
+ type: String
+ },
+ icon2: {
type: String
},
title: {
@@ -14,10 +17,26 @@ const pageHeader = Vue.component("pageHeader", {
default: "Spanish-Quizzer",
}
},
+ computed: {
+ image1: function() {
+ if (this.icon1) return `images/${this.icon1}.svg`;
+ else return null;
+ },
+ image2: function() {
+ if (this.icon2) return `images/${this.icon2}.svg`;
+ else return null;
+ }
+ },
+ methods: {
+ goHome: function() {
+ if (this.$route.name !== 'home') this.$router.push('home');
+ }
+ },
template: `
- <header @click="$emit('back');">
- <img v-if="image" :src="image"/>
- {{ title }}
+ <header>
+ <img v-if="image1" :src="image1" @click="$emit('click1')"/>
+ <span @click="goHome">{{ title }}</span>
+ <img v-if="image2" :src="image2" @click="$emit('click2')"/>
</header>
`
});
@@ -113,6 +132,13 @@ function loadVue() {
props: { category: "vocab" }
},
{
+ path: "/settings",
+ name: "settings",
+ meta: { title: "Settings" },
+ component: settingsPage,
+ props: true
+ },
+ {
path: "/quizzer",
name: "quizzer",
meta: { title: "Quizzer" },
@@ -145,8 +171,12 @@ function loadVue() {
case "reference":
this.$router.push("home");
break;
+ case "settings":
+ this.$router.push(this.$route.params.referer || "home");
+ break;
case "quizzer":
this.$router.push(this.$route.params.referer || "home");
+ break;
}
},
diff --git a/js/filtersPage.js b/js/filtersPage.js
@@ -1,5 +1,5 @@
// filter-input component
-let filterInput = Vue.component("filterInput", {
+const filterInput = Vue.component("filterInput", {
props: {
category: {
type: String,
@@ -213,10 +213,10 @@ let filterInput = Vue.component("filterInput", {
template: `
<div class="filtersInput" ref="container">
<div class="verbSettings" v-show="category === 'verbs'">
- <h2>
+ <h1>
Verb Filters
<button class="icon" @click="AddFilter();"><img src="./images/plus.svg"></button>
- </h2>
+ </h1>
<div v-for="(filter, index) in verbFilters" class="filter">
<select v-model="filter.tense">
@@ -247,10 +247,10 @@ let filterInput = Vue.component("filterInput", {
<div class="vocabSettings" v-show="category === 'vocab'">
- <h2>
+ <h1>
Vocabulary Filters
<button class="icon" @click="AddFilter();"><img src="./images/plus.svg"></button>
- </h2>
+ </h1>
<div v-for="(filter, index) in vocabFilters" class="filter">
<select class="vocabSetName" v-model="filter.category">
@@ -299,99 +299,8 @@ let filterInput = Vue.component("filterInput", {
-// 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>Settings</h2>
- <h3>Appearance</h3>
- <div>
- <input type="checkbox" id="settingsDarkTheme" v-model="value.darkTheme">
- <label for="settingsDarkTheme">Dark Mode</label>
- </div>
-
- <h3>Prompts</h3>
- <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="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>
- <input type="checkbox" id="settingsRemoveDuplicates" v-model="value.removeDuplicates">
- <label for="settingsRemoveDuplicates">Remove duplicate prompts</label>
- </div>
-
- <h3>Grading</h3>
- <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="settingsMultipleAnswers">Multiple answers</label>
- <select id="settingsMultipleAnswers" v-model="value.multipleAnswers">
- <option>Require all</option>
- <option>Require any</option>
- </select>
- </div>
- </div>
- `,
-});
-
-
-
// filters-page component
-let filtersPage = Vue.component("filtersPage", {
+const filtersPage = Vue.component("filtersPage", {
props: {
category: {
type: String,
@@ -450,6 +359,13 @@ let filtersPage = Vue.component("filtersPage", {
},
/**
+ * Open the settings page
+ */
+ openSettings: function() {
+ this.$router.push({name:"settings", params:{referer:this.category}});
+ },
+
+ /**
* Handle a keyup event (implements some keyboard shortcuts).
* @param {object} e - The event args.
*/
@@ -476,7 +392,7 @@ let filtersPage = Vue.component("filtersPage", {
template: `
<div class="filtersPage">
- <page-header @back="$emit('back');" image="images/arrow-left.svg"></page-header>
+ <page-header icon1="arrow-left" @click1="$emit('back');" icon2="settings" @click2="openSettings"></page-header>
<main>
<filter-input ref="filters" :category="category" v-model="filters"></filter-input>
<settings-input v-model="settings"></settings-input>
diff --git a/js/quizzer.js b/js/quizzer.js
@@ -1,4 +1,4 @@
-let quizzer = Vue.component("quizzer", {
+const quizzer = Vue.component("quizzer", {
props: {
startingPrompts: {
type: Array,
@@ -378,7 +378,7 @@ const quizzerPage = Vue.component("quizzerPage", {
template: `
<div class="quizzer-page">
- <page-header @back="$emit('back', referer);" image="images/x.svg"></page-header>
+ <page-header @click1="$emit('back', referer);" icon1="x"></page-header>
<main>
<quizzer :starting-prompts="prompts" :starting-index="index" :settings="settings"
@new-prompt="updateProgress" @finished-prompts="$emit('back', referer);">
diff --git a/js/reference.js b/js/reference.js
@@ -109,7 +109,7 @@ const referenceTables = Vue.component("referenceTables", {
const referencePage = Vue.component("referencePage", {
template: `
<div class="referencePage">
- <page-header @back="$emit('back');" image="images/arrow-left.svg"></page-header>
+ <page-header @click1="$emit('back');" icon1="arrow-left"></page-header>
<main>
<reference-tables :data="this.$root.$data.data"></reference-tables>
</main>
diff --git a/js/settingsPage.js b/js/settingsPage.js
@@ -0,0 +1,114 @@
+// settings-input component
+const settingsInput = Vue.component("settingsInput", {
+ props: {
+ value: {
+ type: Object,
+ default: getSettings(),
+ },
+ },
+
+ watch: {
+ value: {
+ handler: function(value) {
+ setSettings(value);
+
+ this.$emit("input", value);
+ },
+ deep: true,
+ },
+ },
+
+ activated: function() {
+ // Refresh settings
+ this.value = getSettings();
+ },
+
+ template: `
+ <div class="settingsInput" ref="container">
+ <h1>Settings</h1>
+ <h2>Appearance</h2>
+ <div>
+ <input type="checkbox" id="settingsDarkTheme" v-model="value.darkTheme">
+ <label for="settingsDarkTheme">Dark Mode</label>
+ </div>
+
+ <h2>Quizzer Prompts</h2>
+ <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="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>
+ <input type="checkbox" id="settingsRemoveDuplicates" v-model="value.removeDuplicates">
+ <label for="settingsRemoveDuplicates">Remove duplicate prompts</label>
+ </div>
+
+ <h2>Quizzer Grading</h2>
+ <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="settingsMultipleAnswers">Multiple answers</label>
+ <select id="settingsMultipleAnswers" v-model="value.multipleAnswers">
+ <option>Require all</option>
+ <option>Require any</option>
+ </select>
+ </div>
+ </div>
+ `,
+});
+
+
+
+// settings-page component
+const settingsPage = Vue.component("settingsPage", {
+ props: {
+ referer: {
+ type: String,
+ default: "home",
+ },
+ },
+
+ template: `
+ <div class="settingsPage">
+ <page-header @click1="$emit('back', referer);" icon1="x"></page-header>
+ <main>
+ <settings-input></settings-input>
+ </main>
+ </div>
+ `,
+});
diff --git a/service-worker.js b/service-worker.js
@@ -1,5 +1,5 @@
// Initialize constants
-const version = "spanish-quizzer-1";
+const version = "spanish-quizzer-2";
const resources = [
"https://cdn.jsdelivr.net/npm/vue@2.6.12",
"https://cdn.jsdelivr.net/npm/vue-router@3.5.1",
@@ -9,6 +9,7 @@ const resources = [
"./css/global.css",
"./css/quizzer.css",
"./css/reference.css",
+ "./css/settingsPage.css",
"./data/verbs.csv",
"./data/vocab.csv",
"./images/arrow-left.svg",
@@ -27,6 +28,7 @@ const resources = [
"./js/global.js",
"./js/quizzer.js",
"./js/reference.js",
+ "./js/settingsPage.js",
"./index.html",
"./",
];
diff --git a/tests/index.html b/tests/index.html
@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
- <title>Mocha Tests - Spanish-Quizzer</title>
+ <title>Tests - Spanish-Quizzer</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="icon" type="image/png" href="../images/favicon-32.png">
<link rel="apple-touch-icon" sizes="180x180" href="../images/favicon-180.png">
@@ -18,12 +18,13 @@
<!-- Scripts being tested -->
<script src="../js/global.js"></script>
<script src="../js/filtersPage.js"></script>
+ <script src="../js/settingsPage.js"></script>
<script src="../js/filters.js"></script>
<script src="../js/quizzer.js"></script>
<!-- Setup tests -->
<script class="mocha-init">
- mocha.setup('bdd');
+ mocha.setup("bdd");
mocha.checkLeaks();
let expect = chai.expect;
</script>
@@ -31,6 +32,7 @@
<!-- Tests -->
<script src="test.global.js"></script>
<script src="test.filtersPage.js"></script>
+ <script src="test.settingsPage.js"></script>
<script src="test.filters.js"></script>
<script src="test.quizzer.js"></script>
diff --git a/tests/test.filtersPage.js b/tests/test.filtersPage.js
@@ -384,70 +384,6 @@ describe("FilterInput", function() {
-// 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;
diff --git a/tests/test.settingsPage.js b/tests/test.settingsPage.js
@@ -0,0 +1,61 @@
+// 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;
+ });
+ });
+});