feat: enhance error handling with submission error overlay and logging level controls
This commit is contained in:
+73
-1
@@ -1138,7 +1138,7 @@ select option:disabled {
|
||||
}
|
||||
}
|
||||
|
||||
#submit-error-overlay, #view-answers-overlay {
|
||||
#view-answers-overlay {
|
||||
opacity: 0.9;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -1151,6 +1151,78 @@ select option:disabled {
|
||||
z-index: 5000;
|
||||
}
|
||||
|
||||
#submit-error-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.85);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 10000;
|
||||
user-select: text;
|
||||
-moz-user-select: text;
|
||||
-webkit-user-select: text;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#submit-error-overlay .error-card {
|
||||
background-color: #1a1a1a;
|
||||
border: 1px solid #c62828;
|
||||
border-radius: 8px;
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
padding: 25px;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.5);
|
||||
color: #eee;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#submit-error-overlay .error-card h3 {
|
||||
margin-top: 0;
|
||||
color: #ef5350;
|
||||
font-size: 20px;
|
||||
border-bottom: 1px solid #333;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#submit-error-overlay .error-card button {
|
||||
cursor: pointer;
|
||||
background-color: #311B92;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 18px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#submit-error-overlay .error-card button:hover {
|
||||
background-color: #4527A0;
|
||||
}
|
||||
|
||||
#submit-error-overlay .error-card button.btn-secondary {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
#submit-error-overlay .error-card button.btn-secondary:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
#submit-error-overlay .error-card button.btn-danger {
|
||||
background-color: #c62828;
|
||||
}
|
||||
|
||||
#submit-error-overlay .error-card button.btn-danger:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
|
||||
.cache-out-of-date {
|
||||
color: red;
|
||||
}
|
||||
|
||||
+24
@@ -255,6 +255,16 @@
|
||||
View local saved answers
|
||||
</button>
|
||||
</p>
|
||||
<div id="logging-controls" style="margin-top: 15px; border-top: 1px solid #444; padding-top: 10px; font-family: sans-serif; font-size: 13px;">
|
||||
<label for="select-log-level" style="font-weight: bold; color: #fff;">Logging Level:</label>
|
||||
<select id="select-log-level" style="background:#222; color:#fff; border:1px solid #555; padding: 2px 5px; border-radius: 3px; font-size: 13px; margin-left: 5px; cursor: pointer;">
|
||||
<option value="silent">Disabled (Silent)</option>
|
||||
<option value="error">Error</option>
|
||||
<option value="warn">Warn</option>
|
||||
<option value="info">Info</option>
|
||||
<option value="debug">Debug</option>
|
||||
</select>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div id="review-overlay" class="fullscreen-overlay">
|
||||
@@ -358,6 +368,20 @@
|
||||
<button id="btn-login" class="navigation dialog-yes">Login</button>
|
||||
</div>
|
||||
|
||||
<div id="resume-dialog" class="dialog modal noclose">
|
||||
<h3 class="dialog-title">Resume Session?</h3>
|
||||
<div class="dialog-text">
|
||||
<p id="resume-text">Choose a previous session to continue, or start a new one:</p>
|
||||
<div id="resume-sessions-list" style="margin: 15px 0; max-height: 200px; overflow-y: auto;">
|
||||
<!-- Session options list -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog-buttons" style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 15px;">
|
||||
<button id="btn-resume-start-new" class="navigation dialog-no" style="margin: 0;">Start New</button>
|
||||
<button id="btn-resume-continue" class="navigation dialog-yes" style="margin: 0;" disabled>Continue</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="loading" class="fullscreen-overlay">
|
||||
<div class="progress-block">
|
||||
<div id="progress"></div>
|
||||
|
||||
+69
-46
@@ -91,74 +91,97 @@ export function postAnswers(ans, URLS, exam_details) {
|
||||
alert(`Answers sucessfully submitted.`);
|
||||
}
|
||||
} else {
|
||||
submissionError(data, ans, exam_details);
|
||||
submissionError(data, ans, exam_details, URLS);
|
||||
}
|
||||
})
|
||||
.fail((e) => {
|
||||
// Will occur with server error such as 500
|
||||
console.debug("error", e);
|
||||
submissionError(e, ans, exam_details);
|
||||
submissionError(e, ans, exam_details, URLS);
|
||||
});
|
||||
// $.post( "http://localhost:8000/submit_answers", JSON.stringify(ans));
|
||||
}
|
||||
|
||||
export 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}`);
|
||||
} else {
|
||||
alert(`Error submitting answers`);
|
||||
}
|
||||
var docHeight = $(document).height();
|
||||
export function submissionError(data, answer_json, exam_details, URLS) {
|
||||
// Clear any existing overlay
|
||||
$("#submit-error-overlay").remove();
|
||||
|
||||
let answers = JSON.parse(answer_json.answers)
|
||||
let answers = JSON.parse(answer_json.answers);
|
||||
let answer_map = {};
|
||||
|
||||
let answer_map = {}
|
||||
|
||||
answers.forEach((ans, n) => {
|
||||
answers.forEach((ans) => {
|
||||
if (!answer_map.hasOwnProperty(ans.qid)) {
|
||||
answer_map[ans.qid] = [];
|
||||
}
|
||||
|
||||
answer_map[ans.qid].push(ans);
|
||||
|
||||
});
|
||||
|
||||
let html = $("<ul></ul>");
|
||||
|
||||
exam_details.question_order.forEach((i, j) => {
|
||||
console.debug(i, answer_map)
|
||||
if (i in answer_map) {
|
||||
console.debug("YES", i, answer_map)
|
||||
let ans_array = answer_map[i];
|
||||
ans_array.forEach((x, y) => {
|
||||
$(html).append(`<li><b>Question ${j+1}.${y}:</b> ${x.ans}</li>`);
|
||||
let $list = $("<ol style='margin-left: 20px; font-size: 13px; max-height: 150px; overflow-y: auto; color: #ccc;'></ol>");
|
||||
exam_details.question_order.forEach((qid, idx) => {
|
||||
if (qid in answer_map) {
|
||||
let ans_array = answer_map[qid];
|
||||
ans_array.forEach((ans_obj, sub_idx) => {
|
||||
$list.append(`<li><strong>Question ${idx + 1}.${sub_idx + 1}:</strong> ${ans_obj.ans}</li>`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
})
|
||||
let rawJsonStr = JSON.stringify(answer_json, null, 2);
|
||||
|
||||
console.debug(exam_details.question_order);
|
||||
console.debug(answer_map);
|
||||
console.debug(html);
|
||||
|
||||
if ($("#submit-error-overlay").length < 1) {
|
||||
$("body").append(
|
||||
`<div id='submit-error-overlay'><span style='color: white'><p>An error has occurred when submitting your answers. A copy of your answers are displayed below, you may wish to <a id="save-failed-answers" href="#">save a copy</a> if subsequent submissions still fail. Please try submitting again or refresh this page to continue (answers will be saved locally in the browser unless you clear local storage).</p><p>${html.get(0).innerHTML}</p><p>${JSON.stringify(
|
||||
answer_json
|
||||
)}</p></span></div>`
|
||||
);
|
||||
|
||||
$("#btn-submit-nav").prependTo("#submit-error-overlay");
|
||||
|
||||
$("#submit-error-overlay").height(docHeight);
|
||||
|
||||
let file = new Blob([JSON.stringify(answer_json)], {type: "application/json"})
|
||||
|
||||
let link = document.getElementById("save-failed-answers");
|
||||
link.href = URL.createObjectURL(file);
|
||||
link.download = "answers.json";
|
||||
let errorMsg = "An error occurred while submitting your answers.";
|
||||
if (data && data.error) {
|
||||
errorMsg = `Server returned error: ${data.error}`;
|
||||
} else if (data && data.statusText) {
|
||||
errorMsg = `Network error: ${data.statusText} (${data.status})`;
|
||||
}
|
||||
|
||||
let $overlay = $(`
|
||||
<div id="submit-error-overlay">
|
||||
<div class="error-card">
|
||||
<h3>Submission Failed</h3>
|
||||
<p style="margin-bottom: 15px; color: #ef5350; font-weight: bold;">${errorMsg}</p>
|
||||
<p style="font-size: 13px; color: #bbb; line-height: 1.4;">
|
||||
Your answers are saved locally in the browser's database and will not be lost unless you clear your local data.
|
||||
Please try to submit again, or save a backup file of your answers and contact support.
|
||||
</p>
|
||||
|
||||
<div style="margin: 15px 0;">
|
||||
<h4 style="margin: 0 0 5px 0; font-size: 14px; color: #eee;">Submitted Answers:</h4>
|
||||
<div style="background: #111; border: 1px solid #333; padding: 10px; border-radius: 4px;">
|
||||
${$list.prop("outerHTML")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details style="margin: 15px 0; font-size: 12px; cursor: pointer; color: #aaa;">
|
||||
<summary style="font-weight: bold; margin-bottom: 5px;">View Raw Submission JSON</summary>
|
||||
<pre style="background: #111; border: 1px solid #333; padding: 10px; overflow: auto; max-height: 150px; text-align: left; font-family: monospace; white-space: pre-wrap; margin: 0; color: #a5d6a7;">${rawJsonStr}</pre>
|
||||
</details>
|
||||
|
||||
<div style="display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px;">
|
||||
<button id="btn-err-close" class="btn-secondary">Close</button>
|
||||
<a id="btn-err-backup" class="button" style="text-decoration: none;"><button class="btn-secondary">Save Backup</button></a>
|
||||
<button id="btn-err-retry">Retry Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
$("body").append($overlay);
|
||||
|
||||
// Setup download backup link
|
||||
let fileBlob = new Blob([JSON.stringify(answer_json)], {type: "application/json"});
|
||||
let backupUrl = URL.createObjectURL(fileBlob);
|
||||
$overlay.find("#btn-err-backup").attr("href", backupUrl).attr("download", `answers_backup_${exam_details.eid.replace(/\//g, "_")}.json`);
|
||||
|
||||
// Close handler
|
||||
$overlay.find("#btn-err-close").on("click", function() {
|
||||
$("#submit-error-overlay").remove();
|
||||
});
|
||||
|
||||
// Retry handler
|
||||
$overlay.find("#btn-err-retry").on("click", function() {
|
||||
postAnswers(answer_json, URLS, exam_details);
|
||||
});
|
||||
}
|
||||
|
||||
export function getQuestion(url, question_number, question_total) {
|
||||
|
||||
+151
-85
@@ -7,8 +7,42 @@ import { db, question_db } from "./db.js";
|
||||
import { initSync, triggerImmediateSync, updateSyncUI } from "./sync.js";
|
||||
// const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
log.setDefaultLevel("warn")
|
||||
log.setDefaultLevel("debug")
|
||||
function initLogging() {
|
||||
try {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
let logLevel = urlParams.get("log");
|
||||
|
||||
if (logLevel !== null) {
|
||||
if (logLevel === "1" || logLevel === "true") {
|
||||
logLevel = "debug";
|
||||
} else if (logLevel === "0" || logLevel === "false") {
|
||||
logLevel = "silent";
|
||||
}
|
||||
localStorage.setItem("rts.logLevel", logLevel);
|
||||
} else {
|
||||
logLevel = localStorage.getItem("rts.logLevel");
|
||||
}
|
||||
|
||||
if (!logLevel || !["silent", "error", "warn", "info", "debug"].includes(logLevel)) {
|
||||
logLevel = "warn";
|
||||
}
|
||||
|
||||
log.setLevel(logLevel);
|
||||
|
||||
// Redirect console methods through loglevel so they can be silenced consistently
|
||||
console.debug = log.debug.bind(log);
|
||||
console.log = log.info.bind(log);
|
||||
console.warn = log.warn.bind(log);
|
||||
console.error = log.error.bind(log);
|
||||
|
||||
log.info(`RTS logging level initialized to: ${logLevel}`);
|
||||
} catch (e) {
|
||||
log.setLevel("warn");
|
||||
console.warn("Failed to initialize custom log level, falling back to warn:", e);
|
||||
}
|
||||
}
|
||||
|
||||
initLogging();
|
||||
|
||||
|
||||
let URLS = {};
|
||||
@@ -309,7 +343,7 @@ function showLoadingState($btn, $targetContainer) {
|
||||
}
|
||||
|
||||
async function retrievePacketList(target = "all", onComplete = null) {
|
||||
log.debug(`Load users from database...`)
|
||||
log.info(`Load users from database...`)
|
||||
let cid = localStorage.getItem("cid.cid")
|
||||
let passcode = localStorage.getItem("cid.passcode")
|
||||
|
||||
@@ -327,7 +361,7 @@ async function retrievePacketList(target = "all", onComplete = null) {
|
||||
// Update the candidate details display (only shows when both values exist)
|
||||
showCandidateDetails();
|
||||
} else {
|
||||
log.debug("Try and set CID / Passcode from current URL")
|
||||
log.info("Try and set CID / Passcode from current URL")
|
||||
try {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const cidParam = params.get("cid");
|
||||
@@ -337,8 +371,8 @@ async function retrievePacketList(target = "all", onComplete = null) {
|
||||
if (cidParam !== null && passParam !== null && cidParam !== "" && passParam !== "") {
|
||||
cid = cidParam;
|
||||
passcode = passParam;
|
||||
log.debug("CID and passcode extracted from URL", cid, passcode);
|
||||
log.debug("adding to local db");
|
||||
log.info("CID and passcode extracted from URL", cid, passcode);
|
||||
log.info("adding to local db");
|
||||
localStorage.setItem("cid.cid", cid);
|
||||
localStorage.setItem("cid.passcode", passcode);
|
||||
|
||||
@@ -348,10 +382,10 @@ async function retrievePacketList(target = "all", onComplete = null) {
|
||||
showCandidateDetails();
|
||||
|
||||
} else {
|
||||
log.debug("CID or passcode missing in URL; not setting local storage");
|
||||
log.info("CID or passcode missing in URL; not setting local storage");
|
||||
}
|
||||
} catch (e) {
|
||||
log.debug("Error parsing URL params for CID/passcode", e);
|
||||
log.error("Error parsing URL params for CID/passcode", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,7 +419,7 @@ async function retrievePacketList(target = "all", onComplete = null) {
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
log.debug("Unable to load packets, trying legacy '/packets'");
|
||||
log.info("Unable to load packets, trying legacy '/packets'");
|
||||
$.getJSON("packets", function(data) {
|
||||
if (!data.hasOwnProperty("exams")) {
|
||||
loadPacketList(data);
|
||||
@@ -410,7 +444,7 @@ async function retrievePacketList(target = "all", onComplete = null) {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log.debug("Unable to set exam query url", e);
|
||||
log.warn("Unable to set exam query url", e);
|
||||
}
|
||||
|
||||
if (examUrl) {
|
||||
@@ -506,17 +540,17 @@ async function retrievePacketList(target = "all", onComplete = null) {
|
||||
async function loadExamList(data, clearExistingList = true, target = "all") {
|
||||
// If exam mode was enabled via URL param, hide packets by default
|
||||
if (typeof url_exam_mode !== "undefined" && url_exam_mode) {
|
||||
log.debug("URL exam mode active - hiding packets list");
|
||||
log.info("URL exam mode active - hiding packets list");
|
||||
try {
|
||||
$(".packet-wrapper").hide();
|
||||
} catch (e) {
|
||||
log.debug("Unable to hide packet UI elements", e);
|
||||
log.warn("Unable to hide packet UI elements", e);
|
||||
}
|
||||
}
|
||||
log.debug(`Load exam list: ${data}`)
|
||||
log.debug("Loading saved sessions")
|
||||
log.info(`Load exam list: ${data}`)
|
||||
log.info("Loading saved sessions")
|
||||
let sessions = await db.session.toArray().catch(function(error) {
|
||||
log.debug("Error loading session", error);
|
||||
log.error("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."
|
||||
);
|
||||
@@ -539,7 +573,7 @@ async function loadExamList(data, clearExistingList = true, target = "all") {
|
||||
|
||||
let exams_started = [];
|
||||
let exams_completed = [];
|
||||
log.debug("Check status of saved exams")
|
||||
log.info("Check status of saved exams")
|
||||
for (const s of sessions) {
|
||||
log.debug(`checking status of`, s)
|
||||
if (s.status == "active") {
|
||||
@@ -569,7 +603,7 @@ async function loadExamList(data, clearExistingList = true, target = "all") {
|
||||
$("#cache-details ul").empty();
|
||||
}
|
||||
|
||||
log.debug("Loop available exams")
|
||||
log.info("Loop available exams")
|
||||
for (const exam of exam_list) {
|
||||
log.debug(exam)
|
||||
const name = exam["name"];
|
||||
@@ -804,10 +838,10 @@ async function loadExamList(data, clearExistingList = true, target = "all") {
|
||||
async function loadPacketList(data) {
|
||||
// NOTE: currently the packet id is not available prior ot downloading the packet
|
||||
// this severly limits what we can do with it (but to require would limit the utility)
|
||||
log.debug(`loadPacketList`);
|
||||
log.debug("Loading saved sessions")
|
||||
log.info(`loadPacketList`);
|
||||
log.info("Loading saved sessions")
|
||||
let sessions = await db.session.toArray().catch(function(error) {
|
||||
log.debug(`Error loading session ${error}`);
|
||||
log.error(`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."
|
||||
);
|
||||
@@ -826,7 +860,7 @@ async function loadPacketList(data) {
|
||||
// .equals()
|
||||
let packets_started = [];
|
||||
let packets_completed = [];
|
||||
log.debug("Check status of saved packets")
|
||||
log.info("Check status of saved packets")
|
||||
sessions.forEach((s) => {
|
||||
log.debug(`checking status of`, s)
|
||||
if (s.status == "active") {
|
||||
@@ -836,16 +870,16 @@ async function loadPacketList(data) {
|
||||
}
|
||||
});
|
||||
|
||||
log.debug("Packets started")
|
||||
log.info("Packets started")
|
||||
log.debug(packets_started)
|
||||
log.debug("Packets completed")
|
||||
log.info("Packets completed")
|
||||
log.debug(packets_completed)
|
||||
|
||||
let packet_list = [];
|
||||
if (data != null) {
|
||||
packet_list = data.packets;
|
||||
} else {
|
||||
log.debug("data == null")
|
||||
log.warn("data == null")
|
||||
}
|
||||
|
||||
log.debug(packet_list)
|
||||
@@ -1146,40 +1180,38 @@ function setUpPacket(data, path) {
|
||||
}
|
||||
|
||||
function loadSession() {
|
||||
console.debug("load session", exam_details, db.session);
|
||||
log.info("load session", exam_details, db.session);
|
||||
// Either continue session or create a new one
|
||||
db.session
|
||||
// .where("status")
|
||||
// .equals("active")
|
||||
.where("[eid+aid]")
|
||||
.between([exam_details.eid, Dexie.minKey], [exam_details.eid, Dexie.maxKey])
|
||||
.toArray()
|
||||
.then((sessions) => {
|
||||
//console.debug("sessions", sessions);
|
||||
|
||||
// let active_sessions = [];
|
||||
|
||||
// sessions.forEach((s) => {
|
||||
// if (s.status == "active") {
|
||||
// active_sessions.push(s);
|
||||
// }
|
||||
// });
|
||||
|
||||
let load_previous = false;
|
||||
|
||||
// Start new session if no session found
|
||||
if (sessions.length < 1) {
|
||||
exam_details.aid = uuidv4();
|
||||
date_started = Date.now();
|
||||
} else {
|
||||
let text = "Select session to continue (leave blank to create new):";
|
||||
if (exam_details.exam_mode) {
|
||||
text = "Session will be continued";
|
||||
exam_details.start_time = date_started / 1000;
|
||||
setUpQuestions(false);
|
||||
if (!review_mode) {
|
||||
initSync(exam_details, URLS);
|
||||
}
|
||||
//console.debug(sessions.date);
|
||||
} else {
|
||||
// Show resume dialog
|
||||
$("#resume-sessions-list").empty();
|
||||
|
||||
let textPrompt = "Select a session to continue:";
|
||||
if (exam_details.exam_mode) {
|
||||
textPrompt = "An exam session is already in progress. You must continue the existing session:";
|
||||
$("#btn-resume-start-new").hide();
|
||||
} else {
|
||||
$("#btn-resume-start-new").show();
|
||||
}
|
||||
|
||||
$("#resume-text").text(textPrompt);
|
||||
|
||||
for (let i = 0; i < sessions.length; i++) {
|
||||
let d = new Date(sessions[i].date);
|
||||
// Populate session items
|
||||
sessions.forEach((session, i) => {
|
||||
let d = new Date(session.date);
|
||||
let formatted_date =
|
||||
d.getFullYear() +
|
||||
"-" +
|
||||
@@ -1193,58 +1225,83 @@ function loadSession() {
|
||||
":" +
|
||||
d.getSeconds();
|
||||
|
||||
if (sessions[i].status == "active") {
|
||||
text = text + `\r${i}: ${formatted_date} [In progress]`;
|
||||
let label = `${formatted_date}`;
|
||||
if (session.status == "active") {
|
||||
label += " [In progress]";
|
||||
} else {
|
||||
if (!exam_details.exam_mode) {
|
||||
text =
|
||||
text +
|
||||
`\r${i}: ${formatted_date} [Complete - score ${sessions[i].score}/${sessions[i].max_score}]`;
|
||||
label += ` [Complete - score ${session.score}/${session.max_score}]`;
|
||||
} else {
|
||||
text =
|
||||
text +
|
||||
`\r${i}: ${formatted_date} [Complete]`;
|
||||
label += " [Complete]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If in exam mode we don't want to allow multiple sessions
|
||||
let s;
|
||||
let $item = $(`
|
||||
<div class="resume-session-item" data-index="${i}" style="padding: 8px; margin: 4px 0; border: 1px solid #444; border-radius: 4px; cursor: pointer; background: #222; color: #eee; display: flex; align-items: center; gap: 10px;">
|
||||
<input type="radio" name="resume-session-select" id="session-opt-${i}" value="${i}" style="cursor: pointer; margin: 0;">
|
||||
<label for="session-opt-${i}" style="cursor: pointer; display: inline; font-size: 13px; margin: 0; color: #ccc;">${label}</label>
|
||||
</div>
|
||||
`);
|
||||
|
||||
$item.on("click", function() {
|
||||
$(this).find("input[type=radio]").prop("checked", true);
|
||||
$(".resume-session-item").css("border-color", "#444");
|
||||
$(this).css("border-color", "#311B92");
|
||||
$("#btn-resume-continue").prop("disabled", false);
|
||||
});
|
||||
|
||||
$("#resume-sessions-list").append($item);
|
||||
});
|
||||
|
||||
// Auto-select the first one by default if exam_mode is active
|
||||
if (exam_details.exam_mode) {
|
||||
s = "0";
|
||||
alert(text);
|
||||
$(".resume-session-item").first().click();
|
||||
} else {
|
||||
s = prompt(text, "0");
|
||||
$("#btn-resume-continue").prop("disabled", true);
|
||||
}
|
||||
|
||||
if (s != null && s != "") {
|
||||
load_previous = true;
|
||||
// Open the dialog
|
||||
$("#resume-dialog").modal({
|
||||
escapeClose: false,
|
||||
clickClose: false,
|
||||
showClose: false
|
||||
});
|
||||
|
||||
exam_details.aid = sessions[parseInt(s)].aid;
|
||||
exam_details.cid = sessions[parseInt(s)].cid;
|
||||
date_started = sessions[parseInt(s)].date;
|
||||
|
||||
let time_left = sessions[parseInt(s)].time_left;
|
||||
|
||||
exam_time = time_left;
|
||||
|
||||
exam_details.question_order = sessions[parseInt(s)].question_order;
|
||||
|
||||
if (sessions[parseInt(s)].status == "complete") {
|
||||
review_mode = true;
|
||||
}
|
||||
} else {
|
||||
// If cancel is pressed or input is blank
|
||||
// Unbind previous click events to prevent duplicates
|
||||
$("#btn-resume-start-new").off("click").on("click", function() {
|
||||
$.modal.close();
|
||||
exam_details.aid = uuidv4();
|
||||
date_started = Date.now();
|
||||
//console.debug("new date", date_started);
|
||||
}
|
||||
exam_details.start_time = date_started / 1000;
|
||||
}
|
||||
exam_details.start_time = date_started / 1000;
|
||||
setUpQuestions(false);
|
||||
if (!review_mode) {
|
||||
initSync(exam_details, URLS);
|
||||
}
|
||||
});
|
||||
|
||||
setUpQuestions(load_previous);
|
||||
if (!review_mode) {
|
||||
initSync(exam_details, URLS);
|
||||
$("#btn-resume-continue").off("click").on("click", function() {
|
||||
let selectedIdx = parseInt($("input[name=resume-session-select]:checked").val());
|
||||
if (isNaN(selectedIdx)) return;
|
||||
|
||||
$.modal.close();
|
||||
let selectedSession = sessions[selectedIdx];
|
||||
|
||||
exam_details.aid = selectedSession.aid;
|
||||
exam_details.cid = selectedSession.cid;
|
||||
date_started = selectedSession.date;
|
||||
exam_time = selectedSession.time_left;
|
||||
exam_details.question_order = selectedSession.question_order;
|
||||
|
||||
if (selectedSession.status == "complete") {
|
||||
review_mode = true;
|
||||
}
|
||||
|
||||
exam_details.start_time = date_started / 1000;
|
||||
setUpQuestions(true);
|
||||
if (!review_mode) {
|
||||
initSync(exam_details, URLS);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -3287,6 +3344,15 @@ $("#btn-reset-local").click(function(evt) {
|
||||
localStorage.removeItem("cid.passcode")
|
||||
} else {}
|
||||
});
|
||||
// Initialize logging controls
|
||||
$("#select-log-level").val(localStorage.getItem("rts.logLevel") || "warn");
|
||||
$("#select-log-level").on("change", function() {
|
||||
let level = $(this).val();
|
||||
log.setLevel(level);
|
||||
localStorage.setItem("rts.logLevel", level);
|
||||
log.info(`Log level changed to: ${level}`);
|
||||
});
|
||||
|
||||
|
||||
$("#btn-delete-current").click(function(evt) {
|
||||
if (exam_details.question_order.length < 1) {
|
||||
|
||||
Reference in New Issue
Block a user