import './style.css'; import { initDB, closeDb, getDatabaseFile, replaceDatabaseFile } from './sqlite-manager.ts'; import { uploadDatabase, downloadDatabase } from './sync.ts'; import { getWordsForReview, getCardSets, getWordsAndStatsForSet } from './db-manager.ts'; import type { CardSetRecord } from './db-manager.ts'; import type { Word } from './db-manager.ts'; import { SetOrderMode } from './algorithm.ts'; import { TrainingPage } from './training.ts'; import type { WordItem } from './training.ts'; // ----- Конфигурация ----- const API_BASE_URL = 'https://learning.micialware.ru'; // ----- Элементы UI ----- const container = document.getElementById('page-container') as HTMLElement; const navBtns = document.querySelectorAll('.nav-btn'); let currentPage: 'words' | 'sets' | 'training' = 'words'; // ----- Вспомогательные функции ----- function escapeHtml(text: string): string { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function setStatus(msg: string, isError = false) { const statusEl = document.getElementById('status'); if (statusEl) { statusEl.textContent = msg; statusEl.className = 'status-bar'; if (isError) statusEl.classList.add('error'); else statusEl.classList.add('success'); } console.log(`[App] ${msg}`); } function getSyncId(): string { const input = document.getElementById('syncId') as HTMLInputElement | null; const id = input?.value.trim(); if (!id) throw new Error('Введите ID синхронизации'); return id; } // ----- Рендеринг страниц ----- function renderWordsPage() { container.innerHTML = `

📡 Синхронизация

📚 Слова

