// retoor <retoor@molodetz.nl>
import { Component } from "./Component.js";
import { Http } from "../Http.js";
import { Format } from "../Format.js";
const TICK_MS = 1000;
const CHECKING_LABEL = "Checking…";
export class AppQuizPlayer extends Component {
connectedCallback() {
this._attemptUrl = this.attr("data-attempt-url");
this._remaining = this.intAttr("data-remaining", 0);
this._hasLimit = this.attr("data-has-limit") === "1";
this._status = this.attr("data-status", "in_progress");
this._progress = this.querySelector("[data-quiz-progress]");
this._score = this.querySelector("[data-quiz-score]");
this._timer = this.querySelector("[data-quiz-timer]");
this._bindForms();
if (this._hasLimit && this._status === "in_progress") this._startTimer();
}
disconnectedCallback() {
if (this._interval) clearInterval(this._interval);
}
_bindForms() {
this.querySelectorAll("[data-quiz-answer-form]").forEach((form) => {
form.addEventListener("submit", (event) => {
event.preventDefault();
this._submit(form);
});
});
}
_startTimer() {
this._interval = setInterval(() => {
this._remaining = Math.max(0, this._remaining - 1);
if (this._timer) this._timer.textContent = Format.duration(this._remaining);
if (this._remaining === 0) {
clearInterval(this._interval);
window.location.reload();
}
}, TICK_MS);
}
async _submit(form) {
const button = form.querySelector("button[type=submit]");
const original = button ? button.textContent : "";
if (button) {
button.disabled = true;
button.textContent = CHECKING_LABEL;
}
let payload;
try {
payload = await Http.sendForm(this._attemptUrl + "/answer", this._params(form));
} catch (error) {
if (button) {
button.disabled = false;
button.textContent = original;
}
return;
}
this._renderAnswer(form, payload.answer);
this._renderHud(payload.attempt);
this._advance(form);
}
_params(form) {
const params = [];
new FormData(form).forEach((value, key) => params.push([key, value]));
return params;
}
_renderAnswer(form, answer) {
const question = form.closest("[data-quiz-question]");
if (!question || !answer) return;
question.classList.add("quiz-question-answered");
form.querySelectorAll("input, textarea, select, button").forEach((field) => {
field.disabled = true;
});
const actions = form.querySelector(".quiz-answer-actions");
if (actions) actions.remove();
const host = question.querySelector("[data-quiz-result]");
if (!host) return;
host.hidden = false;
host.innerHTML = "";
host.appendChild(this._gradeNode(question, answer));
if (answer.feedback) host.appendChild(this._textNode("quiz-grade-feedback", answer.feedback));
if (answer.graded_by === "fallback") {
host.appendChild(
this._textNode(
"quiz-grade-fallback",
"Automatic review was unavailable, so this answer was scored on keyword overlap.",
),
);
}
}
_gradeNode(question, answer) {
const points = question.querySelector(".quiz-question-points");
const max = points ? points.textContent.trim().split(" ")[0] : "";
const grade = document.createElement("div");
grade.className = "quiz-grade " + (answer.is_correct ? "quiz-grade-correct" : "quiz-grade-wrong");
const verdict = document.createElement("span");
verdict.className = "quiz-grade-verdict";
verdict.textContent = answer.is_correct ? "Correct" : "Not correct";
const scored = document.createElement("span");
scored.className = "quiz-grade-points";
scored.textContent = `${Format.number(answer.awarded_points)} / ${max} points`;
grade.appendChild(verdict);
grade.appendChild(scored);
return grade;
}
_textNode(className, text) {
const node = document.createElement("p");
node.className = className;
node.textContent = text;
return node;
}
_renderHud(attempt) {
if (!attempt) return;
if (this._progress) {
this._progress.textContent = `${attempt.answered_count} / ${attempt.question_count} answered`;
}
if (this._score) {
this._score.textContent = `${Format.number(attempt.score_points)} / ${attempt.max_points} points`;
}
this._remaining = attempt.remaining_seconds;
}
_advance(form) {
const question = form.closest("[data-quiz-question]");
if (!question) return;
const next = question.nextElementSibling;
if (next && next.scrollIntoView) next.scrollIntoView({ block: "start", behavior: "smooth" });
}
}
customElements.define("dp-quiz-player", AppQuizPlayer);