106 lines
3.8 KiB
JavaScript
106 lines
3.8 KiB
JavaScript
let activeFilter = 'all';
|
|
let activeSort = 'az';
|
|
let isLocalMode = false;
|
|
let localEntries = [];
|
|
|
|
function toggleLibraryMode() {
|
|
isLocalMode = !isLocalMode;
|
|
const btn = document.getElementById('library-mode-btn');
|
|
const onlineContent = document.getElementById('online-content');
|
|
const localContent = document.getElementById('local-content');
|
|
|
|
if (isLocalMode) {
|
|
btn.classList.add('active');
|
|
onlineContent.classList.add('hidden');
|
|
localContent.classList.remove('hidden');
|
|
loadLocalEntries();
|
|
} else {
|
|
btn.classList.remove('active');
|
|
onlineContent.classList.remove('hidden');
|
|
localContent.classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
async function loadLocalEntries() {
|
|
const grid = document.getElementById('local-entries-grid');
|
|
grid.innerHTML = '<div class="skeleton-card"></div>'.repeat(6);
|
|
|
|
try {
|
|
const [mangaRes, novelRes] = await Promise.all([
|
|
fetch('/api/library/manga'),
|
|
fetch('/api/library/novels')
|
|
]);
|
|
|
|
const [manga, novel] = await Promise.all([
|
|
mangaRes.json(),
|
|
novelRes.json()
|
|
]);
|
|
|
|
localEntries = [
|
|
...manga.map(e => ({ ...e, type: 'manga' })),
|
|
...novel.map(e => ({ ...e, type: 'novel' }))
|
|
];
|
|
|
|
if (localEntries.length === 0) {
|
|
grid.innerHTML = '<p style="grid-column:1/-1;text-align:center;padding:3rem;">No books found.</p>';
|
|
return;
|
|
}
|
|
|
|
renderLocalEntries(localEntries);
|
|
} catch {
|
|
grid.innerHTML = '<p style="grid-column:1/-1;text-align:center;color:var(--color-danger);padding:3rem;">Error loading library.</p>';
|
|
}
|
|
}
|
|
|
|
function filterLocal(type) {
|
|
if (type === 'all') renderLocalEntries(localEntries);
|
|
else renderLocalEntries(localEntries.filter(e => e.type === type));
|
|
}
|
|
|
|
function renderLocalEntries(entries) {
|
|
const grid = document.getElementById('local-entries-grid');
|
|
grid.innerHTML = entries.map(entry => {
|
|
const title = entry.metadata?.title?.romaji || entry.metadata?.title?.english || entry.id;
|
|
const cover = entry.metadata?.coverImage?.extraLarge || '/public/assets/placeholder.jpg';
|
|
const chapters = entry.metadata?.chapters || '??';
|
|
|
|
return `
|
|
<div class="local-card" onclick="viewLocalEntry(${entry.metadata?.id || 'null'})">
|
|
<div class="card-img-wrap">
|
|
<img src="${cover}" alt="${title}" loading="lazy">
|
|
</div>
|
|
<div class="local-card-info">
|
|
<div class="local-card-title">${title}</div>
|
|
<p style="font-size: 0.85rem; color: var(--color-text-secondary); margin: 0;">
|
|
${chapters} Chapters
|
|
</p>
|
|
<div class="badge">${entry.type}</div>
|
|
<div class="match-status ${entry.matched ? 'status-linked' : 'status-unlinked'}">
|
|
${entry.matched ? '● Linked' : '○ Unlinked'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
async function scanLocalLibrary() {
|
|
const btnText = document.getElementById('scan-text');
|
|
btnText.innerText = "Scanning...";
|
|
try {
|
|
// Asumiendo que el scan de libros usa este query param
|
|
const response = await fetch('/api/library/scan?mode=incremental', { method: 'POST' });
|
|
if (response.ok) {
|
|
await loadLocalEntries();
|
|
if (window.NotificationUtils) NotificationUtils.show('Library scanned!', 'success');
|
|
}
|
|
} catch (err) {
|
|
if (window.NotificationUtils) NotificationUtils.show('Scan failed', 'error');
|
|
} finally {
|
|
btnText.innerText = "Scan Library";
|
|
}
|
|
}
|
|
|
|
function viewLocalEntry(id) {
|
|
if (id) window.location.href = `/book/${id}`;
|
|
} |