Create a first draft of the quiz that lets users order the elements

This commit is contained in:
Lukas Fürderer 2020-08-12 23:00:33 +02:00
commit 1ac81b7541
5 changed files with 12307 additions and 0 deletions

51
index.html Normal file
View File

@ -0,0 +1,51 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Ministranten-Quiz</title>
<link rel="stylesheet" href="quiz.css">
<script src="vue.js"></script>
<script src="quiz.js" async></script>
</head>
<body>
<div id="quiz">
<div class="store">
<div
class="card"
v-for="item_idx in store.items"
v-bind:class="{selected: item_idx == store.selected}"
v-bind:style="{visibility: store_card_visible(item_idx) ? 'visible' : 'hidden'}"
v-on:click="store_select(item_idx)"
>
{{ store_text(item_idx) }}
</div>
</div>
<div class="state">
<div class="helptext" v-if="!all_inserted || order_revealed">{{ helptext }}</div>
<button v-if="all_inserted && !order_revealed" v-on:click="reveal_order">Reihenfolge prüfen</button>
</div>
<div class="solution">
<div class="arrow" v-bind:style="{visibility: arrows_visible ? 'visible' : 'hidden'}">
<svg width="20" height="20" v-on:click="insert_front()">
<path d="M20 0V20L0 10Z" fill="#2b8856" />
</svg>
</div>
<template v-for="elem in solution">
<div class="row">
<div class="card" v-bind:class="{incorrect: incorrects[elem.main_idx]}">
{{ solution_main_text(elem) }}
<svg width=20 height=20 v-if="!order_correct" v-on:click="remove_solution(elem)">
<path d="M2 2L18 18M18 2L2 18" stroke="#f00" stroke-width="2px" />
</svg>
</div>
</div>
<div class="arrow" v-bind:style="{visibility: arrows_visible ? 'visible' : 'hidden'}">
<svg width="20" height="20" v-on:click="insert_after(elem)">
<path d="M20 0V20L0 10Z" fill="#2b8856" />
</svg>
</div>
</template>
</div>
</div>
</body>
</html>

88
quiz.css Normal file
View File

@ -0,0 +1,88 @@
body {
background-color: #2b8856;
}
@font-face {
font-family: 'Roboto Slab';
src: url(https://fonts.jimstatic.com/s/robotoslab/v11/BngbUXZYTXPIvIBgJJSb6s3BzlRRfKOFbvjojISmb2Rj.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
#quiz {
background-color: #fff;
font-family: "Roboto Slab";
font-size: 16px;
margin: 0 auto;
width: 960px;
}
#quiz .store {
border-bottom: 2px solid #000;
padding: 5px;
}
#quiz .card {
background-color: #eee;
display: inline-block;
margin: 5px;
min-height: 50px;
padding: 5px;
vertical-align: top;
width: 166px;
}
#quiz .store .card {
cursor: pointer;
}
#quiz .store .card:hover {
background-color: #c7dad0;
}
#quiz .card.selected {
border: 3px solid #2b8856;
padding: 2px;
}
#quiz .card.incorrect {
background-color: #faa;
}
#quiz .state {
border-bottom: 2px solid #000;
padding: 5px;
}
#quiz .state .helptext {
margin: 5px;
}
#quiz .state button {
background-color: #e8912a;
border-radius: 0;
border-width: 0;
cursor: pointer;
font-family: "Roboto Slab";
font-size: 16px;
margin: 5px;
padding: 5px;
}
#quiz .state button:hover {
background-color: #facc99;
}
#quiz .solution {
padding: 20px 5px;
}
#quiz .row .card {
position: relative;
vertical-align: middle;
}
#quiz .row .card svg {
cursor: pointer;
position: absolute;
right: 5px;
top: 5px;
visibility: hidden;
}
#quiz .row .card:hover svg {
visibility: visible;
}
#quiz .arrow {
height: 0;
width: 0;
}
#quiz .arrow svg {
cursor: pointer;
left: 190px;
position: relative;
top: -10px;
}

197
quiz.js Normal file
View File

