multiple choice capability
This commit is contained in:
+15
-2
@@ -22,14 +22,23 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else-if="currentItem">
|
<div v-else-if="currentItem">
|
||||||
<div class="main"><span id="question" v-html="getFormatedData(mode)"></span></div>
|
<div class="main"><span id="question" v-html="getFormatedData(mode)"></span></div>
|
||||||
<template v-if="showAnswer" v-for="(column, i) in columns">
|
<template v-if="showAnswer && !multiple" v-for="(column, i) in columns">
|
||||||
<div v-if="i !== mode" class="main"><span id="answer" v-html="getFormatedData(i)"></span></div>
|
<div v-if="i !== mode" class="main"><span id="answer" v-html="getFormatedData(i)"></span></div>
|
||||||
</template>
|
</template>
|
||||||
<div class="button-container">
|
<div class="button-container" v-if="!multiple">
|
||||||
<div type="button" class="button long" v-if="!showAnswer" v-on:click="show">Show</div>
|
<div type="button" class="button long" v-if="!showAnswer" v-on:click="show">Show</div>
|
||||||
<div type="button" class="button right" v-if="showAnswer" v-on:click="right">✔</div>
|
<div type="button" class="button right" v-if="showAnswer" v-on:click="right">✔</div>
|
||||||
<div type="button" class="button wrong" v-if="showAnswer" v-on:click="wrong">✘</div>
|
<div type="button" class="button wrong" v-if="showAnswer" v-on:click="wrong">✘</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="button-container" v-else>
|
||||||
|
<template v-for="(answer, j) in answers">
|
||||||
|
<div class="button long" @click="clickAnswer(j)" :class="{right: showAnswer && answer[columns.length] === currentItem[columns.length], wrong: showAnswer && j === answered && answer[columns.length] !== currentItem[columns.length]}">
|
||||||
|
<template v-for="(column, i) in columns">
|
||||||
|
<div v-if="i !== mode"><span v-html="getFormatedDataAnswer(i, j)"></span></div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<hr>
|
<hr>
|
||||||
@@ -50,6 +59,10 @@
|
|||||||
<td><label for="url">URL (CSV data):</label></td>
|
<td><label for="url">URL (CSV data):</label></td>
|
||||||
<td><input id="url" v-model.lazy="url"></td>
|
<td><input id="url" v-model.lazy="url"></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="multiple">Choices (0 = no choices):</label></td>
|
||||||
|
<td><input type="number" id="multiple" v-model.lazy="multiple"></td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<hr>
|
<hr>
|
||||||
<table class="config">
|
<table class="config">
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ const utils = {
|
|||||||
return output;
|
return output;
|
||||||
},
|
},
|
||||||
shuffle: function (array) {
|
shuffle: function (array) {
|
||||||
const output = [ ...array ];
|
const output = [...array];
|
||||||
if (output.length < 2) {
|
if (output.length < 2) {
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
for (let i = 0; i < array.length; i++) {
|
for (let i = 0; i < array.length; i++) {
|
||||||
const i1 = this.randindex(array);
|
const i1 = this.randindex(array);
|
||||||
const i2 = this.randindex(array, i1);
|
const i2 = this.randindex(array, i1);
|
||||||
[ output[i1], output[i2] ] = [ output[i2], output[i1] ];
|
[output[i1], output[i2]] = [output[i2], output[i1]];
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
},
|
},
|
||||||
@@ -50,13 +50,16 @@ let app = {
|
|||||||
failed: {},
|
failed: {},
|
||||||
done: {},
|
done: {},
|
||||||
showConfig: true,
|
showConfig: true,
|
||||||
modes: [ ],
|
modes: [],
|
||||||
size: 0,
|
size: 0,
|
||||||
mode: 0,
|
mode: 0,
|
||||||
title: '',
|
title: "",
|
||||||
url: '',
|
url: "",
|
||||||
error: '',
|
error: "",
|
||||||
|
multiple: 0,
|
||||||
columns: [],
|
columns: [],
|
||||||
|
answers: [],
|
||||||
|
answered: 0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -65,17 +68,20 @@ let app = {
|
|||||||
},
|
},
|
||||||
doneDisplay() {
|
doneDisplay() {
|
||||||
return this.modes
|
return this.modes
|
||||||
.map(m => this.done[m]?.length ?? 0)
|
.map((m) => this.done[m]?.length ?? 0)
|
||||||
.reduce((a, b) => a + b, 0);
|
.reduce((a, b) => a + b, 0);
|
||||||
},
|
},
|
||||||
availableDisplay() {
|
availableDisplay() {
|
||||||
return this.modes.length * this.available.length;
|
return this.modes.length * this.available.length;
|
||||||
},
|
},
|
||||||
allDone() {
|
allDone() {
|
||||||
return this.modes.filter(m => (this.current[m]?.length ?? 0) > 0).length === 0;
|
return (
|
||||||
|
this.modes.filter((m) => (this.current[m]?.length ?? 0) > 0).length ===
|
||||||
|
0
|
||||||
|
);
|
||||||
},
|
},
|
||||||
currentItem() {
|
currentItem() {
|
||||||
if (! this.current[this.mode]?.length) {
|
if (!this.current[this.mode]?.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,13 +90,35 @@ let app = {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getFormatedData(i) {
|
getFormatedData(i) {
|
||||||
if (this.currentItem[i].startsWith('data:')) {
|
if (this.currentItem[i].startsWith("data:")) {
|
||||||
return this.columns[i] + ' :<br><img src="' + this.currentItem[i] + '" />';
|
return (
|
||||||
|
this.columns[i] + ' :<br><img src="' + this.currentItem[i] + '" />'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return this.columns[i] + ' : ' + this.currentItem[i];
|
return this.columns[i] + " : " + this.currentItem[i];
|
||||||
|
},
|
||||||
|
getFormatedDataAnswer(i, j) {
|
||||||
|
if (this.answers[j][i].startsWith("data:")) {
|
||||||
|
return (
|
||||||
|
this.columns[i] + ' :<br><img src="' + this.answers[j][i] + '" />'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.columns[i] + " : " + this.answers[j][i];
|
||||||
},
|
},
|
||||||
showApp() {
|
showApp() {
|
||||||
document.getElementById('app').setAttribute('style', '');
|
document.getElementById("app").setAttribute("style", "");
|
||||||
|
},
|
||||||
|
clickAnswer(i) {
|
||||||
|
if (this.showAnswer) {
|
||||||
|
if (this.answers[this.answered][-1] === this.currentItem[-1]) {
|
||||||
|
this.right();
|
||||||
|
} else {
|
||||||
|
this.wrong();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.showAnswer = true;
|
||||||
|
this.answered = i;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
show() {
|
show() {
|
||||||
this.showAnswer = true;
|
this.showAnswer = true;
|
||||||
@@ -104,9 +132,14 @@ let app = {
|
|||||||
this.nextQuestion();
|
this.nextQuestion();
|
||||||
},
|
},
|
||||||
reset() {
|
reset() {
|
||||||
this.current = Object.fromEntries(this.modes.map(m => [ m, utils.shuffle(utils.cloneObject(this.available)) ]));
|
this.current = Object.fromEntries(
|
||||||
this.done = Object.fromEntries(this.modes.map(m => [ m, [] ]));
|
this.modes.map((m) => [
|
||||||
this.failed = Object.fromEntries(this.modes.map(m => [ m, [] ]));
|
m,
|
||||||
|
utils.shuffle(utils.cloneObject(this.available)),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
this.done = Object.fromEntries(this.modes.map((m) => [m, []]));
|
||||||
|
this.failed = Object.fromEntries(this.modes.map((m) => [m, []]));
|
||||||
if (this.modes.length) {
|
if (this.modes.length) {
|
||||||
this.nextQuestion();
|
this.nextQuestion();
|
||||||
}
|
}
|
||||||
@@ -114,7 +147,7 @@ let app = {
|
|||||||
nextQuestion() {
|
nextQuestion() {
|
||||||
this.showAnswer = false;
|
this.showAnswer = false;
|
||||||
|
|
||||||
this.modes.forEach(m => {
|
this.modes.forEach((m) => {
|
||||||
if (this.current[m].length === 0 && this.failed[m].length > 0) {
|
if (this.current[m].length === 0 && this.failed[m].length > 0) {
|
||||||
this.current[m] = utils.shuffle(utils.cloneObject(this.failed[m]));
|
this.current[m] = utils.shuffle(utils.cloneObject(this.failed[m]));
|
||||||
this.failed[m] = [];
|
this.failed[m] = [];
|
||||||
@@ -129,6 +162,29 @@ let app = {
|
|||||||
} while (this.current[newMode].length === 0 && tries < 100);
|
} while (this.current[newMode].length === 0 && tries < 100);
|
||||||
|
|
||||||
this.mode = newMode;
|
this.mode = newMode;
|
||||||
|
|
||||||
|
if (this.multiple > 0) {
|
||||||
|
this.generateAnswers();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
generateAnswers() {
|
||||||
|
this.answers = [this.currentItem];
|
||||||
|
|
||||||
|
const total = Math.min(this.multiple, this.available.length);
|
||||||
|
|
||||||
|
let tries = 0;
|
||||||
|
let id;
|
||||||
|
const ids = [this.currentItem[-1]];
|
||||||
|
while (this.answers.length < total && tries < 100) {
|
||||||
|
tries++;
|
||||||
|
id = utils.randindex(this.available, ...ids);
|
||||||
|
if (this.available[id][this.mode] !== this.currentItem[this.mode]) {
|
||||||
|
this.answers.push(this.available[id]);
|
||||||
|
ids.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.answers = utils.shuffle(this.answers);
|
||||||
},
|
},
|
||||||
change(n, value) {
|
change(n, value) {
|
||||||
if (!value && this.modes.length > 1) {
|
if (!value && this.modes.length > 1) {
|
||||||
@@ -140,28 +196,33 @@ let app = {
|
|||||||
},
|
},
|
||||||
dataComplete(results) {
|
dataComplete(results) {
|
||||||
if (results.errors.length) {
|
if (results.errors.length) {
|
||||||
this.error = 'CSV file contains errors';
|
this.error = "CSV file contains errors";
|
||||||
} else {
|
} else {
|
||||||
const url = new URL(window.location);
|
const url = new URL(window.location);
|
||||||
this.columns = results.data.shift();
|
this.columns = results.data.shift();
|
||||||
this.modes = this.columns.map((_, i) => i);
|
this.modes = this.columns.map((_, i) => i);
|
||||||
if (url.searchParams.get('modes')) {
|
if (url.searchParams.get("modes")) {
|
||||||
try {
|
try {
|
||||||
this.modes = JSON.parse(url.searchParams.get('modes')).filter(m => m < this.columns.length);
|
this.modes = JSON.parse(url.searchParams.get("modes")).filter(
|
||||||
|
(m) => m < this.columns.length
|
||||||
|
);
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
this.available = results.data;
|
this.available = results.data;
|
||||||
|
this.available.forEach((data, i) => {
|
||||||
|
data.push(i);
|
||||||
|
});
|
||||||
this.reset();
|
this.reset();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dataError() {
|
dataError() {
|
||||||
this.error = 'Could not read file';
|
this.error = "Could not read file";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
async url(newValue) {
|
async url(newValue) {
|
||||||
this.available = [];
|
this.available = [];
|
||||||
this.error = '';
|
this.error = "";
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
Papa.parse(newValue, {
|
Papa.parse(newValue, {
|
||||||
download: true,
|
download: true,
|
||||||
@@ -170,24 +231,33 @@ let app = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
multiple() {
|
||||||
|
this.reset();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
const url = new URL(window.location);
|
const url = new URL(window.location);
|
||||||
this.url = url.searchParams.get('url') ?? '';
|
this.url = url.searchParams.get("url") ?? "";
|
||||||
this.title = url.searchParams.get('title') ?? '';
|
this.title = url.searchParams.get("title") ?? "";
|
||||||
|
this.multiple = parseInt(url.searchParams.get("multiple") ?? 0) ?? 0;
|
||||||
|
if (this.multiple === NaN) {
|
||||||
|
this.multiple = 0;
|
||||||
|
}
|
||||||
this.showConfig = !this.url;
|
this.showConfig = !this.url;
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
const url = new URL(window.location);
|
const url = new URL(window.location);
|
||||||
if (
|
if (
|
||||||
url.searchParams.get('url') !== this.url ||
|
url.searchParams.get("url") !== this.url ||
|
||||||
url.searchParams.get('title') !== this.title ||
|
url.searchParams.get("title") !== this.title ||
|
||||||
url.searchParams.get('modes') !== JSON.stringify(this.modes)
|
url.searchParams.get("modes") !== JSON.stringify(this.modes) ||
|
||||||
|
url.searchParams.get("multiple") !== this.multiple
|
||||||
) {
|
) {
|
||||||
url.searchParams.set('url', this.url);
|
url.searchParams.set("url", this.url);
|
||||||
url.searchParams.set('title', this.title);
|
url.searchParams.set("title", this.title);
|
||||||
url.searchParams.set('modes', JSON.stringify(this.modes));
|
url.searchParams.set("modes", JSON.stringify(this.modes));
|
||||||
window.history.pushState({}, '', url);
|
url.searchParams.set("multiple", this.multiple);
|
||||||
|
window.history.pushState({}, "", url);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
@@ -197,5 +267,5 @@ let app = {
|
|||||||
|
|
||||||
window.onload = () => {
|
window.onload = () => {
|
||||||
app = Vue.createApp(app);
|
app = Vue.createApp(app);
|
||||||
app.mount('#app');
|
app.mount("#app");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ h1 {
|
|||||||
.button {
|
.button {
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
width: 6em;
|
width: 6em;
|
||||||
height: 2.3em;
|
/* height: 2.3em; */
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0.2em;
|
margin: 0.2em;
|
||||||
@@ -79,47 +79,59 @@ h1 {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.long {
|
.button > div {
|
||||||
width: 12em;
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.right {
|
.button.long {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.right,
|
||||||
|
.button.right * {
|
||||||
background-color: #8bc34a;
|
background-color: #8bc34a;
|
||||||
color: #eeeeee;
|
color: #eeeeee;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.wrong {
|
.button.wrong,
|
||||||
|
.button.wrong * {
|
||||||
background-color: #e53935;
|
background-color: #e53935;
|
||||||
color: #eeeeee;
|
color: #eeeeee;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:active {
|
.button:active,
|
||||||
|
.button:active * {
|
||||||
background-color: #757575;
|
background-color: #757575;
|
||||||
color: #eeeeee;
|
color: #eeeeee;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.right:active {
|
.button.right:active,
|
||||||
|
.button.right:active * {
|
||||||
background-color: #558b2f;
|
background-color: #558b2f;
|
||||||
color: #eeeeee;
|
color: #eeeeee;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.wrong:active {
|
.button.wrong:active,
|
||||||
|
.button.wrong:active * {
|
||||||
background-color: #c62828;
|
background-color: #c62828;
|
||||||
color: #eeeeee;
|
color: #eeeeee;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
.button:hover {
|
.button:hover,
|
||||||
|
.button:hover * {
|
||||||
background-color: #e0e0e0;
|
background-color: #e0e0e0;
|
||||||
color: #424242;
|
color: #424242;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.right:hover {
|
.button.right:hover,
|
||||||
|
.button.right:hover * {
|
||||||
background-color: #7cb342;
|
background-color: #7cb342;
|
||||||
color: #eeeeee;
|
color: #eeeeee;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.wrong:hover {
|
.button.wrong:hover,
|
||||||
|
.button.wrong:hover * {
|
||||||
background-color: #d32f2f;
|
background-color: #d32f2f;
|
||||||
color: #eeeeee;
|
color: #eeeeee;
|
||||||
}
|
}
|
||||||
@@ -134,7 +146,8 @@ table.config td {
|
|||||||
max-width: 30%;
|
max-width: 30%;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.config td button, table.config td progress {
|
table.config td button,
|
||||||
|
table.config td progress {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +155,7 @@ table.config td input {
|
|||||||
width: 30em;
|
width: 30em;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.config td input[type=checkbox] {
|
table.config td input[type="checkbox"] {
|
||||||
width: inherit !important;
|
width: inherit !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user