/* global Dexie, cornerstone, cornerstoneTools, cornerstoneBase64ImageLoader, cornerstoneWebImageLoader, cornerstoneWADOImageLoader */ import * as helper from "./helpers.js"; import * as viewer from "./viewer.js"; // const { v4: uuidv4 } = require('uuid'); // cid = null; window.aid = null; window.cid = 1234; window.eid = 5678; window.exam_mode = false; window.packet_list = []; window.packet_name = null; window.questions = null; window.question_order = []; window.number_of_questions = null; window.question_type = null; window.loaded_question = null; window.review = false; window.exam_time = 60; window.date_started = null; window.score = 0; window.allow_self_marking = true; window.timer = null; //window.dfile = null; retrievePacketList(); /** * Retrieves a list of available packets via JSON ajax requests * Will initially try /packets/ (for example if index.php generates the list) * and then fallback to packets.json */ function retrievePacketList() { $.getJSON("packets/", function (data) { loadPacketList(data); }) .done(function () {}) .fail(function () { $.getJSON("packets/packets.json", function (data) { loadPacketList(data); }).fail(function (jqXHR, textStatus, errorThrown) { console.log(jqXHR); console.log(textStatus); console.log(errorThrown); console.log("No packet list available"); showLoginDialog(); }); }) .always(function () {}); } /** * Generate a list of the packets that are available to be loaded * * @param {JSON} data - json containing available packets */ async function loadPacketList(data) { let sessions = await window.db.session.toArray().catch(function(error) { console.log("Error loading session", error); $("#database-error").text("Error loading the database, schema has probably changed and needs updating. You will probably need to delete the local database.") let delete_button = $("

' ); ap.append( '' ); // Handle changing display of optional answer fields and saving of data $("#rapid-option").change(function (evt) { if (evt.target.value == "Abnormal") { $("#rapid-text").css("display", "block"); } else { $("#rapid-text").css("display", "none"); } db.answers.put({ aid: aid, cid: cid, eid: eid, qid: qid, qidn: "1", ans: evt.target.value, }); saveSession(); updateQuestionListPanel(); }); // Save long answers on textchange $(".long-answer").change(function (evt) { // ignore blank text and delete any stored value if (evt.target.value.length < 1) { db.answers.delete([aid, cid, eid, qid, "2"]); $( "#question-list-item-" + window.question_order.indexOf(qid) + "-2" ).removeClass("question-saved-localdb"); return; } // db.answers.put({aid: [cid, eid, qidn], ans: evt.target.value}); db.answers.put({ aid: aid, cid: cid, eid: eid, qid: qid, qidn: "2", ans: evt.target.value, }); $( "#question-list-item-" + window.question_order.indexOf(qid) + "-2" ).addClass("question-saved-localdb"); saveSession(); updateQuestionListPanel(); }); // We chain our db requests as we can only check answers once // they have been loaded (should probably use then or after) db.answers .get({ aid: aid, cid: cid, eid: eid, qid: qid, qidn: "1" }) .then(function (answer) { if (answer != undefined) { $("#rapid-option option:contains(" + answer.ans + ")").prop( "selected", true ); // For some reason a change event is not fired... if (answer.ans == "Abnormal") { $("#rapid-text").css("display", "block"); } $("#rapid-option-not-answered").remove(); } }) .catch(function (error) { console.log("DB", cid, eid, qid); console.log("error-", error); }) .then( db.answers .get({ aid: aid, cid: cid, eid: eid, qid: qid, qidn: "2" }) .then(function (answer) { if (answer != undefined) { console.log(answer); $(".long-answer").text(answer.ans); } markAnswer(qid, current_question); }) .catch(function (error) { console.log("error-", error); }) ); addFlagEvents(); loadFlagsFromDb(qid, "1"); loadFlagsFromDb(qid, "2"); break; } case "anatomy": { ap.append( '

' + display_n + '.1' + current_question.question + '