Загрузка...
`; } function renderWordsList(words: Word[]) { const wordListEl = document.getElementById('word-list') as HTMLUListElement | null; if (!wordListEl) return; wordListEl.innerHTML = ''; if (words.length === 0) { wordListEl.innerHTML = '
  • Нет слов для повторения 🎉
  • '; return; } for (const word of words) { const li = document.createElement('li'); li.className = 'word-item'; let reading = word.key; let translation = word.value; if (word.more) { try { const parsed = JSON.parse(word.more); reading = parsed.reading || word.key; translation = parsed.translation || word.value; } catch { // ignore } } li.innerHTML = ` ${reading} ${escapeHtml(translation)} ${word.tags ? `${escapeHtml(word.tags)}` : ''} `; wordListEl.appendChild(li); } } function renderSetsPage() { container.innerHTML = `
      🗂️
      Выберите колоду
      Загрузка...
      `; } function renderSetsList(sets: CardSetRecord[]) { const listEl = document.getElementById('sets-list') as HTMLUListElement | null; if (!listEl) return; listEl.innerHTML = ''; if (sets.length === 0) { listEl.innerHTML = '
    • Нет колод
    • '; return; } for (const set of sets) { const li = document.createElement('li'); li.textContent = set.name; li.dataset.setId = String(set.id); li.addEventListener('click', () => showSetDetail(set, li)); listEl.appendChild(li); } const first = listEl.querySelector('li') as HTMLLIElement | null; if (first) { first.classList.add('selected'); const id = Number(first.dataset.setId); const set = sets.find(s => s.id === id); if (set) showSetDetail(set, first); } } function showSetDetail(set: CardSetRecord, listItem: HTMLLIElement) { document.querySelectorAll('#sets-list li').forEach(el => el.classList.remove('selected')); listItem.classList.add('selected'); const detailEl = document.getElementById('sets-detail'); if (!detailEl) return; detailEl.innerHTML = `

      ${escapeHtml(set.name)}

      Прямой порядок: ${escapeHtml(set.forward)}

      Обратный порядок: ${escapeHtml(set.backward)}

      Фильтр: ${escapeHtml(set.filter)}

      `; } // ----- Тренировка ----- async function startTraining(setId: number) { const modeSelect = document.getElementById('mode-select') as HTMLSelectElement | null; const mode = (modeSelect?.value as SetOrderMode) || SetOrderMode.Default; // Получаем колоду const sets = await getCardSets(); const set = sets.find(s => s.id === setId); if (!set) { setStatus('Колода не найдена', true); return; } // Получаем слова и статистику const { words, stats } = await getWordsAndStatsForSet(setId); if (words.length === 0) { setStatus('Нет слов для тренировки', true); return; } console.log(`[Training] Запуск тренировки: ${words.length} слов, ${stats.length} записей статистики, режим: ${mode}`); const settings = { id: set.id, name: set.name, forward: set.forward, backward: set.backward, filter: set.filter, count: null, worst_words_list: null, open_mode: mode, }; const wordItems: WordItem[] = words.map((w) => { let parsed: Record = {}; if (w.more) { try { parsed = JSON.parse(w.more); } catch { /* ignore */ } } return { id: w.id, key: w.key, value: w.value, tags: w.tags, more: parsed, group_id: w.group_id, }; }); const cardStats = stats.map((s) => ({ id: s.id, word_id: s.word_id, set_id: s.set_id, last_open: s.last_opened * 1000, score: s.score, })); // Показываем страницу тренировки hideNav(); currentPage = 'training'; container.innerHTML = '
      '; const trainingRoot = document.getElementById('training-root') as HTMLElement; new TrainingPage( trainingRoot, settings, wordItems, cardStats, { onFinish: () => { showNav(); switchPage('sets'); }, }, ); } // ----- Навигация ----- function switchPage(page: 'words' | 'sets') { currentPage = page; showNav(); navBtns.forEach(btn => { btn.classList.toggle('active', btn.dataset.page === page); }); if (page === 'words') { renderWordsPage(); loadWords(); } else { renderSetsPage(); loadSets(); } } function hideNav() { document.getElementById('app-nav')!.style.display = 'none'; } function showNav() { document.getElementById('app-nav')!.style.display = 'flex'; } async function loadWords() { try { const words = await getWordsForReview(); renderWordsList(words); const count = words.length; setStatus(`Слов: ${count}`); } catch (err) { setStatus('Ошибка загрузки слов', true); console.error(err); } } async function loadSets() { try { const sets = await getCardSets(); renderSetsList(sets); setStatus(`Колод: ${sets.length}`); } catch (err) { setStatus('Ошибка загрузки колод', true); console.error(err); } } // ----- Обработчики синхронизации ----- async function onDownload() { try { const id = getSyncId(); const btn = document.getElementById('btnDownload') as HTMLButtonElement | null; if (btn) btn.disabled = true; setStatus('Скачивание...'); await downloadDatabase(id, API_BASE_URL, replaceDatabaseFile, initDB); if (currentPage === 'words') await loadWords(); setStatus('База данных скачана.'); } catch { // ошибка уже обработана в sync.ts } finally { const btn = document.getElementById('btnDownload') as HTMLButtonElement | null; if (btn) btn.disabled = false; } } async function onUpload() { try { const id = getSyncId(); const btn = document.getElementById('btnUpload') as HTMLButtonElement | null; if (btn) btn.disabled = true; setStatus('Загрузка...'); await uploadDatabase(id, API_BASE_URL, closeDb, getDatabaseFile); await initDB(); if (currentPage === 'words') await loadWords(); setStatus('База данных загружена.'); } catch { // ошибка уже обработана в sync.ts } finally { const btn = document.getElementById('btnUpload') as HTMLButtonElement | null; if (btn) btn.disabled = false; } } // ----- Инициализация ----- async function main() { try { await initDB(); console.log('[App] База данных инициализирована.'); // Навигация navBtns.forEach(btn => { btn.addEventListener('click', () => { if (currentPage !== 'training') { switchPage(btn.dataset.page as 'words' | 'sets'); } }); }); // Стартовая страница switchPage('words'); // Глобальные обработчики container.addEventListener('click', async (e) => { const target = e.target as HTMLElement; if (target.id === 'btnDownload') await onDownload(); if (target.id === 'btnUpload') await onUpload(); const trainingBtn = target.closest('.btn-start-training'); if (trainingBtn) { const setId = Number((trainingBtn as HTMLElement).dataset.setId); await startTraining(setId); } }); } catch (err) { console.error('[App] Ошибка инициализации:', err); } } main();