fix incorrect score setup
This commit is contained in:
+197
@@ -0,0 +1,197 @@
|
||||
// ──── История повторений (IndexedDB) ────
|
||||
// Каждая колода хранит до 2000 последних записей.
|
||||
|
||||
const DB_NAME = 'JapLearnHistory';
|
||||
const DB_VERSION = 1;
|
||||
|
||||
export interface RepetitionRecord {
|
||||
id?: number; // автоинкремент
|
||||
set_id: number;
|
||||
word_id: number;
|
||||
timestamp: number; // unix ms
|
||||
mode: string; // easy | ok | hard | none
|
||||
score_before: number;
|
||||
score_after: number;
|
||||
}
|
||||
|
||||
let db: IDBDatabase | null = null;
|
||||
|
||||
function openDB(): Promise<IDBDatabase> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
||||
|
||||
request.onupgradeneeded = () => {
|
||||
const database = request.result;
|
||||
|
||||
// Создаём store для каждой колоды динамически при первой записи,
|
||||
// но для простоты сделаем один store с индексом по set_id
|
||||
if (!database.objectStoreNames.contains('repetitions')) {
|
||||
const store = database.createObjectStore('repetitions', {
|
||||
keyPath: 'id',
|
||||
autoIncrement: true,
|
||||
});
|
||||
store.createIndex('set_id', 'set_id', { unique: false });
|
||||
store.createIndex('timestamp', 'timestamp', { unique: false });
|
||||
}
|
||||
};
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result);
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error('Не удалось открыть IndexedDB'));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function getDB(): Promise<IDBDatabase> {
|
||||
if (db) return db;
|
||||
db = await openDB();
|
||||
return db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет запись о повторении в историю.
|
||||
* После добавления удаляет самые старые записи, если превышен лимит (2000 на колоду).
|
||||
*/
|
||||
export async function addRepetition(record: Omit<RepetitionRecord, 'id'>): Promise<void> {
|
||||
const database = await getDB();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = database.transaction('repetitions', 'readwrite');
|
||||
const store = tx.objectStore('repetitions');
|
||||
|
||||
// Добавляем запись
|
||||
const addRequest = store.add(record);
|
||||
|
||||
addRequest.onsuccess = () => {
|
||||
// После добавления проверяем лимит
|
||||
trimHistory(record.set_id, 2000);
|
||||
resolve();
|
||||
};
|
||||
|
||||
addRequest.onerror = () => {
|
||||
reject(new Error('Ошибка записи в историю'));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаляет самые старые записи для указанной колоды, оставляя не более limit.
|
||||
*/
|
||||
async function trimHistory(setId: number, limit: number): Promise<void> {
|
||||
const database = await getDB();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = database.transaction('repetitions', 'readwrite');
|
||||
const store = tx.objectStore('repetitions');
|
||||
const index = store.index('set_id');
|
||||
const range = IDBKeyRange.only(setId);
|
||||
|
||||
const countRequest = index.count(range);
|
||||
|
||||
countRequest.onsuccess = () => {
|
||||
const count = countRequest.result;
|
||||
|
||||
if (count <= limit) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем все ID для этой колоды, сортируем по timestamp
|
||||
const allRequest = index.getAllKeys(range);
|
||||
|
||||
allRequest.onsuccess = () => {
|
||||
// Получаем записи с их id и timestamp для сортировки
|
||||
const cursorRequest = index.openCursor(range);
|
||||
const entries: { id: number; timestamp: number }[] = [];
|
||||
|
||||
cursorRequest.onsuccess = () => {
|
||||
const cursor = cursorRequest.result;
|
||||
if (cursor) {
|
||||
entries.push({ id: cursor.value.id, timestamp: cursor.value.timestamp });
|
||||
cursor.continue();
|
||||
} else {
|
||||
// Сортируем по timestamp (старые первые)
|
||||
entries.sort((a, b) => a.timestamp - b.timestamp);
|
||||
|
||||
// Сколько нужно удалить
|
||||
const toDelete = entries.length - limit;
|
||||
|
||||
for (let i = 0; i < toDelete; i++) {
|
||||
store.delete(entries[i].id);
|
||||
}
|
||||
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
countRequest.onerror = () => reject(new Error('Ошибка подсчёта записей'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает историю повторений для указанной колоды, отсортированную по времени (новые первые).
|
||||
*/
|
||||
export async function getHistory(setId: number, limit = 2000): Promise<RepetitionRecord[]> {
|
||||
const database = await getDB();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = database.transaction('repetitions', 'readonly');
|
||||
const store = tx.objectStore('repetitions');
|
||||
const index = store.index('set_id');
|
||||
const range = IDBKeyRange.only(setId);
|
||||
|
||||
const records: RepetitionRecord[] = [];
|
||||
|
||||
const cursorRequest = index.openCursor(range, 'prev'); // новые первые
|
||||
|
||||
cursorRequest.onsuccess = () => {
|
||||
const cursor = cursorRequest.result;
|
||||
if (cursor && records.length < limit) {
|
||||
records.push(cursor.value);
|
||||
cursor.continue();
|
||||
} else {
|
||||
resolve(records);
|
||||
}
|
||||
};
|
||||
|
||||
cursorRequest.onerror = () => {
|
||||
reject(new Error('Ошибка чтения истории'));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает список ID колод, для которых есть история.
|
||||
*/
|
||||
export async function getHistorySetIds(): Promise<number[]> {
|
||||
const database = await getDB();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = database.transaction('repetitions', 'readonly');
|
||||
const store = tx.objectStore('repetitions');
|
||||
const index = store.index('set_id');
|
||||
|
||||
const setIdSet = new Set<number>();
|
||||
|
||||
const cursorRequest = index.openCursor();
|
||||
|
||||
cursorRequest.onsuccess = () => {
|
||||
const cursor = cursorRequest.result;
|
||||
if (cursor) {
|
||||
setIdSet.add(cursor.value.set_id);
|
||||
cursor.continue();
|
||||
} else {
|
||||
resolve(Array.from(setIdSet));
|
||||
}
|
||||
};
|
||||
|
||||
cursorRequest.onerror = () => {
|
||||
reject(new Error('Ошибка чтения ID колод'));
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user