diff --git a/server/moodle/mod/livequiz/amd/build/question.min.js b/server/moodle/mod/livequiz/amd/build/question.min.js new file mode 100644 index 000000000..b753a1d2d --- /dev/null +++ b/server/moodle/mod/livequiz/amd/build/question.min.js @@ -0,0 +1,3 @@ +define("mod_livequiz/question",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;let savedQuestions=[],isEditing=!1,editingIndex=null;function create_discard_button(){let discard_question_button=create_element("discard_button","div","discard_question_button","Discard");return discard_question_button.addEventListener("click",(()=>{let toast_promise_deletion_div=create_element("toast_promise_deletion_div","div","toast_promise_deletion_div","Are you sure you want to discard changes?"),cancel_question_deletion_button=create_element("cancel_question_deletion_button","button","cancel_question_deletion_button","No"),continue_question_deletion_button=create_element("continue_question_deletion_button","button","continue_question_deletion_button","Yes");toast_promise_deletion_div.appendChild(cancel_question_deletion_button),toast_promise_deletion_div.appendChild(continue_question_deletion_button);let modal_div=document.querySelector(".Modal_div");modal_div.appendChild(toast_promise_deletion_div),continue_question_deletion_button.addEventListener("click",(()=>{isEditing=!1,editingIndex=null,modal_div.remove()})),cancel_question_deletion_button.addEventListener("click",(()=>{toast_promise_deletion_div.remove()}))})),discard_question_button}function create_file_picker(){let file_picker_input=create_element("file_input","input","file_picker_input","");file_picker_input.type="file",file_picker_input.accept=["image/*","video/*"],file_picker_input.id="file_input";const image=document.createElement("img");let file_label=document.createElement("label");file_label.htmlFor="file_input",file_label.className="file_upload_section",file_label.textContent="+ Add video or image";let preview=document.createElement("img");preview.style.display="none",preview.style.maxWidth="50%",preview.style.maxHeight="200px",preview.style.marginTop="10px",preview.className="preview_image",file_picker_input.addEventListener("change",(()=>{const file=file_picker_input.files[0];image.src=URL.createObjectURL(file),image.style.display="block"}));let file_container=document.createElement("div");return file_container.className="file_upload_section",file_container.appendChild(file_label),file_container.appendChild(file_picker_input),file_container.appendChild(image),file_container}function create_element(element_name,type,class_name,content){return(element_name=document.createElement(type)).className=class_name,element_name.textContent=content,element_name}function save_question(page,question_input,modal_div,answers_div,file_picker){let save_question_button=create_element("save_question_button","button","save_button","Save question");return save_question_button.addEventListener("click",(()=>{let questionText=question_input.value.trim();if(!questionText)return void alert("Please enter a question.");let answers=[];for(let i=0;i{!function(savedQuestion,index){isEditing=!0,editingIndex=index;let modal_div=document.createElement("div");modal_div.className="Modal_div";let question_input=document.createElement("textarea");question_input.placeholder="Enter question",question_input.className="question_input",question_input.value=savedQuestion.question,modal_div.appendChild(question_input);let file_picker=create_file_picker();if(savedQuestion.file){const image=file_picker.querySelector("img");image.src=URL.createObjectURL(savedQuestion.file),image.style.maxWidth="300px"}modal_div.appendChild(file_picker);let all_answers_for_question_div=document.createElement("div");savedQuestion.answers.forEach((answer=>{let answer_container=document.createElement("div");answer_container.className="container_for_new_answer";let answer_input=document.createElement("input");answer_input.className="answer_input",answer_input.value=answer.text,answer_input.setAttribute("required",!0);let answer_checkbox=document.createElement("input");answer_checkbox.setAttribute("type","checkbox"),answer_checkbox.className="answer_checkbox",answer_checkbox.checked=answer.correct;let delete_answer_button=create_element("delete_answer_button","button","delete_answer_button","");delete_answer_button.addEventListener("click",(()=>{answer_container.remove(),answer_count--})),answer_container.appendChild(answer_checkbox),answer_container.appendChild(answer_input),answer_container.appendChild(delete_answer_button),all_answers_for_question_div.appendChild(answer_container)})),modal_div.appendChild(create_answer_button(all_answers_for_question_div)),modal_div.appendChild(all_answers_for_question_div),modal_div.appendChild(save_question(null,question_input,modal_div,all_answers_for_question_div,file_picker)),modal_div.appendChild(create_discard_button()),modal_div.appendChild(function(index){let delete_question_button=create_element("delete_button","button","delete_question_button","Delete Question");return delete_question_button.addEventListener("click",(()=>{let toast_promise_deletion_div=create_element("toast_promise_deletion_div","div","toast_promise_deletion_div","Are you sure you want to delete this question?"),cancel_question_deletion_button=create_element("cancel_question_deletion_button","button","cancel_question_deletion_button","No"),continue_question_deletion_button=create_element("continue_question_deletion_button","button","continue_question_deletion_button","Yes");toast_promise_deletion_div.appendChild(cancel_question_deletion_button),toast_promise_deletion_div.appendChild(continue_question_deletion_button);let modal_div=document.querySelector(".Modal_div");modal_div.appendChild(toast_promise_deletion_div),continue_question_deletion_button.addEventListener("click",(()=>{savedQuestions.splice(index,1);let saved_questions_list=document.getElementById("saved_questions_list"),question_list_item=saved_questions_list.children[index];question_list_item&&saved_questions_list.removeChild(question_list_item);for(let i=index;i{toast_promise_deletion_div.remove()}))})),delete_question_button}(index)),document.getElementById("page-mod-livequiz-quizcreator").appendChild(modal_div)}(savedQuestions[question_list_item.dataset.index],question_list_item.dataset.index)};question_list_item.addEventListener("click",openModalHandler),saved_questions_list.appendChild(question_list_item)}modal_div.remove()})),save_question_button}function create_answer_button(parent_element){let add_new_answer_to_question=create_element("add_answer_button","button","add_new_answer_to_question","Add Answer"),answer_count=0;return add_new_answer_to_question.addEventListener("click",(()=>{if(answer_count<8){let answer_container=document.createElement("div");answer_container.className="container_for_new_answer";let answer_input=document.createElement("input");answer_input.className="answer_input",answer_input.placeholder="Enter answer",answer_input.setAttribute("required",!0);let answer_checkbox=document.createElement("input");answer_checkbox.setAttribute("type","checkbox"),answer_checkbox.className="answer_checkbox";let delete_answer_button=create_element("delete_answer_button","button","delete_answer_button","");answer_container.appendChild(answer_checkbox),answer_container.appendChild(answer_input),answer_container.appendChild(delete_answer_button),delete_answer_button.addEventListener("click",(()=>{answer_container.remove(),answer_count--})),parent_element.appendChild(answer_container),answer_count++}})),add_new_answer_to_question}_exports.init=async()=>{!function(){let add_question_button=document.getElementById("id_buttonaddquestion");add_question_button&&add_question_button.addEventListener("click",(()=>{!function(){if(document.querySelector(".Modal_div"))return;let modal_div=document.createElement("div");modal_div.className="Modal_div";let page=document.getElementById("page-mod-livequiz-quizcreator"),question_input=document.createElement("textarea");question_input.placeholder="Enter question",question_input.className="question_input_large",modal_div.appendChild(question_input);let file_picker=create_file_picker();modal_div.appendChild(create_file_picker()),modal_div.appendChild(function(){let timer_div=document.createElement("div");timer_div.className="time_limit_container";let timer_label=document.createElement("label");timer_label.textContent="Add time limit: ",timer_label.className="time_limit_label";let promise_timer_checkbox=document.createElement("input");promise_timer_checkbox.setAttribute("type","checkbox"),promise_timer_checkbox.className="time_limit_checkbox";let set_timer_input=document.createElement("input");set_timer_input.type="number",set_timer_input.placeholder="0 sec",set_timer_input.className="set_timer_input",set_timer_input.setAttribute("disabled","true"),set_timer_input.style.textAlign="center",promise_timer_checkbox.addEventListener("change",(()=>{promise_timer_checkbox.checked?set_timer_input.removeAttribute("disabled"):(set_timer_input.setAttribute("disabled","true"),set_timer_input.value="")}));let timer_input_container=document.createElement("div");return timer_input_container.className="timer_input_container",timer_input_container.appendChild(promise_timer_checkbox),timer_input_container.appendChild(set_timer_input),timer_div.appendChild(timer_label),timer_div.appendChild(timer_input_container),timer_div}());let all_answers_for_question_div=document.createElement("div");all_answers_for_question_div.className="all_answers_for_question_div",modal_div.appendChild(create_answer_button(all_answers_for_question_div)),modal_div.appendChild(all_answers_for_question_div);let saveButton=save_question(page,question_input,modal_div,all_answers_for_question_div,file_picker);modal_div.appendChild(saveButton),modal_div.appendChild(create_discard_button()),page.appendChild(modal_div)}()}))}();const imageUploadInput=document.getElementById("imageUpload"),imagePreview=document.getElementById("imagePreview");imageUploadInput&&imageUploadInput.addEventListener("change",(()=>{const file=imageUploadInput.files[0];file&&(imagePreview.src=URL.createObjectURL(file),imagePreview.style.display="block")}))}})); + +//# sourceMappingURL=question.min.js.map \ No newline at end of file diff --git a/server/moodle/mod/livequiz/amd/build/question.min.js.map b/server/moodle/mod/livequiz/amd/build/question.min.js.map new file mode 100644 index 000000000..9dc9c3ba9 --- /dev/null +++ b/server/moodle/mod/livequiz/amd/build/question.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"question.min.js","sources":["../src/question.js"],"sourcesContent":["// THIS FILE SHOULD BE DELETED WHEN THE QUESTION CREATION IS IMPLEMENTED IN THE MOODLE PLUGIN\nlet savedQuestions = [];\nlet isEditing = false;\nlet editingIndex = null;\n\nexport const init = async() => {\n open_question_creation_modal();\n\n const imageUploadInput = document.getElementById('imageUpload');\n const imagePreview = document.getElementById('imagePreview');\n\n if (imageUploadInput) {\n imageUploadInput.addEventListener('change', () => {\n const file = imageUploadInput.files[0];\n if (file) {\n imagePreview.src = URL.createObjectURL(file);\n imagePreview.style.display = 'block';\n }\n });\n }\n}\n \n function open_question_creation_modal() {\n let add_question_button = document.getElementById(\"id_buttonaddquestion\");\n if (add_question_button){\n add_question_button.addEventListener('click', () => {create_question_modal()});\n } \n }\n \n \n function create_question_modal() {\n \n if (document.querySelector('.Modal_div')) {\n return; \n }\n \n let modal_div = document.createElement(\"div\"); \n modal_div.className = \"Modal_div\";\n \n let page = document.getElementById(\"page-mod-livequiz-quizcreator\");\n \n let question_input = document.createElement('textarea');\n question_input.placeholder = \"Enter question\";\n question_input.className = \"question_input_large\";\n \n modal_div.appendChild(question_input);\n \n let file_picker = create_file_picker();\n modal_div.appendChild(create_file_picker());\n modal_div.appendChild(create_timer_element());\n \n let all_answers_for_question_div = document.createElement(\"div\");\n all_answers_for_question_div.className = \"all_answers_for_question_div\";\n \n modal_div.appendChild(create_answer_button(all_answers_for_question_div));\n modal_div.appendChild(all_answers_for_question_div);\n let saveButton = save_question(page, question_input, modal_div, all_answers_for_question_div, file_picker)\n modal_div.appendChild(saveButton);\n modal_div.appendChild(create_discard_button());\n \n page.appendChild(modal_div);\n }\n \n function create_discard_button() {\n let discard_question_button = create_element(\"discard_button\", \"div\", \"discard_question_button\", \"Discard\");\n \n discard_question_button.addEventListener('click', () => {\n let toast_promise_deletion_div = create_element(\"toast_promise_deletion_div\", 'div', \"toast_promise_deletion_div\", \"Are you sure you want to discard changes?\");\n let cancel_question_deletion_button = create_element(\"cancel_question_deletion_button\", 'button', \"cancel_question_deletion_button\", \"No\");\n let continue_question_deletion_button = create_element(\"continue_question_deletion_button\", 'button', \"continue_question_deletion_button\", \"Yes\");\n \n toast_promise_deletion_div.appendChild(cancel_question_deletion_button);\n toast_promise_deletion_div.appendChild(continue_question_deletion_button);\n \n let modal_div = document.querySelector('.Modal_div');\n modal_div.appendChild(toast_promise_deletion_div);\n \n continue_question_deletion_button.addEventListener('click', () => {\n isEditing = false;\n editingIndex = null\n modal_div.remove();\n });\n \n cancel_question_deletion_button.addEventListener('click', () => {\n toast_promise_deletion_div.remove();\n });\n });\n \n return discard_question_button;\n }\n\n function create_delete_button(index) {\n let delete_question_button = create_element(\"delete_button\", \"button\", \"delete_question_button\", \"Delete Question\");\n \n delete_question_button.addEventListener('click', () => {\n let toast_promise_deletion_div = create_element(\"toast_promise_deletion_div\", 'div', \"toast_promise_deletion_div\", \"Are you sure you want to delete this question?\");\n let cancel_question_deletion_button = create_element(\"cancel_question_deletion_button\", 'button', \"cancel_question_deletion_button\", \"No\");\n let continue_question_deletion_button = create_element(\"continue_question_deletion_button\", 'button', \"continue_question_deletion_button\", \"Yes\");\n\n toast_promise_deletion_div.appendChild(cancel_question_deletion_button);\n toast_promise_deletion_div.appendChild(continue_question_deletion_button);\n\n let modal_div = document.querySelector('.Modal_div');\n modal_div.appendChild(toast_promise_deletion_div);\n\n continue_question_deletion_button.addEventListener('click', () => {\n savedQuestions.splice(index,1);\n \n let saved_questions_list = document.getElementById(\"saved_questions_list\");\n let question_list_item = saved_questions_list.children[index]\n if (question_list_item){\n saved_questions_list.removeChild(question_list_item);\n }\n for (let i = index; i < saved_questions_list.children.length; i++) {\n saved_questions_list.children[i].dataset.index = i;\n }\n isEditing = false;\n editingIndex = null\n modal_div.remove();\n });\n\n cancel_question_deletion_button.addEventListener('click', () => {\n toast_promise_deletion_div.remove();\n });\n });\n \n return delete_question_button;\n }\n\n function create_cancel_button() {\n let cancel_button = create_element(\"cancel_button\", \"button\", \"cancel_button\", \"Cancel\");\n \n \n cancel_button.addEventListener('click', () => {\n console.log('Cancel button clicked!');\n // Add cancel functionality here\n });\n \n document.body.appendChild(cancel_button); // Append it to the body for fixed positioning\n }\n \n function create_file_picker() {\n let file_picker_input = create_element(\"file_input\", \"input\", \"file_picker_input\", \"\");\n file_picker_input.type = 'file';\n file_picker_input.accept = ['image/*', 'video/*'];\n file_picker_input.id = \"file_input\"; \n \n const image = document.createElement('img');\n \n let file_label = document.createElement('label');\n file_label.htmlFor = \"file_input\"; \n file_label.className = \"file_upload_section\";\n file_label.textContent = \"+ Add video or image\"; \n \n \n let preview = document.createElement(\"img\");\n preview.style.display = \"none\";\n preview.style.maxWidth = \"50%\";\n preview.style.maxHeight = \"200px\";\n preview.style.marginTop = \"10px\";\n preview.className = \"preview_image\";\n \n file_picker_input.addEventListener('change', () => {\n const file = file_picker_input.files[0];\n image.src = URL.createObjectURL(file);\n image.style.display = 'block';\n });\n \n let file_container = document.createElement(\"div\");\n file_container.className = \"file_upload_section\"; \n file_container.appendChild(file_label); \n file_container.appendChild(file_picker_input); \n file_container.appendChild(image);\n \n return file_container;\n }\n \n function create_element(element_name, type, class_name, content) {\n element_name = document.createElement(type);\n element_name.className = class_name;\n element_name.textContent = content;\n return element_name;\n }\n \n function create_timer_element() {\n let timer_div = document.createElement(\"div\");\n timer_div.className = \"time_limit_container\";\n \n \n let timer_label = document.createElement(\"label\");\n timer_label.textContent = \"Add time limit: \";\n timer_label.className = \"time_limit_label\"; \n \n let promise_timer_checkbox = document.createElement(\"input\");\n promise_timer_checkbox.setAttribute(\"type\", \"checkbox\");\n promise_timer_checkbox.className = \"time_limit_checkbox\"; \n \n let set_timer_input = document.createElement(\"input\");\n set_timer_input.type = 'number';\n set_timer_input.placeholder = \"0 sec\";\n set_timer_input.className = \"set_timer_input\"; \n set_timer_input.setAttribute('disabled', 'true'); \n set_timer_input.style.textAlign = \"center\"; \n \n \n promise_timer_checkbox.addEventListener('change', () => {\n if (promise_timer_checkbox.checked) {\n set_timer_input.removeAttribute('disabled'); \n } else {\n set_timer_input.setAttribute('disabled', 'true'); \n set_timer_input.value = \"\";\n }\n });\n \n \n let timer_input_container = document.createElement(\"div\");\n timer_input_container.className = \"timer_input_container\"; \n \n \n timer_input_container.appendChild(promise_timer_checkbox);\n timer_input_container.appendChild(set_timer_input);\n \n \n timer_div.appendChild(timer_label);\n timer_div.appendChild(timer_input_container); \n \n return timer_div;\n }\n \n function save_question(page, question_input, modal_div, answers_div, file_picker) {\n let save_question_button = create_element(\"save_question_button\", 'button', \"save_button\", \"Save question\");\n save_question_button.addEventListener('click', () => {\n let questionText = question_input.value.trim();\n if (!questionText) {\n alert(\"Please enter a question.\");\n return;\n }\n \n let answers = [];\n for (let i = 0; i < answers_div.children.length; i++) {\n let answertext = answers_div.children[i].querySelector(\".answer_input\").value.trim();\n let iscorrect = answers_div.children[i].querySelector(\".answer_checkbox\").checked;\n\n answers.push({ description: answertext, correct: iscorrect, explanation:\"\" });\n }\n \n let file_input = file_picker.querySelector('input[type=\"file\"]');\n let file = file_input.files[0];\n \n let savedQuestion = {\n question: questionText,\n answers: answers,\n file: file,\n description:\"\",\n explanation:\"\",\n timelimit:0 //todo proper element get by id and logic for just empty = 0\n\n };\n \n if (isEditing && editingIndex != null){\n savedQuestions[editingIndex] = savedQuestion;\n \n let saved_questions_list = document.getElementById(\"saved_questions_list\");\n let question_list_item = saved_questions_list.children[editingIndex];\n\n question_list_item.textContent = savedQuestion.question;\n\n\n isEditing = false;\n editingIndex = null;\n } else {\n savedQuestions.push(savedQuestion);\n\n let saved_questions_list = document.getElementById(\"saved_questions_list\");\n let question_list_item = document.createElement('li');\n question_list_item.textContent = savedQuestion.question;\n question_list_item.dataset.index = savedQuestions.length - 1;\n\n const openModalHandler = () => {\n open_saved_question_modal(savedQuestions[question_list_item.dataset.index], question_list_item.dataset.index);\n };\n\n question_list_item.addEventListener('click',openModalHandler);\n saved_questions_list.appendChild(question_list_item);\n }\n \n modal_div.remove();\n });\n \n \n return save_question_button;\n }\n \n function open_saved_question_modal(savedQuestion,index) {\n isEditing = true;\n editingIndex = index;\n\n let modal_div = document.createElement(\"div\");\n modal_div.className = \"Modal_div\";\n\n let question_input = document.createElement('textarea');\n question_input.placeholder = \"Enter question\";\n question_input.className = \"question_input\";\n question_input.value = savedQuestion.question;\n\n modal_div.appendChild(question_input);\n\n let file_picker = create_file_picker();\n if (savedQuestion.file) {\n const image = file_picker.querySelector('img');\n image.src = URL.createObjectURL(savedQuestion.file);\n image.style.maxWidth = \"300px\";\n }\n modal_div.appendChild(file_picker);\n\n let all_answers_for_question_div = document.createElement(\"div\");\n\n savedQuestion.answers.forEach(answer => {\n let answer_container = document.createElement('div');\n answer_container.className = \"container_for_new_answer\";\n\n let answer_input = document.createElement('input');\n answer_input.className = \"answer_input\";\n answer_input.value = answer.text;\n answer_input.setAttribute(\"required\", true);\n\n let answer_checkbox = document.createElement('input');\n answer_checkbox.setAttribute(\"type\", \"checkbox\");\n answer_checkbox.className = \"answer_checkbox\";\n answer_checkbox.checked = answer.correct;\n\n let delete_answer_button = create_element(\"delete_answer_button\", \"button\", \"delete_answer_button\", \"\");\n\n delete_answer_button.addEventListener('click', () => {\n answer_container.remove();\n answer_count--;\n });\n\n answer_container.appendChild(answer_checkbox);\n answer_container.appendChild(answer_input);\n answer_container.appendChild(delete_answer_button);\n\n all_answers_for_question_div.appendChild(answer_container);\n });\n\n modal_div.appendChild(create_answer_button(all_answers_for_question_div));\n modal_div.appendChild(all_answers_for_question_div);\n\n modal_div.appendChild(save_question(null,question_input,modal_div,all_answers_for_question_div,file_picker));\n modal_div.appendChild(create_discard_button());\n modal_div.appendChild(create_delete_button(index));\n\n\n let page = document.getElementById(\"page-mod-livequiz-quizcreator\");\n page.appendChild(modal_div);\n }\n \n function create_answer_button(parent_element) {\n let add_new_answer_to_question = create_element(\"add_answer_button\", 'button', 'add_new_answer_to_question', 'Add Answer');\n let answer_count = 0;\n \n add_new_answer_to_question.addEventListener('click', () => {\n if (answer_count < 8) {\n let answer_container = document.createElement('div');\n answer_container.className = \"container_for_new_answer\";\n \n let answer_input = document.createElement('input');\n answer_input.className = \"answer_input\";\n answer_input.placeholder = \"Enter answer\";\n answer_input.setAttribute(\"required\", true);\n \n let answer_checkbox = document.createElement('input');\n answer_checkbox.setAttribute(\"type\", \"checkbox\");\n answer_checkbox.className = \"answer_checkbox\"; \n \n let delete_answer_button = create_element(\"delete_answer_button\", \"button\", \"delete_answer_button\", \"\");\n \n answer_container.appendChild(answer_checkbox);\n answer_container.appendChild(answer_input);\n answer_container.appendChild(delete_answer_button);\n\n delete_answer_button.addEventListener('click', () => {\n answer_container.remove();\n answer_count--;\n });\n \n parent_element.appendChild(answer_container);\n answer_count++;\n }\n });\n return add_new_answer_to_question;\n }"],"names":["savedQuestions","isEditing","editingIndex","create_discard_button","discard_question_button","create_element","addEventListener","toast_promise_deletion_div","cancel_question_deletion_button","continue_question_deletion_button","appendChild","modal_div","document","querySelector","remove","create_file_picker","file_picker_input","type","accept","id","image","createElement","file_label","htmlFor","className","textContent","preview","style","display","maxWidth","maxHeight","marginTop","file","files","src","URL","createObjectURL","file_container","element_name","class_name","content","save_question","page","question_input","answers_div","file_picker","save_question_button","questionText","value","trim","alert","answers","i","children","length","answertext","iscorrect","checked","push","description","correct","explanation","savedQuestion","question","timelimit","getElementById","saved_questions_list","question_list_item","dataset","index","openModalHandler","placeholder","all_answers_for_question_div","forEach","answer","answer_container","answer_input","text","setAttribute","answer_checkbox","delete_answer_button","answer_count","create_answer_button","delete_question_button","splice","removeChild","create_delete_button","open_saved_question_modal","parent_element","add_new_answer_to_question","async","add_question_button","timer_div","timer_label","promise_timer_checkbox","set_timer_input","textAlign","removeAttribute","timer_input_container","create_timer_element","saveButton","create_question_modal","open_question_creation_modal","imageUploadInput","imagePreview"],"mappings":"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"} \ No newline at end of file diff --git a/server/moodle/mod/livequiz/amd/build/question_editor.min.js b/server/moodle/mod/livequiz/amd/build/question_editor.min.js new file mode 100644 index 000000000..ece17605a --- /dev/null +++ b/server/moodle/mod/livequiz/amd/build/question_editor.min.js @@ -0,0 +1,3 @@ +define("mod_livequiz/question_editor",["exports","core/templates","core/notification","./repository"],(function(_exports,_templates,_notification,_repository){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};let isEditing=!1,editingIndex=0,IDs=0,take_quiz_url="";_exports.init=async(quizid,lecturerid,url)=>{take_quiz_url=url,document.getElementById("id_buttonaddquestion").addEventListener("click",(()=>{!function(quizid,lecturerid){_templates.default.renderForPromise("mod_livequiz/question_menu_popup").then((_ref=>{let{html:html,js:js}=_ref;_templates.default.appendNodeContents(".main-container",html,js),document.querySelector(".add_new_answer_to_question").addEventListener("click",(()=>{!function(){let answer_container=document.createElement("div");answer_container.className="container_for_new_answer";let answer_input=document.createElement("input");answer_input.className="answer_input",answer_input.placeholder="Enter answer",answer_input.id="answer_input_"+(IDs+1),answer_input.setAttribute("required",!0);let answer_checkbox=document.createElement("input");answer_checkbox.setAttribute("type","checkbox"),answer_checkbox.className="answer_checkbox",answer_checkbox.id="answer_checkbox_"+(IDs+1);let delete_answer_button=(element_name="delete_answer_button",type="button",class_name="delete_answer_button",content="X",(element_name=document.createElement(type)).className=class_name,element_name.textContent=content,element_name);var element_name,type,class_name,content;delete_answer_button.id="delete_answer_button_"+(IDs+1),answer_container.appendChild(answer_checkbox),answer_container.appendChild(answer_input),answer_container.appendChild(delete_answer_button),delete_answer_button.addEventListener("click",(()=>{answer_container.remove()})),document.querySelector(".all_answers_for_question_div").appendChild(answer_container),IDs++}()})),function(quizid,lecturerid){document.querySelector(".save_button").addEventListener("click",(()=>{!function(quizid,lecturerid){let question_input_title=document.getElementById("question_title_id"),question_indput_description=document.getElementById("question_description_id"),question_indput_explanation=document.getElementById("question_explanation_id"),questionTitle=question_input_title.value.trim(),questionDesription=question_indput_description.value.trim(),questionExplanation=question_indput_explanation.value.trim();if(!questionDesription)return void alert("Please enter a question description.");questionTitle||(questionTitle="Question");let answers=[],answers_div=document.querySelector(".all_answers_for_question_div");for(let i=0;i{const contextsavedquestions={questions:questions},contexttakequiz={url:take_quiz_url,hasquestions:!0};document.querySelector("#saved_questions_list").remove(),_templates.default.renderForPromise("mod_livequiz/saved_questions_list",contextsavedquestions).then((_ref2=>{let{html:html,js:js}=_ref2;_templates.default.appendNodeContents("#saved-questions-container",html,js)})).catch((error=>(0,_notification.exception)(error)));let no_question_paragraph=document.querySelector(".no-question-text");null!=no_question_paragraph&&(no_question_paragraph.remove(),_templates.default.renderForPromise("mod_livequiz/take_quiz_button",contexttakequiz).then((_ref3=>{let{html:html,js:js}=_ref3;_templates.default.appendNodeContents("#page-mod-livequiz-quizcreator",html,js)})).catch((error=>(0,_notification.exception)(error))))})),document.querySelector(".Modal_div").remove()}(quizid,lecturerid)}))}(quizid,lecturerid),document.querySelector(".discard_question_button").addEventListener("click",(()=>{_templates.default.renderForPromise("mod_livequiz/question_confirmation").then((_ref4=>{let{html:html,js:js}=_ref4;_templates.default.appendNodeContents(".Modal_div",html,js),function(){let toast_promise_deletion_div=document.querySelector(".toast_promise_deletion_div"),cancel_question_deletion_button=document.querySelector(".cancel_question_deletion_button"),continue_question_deletion_button=document.querySelector(".continue_question_deletion_button"),modal_div=document.querySelector(".Modal_div");continue_question_deletion_button.addEventListener("click",(()=>{isEditing=!1,editingIndex=null,modal_div.remove()})),cancel_question_deletion_button.addEventListener("click",(()=>{toast_promise_deletion_div.remove()}))}()})).catch((error=>(0,_notification.exception)(error)))}))})).catch((error=>(0,_notification.exception)(error)))}(quizid,lecturerid)}))}})); + +//# sourceMappingURL=question_editor.min.js.map \ No newline at end of file diff --git a/server/moodle/mod/livequiz/amd/build/question_editor.min.js.map b/server/moodle/mod/livequiz/amd/build/question_editor.min.js.map new file mode 100644 index 000000000..2a86c0af2 --- /dev/null +++ b/server/moodle/mod/livequiz/amd/build/question_editor.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"question_editor.min.js","sources":["../src/question_editor.js"],"sourcesContent":["import Templates from \"core/templates\";\nimport { exception as displayException } from \"core/notification\";\nimport { save_question } from \"./repository\";\n\nlet isEditing = false;\nlet editingIndex = 0;\nlet answer_count = 0;\nlet IDs = 0;\nlet take_quiz_url = \"\";\n\nexport const init = async (quizid, lecturerid, url) => {\n take_quiz_url = url; //Set url to quiz attempt page to global variable\n let add_question_button = document.getElementById(\"id_buttonaddquestion\");\n add_question_button.addEventListener(\"click\", () => {\n render_question_menu_popup(quizid, lecturerid);\n });\n};\n\nfunction render_question_menu_popup(quizid, lecturerid) {\n // This will call the function to load and render our template.\n Templates.renderForPromise(\"mod_livequiz/question_menu_popup\")\n\n // It returns a promise that needs to be resoved.\n .then(({ html, js }) => {\n // Here eventually I have my compiled template, and any javascript that it generated.\n // The templates object has append, prepend and replace functions.\n Templates.appendNodeContents(\".main-container\", html, js);\n add_answer_button_event_listerner();\n add_save_question_button_listener(quizid, lecturerid);\n add_discard_question_button_listener();\n })\n\n // Deal with this exception (Using core/notify exception function is recommended).\n .catch((error) => displayException(error));\n}\n\nfunction add_answer_button_event_listerner() {\n //Adding event listerner to add answer button\n let answer_button = document.querySelector(\".add_new_answer_to_question\");\n answer_button.addEventListener(\"click\", () => {\n append_answer_input();\n });\n}\n\nfunction append_answer_input() {\n let answer_container = document.createElement(\"div\");\n answer_container.className = \"container_for_new_answer\";\n\n let answer_input = document.createElement(\"input\");\n answer_input.className = \"answer_input\";\n answer_input.placeholder = \"Enter answer\";\n answer_input.id = \"answer_input_\" + (IDs + 1);\n answer_input.setAttribute(\"required\", true);\n\n let answer_checkbox = document.createElement(\"input\");\n answer_checkbox.setAttribute(\"type\", \"checkbox\");\n answer_checkbox.className = \"answer_checkbox\";\n answer_checkbox.id = \"answer_checkbox_\" + (IDs + 1);\n\n let delete_answer_button = create_element(\n \"delete_answer_button\",\n \"button\",\n \"delete_answer_button\",\n \"X\"\n );\n delete_answer_button.id = \"delete_answer_button_\" + (IDs + 1);\n\n answer_container.appendChild(answer_checkbox);\n answer_container.appendChild(answer_input);\n answer_container.appendChild(delete_answer_button);\n\n delete_answer_button.addEventListener(\"click\", () => {\n answer_container.remove();\n answer_count--;\n });\n\n let parent_element = document.querySelector(\".all_answers_for_question_div\");\n parent_element.appendChild(answer_container);\n answer_count++;\n IDs++;\n}\n\nfunction add_save_question_button_listener(quizid, lecturerid) {\n let save_question_button = document.querySelector(\".save_button\");\n save_question_button.addEventListener(\"click\", () => {\n question_button(quizid, lecturerid);\n });\n}\n\nfunction question_button(quizid, lecturerid) {\n let question_input_title = document.getElementById(\"question_title_id\");\n let question_indput_description = document.getElementById(\"question_description_id\");\n let question_indput_explanation = document.getElementById(\"question_explanation_id\");\n let questionTitle = question_input_title.value.trim();\n let questionDesription = question_indput_description.value.trim();\n let questionExplanation = question_indput_explanation.value.trim();\n\n if (!questionDesription) {\n alert(\"Please enter a question description.\");\n return;\n }\n if(!questionTitle){\n questionTitle = \"Question\"\n }\n let answers = [];\n let answers_div = document.querySelector(\".all_answers_for_question_div\");\n for (let i = 0; i < answers_div.children.length; i++) {\n let answertext = answers_div.children[i]\n .querySelector(\".answer_input\")\n .value.trim();\n\n let iscorrect =\n answers_div.children[i].querySelector(\".answer_checkbox\").checked;\n iscorrect ? (iscorrect = 1) : (iscorrect = 0);\n\n answers.push({\n description: answertext,\n correct: iscorrect,\n explanation: \"\",\n });\n }\n\n let savedQuestion = {\n title: questionTitle,\n answers: answers,\n description: questionDesription,\n explanation: questionExplanation,\n };\n\n save_question(savedQuestion, lecturerid, quizid).then((questions) => {\n const contextsavedquestions = {\n questions: questions,\n };\n\n const contexttakequiz = {\n url: take_quiz_url,\n hasquestions: true,\n };\n\n //Remove the saved questions list and take quiz button\n let questions_list = document.querySelector(\"#saved_questions_list\");\n questions_list.remove();\n\n Templates.renderForPromise(\n \"mod_livequiz/saved_questions_list\",\n contextsavedquestions\n )\n // It returns a promise that needs to be resoved.\n .then(({ html, js }) => {\n // Here eventually I have my compiled template, and any javascript that it generated.\n // The templates object has append, prepend and replace functions.\n Templates.appendNodeContents(\"#saved-questions-container\", html, js);\n })\n\n // Deal with this exception (Using core/notify exception function is recommended).\n .catch((error) => displayException(error));\n\n let no_question_paragraph = document.querySelector(\".no-question-text\");\n\n if (no_question_paragraph != null) {\n no_question_paragraph.remove(); //We have just added a question so reomve the no question text\n Templates.renderForPromise(\n \"mod_livequiz/take_quiz_button\",\n contexttakequiz\n )\n // It returns a promise that needs to be resoved.\n .then(({ html, js }) => {\n // Here eventually I have my compiled template, and any javascript that it generated.\n // The templates object has append, prepend and replace functions.\n Templates.appendNodeContents(\n \"#page-mod-livequiz-quizcreator\",\n html,\n js\n );\n })\n\n // Deal with this exception (Using core/notify exception function is recommended).\n .catch((error) => displayException(error));\n }\n });\n let modal_div = document.querySelector(\".Modal_div\");\n modal_div.remove();\n}\n\nfunction add_discard_question_button_listener() {\n let discard_question_button = document.querySelector(\n \".discard_question_button\"\n );\n discard_question_button.addEventListener(\"click\", () => {\n render_question_confirmation();\n });\n}\n\nfunction render_question_confirmation() {\n Templates.renderForPromise(\"mod_livequiz/question_confirmation\")\n\n .then(({ html, js }) => {\n Templates.appendNodeContents(\".Modal_div\", html, js);\n question_confirmation();\n })\n .catch((error) => displayException(error));\n}\n\nfunction question_confirmation() {\n let toast_promise_deletion_div = document.querySelector(\n \".toast_promise_deletion_div\"\n );\n let cancel_question_deletion_button = document.querySelector(\n \".cancel_question_deletion_button\"\n );\n let continue_question_deletion_button = document.querySelector(\n \".continue_question_deletion_button\"\n );\n\n let modal_div = document.querySelector(\".Modal_div\");\n\n continue_question_deletion_button.addEventListener(\"click\", () => {\n isEditing = false;\n editingIndex = null;\n modal_div.remove();\n });\n\n cancel_question_deletion_button.addEventListener(\"click\", () => {\n toast_promise_deletion_div.remove();\n });\n}\n\nfunction create_element(element_name, type, class_name, content) {\n element_name = document.createElement(type);\n element_name.className = class_name;\n element_name.textContent = content;\n return element_name;\n}\n"],"names":["isEditing","editingIndex","IDs","take_quiz_url","async","quizid","lecturerid","url","document","getElementById","addEventListener","renderForPromise","then","_ref","html","js","appendNodeContents","querySelector","answer_container","createElement","className","answer_input","placeholder","id","setAttribute","answer_checkbox","delete_answer_button","element_name","type","class_name","content","textContent","appendChild","remove","append_answer_input","question_input_title","question_indput_description","question_indput_explanation","questionTitle","value","trim","questionDesription","questionExplanation","alert","answers","answers_div","i","children","length","answertext","iscorrect","checked","push","description","correct","explanation","savedQuestion","title","questions","contextsavedquestions","contexttakequiz","hasquestions","_ref2","catch","error","no_question_paragraph","_ref3","question_button","add_save_question_button_listener","_ref4","toast_promise_deletion_div","cancel_question_deletion_button","continue_question_deletion_button","modal_div","question_confirmation","render_question_menu_popup"],"mappings":"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"} \ No newline at end of file diff --git a/server/moodle/mod/livequiz/amd/build/question_editor_new.min.js b/server/moodle/mod/livequiz/amd/build/question_editor_new.min.js new file mode 100644 index 000000000..ead3f0af3 --- /dev/null +++ b/server/moodle/mod/livequiz/amd/build/question_editor_new.min.js @@ -0,0 +1,3 @@ +define("mod_livequiz/question_editor_new",["exports","core/templates"],(function(_exports,_templates){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};const context={name:"Tweety bird",intelligence:2};_exports.init=async()=>{_templates.default.renderForPromise("mod_livequiz/question_menu_popup",context).then((_ref=>{let{html:html,js:js}=_ref;_templates.default.appendNodeContents(".main-container",html,js)})).catch((error=>displayException(error)))}})); + +//# sourceMappingURL=question_editor_new.min.js.map \ No newline at end of file diff --git a/server/moodle/mod/livequiz/amd/build/question_editor_new.min.js.map b/server/moodle/mod/livequiz/amd/build/question_editor_new.min.js.map new file mode 100644 index 000000000..731e04b56 --- /dev/null +++ b/server/moodle/mod/livequiz/amd/build/question_editor_new.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"question_editor_new.min.js","sources":["../src/question_editor_new.js"],"sourcesContent":["import Templates from \"core/templates\";\n\n// This will be the context for our template. So {{name}} in the template will resolve to \"Tweety bird\".\nconst context = {\n name: \"Tweety bird\",\n intelligence: 2,\n};\n\nexport const init = async () => {\n // This will call the function to load and render our template.\n Templates.renderForPromise(\"mod_livequiz/question_menu_popup\", context)\n\n // It returns a promise that needs to be resoved.\n .then(({ html, js }) => {\n // Here eventually I have my compiled template, and any javascript that it generated.\n // The templates object has append, prepend and replace functions.\n Templates.appendNodeContents(\".main-container\", html, js);\n })\n\n // Deal with this exception (Using core/notify exception function is recommended).\n .catch((error) => displayException(error));\n};\n"],"names":["context","name","intelligence","async","renderForPromise","then","_ref","html","js","appendNodeContents","catch","error","displayException"],"mappings":"+PAGMA,QAAU,CACdC,KAAM,cACNC,aAAc,iBAGIC,6BAERC,iBAAiB,mCAAoCJ,SAG5DK,MAAKC,WAACC,KAAEA,KAAFC,GAAQA,4BAGHC,mBAAmB,kBAAmBF,KAAMC,OAIvDE,OAAOC,OAAUC,iBAAiBD"} \ No newline at end of file diff --git a/server/moodle/mod/livequiz/amd/build/repository.min.js b/server/moodle/mod/livequiz/amd/build/repository.min.js index f8cae8973..ee148e28a 100644 --- a/server/moodle/mod/livequiz/amd/build/repository.min.js +++ b/server/moodle/mod/livequiz/amd/build/repository.min.js @@ -1,3 +1,3 @@ -define("mod_livequiz/repository",["exports","core/ajax"],(function(_exports,_ajax){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.update_session=_exports.submit_quiz=void 0;_exports.submit_quiz=async(quizid,studentid,resultsurl)=>(0,_ajax.call)([{methodname:"mod_livequiz_submit_quiz",args:{quizid:quizid,studentid:studentid,resultsurl:resultsurl}}])[0];_exports.update_session=(quizid,questionid,answers)=>(0,_ajax.call)([{methodname:"mod_livequiz_update_session",args:{quizid:quizid,questionid:questionid,answers:answers}}])[0]})); +define("mod_livequiz/repository",["exports","core/ajax"],(function(_exports,_ajax){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.update_session=_exports.submit_quiz=_exports.save_question=void 0;_exports.submit_quiz=async(quizid,studentid,resultsurl)=>(0,_ajax.call)([{methodname:"mod_livequiz_submit_quiz",args:{quizid:quizid,studentid:studentid,resultsurl:resultsurl}}])[0];_exports.update_session=(quizid,questionid,answers)=>(0,_ajax.call)([{methodname:"mod_livequiz_update_session",args:{quizid:quizid,questionid:questionid,answers:answers}}])[0];_exports.save_question=(question,lecturerid,quizid)=>(0,_ajax.call)([{methodname:"mod_livequiz_save_question",args:{question:question,lecturerid:lecturerid,quizid:quizid}}])[0]})); //# sourceMappingURL=repository.min.js.map \ No newline at end of file diff --git a/server/moodle/mod/livequiz/amd/build/repository.min.js.map b/server/moodle/mod/livequiz/amd/build/repository.min.js.map index 7abd14280..42a578dfb 100644 --- a/server/moodle/mod/livequiz/amd/build/repository.min.js.map +++ b/server/moodle/mod/livequiz/amd/build/repository.min.js.map @@ -1 +1 @@ -{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["import {call as fetchMany} from 'core/ajax';\n\n// Function to insert a participation.\nexport const submit_quiz = async (quizid, studentid, resultsurl) => fetchMany([\n {\n methodname: 'mod_livequiz_submit_quiz',\n args: {\n quizid,\n studentid,\n resultsurl\n },\n }\n])[0];\n\n// A function to update session.\nexport const update_session = (quizid, questionid, answers) => fetchMany([\n {\n methodname: 'mod_livequiz_update_session',\n args: {\n quizid,\n questionid,\n answers\n },\n }\n])[0];\n\n"],"names":["async","quizid","studentid","resultsurl","methodname","args","questionid","answers"],"mappings":"oNAG2BA,MAAOC,OAAQC,UAAWC,cAAe,cAAU,CAC1E,CACIC,WAAY,2BACZC,KAAM,CACFJ,OAAAA,OACAC,UAAAA,UACAC,WAAAA,eAGT,2BAG2B,CAACF,OAAQK,WAAYC,WAAY,cAAU,CACrE,CACIH,WAAY,8BACZC,KAAM,CACFJ,OAAAA,OACAK,WAAAA,WACAC,QAAAA,YAGT"} \ No newline at end of file +{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["import {call as fetchMany} from 'core/ajax';\n\n// Function to insert a participation.\nexport const submit_quiz = async(quizid, studentid, resultsurl) => fetchMany([\n {\n methodname: 'mod_livequiz_submit_quiz',\n args: {\n quizid,\n studentid,\n resultsurl\n },\n }\n])[0];\n\n// A function to update session.\nexport const update_session = (quizid, questionid, answers) => fetchMany([\n {\n methodname: 'mod_livequiz_update_session',\n args: {\n quizid,\n questionid,\n answers\n },\n }\n])[0];\n\n// Function to save a question.\nexport const save_question = (question, lecturerid, quizid) => fetchMany([\n {\n methodname: 'mod_livequiz_save_question',\n args: {\n question,\n lecturerid,\n quizid\n },\n }\n])[0];\n\n"],"names":["async","quizid","studentid","resultsurl","methodname","args","questionid","answers","question","lecturerid"],"mappings":"2OAG2BA,MAAMC,OAAQC,UAAWC,cAAe,cAAU,CACzE,CACIC,WAAY,2BACZC,KAAM,CACFJ,OAAAA,OACAC,UAAAA,UACAC,WAAAA,eAGT,2BAG2B,CAACF,OAAQK,WAAYC,WAAY,cAAU,CACrE,CACIH,WAAY,8BACZC,KAAM,CACFJ,OAAAA,OACAK,WAAAA,WACAC,QAAAA,YAGT,0BAG0B,CAACC,SAAUC,WAAYR,UAAW,cAAU,CACrE,CACIG,WAAY,6BACZC,KAAM,CACFG,SAAAA,SACAC,WAAAA,WACAR,OAAAA,WAGT"} \ No newline at end of file diff --git a/server/moodle/mod/livequiz/amd/build/submit_quiz.min.js.map b/server/moodle/mod/livequiz/amd/build/submit_quiz.min.js.map index 1d2172e66..190f28328 100644 --- a/server/moodle/mod/livequiz/amd/build/submit_quiz.min.js.map +++ b/server/moodle/mod/livequiz/amd/build/submit_quiz.min.js.map @@ -1 +1 @@ -{"version":3,"file":"submit_quiz.min.js","sources":["../src/submit_quiz.js"],"sourcesContent":["import {submit_quiz} from \"./repository\";\n\n// Setup eventlistener for insterting participation and answer choices upon submitting quiz.\nexport const init = async(quizid, studentid, resultsurl) => {\n const submitQuizButton = document.getElementById(\"submitQuizBtn\");\n submitQuizButton.addEventListener(\"click\", async function() {\n try {\n // Insert participation and the answers given in the quiz.\n await submit_quiz(quizid, studentid);\n window.location.href = resultsurl;\n } catch (error) {\n window.console.error(\"Error in submit_quiz\", error);\n }\n });\n};"],"names":["async","quizid","studentid","resultsurl","document","getElementById","addEventListener","window","location","href","error","console"],"mappings":"wLAGoBA,MAAMC,OAAQC,UAAWC,cAChBC,SAASC,eAAe,iBAChCC,iBAAiB,SAASN,2BAG7B,2BAAYC,OAAQC,WAC1BK,OAAOC,SAASC,KAAON,WACzB,MAAOO,OACLH,OAAOI,QAAQD,MAAM,uBAAwBA"} \ No newline at end of file +{"version":3,"file":"submit_quiz.min.js","sources":["../src/submit_quiz.js"],"sourcesContent":["import {submit_quiz} from \"./repository\";\n\n// Setup eventlistener for inserting participation and answer choices upon submitting quiz.\nexport const init = async(quizid, studentid, resultsurl) => {\n const submitQuizButton = document.getElementById(\"submitQuizBtn\");\n submitQuizButton.addEventListener(\"click\", async function () {\n try {\n // Insert participation and the answers given in the quiz.\n await submit_quiz(quizid, studentid);\n window.location.href = resultsurl;\n } catch (error) {\n window.console.error(\"Error in submit_quiz\", error);\n }\n });\n};\n"],"names":["async","quizid","studentid","resultsurl","document","getElementById","addEventListener","window","location","href","error","console"],"mappings":"wLAGoBA,MAAMC,OAAQC,UAAWC,cAChBC,SAASC,eAAe,iBAChCC,iBAAiB,SAASN,2BAG7B,2BAAYC,OAAQC,WAC1BK,OAAOC,SAASC,KAAON,WACzB,MAAOO,OACLH,OAAOI,QAAQD,MAAM,uBAAwBA"} \ No newline at end of file diff --git a/server/moodle/mod/livequiz/amd/src/question.js b/server/moodle/mod/livequiz/amd/src/question.js new file mode 100644 index 000000000..2d2599106 --- /dev/null +++ b/server/moodle/mod/livequiz/amd/src/question.js @@ -0,0 +1,392 @@ +// THIS FILE SHOULD BE DELETED WHEN THE QUESTION CREATION IS IMPLEMENTED IN THE MOODLE PLUGIN +let savedQuestions = []; +let isEditing = false; +let editingIndex = null; + +export const init = async() => { + open_question_creation_modal(); + + const imageUploadInput = document.getElementById('imageUpload'); + const imagePreview = document.getElementById('imagePreview'); + + if (imageUploadInput) { + imageUploadInput.addEventListener('change', () => { + const file = imageUploadInput.files[0]; + if (file) { + imagePreview.src = URL.createObjectURL(file); + imagePreview.style.display = 'block'; + } + }); + } +} + + function open_question_creation_modal() { + let add_question_button = document.getElementById("id_buttonaddquestion"); + if (add_question_button){ + add_question_button.addEventListener('click', () => {create_question_modal()}); + } + } + + + function create_question_modal() { + + if (document.querySelector('.Modal_div')) { + return; + } + + let modal_div = document.createElement("div"); + modal_div.className = "Modal_div"; + + let page = document.getElementById("page-mod-livequiz-quizcreator"); + + let question_input = document.createElement('textarea'); + question_input.placeholder = "Enter question"; + question_input.className = "question_input_large"; + + modal_div.appendChild(question_input); + + let file_picker = create_file_picker(); + modal_div.appendChild(create_file_picker()); + modal_div.appendChild(create_timer_element()); + + let all_answers_for_question_div = document.createElement("div"); + all_answers_for_question_div.className = "all_answers_for_question_div"; + + modal_div.appendChild(create_answer_button(all_answers_for_question_div)); + modal_div.appendChild(all_answers_for_question_div); + let saveButton = save_question(page, question_input, modal_div, all_answers_for_question_div, file_picker) + modal_div.appendChild(saveButton); + modal_div.appendChild(create_discard_button()); + + page.appendChild(modal_div); + } + + function create_discard_button() { + let discard_question_button = create_element("discard_button", "div", "discard_question_button", "Discard"); + + discard_question_button.addEventListener('click', () => { + let toast_promise_deletion_div = create_element("toast_promise_deletion_div", 'div', "toast_promise_deletion_div", "Are you sure you want to discard changes?"); + let cancel_question_deletion_button = create_element("cancel_question_deletion_button", 'button', "cancel_question_deletion_button", "No"); + let continue_question_deletion_button = create_element("continue_question_deletion_button", 'button', "continue_question_deletion_button", "Yes"); + + toast_promise_deletion_div.appendChild(cancel_question_deletion_button); + toast_promise_deletion_div.appendChild(continue_question_deletion_button); + + let modal_div = document.querySelector('.Modal_div'); + modal_div.appendChild(toast_promise_deletion_div); + + continue_question_deletion_button.addEventListener('click', () => { + isEditing = false; + editingIndex = null + modal_div.remove(); + }); + + cancel_question_deletion_button.addEventListener('click', () => { + toast_promise_deletion_div.remove(); + }); + }); + + return discard_question_button; + } + + function create_delete_button(index) { + let delete_question_button = create_element("delete_button", "button", "delete_question_button", "Delete Question"); + + delete_question_button.addEventListener('click', () => { + let toast_promise_deletion_div = create_element("toast_promise_deletion_div", 'div', "toast_promise_deletion_div", "Are you sure you want to delete this question?"); + let cancel_question_deletion_button = create_element("cancel_question_deletion_button", 'button', "cancel_question_deletion_button", "No"); + let continue_question_deletion_button = create_element("continue_question_deletion_button", 'button', "continue_question_deletion_button", "Yes"); + + toast_promise_deletion_div.appendChild(cancel_question_deletion_button); + toast_promise_deletion_div.appendChild(continue_question_deletion_button); + + let modal_div = document.querySelector('.Modal_div'); + modal_div.appendChild(toast_promise_deletion_div); + + continue_question_deletion_button.addEventListener('click', () => { + savedQuestions.splice(index,1); + + let saved_questions_list = document.getElementById("saved_questions_list"); + let question_list_item = saved_questions_list.children[index] + if (question_list_item){ + saved_questions_list.removeChild(question_list_item); + } + for (let i = index; i < saved_questions_list.children.length; i++) { + saved_questions_list.children[i].dataset.index = i; + } + isEditing = false; + editingIndex = null + modal_div.remove(); + }); + + cancel_question_deletion_button.addEventListener('click', () => { + toast_promise_deletion_div.remove(); + }); + }); + + return delete_question_button; + } + + function create_cancel_button() { + let cancel_button = create_element("cancel_button", "button", "cancel_button", "Cancel"); + + + cancel_button.addEventListener('click', () => { + console.log('Cancel button clicked!'); + // Add cancel functionality here + }); + + document.body.appendChild(cancel_button); // Append it to the body for fixed positioning + } + + function create_file_picker() { + let file_picker_input = create_element("file_input", "input", "file_picker_input", ""); + file_picker_input.type = 'file'; + file_picker_input.accept = ['image/*', 'video/*']; + file_picker_input.id = "file_input"; + + const image = document.createElement('img'); + + let file_label = document.createElement('label'); + file_label.htmlFor = "file_input"; + file_label.className = "file_upload_section"; + file_label.textContent = "+ Add video or image"; + + + let preview = document.createElement("img"); + preview.style.display = "none"; + preview.style.maxWidth = "50%"; + preview.style.maxHeight = "200px"; + preview.style.marginTop = "10px"; + preview.className = "preview_image"; + + file_picker_input.addEventListener('change', () => { + const file = file_picker_input.files[0]; + image.src = URL.createObjectURL(file); + image.style.display = 'block'; + }); + + let file_container = document.createElement("div"); + file_container.className = "file_upload_section"; + file_container.appendChild(file_label); + file_container.appendChild(file_picker_input); + file_container.appendChild(image); + + return file_container; + } + + function create_element(element_name, type, class_name, content) { + element_name = document.createElement(type); + element_name.className = class_name; + element_name.textContent = content; + return element_name; + } + + function create_timer_element() { + let timer_div = document.createElement("div"); + timer_div.className = "time_limit_container"; + + + let timer_label = document.createElement("label"); + timer_label.textContent = "Add time limit: "; + timer_label.className = "time_limit_label"; + + let promise_timer_checkbox = document.createElement("input"); + promise_timer_checkbox.setAttribute("type", "checkbox"); + promise_timer_checkbox.className = "time_limit_checkbox"; + + let set_timer_input = document.createElement("input"); + set_timer_input.type = 'number'; + set_timer_input.placeholder = "0 sec"; + set_timer_input.className = "set_timer_input"; + set_timer_input.setAttribute('disabled', 'true'); + set_timer_input.style.textAlign = "center"; + + + promise_timer_checkbox.addEventListener('change', () => { + if (promise_timer_checkbox.checked) { + set_timer_input.removeAttribute('disabled'); + } else { + set_timer_input.setAttribute('disabled', 'true'); + set_timer_input.value = ""; + } + }); + + + let timer_input_container = document.createElement("div"); + timer_input_container.className = "timer_input_container"; + + + timer_input_container.appendChild(promise_timer_checkbox); + timer_input_container.appendChild(set_timer_input); + + + timer_div.appendChild(timer_label); + timer_div.appendChild(timer_input_container); + + return timer_div; + } + + function save_question(page, question_input, modal_div, answers_div, file_picker) { + let save_question_button = create_element("save_question_button", 'button', "save_button", "Save question"); + save_question_button.addEventListener('click', () => { + let questionText = question_input.value.trim(); + if (!questionText) { + alert("Please enter a question."); + return; + } + + let answers = []; + for (let i = 0; i < answers_div.children.length; i++) { + let answertext = answers_div.children[i].querySelector(".answer_input").value.trim(); + let iscorrect = answers_div.children[i].querySelector(".answer_checkbox").checked; + + answers.push({ description: answertext, correct: iscorrect, explanation:"" }); + } + + let file_input = file_picker.querySelector('input[type="file"]'); + let file = file_input.files[0]; + + let savedQuestion = { + question: questionText, + answers: answers, + file: file, + description:"", + explanation:"", + timelimit:0 //todo proper element get by id and logic for just empty = 0 + + }; + + if (isEditing && editingIndex != null){ + savedQuestions[editingIndex] = savedQuestion; + + let saved_questions_list = document.getElementById("saved_questions_list"); + let question_list_item = saved_questions_list.children[editingIndex]; + + question_list_item.textContent = savedQuestion.question; + + + isEditing = false; + editingIndex = null; + } else { + savedQuestions.push(savedQuestion); + + let saved_questions_list = document.getElementById("saved_questions_list"); + let question_list_item = document.createElement('li'); + question_list_item.textContent = savedQuestion.question; + question_list_item.dataset.index = savedQuestions.length - 1; + + const openModalHandler = () => { + open_saved_question_modal(savedQuestions[question_list_item.dataset.index], question_list_item.dataset.index); + }; + + question_list_item.addEventListener('click',openModalHandler); + saved_questions_list.appendChild(question_list_item); + } + + modal_div.remove(); + }); + + + return save_question_button; + } + + function open_saved_question_modal(savedQuestion,index) { + isEditing = true; + editingIndex = index; + + let modal_div = document.createElement("div"); + modal_div.className = "Modal_div"; + + let question_input = document.createElement('textarea'); + question_input.placeholder = "Enter question"; + question_input.className = "question_input"; + question_input.value = savedQuestion.question; + + modal_div.appendChild(question_input); + + let file_picker = create_file_picker(); + if (savedQuestion.file) { + const image = file_picker.querySelector('img'); + image.src = URL.createObjectURL(savedQuestion.file); + image.style.maxWidth = "300px"; + } + modal_div.appendChild(file_picker); + + let all_answers_for_question_div = document.createElement("div"); + + savedQuestion.answers.forEach(answer => { + let answer_container = document.createElement('div'); + answer_container.className = "container_for_new_answer"; + + let answer_input = document.createElement('input'); + answer_input.className = "answer_input"; + answer_input.value = answer.text; + answer_input.setAttribute("required", true); + + let answer_checkbox = document.createElement('input'); + answer_checkbox.setAttribute("type", "checkbox"); + answer_checkbox.className = "answer_checkbox"; + answer_checkbox.checked = answer.correct; + + let delete_answer_button = create_element("delete_answer_button", "button", "delete_answer_button", ""); + + delete_answer_button.addEventListener('click', () => { + answer_container.remove(); + answer_count--; + }); + + answer_container.appendChild(answer_checkbox); + answer_container.appendChild(answer_input); + answer_container.appendChild(delete_answer_button); + + all_answers_for_question_div.appendChild(answer_container); + }); + + modal_div.appendChild(create_answer_button(all_answers_for_question_div)); + modal_div.appendChild(all_answers_for_question_div); + + modal_div.appendChild(save_question(null,question_input,modal_div,all_answers_for_question_div,file_picker)); + modal_div.appendChild(create_discard_button()); + modal_div.appendChild(create_delete_button(index)); + + + let page = document.getElementById("page-mod-livequiz-quizcreator"); + page.appendChild(modal_div); + } + + function create_answer_button(parent_element) { + let add_new_answer_to_question = create_element("add_answer_button", 'button', 'add_new_answer_to_question', 'Add Answer'); + let answer_count = 0; + + add_new_answer_to_question.addEventListener('click', () => { + if (answer_count < 8) { + let answer_container = document.createElement('div'); + answer_container.className = "container_for_new_answer"; + + let answer_input = document.createElement('input'); + answer_input.className = "answer_input"; + answer_input.placeholder = "Enter answer"; + answer_input.setAttribute("required", true); + + let answer_checkbox = document.createElement('input'); + answer_checkbox.setAttribute("type", "checkbox"); + answer_checkbox.className = "answer_checkbox"; + + let delete_answer_button = create_element("delete_answer_button", "button", "delete_answer_button", ""); + + answer_container.appendChild(answer_checkbox); + answer_container.appendChild(answer_input); + answer_container.appendChild(delete_answer_button); + + delete_answer_button.addEventListener('click', () => { + answer_container.remove(); + answer_count--; + }); + + parent_element.appendChild(answer_container); + answer_count++; + } + }); + return add_new_answer_to_question; + } \ No newline at end of file diff --git a/server/moodle/mod/livequiz/amd/src/question_editor.js b/server/moodle/mod/livequiz/amd/src/question_editor.js new file mode 100644 index 000000000..4817606b2 --- /dev/null +++ b/server/moodle/mod/livequiz/amd/src/question_editor.js @@ -0,0 +1,233 @@ +import Templates from "core/templates"; +import { exception as displayException } from "core/notification"; +import { save_question } from "./repository"; + +let isEditing = false; +let editingIndex = 0; +let answer_count = 0; +let IDs = 0; +let take_quiz_url = ""; + +export const init = async (quizid, lecturerid, url) => { + take_quiz_url = url; //Set url to quiz attempt page to global variable + let add_question_button = document.getElementById("id_buttonaddquestion"); + add_question_button.addEventListener("click", () => { + render_question_menu_popup(quizid, lecturerid); + }); +}; + +function render_question_menu_popup(quizid, lecturerid) { + // This will call the function to load and render our template. + Templates.renderForPromise("mod_livequiz/question_menu_popup") + + // It returns a promise that needs to be resoved. + .then(({ html, js }) => { + // Here eventually I have my compiled template, and any javascript that it generated. + // The templates object has append, prepend and replace functions. + Templates.appendNodeContents(".main-container", html, js); + add_answer_button_event_listerner(); + add_save_question_button_listener(quizid, lecturerid); + add_discard_question_button_listener(); + }) + + // Deal with this exception (Using core/notify exception function is recommended). + .catch((error) => displayException(error)); +} + +function add_answer_button_event_listerner() { + //Adding event listerner to add answer button + let answer_button = document.querySelector(".add_new_answer_to_question"); + answer_button.addEventListener("click", () => { + append_answer_input(); + }); +} + +function append_answer_input() { + let answer_container = document.createElement("div"); + answer_container.className = "container_for_new_answer"; + + let answer_input = document.createElement("input"); + answer_input.className = "answer_input"; + answer_input.placeholder = "Enter answer"; + answer_input.id = "answer_input_" + (IDs + 1); + answer_input.setAttribute("required", true); + + let answer_checkbox = document.createElement("input"); + answer_checkbox.setAttribute("type", "checkbox"); + answer_checkbox.className = "answer_checkbox"; + answer_checkbox.id = "answer_checkbox_" + (IDs + 1); + + let delete_answer_button = create_element( + "delete_answer_button", + "button", + "delete_answer_button", + "X" + ); + delete_answer_button.id = "delete_answer_button_" + (IDs + 1); + + answer_container.appendChild(answer_checkbox); + answer_container.appendChild(answer_input); + answer_container.appendChild(delete_answer_button); + + delete_answer_button.addEventListener("click", () => { + answer_container.remove(); + answer_count--; + }); + + let parent_element = document.querySelector(".all_answers_for_question_div"); + parent_element.appendChild(answer_container); + answer_count++; + IDs++; +} + +function add_save_question_button_listener(quizid, lecturerid) { + let save_question_button = document.querySelector(".save_button"); + save_question_button.addEventListener("click", () => { + question_button(quizid, lecturerid); + }); +} + +function question_button(quizid, lecturerid) { + let question_input_title = document.getElementById("question_title_id"); + let question_indput_description = document.getElementById("question_description_id"); + let question_indput_explanation = document.getElementById("question_explanation_id"); + let questionTitle = question_input_title.value.trim(); + let questionDesription = question_indput_description.value.trim(); + let questionExplanation = question_indput_explanation.value.trim(); + + if (!questionDesription) { + alert("Please enter a question description."); + return; + } + if(!questionTitle){ + questionTitle = "Question" + } + let answers = []; + let answers_div = document.querySelector(".all_answers_for_question_div"); + for (let i = 0; i < answers_div.children.length; i++) { + let answertext = answers_div.children[i] + .querySelector(".answer_input") + .value.trim(); + + let iscorrect = + answers_div.children[i].querySelector(".answer_checkbox").checked; + iscorrect ? (iscorrect = 1) : (iscorrect = 0); + + answers.push({ + description: answertext, + correct: iscorrect, + explanation: "", + }); + } + + let savedQuestion = { + title: questionTitle, + answers: answers, + description: questionDesription, + explanation: questionExplanation, + }; + + save_question(savedQuestion, lecturerid, quizid).then((questions) => { + const contextsavedquestions = { + questions: questions, + }; + + const contexttakequiz = { + url: take_quiz_url, + hasquestions: true, + }; + + //Remove the saved questions list and take quiz button + let questions_list = document.querySelector("#saved_questions_list"); + questions_list.remove(); + + Templates.renderForPromise( + "mod_livequiz/saved_questions_list", + contextsavedquestions + ) + // It returns a promise that needs to be resoved. + .then(({ html, js }) => { + // Here eventually I have my compiled template, and any javascript that it generated. + // The templates object has append, prepend and replace functions. + Templates.appendNodeContents("#saved-questions-container", html, js); + }) + + // Deal with this exception (Using core/notify exception function is recommended). + .catch((error) => displayException(error)); + + let no_question_paragraph = document.querySelector(".no-question-text"); + + if (no_question_paragraph != null) { + no_question_paragraph.remove(); //We have just added a question so reomve the no question text + Templates.renderForPromise( + "mod_livequiz/take_quiz_button", + contexttakequiz + ) + // It returns a promise that needs to be resoved. + .then(({ html, js }) => { + // Here eventually I have my compiled template, and any javascript that it generated. + // The templates object has append, prepend and replace functions. + Templates.appendNodeContents( + "#page-mod-livequiz-quizcreator", + html, + js + ); + }) + + // Deal with this exception (Using core/notify exception function is recommended). + .catch((error) => displayException(error)); + } + }); + let modal_div = document.querySelector(".Modal_div"); + modal_div.remove(); +} + +function add_discard_question_button_listener() { + let discard_question_button = document.querySelector( + ".discard_question_button" + ); + discard_question_button.addEventListener("click", () => { + render_question_confirmation(); + }); +} + +function render_question_confirmation() { + Templates.renderForPromise("mod_livequiz/question_confirmation") + + .then(({ html, js }) => { + Templates.appendNodeContents(".Modal_div", html, js); + question_confirmation(); + }) + .catch((error) => displayException(error)); +} + +function question_confirmation() { + let toast_promise_deletion_div = document.querySelector( + ".toast_promise_deletion_div" + ); + let cancel_question_deletion_button = document.querySelector( + ".cancel_question_deletion_button" + ); + let continue_question_deletion_button = document.querySelector( + ".continue_question_deletion_button" + ); + + let modal_div = document.querySelector(".Modal_div"); + + continue_question_deletion_button.addEventListener("click", () => { + isEditing = false; + editingIndex = null; + modal_div.remove(); + }); + + cancel_question_deletion_button.addEventListener("click", () => { + toast_promise_deletion_div.remove(); + }); +} + +function create_element(element_name, type, class_name, content) { + element_name = document.createElement(type); + element_name.className = class_name; + element_name.textContent = content; + return element_name; +} diff --git a/server/moodle/mod/livequiz/amd/src/repository.js b/server/moodle/mod/livequiz/amd/src/repository.js index e9255fadb..4e71fffa8 100644 --- a/server/moodle/mod/livequiz/amd/src/repository.js +++ b/server/moodle/mod/livequiz/amd/src/repository.js @@ -24,3 +24,15 @@ export const update_session = (quizid, questionid, answers) => fetchMany([ } ])[0]; +// Function to save a question. +export const save_question = (question, lecturerid, quizid) => fetchMany([ + { + methodname: 'mod_livequiz_save_question', + args: { + question, + lecturerid, + quizid + }, + } +])[0]; + diff --git a/server/moodle/mod/livequiz/attempt.php b/server/moodle/mod/livequiz/attempt.php index 14fc1adda..fdc87389e 100644 --- a/server/moodle/mod/livequiz/attempt.php +++ b/server/moodle/mod/livequiz/attempt.php @@ -23,10 +23,8 @@ require_once('../../config.php'); require_once($CFG->libdir . '/accesslib.php'); -require_once('readdemodata.php'); use mod_livequiz\output\take_livequiz_page; -use mod_livequiz\readdemodata; use mod_livequiz\services\livequiz_services; global $PAGE, $OUTPUT, $USER; @@ -37,15 +35,8 @@ [$course, $cm] = get_course_and_cm_from_cmid($cmid, 'livequiz'); $instance = $DB->get_record('livequiz', ['id' => $cm->instance], '*', MUST_EXIST); -// Read demo data - REMOVE WHEN PUSHING TO STAGING. $livequizservice = livequiz_services::get_singleton_service_instance(); $currentquiz = $livequizservice->get_livequiz_instance($instance->id); -if (empty($currentquiz->get_questions())) { // If the quiz has no questions, insert demo data. - $demodatareader = new readdemodata(); - $demoquiz = $demodatareader->insertdemodata($currentquiz); -} else { - $demoquiz = $currentquiz; -} if (!$cm) { // If course module is not set, throw an exception. throw new moodle_exception('invalidcoursemodule', 'error'); @@ -81,7 +72,7 @@ // Rendering. $output = $PAGE->get_renderer('mod_livequiz'); -$takelivequiz = new take_livequiz_page($cmid, $demoquiz, $questionindex, $USER->id); +$takelivequiz = new \mod_livequiz\output\take_livequiz_page($cmid, $currentquiz, $questionindex, $USER->id); // Output. echo $OUTPUT->header(); diff --git a/server/moodle/mod/livequiz/classes/external/save_question.php b/server/moodle/mod/livequiz/classes/external/save_question.php new file mode 100644 index 000000000..a09f91165 --- /dev/null +++ b/server/moodle/mod/livequiz/classes/external/save_question.php @@ -0,0 +1,157 @@ +. + +namespace mod_livequiz\external; + +use core_external\external_function_parameters; +use core_external\external_multiple_structure; +use core_external\external_value; +use core_external\external_single_structure; +use dml_exception; +use mod_livequiz\models\answer; +use mod_livequiz\models\question; +use mod_livequiz\services\livequiz_services; +use PhpXmlRpc\Exception; +use mod_livequiz\models\livequiz; + +/** + * Class submit_quiz + * + * This class extends the core_external\external_api and is used to handle + * the external API for appending participation in the live quiz module. + * + * @return external_function_parameters The parameters required for the execute function. + * @copyright 2024 Software AAU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package mod_livequiz + */ +class save_question extends \core_external\external_api { + /** + * Returns the description of the execute_parameters function. + * @return external_function_parameters The parameters required for the execute function. + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters([ + 'question' => new external_single_structure([ + 'title' => new external_value(PARAM_TEXT, 'Title'), + 'description' => new external_value(PARAM_TEXT, 'Description of Question'), + 'explanation' => new external_value(PARAM_TEXT, 'Explanation of Question'), + 'answers' => new external_multiple_structure( + new external_single_structure([ + 'description' => new external_value(PARAM_TEXT, 'Description of Answer'), + 'correct' => new external_value(PARAM_BOOL, 'Correctness of Answer'), + 'explanation' => new external_value(PARAM_TEXT, 'Explanation of Answer'), + ]), + 'Answers' + ), + ]), + 'lecturerid' => new external_value(PARAM_INT, 'Lecturer ID'), + 'quizid' => new external_value(PARAM_INT, 'Quiz ID'), + ]); + } + + /** + * Summary of execute + * Inserts participation and answers into the DB + * @param array $questiondata + * @param int $lecturerid + * @param int $quizid + * @return bool + * @throws Exception + * @throws \invalid_parameter_exception + * @throws dml_exception + */ + public static function execute(array $questiondata, int $lecturerid, int $quizid): array { + debugging("execute"); + $params = self::validate_parameters(self::execute_parameters(), [ + 'question' => $questiondata, + 'lecturerid' => $lecturerid, + 'quizid' => $quizid, + ]); + $services = livequiz_services::get_singleton_service_instance(); + + // Get livequiz object and add the new question to it. + $livequiz = $services->get_livequiz_instance($quizid); + $question = self::new_question($questiondata); + $livequiz->add_question($question); + + try { + $livequiz = $services->submit_quiz($livequiz, $lecturerid);// Submit the livequiz to the database. + $templatelivequiz = $livequiz->prepare_for_template(); + $templatequestions = $templatelivequiz->questions; + return $templatequestions; + } catch (dml_exception $e) { + debugging('Error inserting participation: ' . $e->getMessage()); + return []; // Return empty array if unsucceful. + } + } + + /** + * Part of the webservice processing flow. Not called directly here, + * but is in moodle's web service framework. + * @return external_value + */ + public static function execute_returns(): external_multiple_structure { + return new external_multiple_structure( + new external_single_structure( + [ + 'questionid' => new external_value(PARAM_INT, 'The ID of the question'), + 'questiontitle' => new external_value(PARAM_TEXT, 'The title of the question'), + 'questiondescription' => new external_value(PARAM_RAW, 'The description of the question'), + 'questiontimelimit' => new external_value(PARAM_INT, 'The time limit for the question'), + 'questionexplanation' => new external_value(PARAM_RAW, 'Explanation of the question'), + 'answers' => new external_multiple_structure( + new external_single_structure( + [ + 'answerid' => new external_value(PARAM_INT, 'The ID of the answer'), + 'answerdescription' => new external_value(PARAM_RAW, 'The description of the answer'), + 'answerexplanation' => new external_value(PARAM_RAW, 'Explanation of the answer'), + 'answercorrect' => new external_value( + PARAM_BOOL, + 'Whether the answer is correct (1 for true, 0 for false)' + ), + ] + ), + 'List of answers for the question' + ), + 'answertype' => new external_value(PARAM_TEXT, 'The type of answers (e.g., checkbox, radio)'), + ] + ), + 'List of questions' + ); + } + + /** + * Create new question from intermediate array-representation + * @param array $questiondata + * @return question + */ + private static function new_question(array $questiondata): question { + $question = new question($questiondata['title'], $questiondata['description'], 0, $questiondata['explanation']); + + if (!empty($questiondata['answers'])) { // Loop through answers and add them to the question. + foreach ($questiondata['answers'] as $answerdata) { + $answer = new answer( + $answerdata['correct'], + $answerdata['description'], + $answerdata['explanation'] + ); + $question->add_answer($answer); + } + } + return $question; + } +} diff --git a/server/moodle/mod/livequiz/classes/output/index_page.php b/server/moodle/mod/livequiz/classes/output/index_page_student.php similarity index 78% rename from server/moodle/mod/livequiz/classes/output/index_page.php rename to server/moodle/mod/livequiz/classes/output/index_page_student.php index 3a73071ec..2d89bc228 100644 --- a/server/moodle/mod/livequiz/classes/output/index_page.php +++ b/server/moodle/mod/livequiz/classes/output/index_page_student.php @@ -17,6 +17,8 @@ namespace mod_livequiz\output; use core\exception\moodle_exception; +use mod_livequiz\models\livequiz; +use mod_livequiz\services\livequiz_services; use renderable; use renderer_base; use templatable; @@ -25,29 +27,36 @@ use mod_livequiz\models\student_quiz_relation; /** - * Class index_page + * Class index_page_student * @package mod_livequiz * @copyright 2024 Software AAU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class index_page implements renderable, templatable { +class index_page_student implements renderable, templatable { + /** @var int $quizid the livequiz to take or retake*/ + private int $quizid; + /** @var int $studentid the id of the student who is interacting with the quiz. + */ + private int $studentid; /** @var int $cmid the course module id */ protected int $cmid; - /** @var int $quizid the course module id */ - protected int $quizid; - /** @var int $studentid the student id */ - protected int $studentid; + + /** @var livequiz $livequiz the livequiz instance */ + private livequiz $livequiz; /** * index_page constructor. * @param int $cmid * @param int $quizid * @param int $studentid + * @param livequiz $livequiz */ public function __construct(int $cmid, int $quizid, int $studentid) { $this->cmid = $cmid; $this->quizid = $quizid; $this->studentid = $studentid; + $service = livequiz_services::get_singleton_service_instance(); + $this->livequiz = $service->get_livequiz_instance($quizid); } /** @@ -59,6 +68,10 @@ public function __construct(int $cmid, int $quizid, int $studentid) { */ public function export_for_template(renderer_base $output): stdClass { $data = new stdClass(); + $data->pagename = "Quiz menu page"; + $data->studentid = $this->studentid; + $data->hasquestions = !empty($this->livequiz->get_questions()); + $data->quizid = $this->quizid; $data->participations = []; $participations = student_quiz_relation::get_all_student_participation_for_quiz($this->quizid, $this->studentid); diff --git a/server/moodle/mod/livequiz/classes/output/index_page_teacher.php b/server/moodle/mod/livequiz/classes/output/index_page_teacher.php new file mode 100644 index 000000000..f5d3a5dab --- /dev/null +++ b/server/moodle/mod/livequiz/classes/output/index_page_teacher.php @@ -0,0 +1,76 @@ +. + +namespace mod_livequiz\output; + +use core\exception\moodle_exception; +use mod_livequiz\models\livequiz; +use mod_livequiz\services\livequiz_services; +use renderable; +use renderer_base; +use templatable; +use stdClass; +use moodle_url; + +/** + * Class index_page_teacher + * @package mod_livequiz + * @copyright 2024 Software AAU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class index_page_teacher implements renderable, templatable { + /** @var int $quizid the livequiz to take or retake*/ + private int $quizid; + /** @var int $teacherid the id of the teacher who is interacting with the quiz. */ + private int $lecturerid; + /** @var int $cmid the course module id */ + protected int $cmid; + + /** @var livequiz $livequiz the livequiz object */ + private livequiz $livequiz; + + /** + * index_page_teacher constructor. + * @param int $quizid + * @param int $lecturerid + * @param int $cmid + */ + public function __construct(int $quizid, int $lecturerid, int $cmid) { + $this->quizid = $quizid; + $this->lecturerid = $lecturerid; + $this->cmid = $cmid; + $service = livequiz_services::get_singleton_service_instance(); + $this->livequiz = $service->get_livequiz_instance($quizid); + } + + /** + * Export this data so it can be used as the context for a mustache template. + * + * @param renderer_base $output + * @return stdClass + * @throws moodle_exception + */ + public function export_for_template(renderer_base $output): stdClass { + $data = $this->livequiz->prepare_for_template(); + $data->hasquestions = !empty($this->livequiz->get_questions()); + $data->pagename = "Quiz editor page"; + $data->lecturerid = $this->lecturerid; + $data->quizid = $this->quizid; + $data->url = new moodle_url('/mod/livequiz/attempt.php', ['cmid' => $this->cmid]); + $data->islecturer = true; + return $data; + } +} diff --git a/server/moodle/mod/livequiz/classes/output/renderer.php b/server/moodle/mod/livequiz/classes/output/renderer.php index 604d726c5..bb0d9b000 100644 --- a/server/moodle/mod/livequiz/classes/output/renderer.php +++ b/server/moodle/mod/livequiz/classes/output/renderer.php @@ -30,12 +30,24 @@ class renderer extends plugin_renderer_base { /** * - * @param index_page $page + * @param index_page_student $page * * @return string html for the page * @throws moodle_exception */ - public function render_index_page(index_page $page): string { + public function render_index_page_student(index_page_student $page): string { + $data = $page->export_for_template($this); + return parent::render_from_template('mod_livequiz/index_page', $data); + } + + /** + * + * @param index_page_teacher $page + * + * @return string html for the page + * @throws moodle_exception + */ + public function render_index_page_teacher(index_page_teacher $page): string { $data = $page->export_for_template($this); return parent::render_from_template('mod_livequiz/index_page', $data); } diff --git a/server/moodle/mod/livequiz/classes/services/livequiz_services.php b/server/moodle/mod/livequiz/classes/services/livequiz_services.php index ec2ad4a82..761456c55 100644 --- a/server/moodle/mod/livequiz/classes/services/livequiz_services.php +++ b/server/moodle/mod/livequiz/classes/services/livequiz_services.php @@ -163,10 +163,7 @@ private function submit_questions(livequiz $livequiz, int $lecturerid): void { if ($questionid == 0) { // Insert new question if ID is 0 (new question). - $questionid = question::insert_question($newquestion); - - quiz_questions_relation::insert_quiz_question_relation($questionid, $quizid); - livequiz_questions_lecturer_relation::append_lecturer_questions_relation($questionid, $lecturerid); + $questionid = $this->insert_question_with_relations($newquestion, $lecturerid, $quizid); $updatedquestionids[] = $questionid; } else if (isset($existingquestionmap[$questionid])) { // Update existing question if found in the map. @@ -188,6 +185,20 @@ private function submit_questions(livequiz $livequiz, int $lecturerid): void { } } + /** + * inserts a question into the database + * + * @throws dml_transaction_exception + * @throws dml_exception + * @throws Exception + */ + private function insert_question_with_relations(question $question, int $lecturerid, int $quizid): int { + $questionid = question::insert_question($question); + quiz_questions_relation::insert_quiz_question_relation($questionid, $quizid); + livequiz_questions_lecturer_relation::append_lecturer_questions_relation($questionid, $lecturerid); + return $questionid; + } + /** * Submits answers to the database. * @@ -222,7 +233,6 @@ private function submit_answers(int $questionid, array $answers): void { $existinganswerids = array_keys($existinganswersmap); $deletedanswers = array_diff($existinganswerids, $updatedanswerids); - /* @var answer $deletedanswer // Type specification for $deletedanswer, for PHPStorm IDE */ foreach ($deletedanswers as $deletedanswer) { self::delete_answer($deletedanswer); } diff --git a/server/moodle/mod/livequiz/db/services.php b/server/moodle/mod/livequiz/db/services.php index 65adc8e12..fbe3afe57 100644 --- a/server/moodle/mod/livequiz/db/services.php +++ b/server/moodle/mod/livequiz/db/services.php @@ -42,6 +42,13 @@ 'ajax' => true, 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE], ], + 'mod_livequiz_save_question' => [ + 'classname' => 'mod_livequiz\external\save_question', + 'description' => 'Save a question.', + 'type' => 'write', + 'ajax' => true, + 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE], + ], ]; $services = []; diff --git a/server/moodle/mod/livequiz/demodata.json b/server/moodle/mod/livequiz/demodata.json deleted file mode 100644 index 303f306a5..000000000 --- a/server/moodle/mod/livequiz/demodata.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "quiz1": { - "id": "1", - "name": "Europe Cities Quiz", - "course": "2", - "intro": "This is a quiz about cities in europe", - "introformat": "1", - "timecreated": "1234567890", - "timemodified": "1234567890", - "questions": [ - { - "id": "1", - "title": "Which of the following cities is in France?", - "description": "This is about France", - "explanation": "Paris is a city in France, Nice is a city in France", - "timelimit": "60", - "answers": [ - { - "id": "1", - "description": "Paris", - "correct": 1, - "explanation": "Paris is a city in France" - }, - { - "id": "2", - "description": "Champagne", - "correct": 0, - "explanation": "Champagne is not a city in France" - }, - { - "id": "3", - "description": "Nice", - "correct": 1, - "explanation": "Nice is a city in France" - } - ] - }, - { - "id": "2", - "title": "What is the Capital of Denmark?", - "description": "This is about Denmark", - "explanation": "The Capital of Denmark is Copenhagen", - "timelimit": "60", - "answers": [ - { - "id": "4", - "description": "Aarhus", - "correct": 0, - "explanation": "Aarhus is not the capital of Denmark" - }, - { - "id": "5", - "description": "Aalborg", - "correct": 0, - "explanation": "Aalborg is not the capital of Denmark" - }, - { - "id": "6", - "description": "Copenhagen", - "correct": 1, - "explanation": "Copenhagen is the capital of Denmark" - } - ] - }, - { - "id": "3", - "title": "Is Hamburg in Germany?", - "description": "German cities", - "explanation": "Hamburg is a city in Northern Germany", - "timelimit": "60", - "answers": [ - { - "id": "7", - "description": "Yes", - "correct": 1, - "explanation": "Hamburg is a city in Germany" - }, - { - "id": "8", - "description": "No", - "correct": 0, - "explanation": "Hamburg is a city in Germany" - } - ] - } - ] - } -} - - diff --git a/server/moodle/mod/livequiz/index.php b/server/moodle/mod/livequiz/index.php deleted file mode 100644 index ef980e08e..000000000 --- a/server/moodle/mod/livequiz/index.php +++ /dev/null @@ -1,38 +0,0 @@ -. - -/** - * Livequiz activity version information. - * - * @package mod_livequiz - * @copyright Computer science Aalborg university {@link http:/aau.dk} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -require_once('../../config.php'); -global $OUTPUT, $PAGE, $COURSE, $DB; - - -// The `id` parameter is the course id. -$id = required_param('id', PARAM_INT); - -// Fetch the requested course. -$course = $DB->get_record('course', ['id' => $id], '*', MUST_EXIST); - -// Require that the user is logged into the course. -require_course_login($course); - -$modinfo = get_fast_modinfo($course); diff --git a/server/moodle/mod/livequiz/readdemodata.php b/server/moodle/mod/livequiz/readdemodata.php deleted file mode 100644 index 3af68ea27..000000000 --- a/server/moodle/mod/livequiz/readdemodata.php +++ /dev/null @@ -1,87 +0,0 @@ -. - -namespace mod_livequiz; - - - -defined('MOODLE_INTERNAL') || die(); - -use mod_livequiz\models\livequiz; -use mod_livequiz\models\question; -use mod_livequiz\models\answer; -use mod_livequiz\services\livequiz_services; - -require_once(dirname(__DIR__) . '/livequiz/classes/models/livequiz.php'); - -/** - * Temporary read demo data class - * @package mod_livequiz - * @copyright 2024 Software AAU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class readdemodata { - /** - * Reads the demo data and creates objects from it - * @return livequiz object - */ - public static function insertdemodata(livequiz $livequiz): livequiz { - global $USER; - $userid = $USER->id; - $livequizservice = livequiz_services::get_singleton_service_instance(); - - // Read the JSON file. - $json = file_get_contents('demodata.json'); - - // Check if the file was read successfully. - if ($json === false) { - die('Error reading the JSON file'); - } - - // Decode the JSON file. - $jsondata = json_decode($json, true); - - // Check if the JSON was decoded successfully. - if ($jsondata === null) { - die('Error decoding the JSON file'); - } - - $jsondata = $jsondata["quiz1"]; - $questions = []; - - // Prepare questions. - foreach ($jsondata["questions"] as $question) { - $modelquestion = new question( - $question["title"], - $question["description"], - $question["timelimit"], - $question["explanation"] - ); - foreach ($question["answers"] as $answer) { - $modelanswer = new answer( - $answer["correct"], - $answer["description"], - $answer["explanation"] - ); - $modelquestion->add_answer($modelanswer); - } - $questions[] = $modelquestion; - } - $livequiz->add_questions($questions); - $livequiz = $livequizservice->submit_quiz($livequiz, $userid); // Insert into database. - return $livequiz; - } -} diff --git a/server/moodle/mod/livequiz/results.php b/server/moodle/mod/livequiz/results.php index 92341191b..40e03ef22 100644 --- a/server/moodle/mod/livequiz/results.php +++ b/server/moodle/mod/livequiz/results.php @@ -23,11 +23,9 @@ require_once('../../config.php'); require_once($CFG->libdir . '/accesslib.php'); -require_once('readdemodata.php'); use mod_livequiz\models\participation; use mod_livequiz\output\results_page; -use mod_livequiz\readdemodata; use mod_livequiz\services\livequiz_services; global $PAGE, $OUTPUT, $DB, $USER; @@ -40,15 +38,8 @@ $instance = $DB->get_record('livequiz', ['id' => $cm->instance], '*', MUST_EXIST); -// Read demo data - REMOVE WHEN PUSHING TO STAGING. $livequizservice = livequiz_services::get_singleton_service_instance(); $currentquiz = $livequizservice->get_livequiz_instance($instance->id); -if (empty($currentquiz->get_questions())) { - $demodatareader = new readdemodata(); - $demoquiz = $demodatareader->insertdemodata($currentquiz); -} else { - $demoquiz = $currentquiz; -} require_login($course, false, $cm); @@ -81,7 +72,7 @@ // Rendering. $output = $PAGE->get_renderer('mod_livequiz'); -$results = new results_page($cmid, $demoquiz, $participation); +$results = new results_page($cmid, $currentquiz, $participation); echo $OUTPUT->header(); echo $output->render($results); diff --git a/server/moodle/mod/livequiz/style.css b/server/moodle/mod/livequiz/style.css index 6b671328c..2b4b409bb 100644 --- a/server/moodle/mod/livequiz/style.css +++ b/server/moodle/mod/livequiz/style.css @@ -1,23 +1,584 @@ +/** + * File style.css. + * Contains styles for Quizcreator, Quizrunner and Navbar. + */ +/** + * Quizcreator. + */ + .Modal_div { + width: 80%; + max-height: 80vh; + overflow-y: auto; + color: #333; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: #f5f5f5; + border: 2px solid #ccc; + border-radius: 15px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + padding: 20px; + z-index: 1000; + } + + .question_input_large { + width: 100%; + height: 100px; + font-size: 1.3em; + padding: 12px; + margin-bottom: 20px; + border-radius: 8px; + border: 2px solid #ccc; + box-shadow: inset 0 3px 6px rgba(0, 0, 0, 0.1); + } + + .all_answers_for_question_div { + display: flex; + flex-wrap: wrap; + gap: 15px; + margin-top: 10px; + } + .flex_container { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + } + + .container_for_new_answer { + display: flex; + width: 49%; + align-items: center; + gap: 10px; + padding: 10px; + border-radius: 8px; + border: 2px solid #ccc; + background-color: #f0f0f0; + position: relative; + margin-bottom: 10px; + } + + .answer_checkbox { + margin-right: 10px; + } + .answer_input { + flex-grow: 1; + padding: 10px; + border: 2px solid #ccc; + background: transparent; + font-size: 1.2em; + border-radius: 5px; + } + .delete_answer_button { + background-color: #e63946; /* A bold red color */ + color: #fff; /* White text */ + border: none; + border-radius: 8px; /* Rounded corners */ + padding: 10px 20px; + font-size: 16px; + font-weight: bold; + cursor: pointer; + transition: background-color 0.3s ease, transform 0.2s ease; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + } + .answer_input:focus { + outline: none; + } + .delete_answer_button:hover { + background-color: #d62828; /* Slightly darker red on hover */ + transform: scale(1.05); /* Grows slightly */ + box-shadow: 0 6px 8px rgba(0, 0, 0, 0.2); + } + .delete_answer_button:active { + background-color: #b51717; /* Even darker red when clicked */ + transform: scale(0.95); /* Shrinks slightly */ + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + .delete_answer_button:focus{ + outline: none; + box-shadow: 0 0 0 3px rgba(230, 57, 70, 0.5); /* A focus ring for accessibility */ + } + + .add_new_answer_to_question:hover { + background-color: #004edf; + } + + /* File upload section */ + .file_upload_section img { + margin-top: 10px; + max-width: 50%; + max-height: 200px; + display: block; + object-fit: contain; + margin-left: auto; + margin-right: auto; + } + .file_upload_section input[type="file"] { + display: none; /* Hide the default file input */ + } + .file_upload_section label { + width: 100%; + height: 80px; + border: 2px dashed #ccc; + border-radius: 10px; + background-color: #fafafa; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + font-size: 1.2em; + color: #888; + font-weight: bold; + text-align: center; + } + + /* Time limit for question styling */ + .time_limit_container { + display: flex; + flex-direction: column; + align-items: flex-start; + margin-bottom: 20px; + } + .time_limit_label { + font-size: 1.2em; + margin-bottom: 5px; + } + .timer_input_container { + display: flex; + align-items: center; + gap: 10px; + } + .time_limit_checkbox { + margin-right: 10px; + } + .set_timer_input { + width: 150px; + padding: 5px 10px; + border: 2px solid #ccc; + border-radius: 10px; + font-size: 1.1em; + text-align: center; + outline: none; + } + + .add_new_answer_to_question { + background-color: #0059ff; + color: white; + padding: 10px 15px; + font-size: 1em; + border-radius: 5px; + cursor: pointer; + border: none; + align-self: flex-end; + z-index: 100; + } + + .save_button:hover { + background-color: #4cae4c; + } + .discard_question_button:hover { + background-color: #c10600; + } + + .file_upload_container { + margin-bottom: 20px; + } + + .file_upload_input { + border: 1px solid #ccc; + padding: 8px; + border-radius: 5px; + font-size: 1rem; + } + + .file_upload_preview { + display: block; + margin-top: 10px; + max-width: 300px; + border: 1px solid #ccc; + border-radius: 5px; + } + .button_container { + display: flex; + justify-content: space-between; + margin-top: 20px; + } + .discard_question_button { + float: right; + background-color: red; + color: white; + padding: 10px 20px; + font-size: large; + border-radius: 5px; + cursor: pointer; + } + .save_button { + background-color: green; + color: white; + padding: 10px 20px; + font-size: large; + border-radius: 5px; + cursor: pointer; + } + /* Styling the cancel button to always appear at the bottom-right of the window */ + .cancel_button { + position: fixed; + bottom: 10px; + right: 100px; + background-color: blue; + color: white; + padding: 10px 20px; + border-radius: 5px; + font-size: large; + cursor: pointer; + z-index: 1000; + } + + .toast_promise_deletion_div { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: #fff; + padding: 20px; + border: 2px solid #ccc; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + text-align: center; + width: 60%; + } + + .toast_promise_deletion_div button { + margin: 10px; + padding: 10px 20px; + font-size: 1.1em; + cursor: pointer; + } + + #id_buttonaddquestion { + background-color: green; + color: white; + padding: 10px 20px; + font-size: large; + border-radius: 5px; + cursor: pointer; + } + + .div_for_question { + margin-top: 25px; + display: flex; + flex-direction: column; + gap: 10px; + } + + .question_for_main_page { + cursor: pointer; + background-color: #0000ff; + color: white; + padding: 10px; + font-size: large; + border-radius: 5px; + } + + .quiz_form_container { + flex-direction: column; + padding: 20px; + box-sizing: border-box; + } + + .quiz_form_container .fitem { + display: flex; + flex-direction: column; + margin-bottom: 15px; + } + + .quiz_form_container label { + margin-bottom: 5px; + font-weight: bold; + } + + .quiz_form_container input, + .quiz_form_container textarea { + width: 100%; + padding: 10px; + border: 1px solid #ccc; + border-radius: 5px; + font-size: 1rem; + } + + .image_upload_container { + position: relative; + margin-top: 20px; + width: 100%; + padding: 10px 0; + } + + #imagePreviewContainer { + width: 300px; + height: 200px; + border: 2px solid #ccc; + border-radius: 10px; + margin-bottom: 15px; + overflow: hidden; + display: flex; + justify-content: center; + align-items: center; + } + + #imagePreview { + max-width: 100%; + max-height: 100%; + display: none; /* Initially hidden until an image is uploaded */ + } + + .custom-file-upload { + display: inline-block; + padding: 10px 20px; + cursor: pointer; + background-color: #007bff; + color: white; + border: 2px solid #007bff; + border-radius: 10px; + font-size: 1rem; + text-align: center; + } + + .custom-file-upload:hover { + background-color: #0056b3; + } + + #imageUpload { + display: none; /* Hide the default file input */ + } + + #saved_questions_container { + width: 40%; + background-color: lightgray; + padding: 20px; + border-radius: 15px; + margin-top: 20px; + box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.3); + } + #saved_questions_list { + list-style-type: decimal; + padding-left: 0; + margin: 0; + } + #saved_questions_list li { + margin-bottom: 5px; + color: blue; + cursor: pointer; + transition: background-color 0.3s; + } + #saved_questions_list li:hover { + background-color: #f0f0f0; + } + + + /** + * Navbar. + */ + + .nav-tabs { + display: flex; + margin-bottom: 20px; + } + .nav-tabs .tab-button { + + padding: 0px 10px; + margin: 0px; + border: none; + + border-radius: 4px; + text-align: center; + text-decoration: none; + cursor: pointer; + transition: background-color 0.3s; + } + /* Styles for active button */ + .nav-tabs .tab-button.active { + background-color: var(--button-active-bg, #9da6b0); + font-weight: bold; + } + /* Optional: Hover effect */ + .nav-tabs .tab-button:hover { + background-color: var(--button-hover-bg, #9da6b0); + } + + /** + * Quizrunner Styles. + */ + + + .main-container { + + } + + .title-container { + + } + + .sidebar { + + } + + .navbar{ + + } -.main-container { - -} - -.title-container { - -} - -.question-container { - -} - -.sidebar { - -} - -.navbar{ - -} - - + .delete_question_button { + float: right; + background-color: red; + color: white; + padding: 10px 20px; + font-size: large; + border-radius: 5px; + cursor: pointer; + margin-right: 10px; + } + + .question-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + } + + .question-container img { + max-width: 100%; + height: auto; + } + + /*multichoice Button Styling */ + .answer.multichoice { + display: grid; + grid-template-columns: repeat(2, 1fr); + column-gap: 20px; + justify-items: center; + } + + /* Ensure all buttons have the same size */ + .answer.multichoice button { + width: 200px; + height: 60px; + font-size: 16px; + padding: 10px; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + background-color: #0073e6; + color: white; + border-radius: 5px; + border: none; + cursor: pointer; + transition: background-color 0.3s ease; + } + + /* Hover effect */ + .answer.multichoice button:hover { + background-color: #005bb5; + } + + /* Responsive Grid Adjustments */ + @media (max-width: 600px) { + .answer.multichoice { + grid-template-columns: 1fr; + } + } + + /* Slider Styles */ + .answer.slider input[type="range"] { + width: 100%; + height: 25px; + background: #ddd; + border-radius: 5px; + outline: none; + transition: background 0.3s; + } + + .answer.slider input[type="range"]::-webkit-slider-thumb { + width: 25px; + height: 25px; + background-color: #0073e6; + border-radius: 50%; + cursor: pointer; + } + + /* Slider Value Display */ + .answer.slider output { + font-size: 20px; + margin-top: 10px; + display: block; + } + + /* Progress Bar Styling */ + .progress-bar { + background-color: #e0e0e0; + border-radius: 5px; + overflow: hidden; + margin: 20px 0; + width: 100%; + max-width: 600px; + height: 20px; + } + + /* Progress Indicator (inner div) */ + .progress-bar div { + height: 100%; + background: linear-gradient(to right, #4CAF50, #81C784); + transition: width 0.4s ease-in-out; + color: white; + text-align: center; + line-height: 20px; + padding: 0 5px; + } + + /* Progress Text (if placed outside the bar) */ + .progress-text { + position: absolute; + width: 100%; + text-align: center; + line-height: 20px; + color: #333; + z-index: 1; + } + + /* Styling for the quiz prompt */ + .prompt { + width: 420px; + background-color: #f9f9f9; + border: 2px solid #0073e6; + border-radius: 10px; + margin: 20px 0; + font-size: 20px; + font-weight: bold; + text-align: center; + color: #333; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); + } + + /* Form Styling */ + form { + display: flex; + flex-direction: column; + align-items: center; + } + + form button { + background-color: #0073e6; /* Moodle's primary button color */ + color: white; + border: none; + padding: 10px 20px; + border-radius: 5px; + font-size: 16px; + cursor: pointer; + margin-top: 20px; + } + + form button:hover { + background-color: #005bb5; + } \ No newline at end of file diff --git a/server/moodle/mod/livequiz/templates/index_page.mustache b/server/moodle/mod/livequiz/templates/index_page.mustache index 0becde95f..e4085fe77 100644 --- a/server/moodle/mod/livequiz/templates/index_page.mustache +++ b/server/moodle/mod/livequiz/templates/index_page.mustache @@ -29,10 +29,18 @@ * fill in later }} +{{#js}} + require(['mod_livequiz/question_editor'], (module) => module.init({{quizid}}, {{lecturerid}}, "{{{url}}}" )); +{{/js}} +
-

