From 792026eb98640a8266b49d908cbfc1c04d21009f Mon Sep 17 00:00:00 2001 From: Ross Date: Fri, 12 Mar 2021 13:47:58 +0000 Subject: [PATCH] fix caching system --- css/main.css | 36 +++- index.html | 9 +- js/helpers.js | 32 +++ js/interact.js | 62 ++++-- js/main.js | 561 +++++++++++++++++++++++++++++++------------------ js/viewer.js | 1 + 6 files changed, 478 insertions(+), 223 deletions(-) diff --git a/css/main.css b/css/main.css index 7a5739e..cd13cf2 100644 --- a/css/main.css +++ b/css/main.css @@ -831,6 +831,23 @@ select option:disabled { transition: all 0.2s; } +.packet-button.cached:after{ + content: "cached"; + width: 12px; + height: 12px; + color: gray; + border-radius: 50%; + position:relative; + transform: translate(15px, 5.5px) rotate(270deg); + display: inline-block; + right: 0; + font-size: 8px; +} + +.packet-button.out-of-date{ + color: darkslategray +} + .save-button { display:inline-block; margin-left: 10px; @@ -1061,4 +1078,21 @@ display: block height: 72px; opacity: 0; } -} \ No newline at end of file +} + +#submit-error-overlay { + opacity: 0.9; + position: absolute; + top: 0; + left: 0; + background-color: black; + width: 100%; + user-select: text; + -moz-user-select: text; + -webkit-user-select: text; + z-index: 5000; +} + +.cache-out-of-date { + color: red; + } \ No newline at end of file diff --git a/index.html b/index.html index 0854bb7..08cadef 100644 --- a/index.html +++ b/index.html @@ -64,7 +64,8 @@
- + +

Available Packets:

@@ -83,6 +84,12 @@
+
+
+
+
    +
