208 lines
6.7 KiB
JavaScript
208 lines
6.7 KiB
JavaScript
class BookViewer {
|
|
constructor() {
|
|
this.selectedBook = null;
|
|
this.selectedChapter = null;
|
|
this.books = [];
|
|
this.chapters = [];
|
|
|
|
this.init();
|
|
}
|
|
|
|
async init() {
|
|
// Wait for DOM to be ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', () => this.setup());
|
|
} else {
|
|
this.setup();
|
|
}
|
|
}
|
|
|
|
async setup() {
|
|
this.bindElements();
|
|
await this.loadBooks();
|
|
}
|
|
|
|
bindElements() {
|
|
this.booksListEl = document.getElementById('books-list');
|
|
this.chaptersListEl = document.getElementById('chapters-list');
|
|
this.chaptersSectionEl = document.getElementById('chapters-section');
|
|
this.pdfPlaceholderEl = document.getElementById('pdf-placeholder');
|
|
this.pdfViewerEl = document.getElementById('pdf-viewer');
|
|
}
|
|
|
|
async loadBooks() {
|
|
try {
|
|
this.showLoading(this.booksListEl, 'Loading books...');
|
|
|
|
const response = await fetch('/api/books');
|
|
if (!response.ok) {
|
|
throw new Error('Failed to load books');
|
|
}
|
|
|
|
const data = await response.json();
|
|
this.books = data.books;
|
|
|
|
this.renderBooks();
|
|
} catch (error) {
|
|
console.error('Error loading books:', error);
|
|
this.showError(this.booksListEl, 'Failed to load books. Please refresh the page.');
|
|
}
|
|
}
|
|
|
|
async loadChapters(bookName) {
|
|
try {
|
|
this.showLoading(this.chaptersListEl, 'Loading chapters...');
|
|
|
|
const response = await fetch(`/api/books/${encodeURIComponent(bookName)}/chapters`);
|
|
if (!response.ok) {
|
|
throw new Error('Failed to load chapters');
|
|
}
|
|
|
|
const data = await response.json();
|
|
this.chapters = data.chapters;
|
|
|
|
this.renderChapters();
|
|
this.showChaptersSection();
|
|
} catch (error) {
|
|
console.error('Error loading chapters:', error);
|
|
this.showError(this.chaptersListEl, 'Failed to load chapters for this book.');
|
|
}
|
|
}
|
|
|
|
renderBooks() {
|
|
this.booksListEl.innerHTML = '';
|
|
|
|
if (this.books.length === 0) {
|
|
this.booksListEl.innerHTML = '<div class="error">No books available</div>';
|
|
return;
|
|
}
|
|
|
|
this.books.forEach(book => {
|
|
const bookEl = document.createElement('div');
|
|
bookEl.className = 'book-item';
|
|
bookEl.textContent = book.displayName;
|
|
bookEl.dataset.bookName = book.name;
|
|
|
|
bookEl.addEventListener('click', () => this.selectBook(book.name, bookEl));
|
|
|
|
this.booksListEl.appendChild(bookEl);
|
|
});
|
|
}
|
|
|
|
renderChapters() {
|
|
this.chaptersListEl.innerHTML = '';
|
|
|
|
if (this.chapters.length === 0) {
|
|
this.chaptersListEl.innerHTML = '<div class="error">No chapters available for this book</div>';
|
|
return;
|
|
}
|
|
|
|
this.chapters.forEach(chapter => {
|
|
const chapterEl = document.createElement('div');
|
|
chapterEl.className = 'chapter-item';
|
|
chapterEl.textContent = chapter.displayName;
|
|
chapterEl.dataset.chapterUrl = chapter.url;
|
|
|
|
chapterEl.addEventListener('click', () => this.selectChapter(chapter.url, chapterEl));
|
|
|
|
this.chaptersListEl.appendChild(chapterEl);
|
|
});
|
|
}
|
|
|
|
async selectBook(bookName, bookEl) {
|
|
// Update selected book UI
|
|
document.querySelectorAll('.book-item').forEach(el => el.classList.remove('selected'));
|
|
bookEl.classList.add('selected');
|
|
|
|
// Clear selected chapter
|
|
this.selectedChapter = null;
|
|
document.querySelectorAll('.chapter-item').forEach(el => el.classList.remove('selected'));
|
|
this.hidePDF();
|
|
|
|
this.selectedBook = bookName;
|
|
|
|
// Load chapters for selected book
|
|
await this.loadChapters(bookName);
|
|
}
|
|
|
|
selectChapter(chapterUrl, chapterEl) {
|
|
// Update selected chapter UI
|
|
document.querySelectorAll('.chapter-item').forEach(el => el.classList.remove('selected'));
|
|
chapterEl.classList.add('selected');
|
|
|
|
this.selectedChapter = chapterUrl;
|
|
this.showPDF(chapterUrl);
|
|
}
|
|
|
|
showPDF(url) {
|
|
this.pdfPlaceholderEl.style.display = 'none';
|
|
this.pdfViewerEl.style.display = 'block';
|
|
this.pdfViewerEl.src = url;
|
|
|
|
// Handle PDF load errors
|
|
this.pdfViewerEl.onload = () => {
|
|
// PDF loaded successfully
|
|
};
|
|
|
|
this.pdfViewerEl.onerror = () => {
|
|
this.showError(this.pdfPlaceholderEl, 'Failed to load PDF. Please try another chapter.');
|
|
this.hidePDF();
|
|
};
|
|
}
|
|
|
|
hidePDF() {
|
|
this.pdfViewerEl.style.display = 'none';
|
|
this.pdfPlaceholderEl.style.display = 'flex';
|
|
this.pdfViewerEl.src = '';
|
|
}
|
|
|
|
showChaptersSection() {
|
|
this.chaptersSectionEl.style.display = 'block';
|
|
}
|
|
|
|
hideChaptersSection() {
|
|
this.chaptersSectionEl.style.display = 'none';
|
|
}
|
|
|
|
showLoading(element, message = 'Loading...') {
|
|
element.innerHTML = `<div class="loading">${message}</div>`;
|
|
}
|
|
|
|
showError(element, message) {
|
|
element.innerHTML = `<div class="error">${message}</div>`;
|
|
}
|
|
}
|
|
|
|
// Initialize the book viewer when the script loads
|
|
const bookViewer = new BookViewer();
|
|
|
|
// Add some keyboard shortcuts for better UX
|
|
document.addEventListener('keydown', (e) => {
|
|
// ESC key to deselect
|
|
if (e.key === 'Escape') {
|
|
const selectedChapter = document.querySelector('.chapter-item.selected');
|
|
if (selectedChapter) {
|
|
selectedChapter.classList.remove('selected');
|
|
bookViewer.hidePDF();
|
|
}
|
|
}
|
|
|
|
// Arrow keys for navigation
|
|
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
|
const chapters = document.querySelectorAll('.chapter-item');
|
|
const selectedChapter = document.querySelector('.chapter-item.selected');
|
|
|
|
if (chapters.length > 0) {
|
|
let currentIndex = Array.from(chapters).indexOf(selectedChapter);
|
|
|
|
if (e.key === 'ArrowUp') {
|
|
currentIndex = Math.max(0, currentIndex - 1);
|
|
} else {
|
|
currentIndex = Math.min(chapters.length - 1, currentIndex + 1);
|
|
}
|
|
|
|
chapters[currentIndex]?.click();
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
}); |