210 lines
7.1 KiB
JavaScript
210 lines
7.1 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');
|
|
const svg = btn.querySelector('svg');
|
|
const label = btn.querySelector('span');
|
|
|
|
if (isLocalMode) {
|
|
// LOCAL MODE
|
|
btn.classList.add('active');
|
|
onlineContent.classList.add('hidden');
|
|
localContent.classList.remove('hidden');
|
|
loadLocalEntries();
|
|
|
|
svg.innerHTML = `
|
|
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
|
<polyline points="9 22 9 12 15 12 15 22"/>
|
|
`;
|
|
} else {
|
|
// ONLINE MODE
|
|
btn.classList.remove('active');
|
|
onlineContent.classList.remove('hidden');
|
|
localContent.classList.add('hidden');
|
|
|
|
svg.innerHTML = `
|
|
<circle cx="12" cy="12" r="10"/>
|
|
<line x1="2" y1="12" x2="22" y2="12"/>
|
|
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
|
|
`;
|
|
}
|
|
}
|
|
|
|
async function loadLocalEntries() {
|
|
const grid = document.getElementById('local-entries-grid');
|
|
grid.innerHTML = '<div class="skeleton-card"></div>'.repeat(8);
|
|
|
|
try {
|
|
const response = await fetch('/api/library/anime');
|
|
const entries = await response.json();
|
|
localEntries = entries;
|
|
|
|
if (entries.length === 0) {
|
|
grid.innerHTML = '<p style="grid-column: 1/-1; text-align: center; color: var(--color-text-secondary); padding: 3rem;">No anime found in your local library. Click "Scan Library" to scan your folders.</p>';
|
|
|
|
return;
|
|
}
|
|
|
|
// Renderizar grid
|
|
grid.innerHTML = entries.map(entry => {
|
|
const title = entry.metadata?.title?.romaji || entry.metadata?.title?.english || entry.id;
|
|
const cover = entry.metadata?.coverImage?.extraLarge || entry.metadata?.coverImage?.large || '/public/assets/placeholder.jpg';
|
|
const score = entry.metadata?.averageScore || '--';
|
|
const episodes = entry.metadata?.episodes || '??';
|
|
|
|
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;">
|
|
${score}% • ${episodes} Eps
|
|
</p>
|
|
<div class="match-status ${entry.matched ? 'status-linked' : 'status-unlinked'}">
|
|
${entry.matched ? '● Linked' : '○ Unlinked'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
} catch (err) {
|
|
console.error('Error loading local entries:', err);
|
|
grid.innerHTML = '<p style="grid-column: 1/-1; text-align: center; color: var(--color-danger); padding: 3rem;">Error loading local library. Make sure the backend is running.</p>';
|
|
}
|
|
}
|
|
|
|
|
|
async function scanLocalLibrary() {
|
|
const btnText = document.getElementById('scan-text');
|
|
const originalText = btnText.innerText;
|
|
btnText.innerText = "Scanning...";
|
|
|
|
try {
|
|
const response = await fetch('/api/library/scan?mode=incremental', {
|
|
method: 'POST'
|
|
});
|
|
|
|
if (response.ok) {
|
|
await loadLocalEntries();
|
|
// Mostrar notificación de éxito si tienes sistema de notificaciones
|
|
if (window.NotificationUtils) {
|
|
NotificationUtils.show('Library scanned successfully!', 'success');
|
|
}
|
|
} else {
|
|
throw new Error('Scan failed');
|
|
}
|
|
} catch (err) {
|
|
console.error("Scan failed", err);
|
|
alert("Failed to scan library. Check console for details.");
|
|
|
|
// Mostrar notificación de error si tienes sistema de notificaciones
|
|
if (window.NotificationUtils) {
|
|
NotificationUtils.show('Failed to scan library', 'error');
|
|
}
|
|
} finally {
|
|
btnText.innerText = originalText;
|
|
}
|
|
}
|
|
|
|
function viewLocalEntry(anilistId) {
|
|
if (!anilistId) {
|
|
console.warn('Anime not linked');
|
|
return;
|
|
}
|
|
window.location.href = `/anime/${anilistId}`;
|
|
}
|
|
|
|
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
|
|
|| entry.metadata?.coverImage?.large
|
|
|| '/public/assets/placeholder.jpg';
|
|
|
|
const score = entry.metadata?.averageScore || '--';
|
|
const episodes = entry.metadata?.episodes || '??';
|
|
|
|
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;">
|
|
${score}% • ${episodes} Eps
|
|
</p>
|
|
<div class="match-status ${entry.matched ? 'status-linked' : 'status-unlinked'}">
|
|
${entry.matched ? '● Linked' : '○ Unlinked'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
function applyLocalFilters() {
|
|
let filtered = [...localEntries];
|
|
|
|
if (activeFilter === 'linked') {
|
|
filtered = filtered.filter(e => e.matched);
|
|
}
|
|
|
|
if (activeFilter === 'unlinked') {
|
|
filtered = filtered.filter(e => !e.matched);
|
|
}
|
|
|
|
if (activeSort === 'az') {
|
|
filtered.sort((a, b) =>
|
|
(a.metadata?.title?.romaji || a.id)
|
|
.localeCompare(b.metadata?.title?.romaji || b.id)
|
|
);
|
|
}
|
|
|
|
if (activeSort === 'za') {
|
|
filtered.sort((a, b) =>
|
|
(b.metadata?.title?.romaji || b.id)
|
|
.localeCompare(a.metadata?.title?.romaji || a.id)
|
|
);
|
|
}
|
|
|
|
renderLocalEntries(filtered);
|
|
}
|
|
|
|
document.addEventListener('click', e => {
|
|
const btn = e.target.closest('.filter-btn');
|
|
if (!btn) return;
|
|
|
|
if (btn.dataset.filter) {
|
|
activeFilter = btn.dataset.filter;
|
|
}
|
|
|
|
if (btn.dataset.sort) {
|
|
activeSort = btn.dataset.sort;
|
|
}
|
|
|
|
btn
|
|
.closest('.local-filters')
|
|
.querySelectorAll('.filter-btn')
|
|
.forEach(b => b.classList.remove('active'));
|
|
|
|
btn.classList.add('active');
|
|
|
|
applyLocalFilters();
|
|
});
|