class Task extends HTMLElement { static sequence_number = 0; constructor(type) { super(); this.task_type = type; this.sequence_number = Task.sequence_number++; this.concluded = false; this.view_only = false; this.upload_answer_cb = null; this.player_answer = null; this.correct_answer = null; this.shadow = this.attachShadow({mode: "open"}); this.createStyle(); this.createElements(); } createStyle() { this.css = document.createElement("style"); this.css.innerHTML = ` span.question { font-size: 1.3em; font-weight: bold; color: #176767; } section.task { display: block; position: relative; margin: 0 auto; width: 40em; padding: 1em; background-color: #d3e5e5; margin-bottom: 0.5em; border-radius: 0.3em; } section.seq-num { display: block; position: absolute; right: 0; padding: 0.5em; bottom: 0; background-color: #176767; color: whitesmoke; border-bottom-right-radius: 0.3em; border-top-left-radius: 0.3em; width: 2em; z-index: 10; } section.answer-container { /* (empty) */ } section.bad-answer { background-color: #e5d8d3; } code { font-family: 'Monaco', monospace; } section#correct-answer { display: none; width: 100%; margin-top: 0.5em; } section#correct-answer[visible="true"] { display: block; } @media only screen and (max-width: 800px) { section.task { width: calc(100vw - 3em); } section.answer-container { margin-bottom: 1.5em; } } `; this.shadow.append(this.css); } createElements() { let task_box = document.createElement("section"); task_box.classList.add("task"); let question_span = document.createElement("span"); question_span.classList.add("question"); let answer_container = document.createElement("section"); answer_container.classList.add("answer-container"); let seq_num_section = document.createElement("section"); seq_num_section.classList.add("seq-num"); seq_num_section.innerText = `${this.sequence_number + 1}.`; let ca_section = document.createElement("section"); ca_section.id = "correct-answer"; task_box.append(question_span); task_box.append(answer_container); task_box.append(seq_num_section); task_box.append(ca_section); this.shadow.append(task_box); this.task_box = task_box; this.question_span = question_span; this.answer_container = answer_container; this.seq_num_section = seq_num_section; this.ca_section = ca_section; } connectedCallback() { } disconnectedCallback() { } get type() { return this.task_type; } set type(type) { this.task_type = type; } get sequenceNumber() { return this.sequence_number; } setQuestion(question) { this.question_span.innerHTML = preprocess_inserts(question); } get isConcluded() { return this.concluded; } set isConcluded(concluded) { this.concluded = concluded; this.ca_section.setAttribute("visible", this.concluded ? "true" : "false"); } get isViewOnly() { return this.view_only; } set isViewOnly(viewOnly) { this.view_only = viewOnly; } get isCorrect() { return false; } set playerAnswer(player_answer) { this.player_answer = player_answer; } get playerAnswer() { return this.player_answer; } set correctAnswer(correct_answer) { this.correct_answer = correct_answer; } get correctAnswer() { return this.correct_answer; } set uploadAnswerCb(cb) { this.upload_answer_cb = cb; } uploadAnswer() { if (this.upload_answer_cb !== null) { this.upload_answer_cb(this.sequence_number, this.playerAnswer); } } displayCorrectAnswer() { } fromArray(a) { this.setQuestion(a["question"]); this.playerAnswer = a["player_answer"]; this.correctAnswer = a["correct_answer"]; let mark = a["mark"]; if ((mark !== undefined) && (mark !== null) && (mark > -1) && (mark !== a["max_mark"])) { this.task_box.classList.add("bad-answer"); } if (this.isConcluded) { this.displayCorrectAnswer(); } } } class PicturedTask extends Task { constructor(type) { super(type); this.img = null; this.img_type = "none"; } createStyle() { super.createStyle(); this.css.innerHTML += ` .question-image { display: none; position: relative; margin: 1em auto; border-radius: 0.3em; max-width: 100%; } `; } createElements() { super.createElements(); } set imgData(data) { switch (this.img_type) { case "url": { this.img = document.createElement("img"); this.img.classList.add("question-image"); data = data.trim(); this.img.src = data.trim(); } break; case "svg": { this.img = document.createElementNS("http://www.w3.org/2000/svg", "svg"); this.img.classList.add("question-image"); this.img.innerHTML = data; } break; } if (this.img != null) { this.img.style.display = (data !== "") ? "block" : "none"; this.task_box.insertBefore(this.img, this.question_span); } } get imgData() { return this.img.src; } set imgType(t) { this.img_type = t; } get imgType() { return this.img_type; } } class SingleChoiceTask extends PicturedTask { constructor() { super("singlechoice"); this.answers = [] this.correct_answer = -1; this.player_answer = -1; } createStyle() { super.createStyle(); this.css.innerHTML += ` section.answer { margin: 0.3em 0.8em; display: block; } section.answer label { margin-left: 0.5em; padding: 0.3em 0.5em; border-radius: 0.3em; display: inline-block; max-width: 85%; vertical-align: middle; } section.answer label.correct-answer { border: 2px solid #176767 !important; background-color: #176767; color: whitesmoke; /*padding: 0.1em;*/ } section.answer input[type="radio"]:checked+label:not(.correct-answer) { background-color: #176767; color: whitesmoke; } section.bad-answer section.answer input[type="radio"]:checked+label:not(.correct-answer) { background-color: #aa8a7d; } .MathJax { display: block; margin: 0.5em auto; font-size: 120%; } @media only screen and (max-width: 800px) { section.answer label { max-width: calc(100% - 4em); } } `; } createElements() { super.createElements(); } // -------- setAnswers(answers) { this.answers = answers; this.answers.forEach((answer, i) => { let answer_section = document.createElement("section"); answer_section.classList.add("answer"); let answer_radio = document.createElement("input"); answer_radio.type = "radio"; answer_radio.id = `${this.sequenceNumber}_${i}`; answer_radio.name = `task_${this.sequenceNumber}`; answer_radio.disabled = this.isConcluded || this.isViewOnly; let answer_N_snapshot = i; answer_radio.addEventListener("input", () => { this.playerAnswer = answer_N_snapshot; this.uploadAnswer(); }); let answer_text = document.createElement("label"); answer_text.innerHTML = preprocess_inserts(answer); answer_text.htmlFor = answer_radio.id; if (this.isConcluded && (this.correctAnswer === i)) { answer_text.classList.add("correct-answer") if (this.playerAnswer !== this.correctAnswer) { this.task_box.classList.add("bad-answer"); } } if (this.playerAnswer === i) { answer_radio.checked = true; } answer_section.append(answer_radio, answer_text); this.answer_container.append(answer_section); }); MathJax.typeset([this.task_box]); } get isCorrect() { return this.player_answer === this.correct_answer; } fromArray(a) { super.fromArray(a); this.setAnswers(a["answers"]); } } class OpenEndedTask extends PicturedTask { constructor() { super("openended"); } createStyle() { super.createStyle(); this.css.innerHTML += ` input[type="text"] { font-family: 'Monaco', monospaced; border-width: 0 0 2.2pt 0; background-color: transparent; width: calc(100% - 4em); margin: 1em 0; border-bottom-color: #176767; font-size: 110%; } input[type="text"]:hover { border-bottom-color: #408d8d; } ` } createElements() { super.createElements(); let answer_tf = document.createElement("input"); answer_tf.type = "text"; answer_tf.placeholder = "(válasz)"; answer_tf.onblur = () => { this.uploadAnswer(); } answer_tf.oninput = () => { this.player_answer = answer_tf.value; }; this.answer_container.append(answer_tf); this.answer_tf = answer_tf; } displayCorrectAnswer() { this.ca_section.innerHTML = "Lehetséges megoldások:
" + this.correctAnswer.join(", VAGY
"); } fromArray(a) { super.fromArray(a); } set playerAnswer(player_answer) { super.playerAnswer = player_answer; this.answer_tf.value = player_answer; } get playerAnswer() { return this.player_answer; } updateAnswerFieldState() { this.answer_tf.disabled = this.isViewOnly || this.isConcluded; } set isConcluded(concluded) { super.isConcluded = concluded; this.updateAnswerFieldState(); } get isConcluded() { return super.isConcluded; } set isViewOnly(is_view_only) { super.isViewOnly = is_view_only; } get isViewOnly() { return super.isViewOnly; } } class NumberConversionTask extends OpenEndedTask { constructor() { super(); this.type = "numberconversion"; } createStyle() { super.createStyle(); this.css.innerHTML += ` input[type="text"] { min-width: 5em; width: unset; } section#src, section#dst { position: relative; display: inline-block; font-family: 'Monaco', monospace; color: #176767; } section#src { margin-right: 1ch; } sub { position: relative; top: 0.8em; } `; } createElements() { super.createElements(); let src_sec = document.createElement("section"); src_sec.id = "src"; let dst_sec = document.createElement("section"); dst_sec.id = "dst"; this.answer_container.insertBefore(src_sec, this.answer_tf); this.answer_container.append(dst_sec); this.src_sec = src_sec; this.dst_sec = dst_sec; this.answer_tf.addEventListener("input", () => { this.updateAnswerFieldLength(); }) } displayCorrectAnswer() { this.ca_section.innerHTML = `Megoldás: ${this.correctAnswer}(${this.dst_base})`; } fromArray(a) { const regex = /([0-9]+)([suc]):([0-9]+)->([0-9]+)([suc]):([0-9]+)/g; let parts = [...a["instruction"].matchAll(regex)][0]; this.src_base = parts[1]; this.dst_base = parts[4]; this.src_len = parts[6]; let src_exp = `${a["source"]}(${parts[1]}) =`; let dst_exp = `(${parts[4]}) (${parts[6]} digiten)`; super.fromArray(a); this.src_sec.innerHTML = src_exp; this.dst_sec.innerHTML = dst_exp; this.updateAnswerFieldLength(); } updateAnswerFieldLength() { this.answer_tf.style.width = this.answer_tf.value.length + "ch"; } } class Switch extends HTMLElement { constructor() { super(); this.state = "-"; this.highlight = "-"; this.M = 2; this.disabled = false; this.shadow = this.attachShadow({mode: "open"}); this.createStyle(); this.createElements(); } createStyle() { this.css = document.createElement("style"); this.css.innerHTML = ` section.frame { display: inline-block; border: 1pt solid black; } section.button { display: inline-block; padding: 0.2em 0.5em; cursor: pointer; font-family: 'Monaco', monospace; } section.button[disabled="false"]:hover { background-color: #408d8d; color: white; } section.button[highlight="true"] { background-color: #aa8a7d; color: white; } section.button[selected="true"] { background-color: #176767; color: white; } section.button:not(:last-child) { border-right: 1pt dotted black; } `; this.shadow.append(this.css); } createElements() { let frame = document.createElement("section"); frame.classList.add("frame"); let btns = []; for (let i = 0; i < this.M; i++) { let btn = document.createElement("section"); btn.classList.add("button"); btn.innerText = i.toString(); btn.id = "btn_" + i.toString(); btn.setAttribute("disabled", "false"); btns.push(btn); btn.addEventListener("click", (e) => { if (!this.disabled) { this.setState(i); this.dispatchEvent(new Event("change")); } }) frame.append(btn); } document.createElement("section"); this.frame = frame; this.btns = btns; this.shadow.append(frame); this.setDisabled(false); } setState(state) { this.state = state; for (let i = 0; i < this.M; i++) { this.btns[i].setAttribute("selected", (i.toString() === this.state.toString()) ? "true" : "false"); } } setHighlight(hl) { this.highlight = hl; for (let i = 0; i < this.M; i++) { this.btns[i].setAttribute("highlight", (i.toString() === this.highlight.toString()) ? "true" : "false"); } } getState() { return this.state; } setDisabled(disabled) { this.disabled = disabled; this.frame.setAttribute("disabled", disabled ? "true" : "false"); } } class TruthTableTask extends PicturedTask { constructor() { super("truthtable"); this.input_variables = []; this.output_variable = ""; this.output_switches = []; } createStyle() { super.createStyle(); this.css.innerHTML += ` table#tt { border: 1.5pt solid #176767; margin: 0.5em auto; border-spacing: 0; font-family: 'Monaco', monospace; } table#tt tr:not(:last-child) td { border-bottom: 1.2pt solid black; } table#tt th { border-bottom: 1.5pt dotted black } table#tt td, table#tt th { min-width: 3ch; text-align: center; } table#tt td:last-child, table#tt th:last-child { border-left: 1.5pt dashed black; } `; } createElements() { super.createElements(); let tt = document.createElement("table"); tt.id = "tt"; this.answer_container.append(tt); this.tt = tt; } updatePlayerAnswer() { let pa = ""; for (let i = 0; i < this.output_switches.length; i++) { pa += this.output_switches[i].getState(); } super.playerAnswer = pa; } buildTTable() { let N = this.input_variables.length; let M = (1 << N); let inside = "" for (let i = 0; i < N; i++) { inside += "" + this.input_variables[i] + ""; } inside += "" + this.output_variable + ""; inside += ""; for (let i = 0; i < M; i++) { inside += ""; for (let j = 0; j < N; j++) { inside += "" + ((i >> (N - j - 1)) & 1).toString() + ""; } inside += "" inside += ""; } this.tt.innerHTML = inside; for (let i = 0; i < M; i++) { let sw = this.shadow.getElementById("out_" + i); sw.addEventListener("change", () => { this.updatePlayerAnswer(); this.uploadAnswer(); }); sw.setDisabled(this.isConcluded || this.isViewOnly); this.output_switches[i] = sw; } this.playerAnswer = "-".repeat(M); } set playerAnswer(playerAnswer) { if (playerAnswer !== null) { super.playerAnswer = playerAnswer; } for (let i = 0; i < this.output_switches.length; i++) { let sw = this.output_switches[i]; let pac = this.playerAnswer.charAt(i); if (!this.isConcluded) { sw.setState(pac); } else { let cac = this.correctAnswer.charAt(i); if (cac !== pac) { sw.setHighlight(pac); } sw.setState(cac); } } } get playerAnswer() { return super.playerAnswer; } fromArray(a) { super.fromArray(a); this.input_variables = a["input_variables"]; this.output_variable = a["output_variable"]; this.buildTTable(); this.playerAnswer = a["player_answer"]; } } class VerilogTask extends PicturedTask { //static observedAttributes = ["language"] constructor() { super("verilog"); } createStyle() { super.createStyle(); this.css.innerHTML += ` section.editor-sec { margin-top: 1em; min-height: 24em; font-family: 'JetBrains Mono', monospace; } div.ace_content { font-variant-ligatures: none; } section#explain-sec { font-family: 'JetBrains Mono', monospace; margin-top: 0.5em; width: calc(100% - 0.4em); padding: 0.2em; background: #e5e5e57f; } section#correct-answer-title { padding: 1em 0 0.5em 0.2em; font-weight: bold; } `; } createElements() { super.createElements(); let editor_sec = document.createElement("section"); editor_sec.classList.add("editor-sec"); this.answer_container.append(editor_sec); let editor = ace.edit(editor_sec, { theme: "ace/theme/chrome", mode: "ace/mode/verilog" }); editor.renderer.attachToShadowRoot(); editor.addEventListener("blur", () => { this.uploadAnswer(); }); let explain_sec = document.createElement("section"); explain_sec.id = "explain-sec"; this.ca_section.append(explain_sec); let solution_title_sec = document.createElement("section"); solution_title_sec.innerText = "Egy lehetséges megoldás:"; solution_title_sec.id = "correct-answer-title"; this.ca_section.append(solution_title_sec); let solution_sec = document.createElement("section"); solution_sec.id = "solution-sec"; solution_sec.classList.add("editor-sec"); let solution_editor = ace.edit(solution_sec, { theme: "ace/theme/chrome", mode: "ace/mode/verilog", }); solution_editor.setReadOnly(true); solution_editor.renderer.attachToShadowRoot(); this.ca_section.append(solution_sec); this.editor_sec = editor_sec; this.editor = editor; this.explain_sec = explain_sec; this.solution_title_sec = solution_title_sec; this.solution_sec = solution_sec; this.solution_editor = solution_editor; } set playerAnswer(player_answer) { this.editor.setValue(player_answer); this.editor.clearSelection(); } get playerAnswer() { return this.editor.getValue(); } set correctAnswer(player_answer) { this.solution_editor.setValue(player_answer); this.solution_editor.clearSelection(); } get correctAnswer() { return super.correctAnswer; } fromArray(a) { super.fromArray(a); this.explain_sec.innerText = a["explanation"]; } updateEditorFreezeState() { this.editor.setReadOnly(this.isConcluded || this.isViewOnly); } set isConcluded(concluded) { super.isConcluded = concluded; this.updateEditorFreezeState(); if (concluded) { this.solution_editor.setValue(this.correctAnswer); } } get isConcluded() { return super.isConcluded; } set isViewOnly(viewOnly) { super.isViewOnly = viewOnly; this.updateEditorFreezeState(); } get isViewOnly() { return super.isViewOnly; } // attributeChangedCallback(name, oldVal, newVal) { // switch (name) { // case "language": // editor.session.setMode("ace/mode/" + newVal.toLowerCase()); // break; // } // } } customElements.define('singlechoice-task', SingleChoiceTask); customElements.define('openended-task', OpenEndedTask); customElements.define('numberconversion-task', NumberConversionTask); customElements.define('truthtable-task', TruthTableTask); customElements.define('slide-switch', Switch); customElements.define('verilog-task', VerilogTask);