+
diff --git a/js/helpers.js b/js/helpers.js index bdf0442..2d5cda7 100644 --- a/js/helpers.js +++ b/js/helpers.js @@ -30,3 +30,35 @@ export function formatBytes(bytes, decimals = 2) { return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; } + +/** + * Format bytes as human-readable text. + * + * @param bytes Number of bytes. + * @param si True to use metric (SI) units, aka powers of 1000. False to use + * binary (IEC), aka powers of 1024. + * @param dp Number of decimal places to display. + * + * @return Formatted string. + */ +export function humanFileSize(bytes, si=false, dp=1) { + const thresh = si ? 1000 : 1024; + + if (Math.abs(bytes) < thresh) { + return bytes + ' B'; + } + + const units = si + ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; + let u = -1; + const r = 10**dp; + + do { + bytes /= thresh; + ++u; + } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1); + + + return bytes.toFixed(dp) + ' ' + units[u]; +} \ No newline at end of file diff --git a/js/interact.js b/js/interact.js index 9c720bc..2168223 100644 --- a/js/interact.js +++ b/js/interact.js @@ -76,18 +76,18 @@ export function postAnswers(ans, config, exam_details) { alert(`${data.question_count} answers sucessfully submitted.`); } } else { - submissionError(data, ans); + submissionError(data, ans, exam_details); } }) .fail((e) => { // Will occur with server error such as 500 console.log("error", e); - submissionError(e, ans); + submissionError(e, ans, exam_details); }); // $.post( "http://localhost:8000/submit_answers", JSON.stringify(ans)); } -function submissionError(data, ans) { +function submissionError(data, answer_json, exam_details) { // error will not be defined with server errors if (data.error != undefined) { alert(`Error submitting answers: ${data.error}`); @@ -96,26 +96,48 @@ function submissionError(data, ans) { } var docHeight = $(document).height(); - $("body").append( - `

An error has occured when submit your answers. A copy of your answers are displayed below, you may wish to save a copy. Please try submitting again or refresh this page to continue.

${JSON.stringify( - ans - )}
` - ); + let answers = JSON.parse(answer_json.answers) - $("#nav-submit-button").prependTo("#submit-error-overlay"); + let answer_map = {} - $("#submit-error-overlay").height(docHeight).css({ - opacity: 0.9, - position: "absolute", - top: 0, - left: 0, - "background-color": "black", - width: "100%", - "user-select": "text", - "-moz-user-select": "text", - "-webkit-user-select": "text", - "z-index": 5000, + answers.forEach((ans, n) => { + if (!answer_map.hasOwnProperty(ans.qid)) { + answer_map[ans.qid] = []; + } + + answer_map[ans.qid].push(ans); + }); + + let html = $(""); + + exam_details.question_order.forEach((i, j) => { + console.log(i, answer_map) + if (i in answer_map) { + console.log("YES", i, answer_map) + let ans_array = answer_map[i]; + ans_array.forEach((x, y) => { + $(html).append(`
  • Question ${j+1}.${y}: ${x.ans}
  • `); + }); + } + + }) + + console.log(exam_details.question_order); + console.log(answer_map); + console.log(html); + + if ($("#submit-error-overlay").length < 1) { + $("body").append( + `

    An error has occured when submit your answers. A copy of your answers are displayed below, you may wish to save a copy. Please try submitting again or refresh this page to continue.

    ${html.get(0).innerHTML}

    ${JSON.stringify( + answer_json + )}

    ` + ); + + $("#nav-submit-button").prependTo("#submit-error-overlay"); + + $("#submit-error-overlay").height(docHeight); + } } export function getQuestion(url, question_number, question_total) { diff --git a/js/main.js b/js/main.js index 949f254..5645fe3 100644 --- a/js/main.js +++ b/js/main.js @@ -5,26 +5,18 @@ import * as interact from "./interact.js"; import * as config from "./config.js"; // const { v4: uuidv4 } = require('uuid'); -// cid = null; -//window.aid = null; -//window.cid = ""; -//window.eid = 5678; - let exam_details = { aid: null, cid: "", eid: 5678, exam_mode: false, number_of_questions: null, + question_order: [], }; -//window.exam_mode = false; - let packet_list = []; let packet_name = null; -//window.questions = null; let questions = null; -let question_order = []; let questions_correct = {}; let question_type = null; let loaded_question = null; @@ -41,10 +33,6 @@ let timer = null; let use_local_question_cache = false; -//window.dfile = null; - -//config = config; - cornerstone.imageCache.setMaximumSizeBytes(5128800); // Set up cornerstone (the dicom viewer) @@ -80,12 +68,14 @@ db.version(1).stores({ const question_db = new Dexie("question_database"); question_db.version(1).stores({ - question_data: "[eid+qid], eid", - saved_exams: "eid, type, exam_mode, name, order, time, generated", + question_data: "&[qid+type], qid, type", + saved_exams: "&eid, type, exam_mode, name, order, time, generated", }); retrievePacketList(); +refreshDatabaseSettings(); + /** * Retrieves a list of available packets via JSON ajax requests * Will initially try /packets/ (for example if index.php generates the list) @@ -123,9 +113,6 @@ function retrievePacketList() { loadPacketList(data); } }).fail(function (jqXHR, textStatus, errorThrown) { - //console.log(jqXHR); - //console.log(textStatus); - //console.log(errorThrown); console.log("No packet list available"); showLoginDialog(); }); @@ -154,9 +141,7 @@ async function loadExamList(data) { $("#database-error").append(delete_button); $("#database-error").show(); }); - // db.session - // .where("packet") - // .equals() + let exams_started = []; let exams_completed = []; sessions.forEach((s) => { @@ -167,15 +152,26 @@ async function loadExamList(data) { } }); + let exam_list = []; if (data != null) { - window.exam_list = data.exams; + exam_list = data.exams; } + + let exam_generated_map = {}; $("#packet-list").empty(); - window.exam_list.forEach(function (exam) { + exam_list.forEach(function (exam) { let name = exam["name"]; let url = exam["url"]; let eid = exam["eid"]; let generated = exam["json_creation_time"]; + + let question_timestamp_hash = {}; + + if (exam.hasOwnProperty("multi_question_json")) { + question_timestamp_hash = exam["multi_question_json"]; + } + exam_generated_map[eid] = [generated, question_timestamp_hash]; + let c = ""; if (exams_started.indexOf(name) > -1) { c = " session-started"; @@ -198,7 +194,9 @@ async function loadExamList(data) { } list.append( - $(`
    `) + $( + `
    ` + ) .text(name) .click(function () { loadPacketFromAjax(url, eid, generated); @@ -214,6 +212,81 @@ async function loadExamList(data) { `
    Results and answers
    ` ); } + + // Check the database for exams that have been saved + question_db.saved_exams.toArray().then((saved_exams) => { + saved_exams.forEach((saved_exam, n) => { + $("#cache-details ul").append( + `
  • Exam: ${saved_exam.exam_name} [${saved_exam.eid}]: ${saved_exam.generated}` + ); + //Compared saved exams to those available + if (exam_generated_map.hasOwnProperty(saved_exam.eid)) { + // If the timestamps differ delete and force a refresh + const exam_timestamp = exam_generated_map[saved_exam.eid][0]; + const question_timestamp_hash = exam_generated_map[saved_exam.eid][1]; + if (Date.parse(saved_exam.generated) != Date.parse(exam_timestamp)) { + question_db.saved_exams.where("eid").equals(saved_exam.eid).delete(); + + $(`li.cache-item[data-eid="${saved_exam.eid}"]`).addClass( + "cache-out-of-date" + ); + } else { + $(`.packet-button[data-eid="${saved_exam.eid}"]`).addClass("cached"); + } + + for (let q in question_timestamp_hash) { + let new_timestamp = question_timestamp_hash[q]; + q = q.toString(); + $("#cache-details ul").append( + `
  • Question (${saved_exam.exam_type}): ${q}` + ); + // If a single question is out of date we invalidate the lot... + const q_object = { qid: q, type: saved_exam.exam_type }; + question_db.question_data + .get(q_object) + .then((d) => { + // d is undefined if the question is not saved + // we should really just requeue the required question for dowload... + if ( + d == undefined || + Date.parse(d.data.generated) != Date.parse(new_timestamp) + ) { + $(`li.cache-item[data-eid="${saved_exam.eid}"]`).addClass( + "cache-out-of-date" + ); + $(`li.cache-item[data-qid="${q}"]`).addClass( + "cache-out-of-date" + ); + $(`.packet-button[data-eid="${saved_exam.eid}"]`).addClass( + "out-of-date" + ); + + //question_db.saved_exams.where("eid").equals(saved_exam.eid).delete(); + question_db.question_data.where(["qid", "type"]).equals([q, saved_exam.exam_type]).delete(); + } + d = null; + }) + .catch((e) => { + // If the question isn't found in the cache... + $(`li.cache-item[data-eid="${saved_exam.eid}"]`).addClass( + "cache-out-of-date" + ); + $(`li.cache-item[data-qid="${q}"]`) + .addClass("cache-out-of-date") + .append("[Not found]"); + $(`.packet-button[data-eid="${saved_exam.eid}"]`).addClass( + "out-of-date" + ); + + //question_db.saved_exams.where("eid").equals(saved_exam.eid).delete(); + question_db.question_data.where(["qid", "type"]).equals([q, saved_exam.exam_type]).delete(); + + }); + } + } + }); + }); + $("#options-panel").show(); } @@ -311,9 +384,8 @@ function loadPacketFromAjax(path, eid, generated) { exam == undefined || Date.parse(exam["generated"]) != Date.parse(generated) ) { - ajaxRequestionPacket(); + ajaxRequestionPacket(true); } else { - console.log(exam); // We have an up to date exam in the database // TODO: check the questions are valid use_local_question_cache = true; @@ -332,14 +404,19 @@ function loadPacketFromAjax(path, eid, generated) { return; } - function ajaxRequestionPacket() { + function ajaxRequestionPacket(force_refresh) { //console.log("loading packet from " + path); $("#progress").html(`Requesting packet: ${path}`); + let browser_cache = true; + if (force_refresh) { + browser_cache = false; + } + $.ajax({ dataType: "json", url: path, - cache: true, + cache: browser_cache, progress: function (e) { if (e.lengthComputable) { var completedPercentage = Math.round((e.loaded * 100) / e.total); @@ -360,109 +437,6 @@ function loadPacketFromAjax(path, eid, generated) { } } -/** - * Build the currently loaded quiz - */ -function setUpQuestions(load_previous) { - if (questions == undefined) { - //return; - } else { - if (!use_local_question_cache) { - Object.keys(questions).forEach(function (e) { - // Store question data into dexie - let d = { - eid: exam_details.eid, - qid: e, - data: questions[e], - }; - - question_db.question_data.put(d); - }); - } - - // Set an order for the questions - if (!load_previous) { - if (question_order.length < 1) { - question_order = []; - Object.keys(questions).forEach(function (e) { - question_order.push(e); - - // Make sure answers are arrays - if (!Array.isArray(questions[e]["answers"])) { - questions[e]["answers"] = [questions[e]["answers"]]; - } - }); - } - - let randomise_order = true; - if (config.randomise_order != undefined) { - randomise_order = config.randomise_order; - } - if (randomise_order) { - question_order = helper.shuffleArray(question_order); - } - } - } - exam_details.number_of_questions = question_order.length; - review_mode = false; - - // Horrible way to get type of questions - // We assume they are all of the same type.... - //question_type = questions[Object.keys(questions)[0]].type; - - if (exam_details.exam_mode) { - $("#options-button, #review-overlay-button").hide(); - } else { - $("#submit-button").hide(); - } - - questions = null; - - //console.log(question_type); - - // exam_time can be defined in packets - if (!load_previous && exam_time == null) { - if (question_type == "rapid") { - exam_time = 35 * 60; - } else if (question_type == "anatomy") { - exam_time = 90 * 60; - } else if (question_type == "long") { - exam_time = 75 * 60; - } - } - - $(".exam-time") - .val(exam_time / 60) - .change(() => { - exam_time = $(".exam-time").val() * 60; - }); - - if (exam_details.exam_mode) { - // NOTE: changing the CID when restoring a session will not affect the time left - $("#candidate-number2") - .val(exam_details.cid) - .change(() => { - exam_details.cid = parseInt($("#candidate-number2").val()); - }); - - $("#start-dialog").addClass("no-close"); - $("#start-dialog .exam-time").prop("disabled", "true"); - $("#exam-candidate-number").show(); - $(".packet-database-options").hide(); - $("#start-dialog").modal({ - closeExisting: false, // Close existing modals. Set this to false if you need to stack multiple modal instances. - escapeClose: false, // Allows the user to close the modal by pressing `ESC` - clickClose: false, // Allows the user to close the modal by clicking the overlay - showClose: false, - }); - } else { - $("#start-dialog").modal(); - } - - loadQuestion(0, 1, true); - createQuestionListPanel(); -} - /** * Parse the packet and extract metadata (if it exists) * Will then call setUpQuestions() to load the packet @@ -499,7 +473,30 @@ function setUpPacket(data, path) { } if (data.hasOwnProperty("exam_order")) { - question_order = data.exam_order; + exam_details.question_order = data.exam_order; + } else { + exam_details.question_order = []; + Object.keys(questions).forEach(function (e) { + exam_details.question_order.push(e); + + // Make sure answers are arrays + if (!Array.isArray(questions[e]["answers"])) { + questions[e]["answers"] = [questions[e]["answers"]]; + } + }); + + let randomise_order = true; + if (config.randomise_order != undefined) { + randomise_order = config.randomise_order; + } + if (randomise_order) { + exam_details.question_order = helper.shuffleArray( + exam_details.question_order + ); + } + // We add this here so the question order is maintained + // when loading from cache + data["exam_order"] = exam_details.question_order } if (data.hasOwnProperty("exam_time")) { @@ -508,19 +505,33 @@ function setUpPacket(data, path) { packet_time = data.exam_time; } - if (use_local_question_cache) { - // If loading form cache nothing else to do here - loadSession(); - return; + //if (use_local_question_cache && force_question_refresh.length < 1) { + // // If loading form cache nothing else to do here + // loadSession(); + // return; + //} + + if (!use_local_question_cache) { + // Save the details to the question database + var clone_data = Object.assign({}, data, { questions: "cached" }); + //clone_data["cached_questions"] = Object.keys(data["questions"]); + question_db.saved_exams.put(clone_data); } - // Save the details to the question database - var clone_data = Object.assign({}, data, { questions: undefined }); - question_db.saved_exams.put(clone_data); - - // If the question_requests is set we need to do a request for each question - if (data.hasOwnProperty("question_requests") && data["question_requests"]) { - const question_total = Object.keys(data["questions"]).length; + // If question_requests is set we need to do a request for each question + if (data.hasOwnProperty("question_requests") && (Object.keys(data["question_requests"]).length > 0)) { + // Will happen if loading from cache + //if (data["questions"] == "cached") { + // let a = {}; + // data["cached_questions"].forEach((q, n) => { + // let s = JSON.stringify() + // if (force_question_refresh.includes(s)) { + // a[q] = 1; + // } + // }); + // data["questions"] = a; + //} + let question_total = Object.keys(data["question_requests"]).length; let question_number = 0; var requests = []; @@ -528,10 +539,18 @@ function setUpPacket(data, path) { // For loop to generate requests (async () => { - for (const n in data["questions"]) { - const question_url = `${path}/${n}`; + for (const n in data["question_requests"]) { question_number++; + let obj = {qid: n, type: question_type}; + let question_in_db = await question_db.question_data.get(obj); + + // If the question is in the db we can load + // invalid questions should have already been removed + if (question_in_db != undefined) {continue} + + const question_url = `${path}/${n}`; + //console.log("Creating ", question_url, question_number, question_total); //$("#progress").html(`Downloading [${question_number}/${question_total}]`); let question_json = {}; @@ -539,8 +558,8 @@ function setUpPacket(data, path) { .getQuestion(question_url, question_number, question_total) .fail((jqXHR, textStatus, errorThrown) => { //console.log(jqXHR, textStatus, errorThrown); - console.log("error loading question"); - data["questions"][n] = {}; + console.log(`error loading question: ${n}`); + //data["questions"][n] = {}; }); if (question_json.hasOwnProperty("cached") && question_json["cached"]) { @@ -561,17 +580,21 @@ function setUpPacket(data, path) { // Store question data into dexie let d = { - eid: exam_details.eid, + //eid: exam_details.eid, qid: n, data: question_json, + type: question_json.type, }; question_db.question_data.put(d); - use_local_question_cache = true; } // Once all questions have been downloaded we carry on loading + use_local_question_cache = true; loadSession(); - })(); + })().catch((error) => { + alert("Error downloading, try refreshing"); + console.log(error); + }); } else { // Just carry on loading loadSession(); @@ -579,7 +602,7 @@ function setUpPacket(data, path) { } function loadSession() { - //console.log("load session"); + console.log("load session", exam_details, db.session); // Either continue session or create a new one db.session // .where("status") @@ -655,7 +678,7 @@ function loadSession() { exam_time = time_left; - question_order = sessions[parseInt(s)].question_order; + exam_details.question_order = sessions[parseInt(s)].question_order; if (sessions[parseInt(s)].status == "complete") { review_mode = true; @@ -672,6 +695,90 @@ function loadSession() { }); } +/** + * Build the currently loaded quiz + */ +function setUpQuestions(load_previous) { + if (questions == undefined) { + //return; + } else { + if (!use_local_question_cache) { + Object.keys(questions).forEach(function (e) { + // Store question data into dexie + let d = { + //eid: exam_details.eid, + qid: e, + type: questions[e].type, + data: questions[e], + //timestamp: questions[e]["generated"], + }; + + question_db.question_data.put(d); + }); + } + + // Set an order for the questions + if (!load_previous) { + } + } + exam_details.number_of_questions = exam_details.question_order.length; + review_mode = false; + + // Horrible way to get type of questions + // We assume they are all of the same type.... + //question_type = questions[Object.keys(questions)[0]].type; + + if (exam_details.exam_mode) { + $("#options-button, #review-overlay-button").hide(); + } else { + $("#submit-button").hide(); + } + + questions = null; + + // exam_time can be defined in packets + if (!load_previous && exam_time == null) { + if (question_type == "rapid") { + exam_time = 35 * 60; + } else if (question_type == "anatomy") { + exam_time = 90 * 60; + } else if (question_type == "long") { + exam_time = 75 * 60; + } + } + + $(".exam-time") + .val(exam_time / 60) + .change(() => { + exam_time = $(".exam-time").val() * 60; + }); + + if (exam_details.exam_mode) { + // NOTE: changing the CID when restoring a session will not affect the time left + $("#candidate-number2") + .val(exam_details.cid) + .change(() => { + exam_details.cid = parseInt($("#candidate-number2").val()); + }); + + $("#start-dialog").addClass("no-close"); + $("#start-dialog .exam-time").prop("disabled", "true"); + $("#exam-candidate-number").show(); + $(".packet-database-options").hide(); + $("#start-dialog").modal({ + closeExisting: false, // Close existing modals. Set this to false if you need to stack multiple modal instances. + escapeClose: false, // Allows the user to close the modal by pressing `ESC` + clickClose: false, // Allows the user to close the modal by clicking the overlay + showClose: false, + }); + } else { + $("#start-dialog").modal(); + } + + loadQuestion(0, 1, true); + createQuestionListPanel(); +} + /** * Loads a specific question * @param {number} n - Question number to load @@ -688,16 +795,6 @@ async function loadQuestion(n, section = 1, force_reload = false) { n = parseInt(n); //console.log("loading question (n)", n); - const qid = question_order[n]; - - let q = { qid: qid.toString(), eid: eid }; - - let return_data = await question_db.question_data.get(q); - const question_data = return_data.data; - return_data = null; - - //const current_question = window.questions[qid]; - if (n == loaded_question && force_reload == false) { // Question already loaded setFocus(section); @@ -705,6 +802,15 @@ async function loadQuestion(n, section = 1, force_reload = false) { return; } + const qid = exam_details.question_order[n]; + + let q = { qid: qid.toString(), type: question_type }; + + let return_data = await question_db.question_data.get(q); + const question_data = return_data.data; + return_data = null; + + loaded_question = n; // Set up question navigation @@ -897,7 +1003,9 @@ async function loadQuestion(n, section = 1, force_reload = false) { if (evt.target.value.length < 1) { db.answers.delete([aid, cid, eid, qid, "2"]); $( - "#question-list-item-" + question_order.indexOf(qid) + "-2" + "#question-list-item-" + + exam_details.question_order.indexOf(qid) + + "-2" ).removeClass("question-saved-localdb"); return; } @@ -913,9 +1021,11 @@ async function loadQuestion(n, section = 1, force_reload = false) { // db.answers.put({aid: [cid, eid, qidn], ans: evt.target.value}); db.answers.put(answer); - $("#question-list-item-" + question_order.indexOf(qid) + "-2").addClass( - "question-saved-localdb" - ); + $( + "#question-list-item-" + + exam_details.question_order.indexOf(qid) + + "-2" + ).addClass("question-saved-localdb"); saveSession(); updateQuestionListPanel(answer); @@ -979,7 +1089,9 @@ async function loadQuestion(n, section = 1, force_reload = false) { if (evt.target.value.length < 1) { db.answers.delete([aid, cid, eid, qid, "1"]); $( - "#question-list-item-" + question_order.indexOf(qid) + "-1" + "#question-list-item-" + + exam_details.question_order.indexOf(qid) + + "-1" ).removeClass("question-saved-localdb"); return; } @@ -995,9 +1107,11 @@ async function loadQuestion(n, section = 1, force_reload = false) { // db.answers.put({aid: [cid, eid, qidn], ans: evt.target.value}); db.answers.put(answer); - $("#question-list-item-" + question_order.indexOf(qid) + "-1").addClass( - "question-saved-localdb" - ); + $( + "#question-list-item-" + + exam_details.question_order.indexOf(qid) + + "-1" + ).addClass("question-saved-localdb"); saveSession(); updateQuestionListPanel(answer); }); @@ -1065,7 +1179,10 @@ async function loadQuestion(n, section = 1, force_reload = false) { if (evt.target.value.length < 1) { db.answers.delete([aid, cid, eid, qid, qidn]); $( - "#question-list-item-" + question_order.indexOf(qid) + "-" + qidn + "#question-list-item-" + + exam_details.question_order.indexOf(qid) + + "-" + + qidn ).removeClass("question-saved-localdb"); return; } @@ -1080,10 +1197,12 @@ async function loadQuestion(n, section = 1, force_reload = false) { }; db.answers.put(answer); - console.log("SAVE", qid, qidn, evt.target.value); $( - "#question-list-item-" + question_order.indexOf(qid) + "-" + qidn + "#question-list-item-" + + exam_details.question_order.indexOf(qid) + + "-" + + qidn ).addClass("question-saved-localdb"); saveSession(); updateQuestionListPanel(answer); @@ -1094,14 +1213,15 @@ async function loadQuestion(n, section = 1, force_reload = false) { // for (let qidn_count = 1; qidn_count < 6; qidn_count++) { - db.answers - .get({ + let obj = { aid: aid, cid: cid, eid: eid, qid: qid, qidn: qidn_count.toString(), - }) + } + db.answers + .get(obj) .then(function (answer) { if (answer != undefined) { $(".long-answer") @@ -1165,22 +1285,24 @@ function setFocus(section) { function updateQuestionListPanel(answer) { $( "#question-list-item-" + - question_order.indexOf(answer.qid) + + exam_details.question_order.indexOf(answer.qid) + "-" + answer.qidn ).addClass("question-saved-localdb"); if (question_type == "rapid" && answer.qidn == "1") { if (answer.ans == "Abnormal") { - $("#question-list-item-" + question_order.indexOf(answer.qid) + "-2").css( - "display", - "block" - ); + $( + "#question-list-item-" + + exam_details.question_order.indexOf(answer.qid) + + "-2" + ).css("display", "block"); } else { - $("#question-list-item-" + question_order.indexOf(answer.qid) + "-2").css( - "display", - "none" - ); + $( + "#question-list-item-" + + exam_details.question_order.indexOf(answer.qid) + + "-2" + ).css("display", "none"); } } } @@ -1188,7 +1310,7 @@ function updateQuestionListPanel(answer) { function deleteQuestionListPanelFlags(answer) { $( "#question-list-item-" + - question_order.indexOf(answer.qid) + + exam_details.question_order.indexOf(answer.qid) + "-" + answer.qidn + " span" @@ -1198,7 +1320,7 @@ function deleteQuestionListPanelFlags(answer) { function updateQuestionListPanelFlags(answer) { $( "#question-list-item-" + - question_order.indexOf(answer.qid) + + exam_details.question_order.indexOf(answer.qid) + "-" + answer.qidn + " span" @@ -1214,7 +1336,6 @@ function rebuildQuestionListPanel() { const cid = exam_details.cid; const eid = exam_details.eid; - //console.log("UP"); db.answers .where({ aid: aid, cid: cid, eid: eid }) .toArray() @@ -1250,7 +1371,7 @@ function rebuildQuestionListPanel() { if (review_mode == true) { $(".question-panel-ans").remove(); for (const qid in questions_correct) { - let q_no = question_order.indexOf(qid); + let q_no = exam_details.question_order.indexOf(qid); //console.log("q", q_no, qid, questions_correct[qid]); let question_list_elements = $(`.question-list-item[data-qid='${q_no}']`); //console.log(question_list_elements, questions_correct[qid]); @@ -1468,7 +1589,7 @@ function reviewQuestions() { let undercall_number = 0; let overcall_number = 0; let incorrectcall_number = 0; - question_order.forEach(function (qid, n) { + exam_details.question_order.forEach(function (qid, n) { if (question_type == "long") { $("#review-answer-table").append( $( @@ -1700,7 +1821,10 @@ function reviewQuestions() { score = correct_count; $("#review-score").text( - "Score: " + correct_count + " out of " + question_order.length + "Score: " + + correct_count + + " out of " + + exam_details.question_order.length ); if (question_type == "rapid") { @@ -1915,7 +2039,7 @@ function addFlagEvents() { $("button.flag").click(function (event) { const el = $(this); const nqid = el.attr("data-qid"); - const qid = question_order[nqid]; + const qid = exam_details.question_order[nqid]; const qidn = el.attr("data-qidn"); el.toggleClass("flag flag-selected"); @@ -2047,7 +2171,7 @@ $(".start-packet-button").click(function (evt) { $.modal.close(); }); -$("#btn-delete-databases").click(function (evt) { +$("#btn-delete-answer-databases").click(function (evt) { var r = confirm( "This will delete ALL saved answers (including saved correct answers)!" ); @@ -2071,16 +2195,40 @@ $("#btn-delete-databases").click(function (evt) { } else { } }); +$("#btn-delete-cached-questions").click(function (evt) { + var r = confirm( + "Delete cached questions?" + ); + if (r == true) { + question_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 (question_order.length < 1) { + if (exam_details.question_order.length < 1) { $.notify("No packet is currently loaded", "warn"); return; } db.flags .where("qid") - .anyOf(question_order) + .anyOf(exam_details.question_order) .delete() .then(function (deleteCount) { $.notify("Packet flags deleted", "success"); @@ -2092,7 +2240,7 @@ $("#btn-delete-current").click(function (evt) { db.answers .where("qid") - .anyOf(question_order) + .anyOf(exam_details.question_order) .delete() .then(function (deleteCount) { $.notify("Packet successfully reset", "success"); @@ -2111,14 +2259,14 @@ $("#btn-delete-current").click(function (evt) { }); $("#btn-delete-current-saved-answers").click(function (evt) { - if (question_order.length < 1) { + if (exam_details.question_order.length < 1) { $.notify("No packet is currently loaded", "warn"); return; } db.user_answers .where("qid") - .anyOf(question_order) + .anyOf(exam_details.question_order) .delete() .then(function (deleteCount) { $.notify("Packet flags deleted", "success"); @@ -2138,7 +2286,6 @@ $(document).ajaxStop(function () { }); function saveSession(start_review) { - if (start_review == true) { review_mode = true; } @@ -2165,12 +2312,24 @@ function saveSession(start_review) { max_score: exam_details.number_of_questions, exam_time: exam_time, time_left: time_remaining, - question_order: question_order, + question_order: exam_details.question_order, questions_answered: 0, // TODO total_questions: exam_details.number_of_questions, }); } //window.saveSession = saveSession; +function refreshDatabaseSettings() { + navigator.storage.estimate().then((value) => { + $("#storage-details").empty(); + $("#storage-details").append( + `Local space used: ${helper.humanFileSize( + value.usage + )}, available: ${helper.humanFileSize(value.quota)}` + ); + }); +} -$(document).on("saveSessionEvent", {}, (evt, review) => { saveSession(review) }) \ No newline at end of file +$(document).on("saveSessionEvent", {}, (evt, review) => { + saveSession(review); +}); diff --git a/js/viewer.js b/js/viewer.js index 0c24495..9de50cf 100644 --- a/js/viewer.js +++ b/js/viewer.js @@ -336,6 +336,7 @@ export function changeControlSelection() { disableFullscreen(dicom_element); } if (dicom_element != undefined) { + // See https://github.com/cornerstonejs/cornerstoneTools/issues/1337 cornerstone.removeElementData(dicom_element); cornerstone.disable(dicom_element); $(".canvas-panel").remove();