index_page

-

{{sometext}}

- Take Quiz - {{> mod_livequiz/participations_section}} {{! participations_section import - Context remains the same}} +

{{pagename}}

+ {{#islecturer}} + {{> mod_livequiz/quiz_editor_page}} + {{/islecturer}} + {{> mod_livequiz/take_quiz_button}} + {{#studentid}} + {{> mod_livequiz/participations_section}} {{! participations_section import - Context remains the same}} + {{/studentid}}
diff --git a/server/moodle/mod/livequiz/templates/question_confirmation.mustache b/server/moodle/mod/livequiz/templates/question_confirmation.mustache new file mode 100644 index 000000000..bd15d3a3b --- /dev/null +++ b/server/moodle/mod/livequiz/templates/question_confirmation.mustache @@ -0,0 +1,5 @@ +
+ Are you sure you want to discard changes? + + +
\ No newline at end of file diff --git a/server/moodle/mod/livequiz/templates/question_menu_popup.mustache b/server/moodle/mod/livequiz/templates/question_menu_popup.mustache new file mode 100644 index 000000000..12e78f173 --- /dev/null +++ b/server/moodle/mod/livequiz/templates/question_menu_popup.mustache @@ -0,0 +1,66 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template mod_livequiz/index_page + + Index Page for the livequiz plugin + + Classes required for JS: + * none + + Context variables required for this template: + * fill in later + + Example context (json): + * fill in later + +}} + + diff --git a/server/moodle/mod/livequiz/templates/question_section.mustache b/server/moodle/mod/livequiz/templates/question_section.mustache index d47f88478..7aafa00d1 100644 --- a/server/moodle/mod/livequiz/templates/question_section.mustache +++ b/server/moodle/mod/livequiz/templates/question_section.mustache @@ -30,9 +30,6 @@ }}
-
- {{#isattempting}}

{{quiztitle}}

{{/isattempting}} -

{{questiontitle}}

diff --git a/server/moodle/mod/livequiz/templates/quiz_editor_page.mustache b/server/moodle/mod/livequiz/templates/quiz_editor_page.mustache new file mode 100644 index 000000000..aeae2bc67 --- /dev/null +++ b/server/moodle/mod/livequiz/templates/quiz_editor_page.mustache @@ -0,0 +1,33 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +
+

{{quizdata.title}}

+ +
+ +
+ +
+

Saved Questions:

+ {{> mod_livequiz/saved_questions_list}} +
+ +
+ {{{quizdata.filepicker}}} +
+ +
\ No newline at end of file diff --git a/server/moodle/mod/livequiz/templates/saved_questions_list.mustache b/server/moodle/mod/livequiz/templates/saved_questions_list.mustache new file mode 100644 index 000000000..5718b8798 --- /dev/null +++ b/server/moodle/mod/livequiz/templates/saved_questions_list.mustache @@ -0,0 +1,22 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} + +
    + {{#questions}} +
  1. {{questiontitle}}
  2. + {{/questions}} +
\ No newline at end of file diff --git a/server/moodle/mod/livequiz/templates/take_quiz_button.mustache b/server/moodle/mod/livequiz/templates/take_quiz_button.mustache new file mode 100644 index 000000000..60964d957 --- /dev/null +++ b/server/moodle/mod/livequiz/templates/take_quiz_button.mustache @@ -0,0 +1,38 @@ +{{! + This file is part of Moodle - http://moodle.org/ + + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template mod_livequiz/index_page + + Index Page for the livequiz plugin + + Classes required for JS: + * none + + Context variables required for this template: + * fill in later + + Example context (json): + * fill in later + +}} + {{#hasquestions}} + Take Quiz + {{/hasquestions}} + + {{^hasquestions}} +

There are no questions present in this quiz

+ {{/hasquestions}} diff --git a/server/moodle/mod/livequiz/tests/behat/add_livequiz.feature b/server/moodle/mod/livequiz/tests/behat/add_livequiz.feature index 7be494656..1755596d3 100644 --- a/server/moodle/mod/livequiz/tests/behat/add_livequiz.feature +++ b/server/moodle/mod/livequiz/tests/behat/add_livequiz.feature @@ -39,5 +39,5 @@ Scenario: Add a livequiz to a course And I should see "livequiz" When I click on "livequiz" "link" in the "livequiz" activity And I should see "livequiz" - And I should see "index_page" + And I should see "Quiz editor page" diff --git a/server/moodle/mod/livequiz/tests/behat/behat_mod_livequiz.php b/server/moodle/mod/livequiz/tests/behat/behat_mod_livequiz.php index 0889be599..7da961cda 100644 --- a/server/moodle/mod/livequiz/tests/behat/behat_mod_livequiz.php +++ b/server/moodle/mod/livequiz/tests/behat/behat_mod_livequiz.php @@ -46,6 +46,23 @@ public function assertischecked($element): void { $this->assertSession()->checkboxChecked($element); } + /** + * Asserts the id for a checkbox or radio button. + * @Then the :selectorType with id :checkboxid should exist + */ + public function assertelementidexists($checkboxid) { + $this->assertSession()->elementExists('css', '#' . $checkboxid); + } + + /** + * Asserts the id for a checkbox or radio button. + * @Then the :selectorType with id :checkboxid should not exist + */ + public function assertelemtenidnotexists($checkboxid) { + $this->assertSession()->elementNotExists('css', '#' . $checkboxid); + } + + /** * Asserts whether the given element is not checked. * @Then the :checkbox answer should not be checked diff --git a/server/moodle/mod/livequiz/tests/behat/behatdata.json b/server/moodle/mod/livequiz/tests/behat/behatdata.json index 7fd027ef2..bbde895b1 100644 --- a/server/moodle/mod/livequiz/tests/behat/behatdata.json +++ b/server/moodle/mod/livequiz/tests/behat/behatdata.json @@ -10,8 +10,8 @@ "questions": [ { "id": "1", - "title": "Which of the following cities is in France?", - "description": "This is about France", + "title": "Question 1", + "description": "Which of the following cities is in France?", "explanation": "Paris is a city in France, Nice is a city in France", "timelimit": "60", "answers": [ @@ -37,9 +37,9 @@ }, { "id": "2", - "title": "What is the Capital of Denmark?", - "description": "This is about Denmark", - "explanation": "The Capital of Denmark is Copenhagen", + "title": "Question 2", + "description": "What is the Capital of Denmark?", + "explanation": "Danish capital", "timelimit": "60", "answers": [ { @@ -64,8 +64,8 @@ }, { "id": "3", - "title": "Is Hamburg in Germany?", - "description": "German cities", + "title": "Question 3", + "description": "Is Hamburg in Germany?", "explanation": "Hamburg is a city in Northern Germany", "timelimit": "60", "answers": [ diff --git a/server/moodle/mod/livequiz/tests/behat/click_livequiz.feature b/server/moodle/mod/livequiz/tests/behat/click_livequiz.feature index 29ef4a1a3..32445ebd7 100644 --- a/server/moodle/mod/livequiz/tests/behat/click_livequiz.feature +++ b/server/moodle/mod/livequiz/tests/behat/click_livequiz.feature @@ -32,7 +32,7 @@ Scenario: Open a livequiz on course And I should see "Live Quiz" And I should see "livequiz_europe_quiz" And I should see "Test description" - And I should see "index_page" + And I should see "Quiz editor page" Scenario: Delete livequiz #Testing we can delete the livequiz activity diff --git a/server/moodle/mod/livequiz/tests/behat/displayquiz_livequiz.feature b/server/moodle/mod/livequiz/tests/behat/displayquiz_livequiz.feature index 6fc8ea1eb..2551056d3 100644 --- a/server/moodle/mod/livequiz/tests/behat/displayquiz_livequiz.feature +++ b/server/moodle/mod/livequiz/tests/behat/displayquiz_livequiz.feature @@ -32,8 +32,8 @@ Feature: View livequiz activity When I click on "livequiz_europe_quiz" "link" in the "livequiz" activity And I should see "Take Quiz" And I click on "Take Quiz" "link" + Then I should see "Question 1" And I should see "Which of the following cities is in France?" - And I should see "This is about France" #The answer options are shown as checkboxes And "Paris" "checkbox" should exist And "Champagne" "checkbox" should exist @@ -41,8 +41,8 @@ Feature: View livequiz activity And "Next Question" "link" should exist And I should not see "Previous Question" And I click on "Next Question" "link" - Then I should see "What is the Capital of Denmark?" - And I should see "This is about Denmark" + Then I should see "Question 2" + And I should see "What is the Capital of Denmark?" #The answer options are shown as radio buttons And "Aarhus" "radio" should exist And "Aalborg" "radio" should exist @@ -50,15 +50,15 @@ Feature: View livequiz activity And "Next Question" "link" should exist And "Previous Question" "link" should exist And I click on "Next Question" "link" - Then I should see "Is Hamburg in Germany?" - And I should see "German cities" + Then I should see "Question 3" + And I should see "Is Hamburg in Germany?" And "Yes" "radio" should exist And "No" "radio" should exist And "Previous Question" "link" should exist And I should not see "Next Question" And I click on "Previous Question" "link" - Then I should see "What is the Capital of Denmark?" - And I should see "This is about Denmark" + Then I should see "Question 2" + And I should see "What is the Capital of Denmark?" And "Aarhus" "radio" should exist And "Aalborg" "radio" should exist And "Copenhagen" "radio" should exist diff --git a/server/moodle/mod/livequiz/tests/behat/quizcreator_livequiz.feature b/server/moodle/mod/livequiz/tests/behat/quizcreator_livequiz.feature new file mode 100644 index 000000000..2cde107e2 --- /dev/null +++ b/server/moodle/mod/livequiz/tests/behat/quizcreator_livequiz.feature @@ -0,0 +1,100 @@ +@mod @mod_livequiz @javascript + +Feature: View livequiz activity + as a student + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + And the following "courses" exist: + | fullname | shortname | + | Test Course | TC | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | TC | editingteacher | + And the following "activity" exists: + # Create a livequiz activity before the test + | activity | livequiz | + | course | TC | + | idnumber | 1 | + | name | livequiz_europe_quiz | + | intro | Test description | + | section | 0 | + And I use demodata for the course "TC" and activity "livequiz" + And I log in as "teacher1" + And I am on "Test Course" course homepage with editing mode on + + Scenario: Add questions to a livequiz + When I click on "livequiz_europe_quiz" "link" in the "livequiz" activity + Then I should see "Quiz editor page" + And "Add Question" "button" should exist + And I should see "Saved Questions" + And "Question 1" "list_item" should exist + And "Question 2" "list_item" should exist + And "Question 3" "list_item" should exist + And I click on "Add Question" "button" + Then the "field" with id "question_title_id" should exist + Then the "field" with id "question_description_id" should exist + Then the "field" with id "question_explanation_id" should exist + And "Add Answer" "button" should exist + And "Save Question" "button" should exist + And "Discard" "button" should exist + Then I set the field "question_title_id" to "Geography 1" + Then I set the field "question_description_id" to "What is the Capital of Sweden?" + Then I set the field "question_explanation_id" to "Stockholm is the capital of Sweden" + Then I click on "Add Answer" "button" + Then the "button" with id "delete_answer_button_1" should exist + Then the "field" with id "answer_input_1" should exist + Then the "checkbox" with id "answer_checkbox_1" should exist + And I set the field "answer_input_1" to "Stockholm" + And I click on "answer_checkbox_1" "checkbox" + Then I click on "Add Answer" "button" + Then the "field" with id "answer_input_2" should exist + Then the "button" with id "delete_answer_button_2" should exist + Then the checkbox with id "answer_checkbox_2" should exist + # Next step should be deleted when css is fixed + And I click on "Close block drawer" "button" + And I click on "delete_answer_button_2" "button" + Then the "field" with id "answer_input_2" should not exist + And I click on "Add Answer" "button" + # The id of answers are just counted up thus the id is 3 + Then the "field" with id "answer_input_3" should exist + Then the "button" with id "delete_answer_button_3" should exist + Then the checkbox with id "answer_checkbox_3" should exist + And I set the field "answer_input_3" to "Malmö" + And I click on "Save Question" "button" + Then "Geography 1" "list_item" should exist + + Scenario: Discard questions for livequiz + When I click on "livequiz_europe_quiz" "link" in the "livequiz" activity + Then I should see "Quiz editor page" + And "Add Question" "button" should exist + And I should see "Saved Questions" + And "Question 1" "list_item" should exist + And "Question 2" "list_item" should exist + And "Question 3" "list_item" should exist + And I click on "Add Question" "button" + Then the "field" with id "question_title_id" should exist + Then the "field" with id "question_description_id" should exist + Then the "field" with id "question_explanation_id" should exist + And "Add Answer" "button" should exist + And "Save Question" "button" should exist + And "Discard" "button" should exist + # Next step should be deleted when css is fixed + And I click on "Close block drawer" "button" + Then I set the field "question_title_id" to "Geography 1" + Then I set the field "question_description_id" to "What is the Capital of Sweden?" + Then I set the field "question_explanation_id" to "Stockholm is the capital of Sweden" + And I click on "Discard" "button" + Then I should see "Are you sure you want to discard changes?" + And "No" "button" should exist + And "Yes" "button" should exist + And I click on "No" "button" + Then the field "question_description_id" matches value "What is the Capital of Sweden?" + And I click on "Discard" "button" + And I click on "Yes" "button" + Then "Question 1" "list_item" should exist + And "Question 2" "list_item" should exist + And "Question 3" "list_item" should exist + And "What is the Capital of Sweden?" "list_item" should not exist \ No newline at end of file diff --git a/server/moodle/mod/livequiz/tests/behat/show_participations.feature b/server/moodle/mod/livequiz/tests/behat/show_participations.feature index fdd14e2e8..63e7a59ec 100644 --- a/server/moodle/mod/livequiz/tests/behat/show_participations.feature +++ b/server/moodle/mod/livequiz/tests/behat/show_participations.feature @@ -30,7 +30,7 @@ Feature: View livequiz activity When I click on "livequiz_europe_quiz" "link" in the "livequiz" activity And I should see "Take Quiz" And I click on "Take Quiz" "link" - And I should see "This is about France" + And I should see "Which of the following cities is in France?" And "Paris" "checkbox" should exist And I click on "Paris" "checkbox" And "Next Question" "link" should exist @@ -44,7 +44,7 @@ Feature: View livequiz activity And "Submit Quiz" "button" should exist And I click on "Submit Quiz" "button" Then I should see "Results for attempt" - Then I should see "This is about France" + Then I should see "Which of the following cities is in France?" Then the "Paris" answer should be checked Then the "Copenhagen" answer should be checked Then the "Yes" answer should be checked @@ -54,7 +54,7 @@ Feature: View livequiz activity #Making another participation to test multiple can be shown And I should see "Take Quiz" And I click on "Take Quiz" "link" - And I should see "This is about France" + And I should see "Which of the following cities is in France?" And "Paris" "checkbox" should exist Then the "Paris" answer should not be checked And "Nice" "checkbox" should exist @@ -79,7 +79,7 @@ Feature: View livequiz activity Then "Go to participation 1" "button" should exist And "Go to participation 2" "button" should exist And I press "Go to participation 1" - Then I should see "This is about France" + Then I should see "Which of the following cities is in France?" Then the "Nice" answer should be checked Then the "Aalborg" answer should be checked Then the "No" answer should be checked diff --git a/server/moodle/mod/livequiz/tests/phpunit/externalclasstest/save_question_test.php b/server/moodle/mod/livequiz/tests/phpunit/externalclasstest/save_question_test.php new file mode 100644 index 000000000..3171f6cbd --- /dev/null +++ b/server/moodle/mod/livequiz/tests/phpunit/externalclasstest/save_question_test.php @@ -0,0 +1,89 @@ +. + +/** + * Unit tests for external class submit_quiz. + * + * @package mod_livequiz + * @category test + * @copyright 2023 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_livequiz; + +use advanced_testcase; +use dml_exception; +use mod_livequiz\external\save_question; +use mod_livequiz\models\livequiz; +use mod_livequiz\models\question; +use ReflectionClass; +use function PHPUnit\Framework\assertEquals; + +/** + * Unit tests for external class save_question. + */ +final class save_question_test extends advanced_testcase { + /** + * Test the save_question external function. + * This function tests if a question can be saved to the database. + * @covers \mod_livequiz\external\save_question::new_question + */ + public function test_new_question(): void { + $this->resetAfterTest(); + $questiondata = [ + 'title' => 'Question Title 1', + 'description' => 'Question Description 1', + 'explanation' => 'Question Explanation 1', + 'answers' => [ + [ + 'description' => 'Answer 1', + 'correct' => 1, + 'explanation' => 'Answer 1 Explanation', + ], + [ + 'description' => 'Answer 2', + 'correct' => 0, + 'explanation' => 'Answer 2 Explanation', + ], + ], + ]; + + $question = self::call_new_question([$questiondata]); + $answers = $question->get_answers(); + + $this->assertInstanceOf(question::class, $question); + assertEquals(0, $question->get_id()); + assertEquals($questiondata['title'], $question->get_title()); + assertEquals($questiondata['description'], $question->get_description()); + assertEquals($questiondata['explanation'], $question->get_explanation()); + assertEquals(2, count($answers)); + assertEquals($questiondata['answers'][0]['description'], $answers[0]->get_description()); + assertEquals($questiondata['answers'][0]['correct'], $answers[0]->get_correct()); + assertEquals($questiondata['answers'][0]['explanation'], $answers[0]->get_explanation()); + assertEquals($questiondata['answers'][1]['description'], $answers[1]->get_description()); + assertEquals($questiondata['answers'][1]['correct'], $answers[1]->get_correct()); + assertEquals($questiondata['answers'][1]['explanation'], $answers[1]->get_explanation()); + } + /** + * Allows calling of private function new_question + * @covers \mod_livequiz\external\save_question::new_question + */ + private static function call_new_question(array $questiondata): question { + $class = new ReflectionClass(save_question::class); + $method = $class->getMethod('new_question'); + return $method->invokeArgs(null, $questiondata); + } +} diff --git a/server/moodle/mod/livequiz/tests/phpunit/test_utility.php b/server/moodle/mod/livequiz/tests/phpunit/test_utility.php index 8c2f16ffd..76c3be43e 100644 --- a/server/moodle/mod/livequiz/tests/phpunit/test_utility.php +++ b/server/moodle/mod/livequiz/tests/phpunit/test_utility.php @@ -29,7 +29,7 @@ * @copyright 2024 Software AAU * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -final class test_utility extends advanced_testcase { +final class test_utility { /** * Function to construct a livequiz using reflection to access the private constructor * @param int $testid diff --git a/server/moodle/mod/livequiz/version.php b/server/moodle/mod/livequiz/version.php index 579611255..e73529895 100644 --- a/server/moodle/mod/livequiz/version.php +++ b/server/moodle/mod/livequiz/version.php @@ -28,6 +28,6 @@ $plugin = new stdClass(); // Initialize $plugin as an object. -$plugin->version = 2024072528; +$plugin->version = 2024072530; $plugin->requires = 2024041600; $plugin->component = 'mod_livequiz'; diff --git a/server/moodle/mod/livequiz/view.php b/server/moodle/mod/livequiz/view.php index b6561c92b..ee7da012b 100644 --- a/server/moodle/mod/livequiz/view.php +++ b/server/moodle/mod/livequiz/view.php @@ -23,13 +23,13 @@ require_once('../../config.php'); require_once($CFG->libdir . '/accesslib.php'); // Include the access library for context_module. -require_once('readdemodata.php'); -use mod_livequiz\output\index_page; +use mod_livequiz\output\index_page_student; +use mod_livequiz\output\index_page_teacher; global $OUTPUT, $PAGE, $DB, $USER; -$cmid = required_param('id', PARAM_INT); // Course module ID. +$cmid = required_param('id', PARAM_INT); // Course module ID. (the param has to be named "id" as moodle will send id and not cmid). [$course, $cm] = get_course_and_cm_from_cmid($cmid, 'livequiz'); $instance = $DB->get_record('livequiz', ['id' => $cm->instance], '*', MUST_EXIST); @@ -39,21 +39,28 @@ throw new moodle_exception('usernotauthenticated', 'error', '', null, 'User is not logged in or user data is missing.'); } - $context = context_module::instance($cm->id); // Set the context for the course module. $PAGE->set_cacheable(false); $PAGE->set_context($context); // Make sure to set the page context. +$PAGE->requires->css('/mod/livequiz/style.css'); // Adds styling to the page. -$PAGE->set_url(new moodle_url('/mod/livequiz/view.php', ['cmid' => $cmid])); +$PAGE->set_url(new moodle_url('/mod/livequiz/view.php', ['id' => $cmid])); $PAGE->set_title(get_string('modulename', 'mod_livequiz')); $PAGE->set_heading(get_string('modulename', 'mod_livequiz')); // Rendering. -$output = $PAGE->get_renderer('mod_livequiz'); -$renderable = new index_page($cmid, $instance->id, $USER->id); +$output = ""; +$renderer = $PAGE->get_renderer('mod_livequiz'); +if (has_capability('moodle/course:manageactivities', $context)) { + $renderable = new index_page_teacher($instance->id, $USER->id, $cmid); + $output = $renderer->render_index_page_teacher($renderable); +} else { + $renderable = new index_page_student($cmid, $instance->id, $USER->id); + $output = $renderer->render_index_page_student($renderable); +} unset($_SESSION['completed']); echo $OUTPUT->header(); -echo $output->render($renderable); +echo $output; echo $OUTPUT->footer();