// Main application JavaScript document.addEventListener('DOMContentLoaded', () => { // DOM Elements const elements = { searchInput: document.getElementById('search-input'), searchButton: document.getElementById('search-button'), resultsSection: document.getElementById('results-section'), resultsHeading: document.getElementById('results-heading'), resultsTable: document.getElementById('results-table'), resultsTableBody: document.querySelector('#results-table tbody'), searchLoading: document.getElementById('search-loading'), statusLoading: document.getElementById('status-loading'), statusTable: document.getElementById('status-table'), statusTableBody: document.querySelector('#status-table tbody'), modalOverlay: document.getElementById('modal-overlay'), detailsContainer: document.getElementById('details-container') }; // State let currentBookDetails = null; const STATE = { isSearching: false, isLoadingDetails: false }; // Constants const REFRESH_INTERVAL = 60000; // 60 seconds const API_ENDPOINTS = { search: '/request/api/search', info: '/request/api/info', download: '/request/api/download', status: '/request/api/status' }; // Utility Functions const utils = { debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }, showLoading(element) { element.style.display = 'block'; }, hideLoading(element) { element.style.display = 'none'; }, async fetchJson(url, options = {}) { try { const response = await fetch(url, options); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error('Fetch error:', error); throw error; } }, createElement(tag, attributes = {}, children = []) { const element = document.createElement(tag); Object.entries(attributes).forEach(([key, value]) => { element[key] = value; }); children.forEach(child => { if (typeof child === 'string') { element.appendChild(document.createTextNode(child)); } else { element.appendChild(child); } }); return element; } }; // Search Functions const search = { async performSearch(query) { if (STATE.isSearching) return; try { STATE.isSearching = true; elements.resultsSection.classList.remove('collapsed'); utils.showLoading(elements.searchLoading); const data = await utils.fetchJson( `${API_ENDPOINTS.search}?query=${encodeURIComponent(query)}` ); this.displayResults(data); } catch (error) { this.handleSearchError(error); } finally { STATE.isSearching = false; utils.hideLoading(elements.searchLoading); } }, displayResults(books) { elements.resultsTableBody.innerHTML = ''; if (!books.length) { this.displayNoResults(); return; } books.forEach((book, index) => { const row = this.createBookRow(book, index); elements.resultsTableBody.appendChild(row); }); }, displayNoResults() { const row = utils.createElement('tr', {}, [ utils.createElement('td', { colSpan: '10', textContent: 'No results found.' }) ]); elements.resultsTableBody.appendChild(row); }, createBookRow(book, index) { return utils.createElement('tr', {}, [ utils.createElement('td', { textContent: index + 1 }), this.createPreviewCell(book.preview), utils.createElement('td', { textContent: book.title || 'N/A' }), utils.createElement('td', { textContent: book.author || 'N/A' }), utils.createElement('td', { textContent: book.publisher || 'N/A' }), utils.createElement('td', { textContent: book.year || 'N/A' }), utils.createElement('td', { textContent: book.language || 'N/A' }), utils.createElement('td', { textContent: book.format || 'N/A' }), utils.createElement('td', { textContent: book.size || 'N/A' }), this.createActionCell(book) ]); }, createPreviewCell(previewUrl) { if (!previewUrl) { return utils.createElement('td', { textContent: 'N/A' }); } const img = utils.createElement('img', { src: previewUrl, alt: 'Book Preview', style: 'max-width: 60px;' }); return utils.createElement('td', {}, [img]); }, createActionCell(book) { const buttonDetails = utils.createElement('button', { className: 'details-button', onclick: () => bookDetails.show(book.id) }, [utils.createElement('span', { textContent: 'Details' })]); const downloadButton = utils.createElement('button', { className: 'download-button', onclick: () => bookDetails.downloadBook(book) }, [utils.createElement('span', { textContent: 'Download' })]); return utils.createElement('td', {}, [buttonDetails, downloadButton]); }, handleSearchError(error) { console.error('Search error:', error); elements.resultsTableBody.innerHTML = ''; const errorRow = utils.createElement('tr', {}, [ utils.createElement('td', { colSpan: '10', textContent: 'An error occurred while searching. Please try again.' }) ]); elements.resultsTableBody.appendChild(errorRow); } }; // Book Details Functions const bookDetails = { async show(bookId) { if (STATE.isLoadingDetails) return; try { STATE.isLoadingDetails = true; modal.open(); elements.detailsContainer.innerHTML = '
Loading details...
'; const book = await utils.fetchJson( `${API_ENDPOINTS.info}?id=${encodeURIComponent(bookId)}` ); currentBookDetails = book; this.displayDetails(book); } catch (error) { this.handleDetailsError(error); } finally { STATE.isLoadingDetails = false; } }, displayDetails(book) { elements.detailsContainer.innerHTML = this.generateDetailsHTML(book); // Add event listeners document.getElementById('download-button') .addEventListener('click', () => this.downloadBook(book)); document.getElementById('close-details') .addEventListener('click', modal.close); }, generateDetailsHTML(book) { return `Author: ${book.author || 'N/A'}
Publisher: ${book.publisher || 'N/A'}
Year: ${book.year || 'N/A'}
Language: ${book.language || 'N/A'}
Format: ${book.format || 'N/A'}
Size: ${book.size || 'N/A'}
Error loading details. Please try again.