Files
COS498-HW3/frontend/public/scripts/app.js
T
2025-11-25 22:55:02 +00:00

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();
}
}
});