diff --git a/index.html b/index.html
index 6a2311e..d80b1b2 100644
--- a/index.html
+++ b/index.html
@@ -22,14 +22,23 @@
-
+
-
@@ -50,6 +59,10 @@
|
|
+
+ |
+ |
+
diff --git a/main.js b/main.js
index 730c0c5..33034cd 100644
--- a/main.js
+++ b/main.js
@@ -1,201 +1,271 @@
/* exported app, utils */
const utils = {
- cloneObject: function (obj) {
- return JSON.parse(JSON.stringify(obj));
- },
- randint: function (min, max) {
- return Math.floor(Math.random() * (max - min)) + min;
- },
- randindex: function (array, ...toIgnore) {
- let index;
- do {
- index = this.randint(0, array.length);
- } while (this.contains(toIgnore, index));
- return index;
- },
- randitem: function (array) {
- return array[this.randindex(array)];
- },
- randindexes: function (array, number, ...toIgnore) {
- const output = [];
- for (let i = 0; i < number; i++) {
- output.push(this.randindex(array, ...output, ...toIgnore));
- }
- return output;
- },
- shuffle: function (array) {
- const output = [ ...array ];
- if (output.length < 2) {
- return output;
- }
- for (let i = 0; i < array.length; i++) {
- const i1 = this.randindex(array);
- const i2 = this.randindex(array, i1);
- [ output[i1], output[i2] ] = [ output[i2], output[i1] ];
- }
- return output;
- },
- contains: function (array, item) {
- return array.indexOf(item) >= 0;
- },
+ cloneObject: function (obj) {
+ return JSON.parse(JSON.stringify(obj));
+ },
+ randint: function (min, max) {
+ return Math.floor(Math.random() * (max - min)) + min;
+ },
+ randindex: function (array, ...toIgnore) {
+ let index;
+ do {
+ index = this.randint(0, array.length);
+ } while (this.contains(toIgnore, index));
+ return index;
+ },
+ randitem: function (array) {
+ return array[this.randindex(array)];
+ },
+ randindexes: function (array, number, ...toIgnore) {
+ const output = [];
+ for (let i = 0; i < number; i++) {
+ output.push(this.randindex(array, ...output, ...toIgnore));
+ }
+ return output;
+ },
+ shuffle: function (array) {
+ const output = [...array];
+ if (output.length < 2) {
+ return output;
+ }
+ for (let i = 0; i < array.length; i++) {
+ const i1 = this.randindex(array);
+ const i2 = this.randindex(array, i1);
+ [output[i1], output[i2]] = [output[i2], output[i1]];
+ }
+ return output;
+ },
+ contains: function (array, item) {
+ return array.indexOf(item) >= 0;
+ },
};
let app = {
- data() {
- return {
- showAnswer: false,
- available: [],
- current: {},
- failed: {},
- done: {},
- showConfig: true,
- modes: [ ],
- size: 0,
- mode: 0,
- title: '',
- url: '',
- error: '',
- columns: [],
- };
+ data() {
+ return {
+ showAnswer: false,
+ available: [],
+ current: {},
+ failed: {},
+ done: {},
+ showConfig: true,
+ modes: [],
+ size: 0,
+ mode: 0,
+ title: "",
+ url: "",
+ error: "",
+ multiple: 0,
+ columns: [],
+ answers: [],
+ answered: 0,
+ };
+ },
+ computed: {
+ currentYear() {
+ return new Date().getFullYear();
},
- computed: {
- currentYear() {
- return new Date().getFullYear();
- },
- doneDisplay() {
- return this.modes
- .map(m => this.done[m]?.length ?? 0)
- .reduce((a, b) => a + b, 0);
- },
- availableDisplay() {
- return this.modes.length * this.available.length;
- },
- allDone() {
- return this.modes.filter(m => (this.current[m]?.length ?? 0) > 0).length === 0;
- },
- currentItem() {
- if (! this.current[this.mode]?.length) {
- return null;
- }
+ doneDisplay() {
+ return this.modes
+ .map((m) => this.done[m]?.length ?? 0)
+ .reduce((a, b) => a + b, 0);
+ },
+ availableDisplay() {
+ return this.modes.length * this.available.length;
+ },
+ allDone() {
+ return (
+ this.modes.filter((m) => (this.current[m]?.length ?? 0) > 0).length ===
+ 0
+ );
+ },
+ currentItem() {
+ if (!this.current[this.mode]?.length) {
+ return null;
+ }
- return this.current[this.mode][0];
- },
+ return this.current[this.mode][0];
},
- methods: {
- getFormatedData(i) {
- if (this.currentItem[i].startsWith('data:')) {
- return this.columns[i] + ' :
';
- }
- return this.columns[i] + ' : ' + this.currentItem[i];
- },
- showApp() {
- document.getElementById('app').setAttribute('style', '');
- },
- show() {
- this.showAnswer = true;
- },
- right() {
- this.done[this.mode].push(this.current[this.mode].shift());
- this.nextQuestion();
- },
- wrong() {
- this.failed[this.mode].push(this.current[this.mode].shift());
- this.nextQuestion();
- },
- reset() {
- this.current = Object.fromEntries(this.modes.map(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) {
- this.nextQuestion();
- }
- },
- nextQuestion() {
- this.showAnswer = false;
-
- this.modes.forEach(m => {
- if (this.current[m].length === 0 && this.failed[m].length > 0) {
- this.current[m] = utils.shuffle(utils.cloneObject(this.failed[m]));
- this.failed[m] = [];
- }
- });
-
- let tries = 0;
- let newMode;
- do {
- tries++;
- newMode = utils.randitem(this.modes);
- } while (this.current[newMode].length === 0 && tries < 100);
-
- this.mode = newMode;
- },
- change(n, value) {
- if (!value && this.modes.length > 1) {
- this.modes.splice(this.modes.indexOf(n), 1);
- } else if (value && !utils.contains(this.modes, n)) {
- this.modes.push(n);
- }
- this.reset();
- },
- dataComplete(results) {
- if (results.errors.length) {
- this.error = 'CSV file contains errors';
- } else {
- const url = new URL(window.location);
- this.columns = results.data.shift();
- this.modes = this.columns.map((_, i) => i);
- if (url.searchParams.get('modes')) {
- try {
- this.modes = JSON.parse(url.searchParams.get('modes')).filter(m => m < this.columns.length);
- } catch {}
- }
- this.available = results.data;
- this.reset();
- }
- },
- dataError() {
- this.error = 'Could not read file';
- },
+ },
+ methods: {
+ getFormatedData(i) {
+ if (this.currentItem[i].startsWith("data:")) {
+ return (
+ this.columns[i] + ' :
'
+ );
+ }
+ return this.columns[i] + " : " + this.currentItem[i];
},
- watch: {
- async url(newValue) {
- this.available = [];
- this.error = '';
- if (newValue) {
- Papa.parse(newValue, {
- download: true,
- complete: this.dataComplete,
- error: this.dataError,
- });
- }
- },
+ getFormatedDataAnswer(i, j) {
+ if (this.answers[j][i].startsWith("data:")) {
+ return (
+ this.columns[i] + ' :
'
+ );
+ }
+ return this.columns[i] + " : " + this.answers[j][i];
},
- beforeMount() {
- const url = new URL(window.location);
- this.url = url.searchParams.get('url') ?? '';
- this.title = url.searchParams.get('title') ?? '';
- this.showConfig = !this.url;
+ showApp() {
+ document.getElementById("app").setAttribute("style", "");
},
- updated() {
- const url = new URL(window.location);
- if (
- url.searchParams.get('url') !== this.url ||
- url.searchParams.get('title') !== this.title ||
- url.searchParams.get('modes') !== JSON.stringify(this.modes)
- ) {
- url.searchParams.set('url', this.url);
- url.searchParams.set('title', this.title);
- url.searchParams.set('modes', JSON.stringify(this.modes));
- window.history.pushState({}, '', url);
+ 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;
+ }
},
- mounted: function () {
- setTimeout(this.showApp);
+ show() {
+ this.showAnswer = true;
},
+ right() {
+ this.done[this.mode].push(this.current[this.mode].shift());
+ this.nextQuestion();
+ },
+ wrong() {
+ this.failed[this.mode].push(this.current[this.mode].shift());
+ this.nextQuestion();
+ },
+ reset() {
+ this.current = Object.fromEntries(
+ this.modes.map((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) {
+ this.nextQuestion();
+ }
+ },
+ nextQuestion() {
+ this.showAnswer = false;
+
+ this.modes.forEach((m) => {
+ if (this.current[m].length === 0 && this.failed[m].length > 0) {
+ this.current[m] = utils.shuffle(utils.cloneObject(this.failed[m]));
+ this.failed[m] = [];
+ }
+ });
+
+ let tries = 0;
+ let newMode;
+ do {
+ tries++;
+ newMode = utils.randitem(this.modes);
+ } while (this.current[newMode].length === 0 && tries < 100);
+
+ 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) {
+ if (!value && this.modes.length > 1) {
+ this.modes.splice(this.modes.indexOf(n), 1);
+ } else if (value && !utils.contains(this.modes, n)) {
+ this.modes.push(n);
+ }
+ this.reset();
+ },
+ dataComplete(results) {
+ if (results.errors.length) {
+ this.error = "CSV file contains errors";
+ } else {
+ const url = new URL(window.location);
+ this.columns = results.data.shift();
+ this.modes = this.columns.map((_, i) => i);
+ if (url.searchParams.get("modes")) {
+ try {
+ this.modes = JSON.parse(url.searchParams.get("modes")).filter(
+ (m) => m < this.columns.length
+ );
+ } catch {}
+ }
+ this.available = results.data;
+ this.available.forEach((data, i) => {
+ data.push(i);
+ });
+ this.reset();
+ }
+ },
+ dataError() {
+ this.error = "Could not read file";
+ },
+ },
+ watch: {
+ async url(newValue) {
+ this.available = [];
+ this.error = "";
+ if (newValue) {
+ Papa.parse(newValue, {
+ download: true,
+ complete: this.dataComplete,
+ error: this.dataError,
+ });
+ }
+ },
+ multiple() {
+ this.reset();
+ },
+ },
+ beforeMount() {
+ const url = new URL(window.location);
+ this.url = url.searchParams.get("url") ?? "";
+ 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;
+ },
+ updated() {
+ const url = new URL(window.location);
+ if (
+ url.searchParams.get("url") !== this.url ||
+ url.searchParams.get("title") !== this.title ||
+ url.searchParams.get("modes") !== JSON.stringify(this.modes) ||
+ url.searchParams.get("multiple") !== this.multiple
+ ) {
+ url.searchParams.set("url", this.url);
+ url.searchParams.set("title", this.title);
+ url.searchParams.set("modes", JSON.stringify(this.modes));
+ url.searchParams.set("multiple", this.multiple);
+ window.history.pushState({}, "", url);
+ }
+ },
+ mounted: function () {
+ setTimeout(this.showApp);
+ },
};
window.onload = () => {
- app = Vue.createApp(app);
- app.mount('#app');
+ app = Vue.createApp(app);
+ app.mount("#app");
};
diff --git a/style.css b/style.css
index f0911d8..d0efd38 100644
--- a/style.css
+++ b/style.css
@@ -65,7 +65,7 @@ h1 {
.button {
font-size: 1.5em;
width: 6em;
- height: 2.3em;
+ /* height: 2.3em; */
text-align: center;
padding: 0;
margin: 0.2em;
@@ -79,47 +79,59 @@ h1 {
user-select: none;
}
-.button.long {
- width: 12em;
+.button > div {
+ line-height: 1.5em;
}
-.button.right {
+.button.long {
+ width: 100%;
+}
+
+.button.right,
+.button.right * {
background-color: #8bc34a;
color: #eeeeee;
}
-.button.wrong {
+.button.wrong,
+.button.wrong * {
background-color: #e53935;
color: #eeeeee;
}
-.button:active {
+.button:active,
+.button:active * {
background-color: #757575;
color: #eeeeee;
}
-.button.right:active {
+.button.right:active,
+.button.right:active * {
background-color: #558b2f;
color: #eeeeee;
}
-.button.wrong:active {
+.button.wrong:active,
+.button.wrong:active * {
background-color: #c62828;
color: #eeeeee;
}
@media (hover: hover) {
- .button:hover {
+ .button:hover,
+ .button:hover * {
background-color: #e0e0e0;
color: #424242;
}
- .button.right:hover {
+ .button.right:hover,
+ .button.right:hover * {
background-color: #7cb342;
color: #eeeeee;
}
- .button.wrong:hover {
+ .button.wrong:hover,
+ .button.wrong:hover * {
background-color: #d32f2f;
color: #eeeeee;
}
@@ -134,7 +146,8 @@ table.config td {
max-width: 30%;
}
-table.config td button, table.config td progress {
+table.config td button,
+table.config td progress {
width: 100%;
}
@@ -142,7 +155,7 @@ table.config td input {
width: 30em;
}
-table.config td input[type=checkbox] {
+table.config td input[type="checkbox"] {
width: inherit !important;
}