refactor: optimized python code and offload things to vue code
This commit is contained in:
+110
-28
@@ -7,22 +7,67 @@
|
|||||||
var ANSWER_LABELS = ["A", "B", "C", "D"];
|
var ANSWER_LABELS = ["A", "B", "C", "D"];
|
||||||
|
|
||||||
var PART_DEFINITIONS = {
|
var PART_DEFINITIONS = {
|
||||||
B: { label: "Betriebliche Kenntnisse", formLabel: "Betriebliche Kenntnisse", minutes: 45 },
|
B: { label: "Betriebliche Kenntnisse", minutes: 45 },
|
||||||
V: { label: "Kenntnisse von Vorschriften", formLabel: "Kenntnisse von Vorschriften", minutes: 45 },
|
V: { label: "Kenntnisse von Vorschriften", minutes: 45 },
|
||||||
N: { label: "Technische Kenntnisse Klasse N", formLabel: "Technische Kenntnisse Klasse N", minutes: 45 },
|
N: { label: "Technische Kenntnisse Klasse N", minutes: 45 },
|
||||||
E: { label: "Technische Kenntnisse Klasse E", formLabel: "Technische Kenntnisse Klasse E", minutes: 45 },
|
E: { label: "Technische Kenntnisse Klasse E", minutes: 45 },
|
||||||
A: { label: "Technische Kenntnisse Klasse A", formLabel: "Technische Kenntnisse Klasse A", minutes: 60 }
|
A: { label: "Technische Kenntnisse Klasse A", minutes: 60 }
|
||||||
};
|
};
|
||||||
|
|
||||||
var EXAM_CHOICES = [
|
var EXAM_CHOICES = [
|
||||||
{ id: "N", group: "primary", shortLabel: "N", label: "Prüfung Klasse N", parts: ["B", "V", "N"], color: "#47ABE8" },
|
{ id: "N", group: "primary", label: "Klasse N", detail: "B + V + N", parts: ["B", "V", "N"], color: "#47ABE8" },
|
||||||
{ id: "E", group: "primary", shortLabel: "N + E", label: "Prüfung Klasse E", parts: ["B", "V", "N", "E"], color: "#FE756C" },
|
{ id: "E", group: "primary", label: "Klasse E", detail: "B + V + N + E", parts: ["B", "V", "N", "E"], color: "#FE756C" },
|
||||||
{ id: "A", group: "primary", shortLabel: "N + E + A", label: "Prüfung Klasse A", parts: ["B", "V", "N", "E", "A"], color: "#3BB583" },
|
{ id: "A", group: "primary", label: "Klasse A", detail: "B + V + N + E + A", parts: ["B", "V", "N", "E", "A"], color: "#3BB583" },
|
||||||
{ id: "N-E", group: "upgrade", shortLabel: "N → E", label: "Aufstockung von N auf E", parts: ["E"], color: "#FE756C" },
|
{ id: "N-E", group: "upgrade", label: "N → E", detail: "Technik E", parts: ["E"], color: "#FE756C" },
|
||||||
{ id: "E-A", group: "upgrade", shortLabel: "E → A", label: "Aufstockung von E auf A", parts: ["A"], color: "#3BB583" },
|
{ id: "N-A", group: "upgrade", label: "N → A", detail: "Technik E + A", parts: ["E", "A"], color: "#3BB583" },
|
||||||
{ id: "N-A", group: "upgrade", shortLabel: "N → A", label: "Aufstockung von N auf A", parts: ["E", "A"], color: "#3BB583" }
|
{ id: "E-A", group: "upgrade", label: "E → A", detail: "Technik A", parts: ["A"], color: "#3BB583" }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function normalizeCatalog(rawCatalog, metadata, questionIndex) {
|
||||||
|
var questions = {};
|
||||||
|
var partForSection = { 1: "B", 2: "V" };
|
||||||
|
var partForClass = { "1": "N", "2": "E", "3": "A" };
|
||||||
|
|
||||||
|
function pictureId(value) {
|
||||||
|
return value ? String(value) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function walk(section, sectionIndex, category) {
|
||||||
|
(section.questions || []).forEach(function (question) {
|
||||||
|
var part = sectionIndex === 0
|
||||||
|
? partForClass[String(question.class)]
|
||||||
|
: partForSection[sectionIndex];
|
||||||
|
if (!part) return;
|
||||||
|
|
||||||
|
var questionMetadata = metadata[question.number] || {};
|
||||||
|
questions[question.number] = {
|
||||||
|
id: question.number,
|
||||||
|
part: part,
|
||||||
|
category: category.slice(),
|
||||||
|
question: question.question,
|
||||||
|
layout: questionMetadata.layout || "default",
|
||||||
|
picture: pictureId(questionMetadata.picture_question),
|
||||||
|
answers: ["a", "b", "c", "d"].map(function (letter) {
|
||||||
|
return {
|
||||||
|
text: question["answer_" + letter] || "",
|
||||||
|
picture: pictureId(questionMetadata["picture_" + letter])
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
hasSolution: Boolean(questionIndex[question.number] && questionIndex[question.number].has_solution)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
(section.sections || []).forEach(function (child, childIndex) {
|
||||||
|
walk(child, sectionIndex, category.concat(childIndex));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(rawCatalog.sections || []).forEach(function (section, sectionIndex) {
|
||||||
|
walk(section, sectionIndex, []);
|
||||||
|
});
|
||||||
|
return questions;
|
||||||
|
}
|
||||||
|
|
||||||
function shuffled(values) {
|
function shuffled(values) {
|
||||||
var result = values.slice();
|
var result = values.slice();
|
||||||
for (var i = result.length - 1; i > 0; i -= 1) {
|
for (var i = result.length - 1; i > 0; i -= 1) {
|
||||||
@@ -120,6 +165,8 @@
|
|||||||
var loading = ref(true);
|
var loading = ref(true);
|
||||||
var loadError = ref("");
|
var loadError = ref("");
|
||||||
var catalog = ref({});
|
var catalog = ref({});
|
||||||
|
var altTexts = Vue.reactive({});
|
||||||
|
var requestedAltTexts = new Set();
|
||||||
var session = ref(null);
|
var session = ref(null);
|
||||||
var view = ref("exam");
|
var view = ref("exam");
|
||||||
var reviewPartIndex = ref(null);
|
var reviewPartIndex = ref(null);
|
||||||
@@ -199,14 +246,38 @@
|
|||||||
return catalog.value[questionState.id];
|
return catalog.value[questionState.id];
|
||||||
}
|
}
|
||||||
|
|
||||||
function partSummary(parts) {
|
|
||||||
return parts.join(" + ");
|
|
||||||
}
|
|
||||||
|
|
||||||
function padNumber(number) {
|
function padNumber(number) {
|
||||||
return String(number).padStart(2, "0");
|
return String(number).padStart(2, "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pictureAlt(picture, fallback) {
|
||||||
|
return altTexts[picture] || fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadAltText(picture) {
|
||||||
|
if (!picture || requestedAltTexts.has(picture)) return;
|
||||||
|
requestedAltTexts.add(picture);
|
||||||
|
fetch("pictures/" + encodeURIComponent(picture) + ".txt")
|
||||||
|
.then(function (response) {
|
||||||
|
if (!response.ok) return "";
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then(function (description) {
|
||||||
|
if (description && description.trim()) altTexts[picture] = description.trim();
|
||||||
|
})
|
||||||
|
.catch(function () { /* the generic alternative text remains available */ });
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPartAltTexts(part) {
|
||||||
|
if (!part) return;
|
||||||
|
part.questions.forEach(function (questionState) {
|
||||||
|
var question = questionData(questionState);
|
||||||
|
if (!question) return;
|
||||||
|
loadAltText(question.picture);
|
||||||
|
question.answers.forEach(function (answer) { loadAltText(answer.picture); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function createPart(code) {
|
function createPart(code) {
|
||||||
var picked = balancedSample(Object.values(catalog.value).filter(function (question) {
|
var picked = balancedSample(Object.values(catalog.value).filter(function (question) {
|
||||||
return question.part === code;
|
return question.part === code;
|
||||||
@@ -351,8 +422,7 @@
|
|||||||
try { window.localStorage.removeItem(STORAGE_KEY); } catch (error) { /* storage can be unavailable */ }
|
try { window.localStorage.removeItem(STORAGE_KEY); } catch (error) { /* storage can be unavailable */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
function abortExam() {
|
function resetExam() {
|
||||||
if (!window.confirm("Möchtest du diese Simulation abbrechen? Der gespeicherte Prüfungsstand wird gelöscht.")) return;
|
|
||||||
clearStoredSession();
|
clearStoredSession();
|
||||||
session.value = null;
|
session.value = null;
|
||||||
view.value = "exam";
|
view.value = "exam";
|
||||||
@@ -360,12 +430,13 @@
|
|||||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function abortExam() {
|
||||||
|
if (!window.confirm("Möchtest du diese Simulation abbrechen? Der gespeicherte Prüfungsstand wird gelöscht.")) return;
|
||||||
|
resetExam();
|
||||||
|
}
|
||||||
|
|
||||||
function newExam() {
|
function newExam() {
|
||||||
clearStoredSession();
|
resetExam();
|
||||||
session.value = null;
|
|
||||||
view.value = "exam";
|
|
||||||
reviewPartIndex.value = null;
|
|
||||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToQuestion(index) {
|
function scrollToQuestion(index) {
|
||||||
@@ -445,18 +516,29 @@
|
|||||||
} catch (error) { /* simulator remains usable without persistence */ }
|
} catch (error) { /* simulator remains usable without persistence */ }
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
||||||
|
watch(currentPart, function (part) {
|
||||||
|
loadPartAltTexts(part);
|
||||||
|
});
|
||||||
|
|
||||||
watch(remainingSeconds, function (seconds) {
|
watch(remainingSeconds, function (seconds) {
|
||||||
if (session.value && currentPart.value && currentPart.value.status === "running" && seconds <= 0) submitPart(true);
|
if (session.value && currentPart.value && currentPart.value.status === "running" && seconds <= 0) submitPart(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
function loadCatalog() {
|
function loadCatalog() {
|
||||||
fetch("assets/exam-questions.json")
|
Promise.all([
|
||||||
.then(function (response) {
|
fetch("assets/fragenkatalog.json"),
|
||||||
if (!response.ok) throw new Error("Fragenkatalog nicht gefunden (HTTP " + response.status + ").");
|
fetch("assets/metadata.json"),
|
||||||
return response.json();
|
fetch("assets/question_index.json")
|
||||||
|
])
|
||||||
|
.then(function (responses) {
|
||||||
|
var names = ["Fragenkatalog", "Metadaten", "Fragenindex"];
|
||||||
|
responses.forEach(function (response, index) {
|
||||||
|
if (!response.ok) throw new Error(names[index] + " nicht gefunden (HTTP " + response.status + ").");
|
||||||
|
});
|
||||||
|
return Promise.all(responses.map(function (response) { return response.json(); }));
|
||||||
})
|
})
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
catalog.value = data.questions || {};
|
catalog.value = normalizeCatalog(data[0], data[1] || {}, data[2] || {});
|
||||||
if (Object.keys(catalog.value).length === 0) throw new Error("Der Fragenkatalog ist leer.");
|
if (Object.keys(catalog.value).length === 0) throw new Error("Der Fragenkatalog ist leer.");
|
||||||
restoreSession();
|
restoreSession();
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@@ -520,8 +602,8 @@
|
|||||||
overallIcon: overallIcon,
|
overallIcon: overallIcon,
|
||||||
overallTitle: overallTitle,
|
overallTitle: overallTitle,
|
||||||
overallText: overallText,
|
overallText: overallText,
|
||||||
partSummary: partSummary,
|
|
||||||
padNumber: padNumber,
|
padNumber: padNumber,
|
||||||
|
pictureAlt: pictureAlt,
|
||||||
questionData: questionData,
|
questionData: questionData,
|
||||||
startExam: startExam,
|
startExam: startExam,
|
||||||
selectAnswer: selectAnswer,
|
selectAnswer: selectAnswer,
|
||||||
|
|||||||
+34
-95
@@ -24,7 +24,11 @@ class Build:
|
|||||||
|
|
||||||
self.env = Environment(loader=FileSystemLoader(self.config.p_templates))
|
self.env = Environment(loader=FileSystemLoader(self.config.p_templates))
|
||||||
self.env.filters["shuffle_answers"] = self.__filter_shuffle_answers
|
self.env.filters["shuffle_answers"] = self.__filter_shuffle_answers
|
||||||
self.questions = self.__parse_katalog()
|
with self.config.p_data_fragenkatalog.open(encoding="utf-8") as file:
|
||||||
|
self.fragenkatalog = json.load(file)
|
||||||
|
with self.config.p_data_metadata.open(encoding="utf-8") as file:
|
||||||
|
self.question_metadata = json.load(file)
|
||||||
|
self.questions = self.__parse_katalog(self.fragenkatalog)
|
||||||
|
|
||||||
self.question_index = {}
|
self.question_index = {}
|
||||||
self.question_token_pattern = re.compile(r"^\s*\[question:([\w\d]+)\]", re.MULTILINE)
|
self.question_token_pattern = re.compile(r"^\s*\[question:([\w\d]+)\]", re.MULTILINE)
|
||||||
@@ -32,22 +36,18 @@ class Build:
|
|||||||
self.index_entries = {}
|
self.index_entries = {}
|
||||||
self.index_token_pattern = Index.pattern
|
self.index_token_pattern = Index.pattern
|
||||||
|
|
||||||
def __parse_katalog(self):
|
@staticmethod
|
||||||
with self.config.p_data_fragenkatalog.open(encoding="utf-8") as file:
|
def __parse_katalog(fragenkatalog):
|
||||||
fragenkatalog = json.load(file)
|
|
||||||
|
|
||||||
questions = {}
|
questions = {}
|
||||||
|
|
||||||
for exampart in fragenkatalog["sections"]:
|
def collect(section):
|
||||||
for chapter in exampart["sections"]:
|
for question in section.get("questions", []):
|
||||||
if "questions" in chapter:
|
|
||||||
for question in chapter["questions"]:
|
|
||||||
questions[question["number"]] = question
|
|
||||||
if "sections" in chapter:
|
|
||||||
for section in chapter["sections"]:
|
|
||||||
for question in section["questions"]:
|
|
||||||
questions[question["number"]] = question
|
questions[question["number"]] = question
|
||||||
|
for child in section.get("sections", []):
|
||||||
|
collect(child)
|
||||||
|
|
||||||
|
for exam_part in fragenkatalog["sections"]:
|
||||||
|
collect(exam_part)
|
||||||
return questions
|
return questions
|
||||||
|
|
||||||
def __build_question(self, number, template_file="html/question.html"):
|
def __build_question(self, number, template_file="html/question.html"):
|
||||||
@@ -55,17 +55,8 @@ class Build:
|
|||||||
|
|
||||||
question_template = self.env.get_template(template_file)
|
question_template = self.env.get_template(template_file)
|
||||||
|
|
||||||
with (self.config.p_data_metadata).open(encoding="utf-8") as file:
|
question = self.questions.get(number)
|
||||||
metadata_json = json.load(file)
|
metadata = self.question_metadata.get(number)
|
||||||
|
|
||||||
question = None
|
|
||||||
metadata = None
|
|
||||||
|
|
||||||
if number in self.questions:
|
|
||||||
question = self.questions[number]
|
|
||||||
|
|
||||||
if number in metadata_json:
|
|
||||||
metadata = metadata_json[number]
|
|
||||||
|
|
||||||
if question is None or metadata is None:
|
if question is None or metadata is None:
|
||||||
tqdm.write(
|
tqdm.write(
|
||||||
@@ -148,73 +139,22 @@ class Build:
|
|||||||
page_scripts=page_scripts or [],
|
page_scripts=page_scripts or [],
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __walk_exam_questions(section, category=()):
|
|
||||||
for question in section.get("questions", []):
|
|
||||||
yield category, question
|
|
||||||
for index, child in enumerate(section.get("sections", [])):
|
|
||||||
yield from Build.__walk_exam_questions(child, (*category, index))
|
|
||||||
|
|
||||||
def __build_exam_catalog(self):
|
|
||||||
"""Create the compact client-side catalog used by the exam simulator."""
|
|
||||||
with (
|
|
||||||
self.config.p_data_fragenkatalog.open(encoding="utf-8") as catalog_file,
|
|
||||||
self.config.p_data_metadata.open(encoding="utf-8") as metadata_file,
|
|
||||||
):
|
|
||||||
catalog = json.load(catalog_file)
|
|
||||||
metadata = json.load(metadata_file)
|
|
||||||
|
|
||||||
part_for_section = {1: "B", 2: "V"}
|
|
||||||
part_for_class = {"1": "N", "2": "E", "3": "A"}
|
|
||||||
questions = {}
|
|
||||||
|
|
||||||
def picture_data(picture_id):
|
|
||||||
if not picture_id:
|
|
||||||
return {"picture": "", "pictureAlt": ""}
|
|
||||||
return {
|
|
||||||
"picture": picture_id,
|
|
||||||
"pictureAlt": self.__picture_handler(picture_id) or "Bildbeschreibung noch nicht verfügbar",
|
|
||||||
}
|
|
||||||
|
|
||||||
for section_index, section in enumerate(catalog["sections"]):
|
|
||||||
for category, question in self.__walk_exam_questions(section):
|
|
||||||
if section_index == 0:
|
|
||||||
part = part_for_class.get(str(question.get("class")))
|
|
||||||
else:
|
|
||||||
part = part_for_section.get(section_index)
|
|
||||||
if part is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
question_metadata = metadata.get(question["number"], {})
|
|
||||||
question_picture = picture_data(question_metadata.get("picture_question", ""))
|
|
||||||
answers = []
|
|
||||||
for answer_letter in "abcd":
|
|
||||||
answer_picture = picture_data(question_metadata.get(f"picture_{answer_letter}", ""))
|
|
||||||
answers.append(
|
|
||||||
{
|
|
||||||
"text": question.get(f"answer_{answer_letter}", ""),
|
|
||||||
**answer_picture,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
questions[question["number"]] = {
|
|
||||||
"id": question["number"],
|
|
||||||
"part": part,
|
|
||||||
"category": list(category),
|
|
||||||
"question": question["question"],
|
|
||||||
"layout": question_metadata.get("layout", "") or "default",
|
|
||||||
"answers": answers,
|
|
||||||
"hasSolution": self.__has_solution(question["number"]),
|
|
||||||
**question_picture,
|
|
||||||
}
|
|
||||||
|
|
||||||
self.config.p_build_assets.mkdir(parents=True, exist_ok=True)
|
|
||||||
with (self.config.p_build_assets / "exam-questions.json").open("w", encoding="utf-8") as file:
|
|
||||||
json.dump({"version": 1, "questions": questions}, file, ensure_ascii=False, separators=(",", ":"))
|
|
||||||
file.write("\n")
|
|
||||||
|
|
||||||
def __build_exam_simulator(self):
|
def __build_exam_simulator(self):
|
||||||
self.__build_exam_catalog()
|
self.config.p_build_assets.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copyfile(self.config.p_data_fragenkatalog, self.config.p_build_assets / "fragenkatalog.json")
|
||||||
|
shutil.copyfile(self.config.p_data_metadata, self.config.p_build_assets / "metadata.json")
|
||||||
|
(self.config.p_build_assets / "exam-questions.json").unlink(missing_ok=True)
|
||||||
|
|
||||||
|
picture_fields = ("picture_question", "picture_a", "picture_b", "picture_c", "picture_d")
|
||||||
|
picture_ids = {
|
||||||
|
metadata[field]
|
||||||
|
for metadata in self.question_metadata.values()
|
||||||
|
for field in picture_fields
|
||||||
|
if metadata.get(field)
|
||||||
|
}
|
||||||
|
for picture_id in picture_ids:
|
||||||
|
self.__picture_handler(picture_id)
|
||||||
|
|
||||||
exam_template = self.env.get_template("html/exam-simulator.html")
|
exam_template = self.env.get_template("html/exam-simulator.html")
|
||||||
content = exam_template.render()
|
content = exam_template.render()
|
||||||
page = self.__build_page(
|
page = self.__build_page(
|
||||||
@@ -222,9 +162,6 @@ class Build:
|
|||||||
page_stylesheets=["assets/exam-simulator.css"],
|
page_stylesheets=["assets/exam-simulator.css"],
|
||||||
page_scripts=["assets/vue.global.prod.js", "assets/exam-simulator.js"],
|
page_scripts=["assets/vue.global.prod.js", "assets/exam-simulator.js"],
|
||||||
)
|
)
|
||||||
legacy_path = self.config.p_build / "probeprüfung.html"
|
|
||||||
if legacy_path.exists():
|
|
||||||
legacy_path.unlink()
|
|
||||||
with (self.config.p_build / "simulation.html").open("w", encoding="utf-8") as file:
|
with (self.config.p_build / "simulation.html").open("w", encoding="utf-8") as file:
|
||||||
file.write(page)
|
file.write(page)
|
||||||
|
|
||||||
@@ -232,8 +169,10 @@ class Build:
|
|||||||
file = f"{id}.svg"
|
file = f"{id}.svg"
|
||||||
try:
|
try:
|
||||||
shutil.copyfile(self.config.p_data_pictures / file, self.config.p_build_pictures / file)
|
shutil.copyfile(self.config.p_data_pictures / file, self.config.p_build_pictures / file)
|
||||||
if (self.config.p_data_pictures / f"{id}.txt").exists():
|
description_source = self.config.p_data_pictures / f"{id}.txt"
|
||||||
return (self.config.p_data_pictures / f"{id}.txt").read_text(encoding="utf-8")
|
if description_source.exists():
|
||||||
|
shutil.copyfile(description_source, self.config.p_build_pictures / description_source.name)
|
||||||
|
return description_source.read_text(encoding="utf-8")
|
||||||
else:
|
else:
|
||||||
return "Bildbeschreibung noch nicht verfügbar"
|
return "Bildbeschreibung noch nicht verfügbar"
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
|||||||
@@ -11,14 +11,15 @@
|
|||||||
|
|
||||||
<section v-else-if="!session" class="mx-auto mb-5 exam-start" aria-labelledby="exam-heading">
|
<section v-else-if="!session" class="mx-auto mb-5 exam-start" aria-labelledby="exam-heading">
|
||||||
<h1 id="exam-heading">Prüfungssimulation</h1>
|
<h1 id="exam-heading">Prüfungssimulation</h1>
|
||||||
<p class="lead">Wähle die Prüfung, die du unter möglichst realistischen Bedingungen üben möchtest.</p>
|
<p class="lead">Welche Prüfung möchtest du simulieren?</p>
|
||||||
|
|
||||||
<h2 class="h4 mt-5">Erstprüfung</h2>
|
<h2 class="h4 mt-5">Erstprüfung</h2>
|
||||||
<div class="row row-cols-1 row-cols-md-3 g-2">
|
<div class="row row-cols-1 row-cols-md-3 g-2">
|
||||||
<div v-for="choice in primaryChoices" :key="choice.id" class="col">
|
<div v-for="choice in primaryChoices" :key="choice.id" class="col">
|
||||||
<button type="button" class="btn w-100 h-100 py-3 text-white"
|
<button type="button" class="btn w-100 h-100 py-3 text-white"
|
||||||
:style="{ backgroundColor: choice.color, borderColor: choice.color }" @click="startExam(choice)">
|
:style="{ backgroundColor: choice.color, borderColor: choice.color }" @click="startExam(choice)">
|
||||||
<span v-text="choice.label + ' (' + partSummary(choice.parts) + ')' "></span>
|
<span class="d-block" v-text="choice.label"></span>
|
||||||
|
<small class="d-block" v-text="choice.detail"></small>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -28,7 +29,8 @@
|
|||||||
<div v-for="choice in upgradeChoices" :key="choice.id" class="col">
|
<div v-for="choice in upgradeChoices" :key="choice.id" class="col">
|
||||||
<button type="button" class="btn w-100 h-100 py-3 text-white"
|
<button type="button" class="btn w-100 h-100 py-3 text-white"
|
||||||
:style="{ backgroundColor: choice.color, borderColor: choice.color }" @click="startExam(choice)">
|
:style="{ backgroundColor: choice.color, borderColor: choice.color }" @click="startExam(choice)">
|
||||||
<span v-text="choice.label + ' (' + partSummary(choice.parts) + ')' "></span>
|
<span class="d-block" v-text="choice.label"></span>
|
||||||
|
<small class="d-block" v-text="choice.detail"></small>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,7 +117,8 @@
|
|||||||
<span v-text="padNumber(questionIndex + 1) + ': '"></span><span v-html="questionData(questionState).question"></span>
|
<span v-text="padNumber(questionIndex + 1) + ': '"></span><span v-html="questionData(questionState).question"></span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="questionData(questionState).picture" class="question-picture">
|
<div v-if="questionData(questionState).picture" class="question-picture">
|
||||||
<img :src="'pictures/' + questionData(questionState).picture + '.svg'" :alt="questionData(questionState).pictureAlt">
|
<img :src="'pictures/' + questionData(questionState).picture + '.svg'"
|
||||||
|
:alt="pictureAlt(questionData(questionState).picture, 'Abbildung zu Frage ' + questionState.id)">
|
||||||
</div>
|
</div>
|
||||||
<div class="answers">
|
<div class="answers">
|
||||||
<button v-for="(sourceIndex, answerIndex) in questionState.order" :key="answerIndex" type="button"
|
<button v-for="(sourceIndex, answerIndex) in questionState.order" :key="answerIndex" type="button"
|
||||||
@@ -125,7 +128,8 @@
|
|||||||
v-html="questionData(questionState).answers[sourceIndex].text"></span>
|
v-html="questionData(questionState).answers[sourceIndex].text"></span>
|
||||||
<span v-if="questionData(questionState).answers[sourceIndex].picture" class="question-picture">
|
<span v-if="questionData(questionState).answers[sourceIndex].picture" class="question-picture">
|
||||||
<img :src="'pictures/' + questionData(questionState).answers[sourceIndex].picture + '.svg'"
|
<img :src="'pictures/' + questionData(questionState).answers[sourceIndex].picture + '.svg'"
|
||||||
:alt="questionData(questionState).answers[sourceIndex].pictureAlt">
|
:alt="pictureAlt(questionData(questionState).answers[sourceIndex].picture,
|
||||||
|
'Abbildung zu Antwort ' + answerLabels[answerIndex] + ' bei Frage ' + questionState.id)">
|
||||||
</span>
|
</span>
|
||||||
<span v-if="currentPart.status === 'evaluated' && sourceIndex === 0" class="result material-icons">check</span>
|
<span v-if="currentPart.status === 'evaluated' && sourceIndex === 0" class="result material-icons">check</span>
|
||||||
<span v-else-if="currentPart.status === 'evaluated' && questionState.selected === answerIndex" class="result material-icons">clear</span>
|
<span v-else-if="currentPart.status === 'evaluated' && questionState.selected === answerIndex" class="result material-icons">clear</span>
|
||||||
@@ -162,7 +166,7 @@
|
|||||||
<div class="border border-dark bg-white p-2">
|
<div class="border border-dark bg-white p-2">
|
||||||
<h2 class="h5 text-center">Antwortbogen</h2>
|
<h2 class="h5 text-center">Antwortbogen</h2>
|
||||||
<div class="border-top border-bottom border-dark py-1 mb-2">
|
<div class="border-top border-bottom border-dark py-1 mb-2">
|
||||||
<strong class="d-block" v-text="currentPartDefinition.formLabel"></strong>
|
<strong class="d-block" v-text="currentPartDefinition.label"></strong>
|
||||||
<small><span v-text="currentPartDefinition.minutes"></span> Minuten · 25 Fragen</small>
|
<small><span v-text="currentPartDefinition.minutes"></span> Minuten · 25 Fragen</small>
|
||||||
</div>
|
</div>
|
||||||
<table class="answer-table">
|
<table class="answer-table">
|
||||||
|
|||||||
Reference in New Issue
Block a user