' ); $(".long-answer").change(function (evt) { // ignore blank text and delete any stored value if (evt.target.value.length < 1) { db.answers.delete([aid, cid, eid, qid, "1"]); $( "#question-list-item-" + window.question_order.indexOf(qid) + "-1" ).removeClass("question-saved-localdb"); return; } // db.answers.put({aid: [cid, eid, qidn], ans: evt.target.value}); db.answers.put({ aid: aid, cid: cid, eid: eid, qid: qid, qidn: "1", ans: evt.target.value, }); $( "#question-list-item-" + window.question_order.indexOf(qid) + "-1" ).addClass("question-saved-localdb"); saveSession(); updateQuestionListPanel(); }); db.answers .get({ aid: aid, cid: cid, eid: eid, qid: qid, qidn: "1" }) .then(function (answer) { if (answer != undefined) { $(".long-answer").text(answer.ans); } markAnswer(qid, current_question); }) .catch(function (error) { console.log(error); }); addFlagEvents(); loadFlagsFromDb(qid, "1"); break; } case "long": { ap.append( '

' + display_n + '.1Observations

' ); ap.append( '

' + display_n + '.2Interpretation

' ); ap.append( '

' + display_n + '.3Principal Diagnosis

' ); ap.append( '

' + display_n + '.4Differential Diagnosis

' ); ap.append( '

' + display_n + '.5Management (if appropriate)