@ -0,0 +1,197 @@
let cards_content = [
[
"Einzug",
"Kreuzzeichen und liturgischer Gruß",
"Begrüßung und Einführung in die Feier",
"Schuldbekenntnis und Vergebungsbitte (Bußakt)",
"Kyrie",
"Gloria",
"Tagesgebet",
"Lesung (Altes Testament)",
"Zwischengesang / Antwortpsalm",
"Lesung (Neues Testament)",
"Hallelujaruf",
"Evangelium",
"Homilie / Predigt",
"Credo / Glaubensbekenntnis",
"Fürbitten",
"Gabenbereitung mit Gabengebet",
"Eucharistisches Hochgebet: Präfation",
"Sanctus",
"Einsetzungsbericht",
"Vaterunser",
"Friedensgruß",
"Brechen des Brotes / Agnus Dei Lamm Gottes",
"Kommunion",
"Dank- / Schlussgebet",
"Segen",
"Entlassung",
],
];
function shuffle(a) {
for (let x = a.length-1; x>0; x--) {
let y = Math.floor(Math.random() * (x + 1));
let temp = a[x];
a[x] = a[y];
a[y] = temp;
}
}
function shuffle_cards() {
let a = [];
for (let i=0; i<cards_content[0].length; i++) {
a.push(i);
}
shuffle(a);
return a;
}
function choose_incorrects(idx_vals) {
let options = {
0: [],
};
function find_longest_chain(maxkey) {
let longest = null;
for (let key in options) {
if (
(
maxkey == null
|| key <= maxkey
)
&& (
longest == null
|| options[key].length > options[longest].length
|| (
options[key].length == options[longest].length
&& Number.parseInt(key) < Number.parseInt(longest)
)
)
) {
longest = key;
}
}
return longest;
}
idx_vals.forEach((val) => {
let longest = find_longest_chain(val);
let mychain = options[longest].slice();
mychain.push(val);
options[val + 1] = mychain;
});
let result_chain = find_longest_chain(null);
let a = [];
for (let i=0; i<cards_content[0].length; i++) {
a.push(true);
}
options[result_chain].forEach((elem) => {
a[elem] = false;
});
return a;
}
var app = new Vue({
el: "#quiz",
data: {
cards_content,
store: {
section: 0,
selected: null,
items: shuffle_cards(),
},
solution: [],
order_revealed: false,
},
computed: {
helptext: function() {
if (this.order_revealed) {
if (this.order_correct) {
return "Perfekt! Die Reihenfolge ist korrekt.";
} else {
return "Die Reihenfolge passt leider noch nicht ganz. Falsch eingeordnete Elemente sind rot markiert. Entferne diese mit einem Klick auf das rote X und füge sie neu ein, bis die Reihenfolge stimmt.";
}
} else {
if (this.solution.length == 0) {
return "Klicke oben auf ein Element des Gottesdienstablaufs, um es nach unten zu übernehmen.";
} else if (this.solution.length == 1) {
if (this.store.selected == null) {
return "Wähle nun von oben das nächste Element aus.";
} else {
return "Klicke unten auf einen grünen Pfeil, um es an der richtigen Stelle in die Liste einzuordnen.";
}
} else {
return "Bringe nun auch alle weiteren Elemente in die richtige Reihenfolge.";
}
}
},
all_inserted: function() {
return this.solution.length == this.cards_content[0].length;
},
arrows_visible: function() {
return this.store.selected != null;
},
incorrects: function() {
if (this.order_revealed) {
let idx_vals = this.solution.map(elem => elem.main_idx);
return choose_incorrects(idx_vals);
} else {
return [];
}
},
order_correct: function() {
return this.order_revealed && this.all_inserted && !this.incorrects.includes(true);
},
},
methods: {
store_text: function(idx) {
return this.cards_content[this.store.section][idx];
},
store_card_visible: function(idx) {
for (let i=0; i<this.solution.length; i++) {
if (this.solution[i].main_idx == idx) {
return false;
}
}
return true;
},
store_select: function(i) {
if (this.solution.length == 0) {
this.solution.push({
main_idx: i,
});
} else if (i == this.store.selected) {
this.store.selected = null;
} else {
this.store.selected = i;
}
},
solution_main_text: function(elem) {
return cards_content[0][elem.main_idx];
},
insert_front: function() {
this.solution.splice(0, 0, {
main_idx: this.store.selected,
});
this.store.selected = null;
},
find_solution: function(elem) {
for (let i=0; i<this.solution.length; i++) {
if (this.solution[i] == elem) {
return i;
}
}
},
insert_after: function(elem) {
this.solution.splice(this.find_solution(elem)+1, 0, {
main_idx: this.store.selected,
});
this.store.selected = null;
},
remove_solution: function(elem) {
this.solution.splice(this.find_solution(elem), 1);
},
reveal_order: function() {
this.order_revealed = true;
},
},
});

11965
vue-debug.js Normal file

File diff suppressed because it is too large Load Diff

6
vue.js Normal file

File diff suppressed because one or more lines are too long