diff --git a/assets/js/offline-engine.js b/assets/js/offline-engine.js new file mode 100644 index 0000000..cf9305c --- /dev/null +++ b/assets/js/offline-engine.js @@ -0,0 +1,195 @@ +import { getDB } from './db.js'; + +function getStore(storeName, mode = 'readonly') { + + const db = getDB(); + + const tx = db.transaction(storeName, mode); + + return tx.objectStore(storeName); +} + +export async function saveWords(lang, list, words) { + + const store = getStore('words', 'readwrite'); + + for (const word of words) { + + store.put({ + key: `${lang}:${list}:${word.id}`, + lang, + list, + ...word + }); + } +} + +export async function getWords(lang, list) { + + return new Promise((resolve) => { + + const store = getStore('words'); + + const req = store.getAll(); + + req.onsuccess = () => { + + const result = req.result.filter( + w => w.lang === lang && w.list === list + ); + + resolve(result); + }; + }); +} + +export async function getNextWord(lang, list) { + + const words = await getWords(lang, list); + + if (!words.length) { + return null; + } + + const today = new Date().toISOString().split('T')[0]; + + let due = words.filter(word => { + return !word.nextReview || + word.nextReview <= today; + }); + + if (!due.length) { + due = words; + } + + const random = + Math.floor(Math.random() * due.length); + + return due[random]; +} + +export async function answerWord(word, answer) { + + const correct = + word.answer.trim().toLowerCase() === + answer.trim().toLowerCase(); + + const store = + getStore('words', 'readwrite'); + + const updated = { ...word }; + + if (correct) { + + const days = + updated.interval + ? updated.interval * 2 + : 1; + + updated.interval = days; + + const d = new Date(); + d.setDate(d.getDate() + days); + + updated.nextReview = + d.toISOString().split('T')[0]; + + } else { + + updated.interval = 1; + + const d = new Date(); + d.setDate(d.getDate() + 1); + + updated.nextReview = + d.toISOString().split('T')[0]; + } + + store.put(updated); + + await updateSessionStats(correct); + + await queueSync({ + wordId: word.id, + lang: word.lang, + list: word.list, + correct, + timestamp: Date.now() + }); + + return { + correct + }; +} + +async function queueSync(item) { + + const store = + getStore('sync_queue', 'readwrite'); + + store.add(item); +} + +export async function updateSessionStats(correct) { + + const store = + getStore('session', 'readwrite'); + + const req = + store.get('stats'); + + req.onsuccess = () => { + + let stats = req.result; + + if (!stats) { + stats = { + key: 'stats', + correct: 0, + wrong: 0 + }; + } + + if (correct) { + stats.correct++; + } else { + stats.wrong++; + } + + store.put(stats); + }; +} + +export async function getSessionStats() { + + return new Promise(resolve => { + + const store = + getStore('session'); + + const req = + store.get('stats'); + + req.onsuccess = () => { + + resolve( + req.result || { + correct: 0, + wrong: 0 + } + ); + }; + }); +} + +export async function resetSessionStats() { + + const store = + getStore('session', 'readwrite'); + + store.put({ + key: 'stats', + correct: 0, + wrong: 0 + }); +} \ No newline at end of file