' ); // Save long answers on textchange $(".long-answer").change(function (evt) { const qidn = evt.target.dataset.qidn.toString(); // ignore blank text and delete any stored value if (evt.target.value.length < 1) { db.answers.delete([aid, cid, eid, qid, qidn]); $( "#question-list-item-" + window.question_order.indexOf(qid) + "-" + qidn ).removeClass("question-saved-localdb"); return; } // db.answers.put({aid: [cid, eid, qidn], ans: evt.target.value}); db.answers.put({ aid: aid, cid: cid, eid: eid, qid: qid, qidn: qidn, ans: evt.target.value, }); console.log("SAVE", qid, qidn, evt.target.value); $( "#question-list-item-" + window.question_order.indexOf(qid) + "-" + qidn ).addClass("question-saved-localdb"); saveSession(); updateQuestionListPanel(); }); // Loop through the 5 text areas and retrieve from db // could all be retrieved at once with an addition key index // for (let qidn_count = 1; qidn_count < 6; qidn_count++) { db.answers .get({ aid: aid, cid: cid, eid: eid, qid: qid, qidn: qidn_count.toString(), }) .then(function (answer) { if (answer != undefined) { $(".long-answer") .eq(parseInt(answer.qidn) - 1) .text(answer.ans); } // We only want to mark once... if (qidn_count == 5) { markAnswer(qid, current_question); } }) .catch(function (error) { console.log(error); }); } addFlagEvents(); loadFlagsFromDb(qid, "1"); loadFlagsFromDb(qid, "2"); loadFlagsFromDb(qid, "3"); loadFlagsFromDb(qid, "4"); loadFlagsFromDb(qid, "5"); break; } } updateQuestionListPanel(); setFocus(section); } /** * Sets the focus to the required question answer section * @param {number} section - section to focus */ function setFocus(section) { // Horrible (but it works) setTimeout(function () { // In pratique it selects the end of a textarea but I'm not sure if I can be bothered... // Apparently I can... const el = $("*[data-answer-section-qidn=" + section + "]"); // If the target is a textarea we move focus and reapply the value // to move the cursor to the end if (el.length < 1) { return; } if (el.get(0).type == "textarea") { const val = el.val(); el.focus().val("").val(val); // else we just focus } else { el.focus(); } }, 200); } /** * Updates the question list panel * Test with just a global update (may need to do these individually * if it gets slow...) */ function updateQuestionListPanel() { const cid = window.cid; const eid = window.eid; console.log("UP"); // Reset all classes db.answers .where({ aid: aid, cid: cid, eid: eid }) .toArray() .then(function (answers) { $(".question-list-panel div") .slice(1) .attr("class", "question-list-item"); answers.forEach(function (answer, n) { $( "#question-list-item-" + window.question_order.indexOf(answer.qid) + "-" + answer.qidn ).addClass("question-saved-localdb"); if (window.question_type == "rapid" && answer.qidn == "1") { if (answer.ans == "Abnormal") { $( "#question-list-item-" + window.question_order.indexOf(answer.qid) + "-2" ).css("display", "block"); } else { $( "#question-list-item-" + window.question_order.indexOf(answer.qid) + "-2" ).css("display", "none"); } } }); // $(".long-answer").text(answer.ans); }) .catch(function (error) { console.log("error - ", error); }); db.flags .where({ aid: aid, cid: cid, eid: eid }) .toArray() .then(function (answers) { answers.forEach(function (answer, n) { $( "#question-list-item-" + window.question_order.indexOf(answer.qid) + "-" + answer.qidn + " span" ).css("visibility", "visible"); }); // $(".long-answer").text(answer.ans); }) .catch(function (error) { console.log("error - ", error); }); } /** * Builds the QuestionListPanel */ function createQuestionListPanel() { $(".question-list-panel").empty(); $(".question-list-panel").append( '
Questions
' ); /** * Appends QuestionList item * @param {number} n - question number * @param {number} qidn - question section number * @return {*} el - element that has been created */ function appendQuestionListItem(n, qidn) { const qn = n - 1; const el = $( '
' + n + "." + qidn + '
' ); $(".question-list-panel").append(el); // return the new element so it can be hidden if necessary return el; } if (window.question_type == "rapid") { // Loop through all questions and append list items for (let n = 1; n < window.number_of_questions + 1; n++) { appendQuestionListItem(n, "1"); appendQuestionListItem(n, "2").hide(); } } else if (window.question_type == "anatomy") { for (let n = 1; n < window.number_of_questions + 1; n++) { appendQuestionListItem(n, "1"); } } else if (window.question_type == "long") { for (let n = 1; n < window.number_of_questions + 1; n++) { appendQuestionListItem(n, "1"); appendQuestionListItem(n, "2"); appendQuestionListItem(n, "3"); appendQuestionListItem(n, "4"); appendQuestionListItem(n, "5"); } } $(".question-list-item").click(function (evt) { loadQuestion($(this).attr("data-qid"), $(this).attr("data-qidn")); }); } /** * Handle key button events * These are the same as in the RCR pratique implementation * @param {*} event - KeyEvent */ function keydown_handler(event) { const target_element = event.target.tagName; // Catch all keypresses unless user is typing if (target_element == "INPUT" || target_element == "TEXTAREA") { return; } const sel = $(".control-overlay").get(0); switch (event.code) { case "KeyP": sel.selectedIndex = viewer.find_option(sel, "pan"); viewer.changeControlSelection(); break; case "KeyZ": sel.selectedIndex = viewer.find_option(sel, "zoom"); viewer.changeControlSelection(); break; case "KeyR": sel.selectedIndex = viewer.find_option(sel, "rotate"); viewer.changeControlSelection(); break; case "KeyS": sel.selectedIndex = viewer.find_option(sel, "scroll"); viewer.changeControlSelection(); break; case "KeyW": sel.selectedIndex = viewer.find_option(sel, "window"); viewer.changeControlSelection(); break; case "KeyA": sel.selectedIndex = viewer.find_option(sel, "abdomen"); viewer.changeControlSelection(); break; case "KeyU": sel.selectedIndex = viewer.find_option(sel, "pulmonary"); viewer.changeControlSelection(); break; case "KeyB": sel.selectedIndex = viewer.find_option(sel, "brain"); viewer.changeControlSelection(); break; case "KeyO": sel.selectedIndex = viewer.find_option(sel, "bone"); viewer.changeControlSelection(); break; case "KeyE": sel.selectedIndex = viewer.find_option(sel, "reset"); // $("#dicom-image").get(0).scrollIntoView(false); viewer.changeControlSelection(); break; // Escape and C do the same case "KeyC": case "Escape": sel.selectedIndex = viewer.find_option(sel, "close"); viewer.changeControlSelection(); break; case "Enter": t.focus(); break; // TODO: implement arrow stuff // case "ArrowUp": { // Arrow function depends on the currently selected option switch (sel.options[sel.selectedIndex].value) { case "pan": viewer.manualPanDicom(0, -1); break; case "zoom": // _this.current_dicom.current_view.pre_scale_at(1.01, 1.01, _this.current_dicom.cols / 2, _this.current_dicom.rows / 2); viewer.manualZoomDicom(1); break; case "rotate": viewer.manualRotateDicom(-1); break; case "scroll": viewer.manualScrollDicom(-1); break; case "window": viewer.manualWindowDicom(1, 0); break; } break; } case "ArrowDown": { switch (sel.options[sel.selectedIndex].value) { case "pan": viewer.manualPanDicom(0, 1); break; case "zoom": viewer.manualZoomDicom(-1); break; case "rotate": viewer.manualRotateDicom(1); break; case "scroll": viewer.manualScrollDicom(1); break; case "window": viewer.manualWindowDicom(-1, 0); break; } break; } case "ArrowLeft": { switch (sel.options[sel.selectedIndex].value) { case "pan": viewer.manualPanDicom(-1, 0); break; case "zoom": viewer.manualZoomDicom(-1); break; case "rotate": viewer.manualRotateDicom(1); break; case "scroll": viewer.manualScrollDicom(1); break; case "window": viewer.manualWindowDicom(0, -1); break; } break; } case "ArrowRight": { switch (sel.options[sel.selectedIndex].value) { case "pan": viewer.manualPanDicom(1, 0); break; case "zoom": viewer.manualZoomDicom(1); break; case "rotate": viewer.manualRotateDicom(-1); break; case "scroll": viewer.manualScrollDicom(-1); break; case "window": viewer.manualWindowDicom(0, 1); break; } break; } } event.preventDefault(); } // Register Key Event Listener window.addEventListener("keydown", keydown_handler); $("#btn-local-file-load").click(function (evt) { loadLocalQuestionSet(); }); $("#submit-button").click(function (evt) { submitAnswers(); }); $("#review-button").click(function (evt) { $(".question-list-panel").toggle(); }); $("#options-button").click(function (evt) { $("#options-panel").toggle(); }); $("#review-overlay-button").click(function (evt) { if (window.review == true) { reviewQuestions(); } else { $("#finish-dialog").modal(); } }); $("#finish-exam, #time-up-review-button").click(function (evt) { window.review = true; reviewQuestions(); $.modal.close(); }); $("#time-up-continue-button").click(function (evt) { $.modal.close(); }); $("#finish-cancel").click(function (evt) { $.modal.close(); }); $("#overlay-close").click(function (evt) { $("#options-panel").hide(); }); $("#review-overlay-close").click(function (evt) { $("#review-overlay").hide(); }); /** * Gets a json representation of answers * @return {JSON} - answers */ function getJsonAnswers() { const cid = window.cid; const eid = window.eid; return db.answers.where({ aid: aid, cid: cid, eid: eid }).toArray(); // .then(function(ans) { // console.log(ans); // submitAnswers(ans); // }); } /** * Posts answers to url * @param {*} ans - json representation of answers */ function postAnswers(ans) { console.log(ans); // ans = {"test" : 1} $.post("http://localhost:8000/submit_answers", JSON.stringify(ans)).done( (data) => { console.log(data); } ); // $.post( "http://localhost:8000/submit_answers", JSON.stringify(ans)); } /** * Loads a local question set (from a file) * This is call in index.html */ function loadLocalQuestionSet() { let input; let file; let fr; if (typeof window.FileReader !== "function") { alert("The file API isn't supported on this browser yet."); return; } input = document.getElementById("fileinput"); if (!input) { alert("No fileinput element."); } else if (!input.files) { alert( "This browser doesn't seem to support the `files` property of file inputs." ); } else if (!input.files[0]) { alert("Please select a file before clicking 'Load'"); } else { file = input.files[0]; fr = new FileReader(); fr.onload = receivedText; fr.readAsText(file); } /** * Reads text from event and loads the packet * @param {*} e - event */ function receivedText(e) { const lines = e.target.result; let j = JSON.parse(lines); setUpPacket(j); $("#options-panel").hide(); } } /** * Submits answers */ function submitAnswers() { console.log( getJsonAnswers().then((a) => { postAnswers(a); }) ); } /** * Displays the review question panel with a summary of marks */ function reviewQuestions() { // Stop timer window.timer.stop(); const cid = window.cid; const eid = window.eid; $("#review-overlay").show(); $("#review-answer-list").empty(); $("#review-answer-table").empty(); $("#review-score").empty(); $("#exam-stats").hide(); loadQuestion(0, 0, true); db.answers .where({ aid: aid, cid: cid, eid: eid }) .toArray() .then(function (answers) { let current_answers = {}; answers.forEach(function (arr, n) { let answer = arr["ans"]; if (answer == undefined) { answer = "Not answered"; } current_answers[[arr["qid"], arr["qidn"]]] = answer; }); let correct_count = 0; let not_answered = 0; let answered_normal = 0; let answered_abnormal = 0; let undercall_number = 0; let overcall_number = 0; let incorrectcall_number = 0; window.question_order.forEach(function (qid, n) { if (window.question_type == "long") { $("#review-answer-table").append( $( `Question ${n + 1}` ).click(() => { loadQuestion(n); $("#review-overlay").hide(); }) ); // $("#review-answer-table").append(``); $("#review-answer-table").append( `Your answersModel answers` ); const model_answers = window.questions[qid].answers; const titles = [ "Observations", "Interpretation", "Principal Diagnosis", "Differential Diagnosis", "Management", ]; // let user_td = $( // `#review-answer-${qid} td .review-user-answer-cell` // ); // let model_td = $( // `#review-answer-${qid} td .review-model-answer-cell` // ); titles.forEach((title, n) => { let user_answer = current_answers[`${qid},${n}`]; if (user_answer == undefined) { user_answer = "Not answered"; } let user_ans = "

