prepare for better integration and testing

This commit is contained in:
Ross
2026-06-29 10:44:50 +01:00
parent 4380d03d89
commit 9afc75763e
8 changed files with 6801 additions and 397 deletions
+16
View File
@@ -0,0 +1,16 @@
/* global Dexie */
// Set up database
export const db = new Dexie("answers_database");
db.version(1).stores({
answers: "[aid+cid+eid+qid+qidn], [aid+cid+eid], qid, ans, eid",
flags: "[aid+cid+eid+qid+qidn], [aid+cid+eid], qid",
user_answers: "[qid+type+ans], [qid+type]",
session: "[eid+aid], eid, packet, aid, status, date, score, max_score, exam_time, time_left, question_order, questions_answered, total_questions",
});
export const question_db = new Dexie("question_database");
question_db.version(1).stores({
question_data: "&[qid+type], qid, type",
saved_exams: "&eid, type, exam_mode, name, order, time, exam_json_id",
});
+19 -19
View File
@@ -3,6 +3,8 @@ import * as helper from "./helpers.js";
import * as viewer from "./viewer.js";
import * as interact from "./interact.js";
import * as config from "./config.js";
import { db, question_db } from "./db.js";
import { initSync, triggerImmediateSync } from "./sync.js";
// const { v4: uuidv4 } = require('uuid');
log.setDefaultLevel("warn")
@@ -199,20 +201,13 @@ cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
cornerstoneTools.init();
// Set up database
const db = new Dexie("answers_database");
db.version(1).stores({
answers: "[aid+cid+eid+qid+qidn], [aid+cid+eid], qid, ans, eid",
flags: "[aid+cid+eid+qid+qidn], [aid+cid+eid], qid",
user_answers: "[qid+type+ans], [qid+type]",
session: "[eid+aid], eid, packet, aid, status, date, score, max_score, exam_time, time_left, question_order, questions_answered, total_questions",
});
const question_db = new Dexie("question_database");
question_db.version(1).stores({
question_data: "&[qid+type], qid, type",
saved_exams: "&eid, type, exam_mode, name, order, time, exam_json_id",
});
function putAnswer(answer) {
answer.time_answered = new Date().toISOString();
answer.synced = false;
db.answers.put(answer).then(() => {
triggerImmediateSync(exam_details, URLS);
});
}
retrievePacketList();
@@ -1102,9 +1097,13 @@ function loadSession() {
date_started = Date.now();
//console.debug("new date", date_started);
}
exam_details.start_time = date_started / 1000;
}
setUpQuestions(load_previous);
if (!review_mode) {
initSync(exam_details, URLS);
}
});
}
@@ -1454,7 +1453,7 @@ async function loadQuestion(n, section = 1, force_reload = false) {
passcode: passcode,
ans: evt.target.value,
};
db.answers.put(answer);
putAnswer(answer);
$(
"#question-list-item-" +
@@ -1526,7 +1525,7 @@ async function loadQuestion(n, section = 1, force_reload = false) {
ans: evt.target.value,
};
db.answers.put(answer);
putAnswer(answer);
console.debug("Save ", answer)
saveSession();
updateQuestionListPanel(answer);
@@ -1556,7 +1555,7 @@ async function loadQuestion(n, section = 1, force_reload = false) {
};
// db.answers.put({aid: [cid, eid, qidn], ans: evt.target.value});
console.debug("Save ", answer)
db.answers.put(answer);
putAnswer(answer);
$(
"#question-list-item-" +
@@ -1657,7 +1656,7 @@ async function loadQuestion(n, section = 1, force_reload = false) {
passcode: passcode,
ans: evt.target.value,
};
db.answers.put(answer);
putAnswer(answer);
$(
"#question-list-item-" +
@@ -1754,7 +1753,7 @@ async function loadQuestion(n, section = 1, force_reload = false) {
ans: evt.target.value,
};
db.answers.put(answer);
putAnswer(answer);
$(
"#question-list-item-" +
@@ -2813,6 +2812,7 @@ $(".start-packet-button").click(function(evt) {
// if in exam mode
exam_details.start_time = new Date().getTime() / 1000;
initSync(exam_details, URLS);
if (timer != null) {
timer.stop();
+129
View File
@@ -0,0 +1,129 @@
// Background Sync Module for RTS
import { db } from "./db.js";
let syncInterval = null;
let debounceTimeout = null;
let currentStatus = "saved"; // "saved", "syncing", "failed"
/**
* Update the sync UI state
* @param {string} status - "saved", "syncing", "failed"
*/
export function updateSyncUI(status) {
currentStatus = status;
const container = document.getElementById("sync-status");
if (!container) return;
let html = "";
switch (status) {
case "syncing":
html = `
<span class="sync-dot syncing"></span>
<span class="sync-text">Saving to server...</span>
`;
break;
case "failed":
html = `
<span class="sync-dot failed"></span>
<span class="sync-text">Offline - Save failed</span>
`;
break;
case "saved":
default:
html = `
<span class="sync-dot saved"></span>
<span class="sync-text">Saved to server</span>
`;
break;
}
container.innerHTML = html;
}
/**
* Perform background synchronization of unsynced answers
*/
export async function performSync(exam_details, URLS) {
if (!exam_details || !exam_details.eid) return;
// Find all unsynced answers for this exam/cid/aid
const unsynced = await db.answers
.where({
aid: exam_details.aid,
cid: exam_details.cid,
eid: exam_details.eid,
})
.filter(ans => ans.synced === false)
.toArray();
if (unsynced.length === 0) {
if (currentStatus === "syncing") {
updateSyncUI("saved");
}
return;
}
updateSyncUI("syncing");
const json = {
eid: exam_details.eid,
cid: exam_details.cid,
start_time: exam_details.start_time,
answers: JSON.stringify(unsynced),
in_progress: "true",
};
try {
const response = await new Promise((resolve, reject) => {
$.post(URLS.exam_submit_url, json, null, "json")
.done(resolve)
.fail(reject);
});
if (response && response.success) {
// Mark as synced in Dexie database
await db.transaction("rw", db.answers, async () => {
for (const ans of unsynced) {
ans.synced = true;
await db.answers.put(ans);
}
});
updateSyncUI("saved");
} else {
updateSyncUI("failed");
}
} catch (error) {
console.error("Autosubmission failed:", error);
updateSyncUI("failed");
}
}
/**
* Trigger an immediate, debounced sync (2s delay after last change)
*/
export function triggerImmediateSync(exam_details, URLS) {
if (debounceTimeout) {
clearTimeout(debounceTimeout);
}
debounceTimeout = setTimeout(() => {
performSync(exam_details, URLS);
}, 2000);
}
/**
* Initialize background sync process
*/
export function initSync(exam_details, URLS) {
// Clear any existing sync process
if (syncInterval) {
clearInterval(syncInterval);
}
// Set initial state
updateSyncUI("saved");
// Run periodic sync every 15 seconds
syncInterval = setInterval(() => {
performSync(exam_details, URLS);
}, 15000);
}