" + title + "

" + user_answer; let model_ans = `

` + title + "

" + model_answers[0][title.toLowerCase()]; $("#review-answer-table").append( `${user_ans}${model_ans}` ); }); return; } $("#review-answer-list").append( "
  • Question " + (n + 1) + ": Not answered
  • " ); $("#review-answer-list a").click(function (evt) { loadQuestion(this.dataset.qid); $("#review-overlay").hide(); }); db.user_answers .get({ qid: qid }) .then(function (answers) { // Merge the question answers with the user saved answers let question_answers = window.questions[qid]["answers"]; if (answers != undefined) { question_answers = question_answers.concat(answers.ans); } let section_1_answer = current_answers[[qid, "1"]]; let section_2_answer = current_answers[[qid, "2"]]; if (section_1_answer == undefined) { section_1_answer = "Not Answered"; not_answered++; } if (section_2_answer == undefined) { if (section_1_answer == "Normal") { section_2_answer = "Normal"; answered_normal++; } else { section_2_answer = "Not Answered"; } } else { answered_abnormal++; } let el = $("#review-answer-" + qid + " span"); /** * Helper function to define how review items are displayed * Yes it is a bit shit * @param {*} el - element * @param {*} c - class * @param {*} user_answer - * @param {*} normal - * @param {*} question_answers - */ function setReviewAnswer( el, c, user_answer, normal, question_answers ) { if (normal) { el.html( "Answer: " + user_answer + " (Normal)" ); } else { el.html( "Answer: " + user_answer + " (Abnormal: " + question_answers.join(", ") + ")" ); } } if (window.question_type == "rapid") { // First check normal vs abnormal if (window.questions[qid]["normal"] == true) { if (section_1_answer == "Normal") { setReviewAnswer( el, "correct", section_1_answer, true, question_answers ); correct_count++; } else { if (section_1_answer != "Not Answered") { overcall_number++; } setReviewAnswer( el, "incorrect", section_1_answer, true, question_answers ); } } else { if (answerInArray(question_answers, section_2_answer)) { correct_count++; setReviewAnswer( el, "correct", section_2_answer, false, question_answers ); } else { setReviewAnswer( el, "incorrect", section_2_answer, false, question_answers ); if (section_1_answer == "Not Answered") { } else if (section_1_answer == "Normal") { undercall_number++; } else { // Incorrect calls could be correct if // the answer is not in the database incorrectcall_number++; el.append( "[Mark correct]" ).click(() => { markCorrect(qid, section_2_answer); reviewQuestions(); }); } } } } else if (window.question_type == "anatomy") { // Anatomy answers are either correct or incorrect if (answerInArray(question_answers, section_1_answer)) { correct_count++; setReviewAnswer( el, "correct", section_1_answer, false, question_answers ); } else { setReviewAnswer( el, "incorrect", section_1_answer, false, question_answers ); } } window.score = correct_count; $("#review-score").text( "Score: " + correct_count + " out of " + window.question_order.length ); if (window.question_type == "rapid") { $("#exam-stats").show(); $("#unanswered-number").html(not_answered); $("#normal-number").html(answered_normal); $("#abnormal-number").html(answered_abnormal); $("#undercall-number").html(undercall_number); $("#overcall-number").html(overcall_number); $("#incorrectcall-number").html(incorrectcall_number); } else { $("#exam-stats").show(); } }) .catch(function (error) { console.log("error-", error); }) .finally(() => { // This will lead to saveSession being called after each question is marked saveSession(); }); }); }) .catch(function (error) { console.log("error - ", error); }); } /** * Marks the loaded question and updates display * @param {*} qid - * @param {*} current_question - */ function markAnswer(qid, current_question) { const type = current_question.type; let option = null; if (window.review == true) { // Disable all possible answer elements $("#rapid-option,.long-answer").attr("disabled", "true"); if (type == "rapid") { option = document.getElementById("rapid-option"); // If the current question is normal if (current_question.normal == true) { $(".answer-panel").append( "
    This is normal
    " ); if (option.value == "Normal") { option.classList.add("correct"); } else { option.classList.add("incorrect"); } // If answer is normal we have nothing else to add. addFeedback(current_question); return; } } if (type == "long") { // For long cases we simple disable the texareas and append the // model answers const model_answers = current_question.answers[0]; for (let key of Object.keys(model_answers)) { $("textarea[name*='" + key + "' i]").after(model_answers[key]); } return; } $(".answer-panel").append( "
    Correct answer(s):
    " ); let ul = $("#answer-list"); let textarea = $(".long-answer"); textarea.addClass("incorrect"); let user_answer = textarea.val(); // Load user saved answers db.user_answers .get({ qid: qid }) .then(function (saved_user_answers) { // check if given answer matches a user saved answer if (saved_user_answers != undefined) { saved_user_answers.ans.forEach(function (saved_answer, n) { ul.append("
  • " + saved_answer + "
  • "); if (compareString(saved_answer, user_answer)) { textarea.removeClass("incorrect").addClass("correct"); } }); } console.log(current_question); // check if given answer matches an answer from the question current_question.answers.forEach(function (answer, n) { ul.append("
  • " + answer + "
  • "); if (compareString(answer, user_answer)) { textarea.removeClass("incorrect").addClass("correct"); } }); // If the given answer is not abnormal it must be incorrect if (type == "rapid" && option.value != "Abnormal") { option.classList.add("incorrect"); } else { // If it is blank it must also be incorrect if ( textarea.hasClass("incorrect") == true && user_answer.replace(/\s/g, "") != "" ) { // Otherwise there is a chance that it may be correct // so we give the option to mark it correct if (window.allow_self_marking) { $(".answer-panel").append( $("").click( function () { markCorrect(qid, user_answer); } ) ); } } } }) .catch(function (error) { console.log("error-", error); }); addFeedback(current_question); } } function addFeedback(current_question) { if ( current_question.hasOwnProperty("feedback") && current_question.feedback.length > 0 ) { $(".answer-panel").append( $( "

    Feedback

    " + current_question.feedback + "
    " ) ); } } function markCorrect(qid, user_answer) { if (user_answer == "") { return; } db.user_answers .get({ qid: qid }) .then(function (answers) { let new_answers = []; if (answers != undefined) { new_answers = answers.ans; } new_answers.push(user_answer); db.user_answers.put({ qid: qid, ans: new_answers }); }) .catch(function (error) { console.log("error-", error); }); if (window.allow_self_marking) { $("#mark-correct").remove(); } let textarea = $(".long-answer").attr("class", "correct"); } function compareString(a, b) { return ( a.toLowerCase().replace(/\s/g, "") == b.toLowerCase().replace(/\s/g, "") ); } function answerInArray(arr, ans) { return ( arr .map((v) => v.toLowerCase().replace(/\s/g, "")) .includes(ans.toLowerCase().replace(/\s/g, "")) == true ); } function addFlagEvents() { const aid = window.aid; const cid = window.cid; const eid = window.eid; // Bind flag change events $("button.flag").click(function (event) { const el = $(this); const nqid = el.attr("data-qid"); const qid = window.question_order[nqid]; const qidn = el.attr("data-qidn"); el.toggleClass("flag flag-selected"); if (el.hasClass("flag")) { $("#question-list-item-" + nqid + "-" + qidn + " span").css( "visibility", "hidden" ); db.flags.delete([aid, cid, eid, qid, qidn]); } else { $("#question-list-item-" + nqid + "-" + qidn + " span").css( "visibility", "visible" ); db.flags.put({ aid: aid, cid: cid, eid: eid, qid: qid, qidn: qidn }); } updateQuestionListPanel(); }); } function loadFlagsFromDb(qid, n) { const cid = window.cid; const eid = window.eid; db.flags .get({ aid: aid, cid: cid, eid: eid, qid: qid, qidn: n }) .then(function (answer) { if (answer != undefined) { $("button.flag, button.flag-selected") .eq(parseInt(answer.qidn) - 1) .toggleClass("flag flag-selected"); } }) .catch(function (error) { console.log(error); }); } function showLoginDialog() { $("#login-dialog").modal(); } $("#start-exam-button").click(function (evt) { window.cid = parseInt($("#candidate-number").val()); $.modal.close(); }); $("#start-packet-button").click(function (evt) { if (window.timer != null) { window.timer.stop(); } window.timer = null; let timer = new easytimer.Timer(); //window.exam_time = 2; timer.start({ countdown: true, startValues: { seconds: window.exam_time } }); timer.addEventListener("secondsUpdated", function (e) { $('#timer').html("Time left: " + timer.getTimeValues().toString()); }); timer.addEventListener("targetAchieved", function (e) { $("#time-up-dialog").modal(); }); window.timer = timer; $("#timer").click(() => { window.timer.pause(); let time_remaining = window.timer.getTimeValues().toString(); $("#pause").empty(); $("#pause").append( $( `
    Session paused
    You have ${time_remaining} remaining.
    ` ).click(() => { $("#pause").hide(); timer.start(); }) ); $("#pause").show(); }); // //const t = 2; // let display = document.querySelector("#timer"); // let timer = new helper.CountDownTimer(window.exam_time); // let timeObj = helper.CountDownTimer.parse(window.exam_time); // format(timeObj.minutes, timeObj.seconds); // timer.onTick(format); // timer.start(); // function format(minutes, seconds) { // minutes = minutes < 10 ? "0" + minutes : minutes; // seconds = seconds < 10 ? "0" + seconds : seconds; // display.textContent = "Time left: " + minutes + ":" + seconds; // if (minutes == 0 && seconds == 0) { // $("#time-up-dialog").modal(); // } // } $.modal.close(); }); $("#btn-delete-databases").click(function (evt) { var r = confirm( "This will delete ALL saved answers (including saved correct answers)!" ); if (r == true) { db.delete() .then(() => { $.notify("Database successfully deleted", "success"); $.notify("The page will now reload", "warn"); setTimeout(() => { location.reload(); }, 2000); console.log("Database successfully deleted"); }) .catch((err) => { $.notify("Error deleting databases", "error"); console.error("Could not delete database"); }) .finally(() => { // Do what should be done next... }); } else { } }); $("#btn-delete-current").click(function (evt) { if (window.question_order.length < 1) { $.notify("No packet is currently loaded", "warn"); return; } db.flags .where("qid") .anyOf(window.question_order) .delete() .then(function (deleteCount) { $.notify("Packet flags deleted", "success"); }) .catch((err) => { $.notify("Error deleting packet flags", "error"); $.notify("You may have to delete all answers this time", "info"); }); db.answers .where("qid") .anyOf(window.question_order) .delete() .then(function (deleteCount) { $.notify("Packet successfully reset", "success"); loadQuestion(0, 0, true); // $.notify("The page will now reload", "warn"); // setTimeout(() => { // location.reload(); // }, 2000); console.log("Deleted " + deleteCount + " objects"); }) .catch((err) => { $.notify("Error reseting packet", "error"); $.notify("You may have to delete all answers this time", "info"); console.log(err); }); }); $("#btn-delete-current-saved-answers").click(function (evt) { if (window.question_order.length < 1) { $.notify("No packet is currently loaded", "warn"); return; } db.user_answers .where("qid") .anyOf(window.question_order) .delete() .then(function (deleteCount) { $.notify("Packet flags deleted", "success"); }) .catch((err) => { $.notify("Error deleting saved answers", "error"); $.notify("You may have to delete all answers this time", "info"); }); }); $(document).ajaxStart(function () { $("#loading").show(); }); $(document).ajaxComplete(function () { $("#loading").hide(); }); function saveSession() { let status = "active"; if (window.review == true) { status = "complete"; } console.log("save session", window.score); let time_values = window.timer.getTimeValues(); let time_remaining = time_values.hours * 60 * 60 + time_values.minutes * 60 + time_values.seconds; db.session.put({ packet: window.packet_name, aid: window.aid, status: status, date: window.date_started, score: window.score, max_score: window.number_of_questions, exam_time: window.exam_time, time_left: time_remaining, question_order: window.question_order, questions_answered: 0, // TODO total_questions: window.number_of_questions, }); }