// Copyright (c) 2026 Gordon Bolton. MIT License. import { state, HISTORY_REFRESH_MS } from '../state.js'; import { loadHistory as apiLoadHistory } from '../api.js'; import { saveHistoryDays, saveIgnoreAvailable } from '../utils/storage.js'; import { formatDate, formatEpisodeInfo, escapeHtml } from '../utils/format.js'; import { renderTagBadges } from './downloads.js'; function createServiceIcons(item) { const container = document.createElement('span'); container.className = 'service-icons-container'; // Add Ombi icon for all users if ombiLink exists if (item.ombiLink) { const ombiIcon = document.createElement('img'); ombiIcon.className = 'service-icon ombi'; ombiIcon.src = '/images/ombi.svg'; ombiIcon.alt = 'Ombi'; ombiIcon.title = item.ombiTooltip || 'Ombi'; const ombiLink = document.createElement('a'); ombiLink.href = item.ombiLink; ombiLink.target = '_blank'; ombiLink.appendChild(ombiIcon); container.appendChild(ombiLink); } // Add Sonarr/Radarr icon for admin users if arrLink exists if (state.isAdmin && item.arrLink) { const arrIcon = document.createElement('img'); if (item.arrType === 'sonarr') { arrIcon.className = 'service-icon sonarr'; arrIcon.src = '/images/sonarr.svg'; arrIcon.alt = 'Sonarr'; } else if (item.arrType === 'radarr') { arrIcon.className = 'service-icon radarr'; arrIcon.src = '/images/radarr.svg'; arrIcon.alt = 'Radarr'; } arrIcon.title = item.arrType === 'sonarr' ? 'Sonarr' : 'Radarr'; const arrLink = document.createElement('a'); arrLink.href = item.arrLink; arrLink.target = '_blank'; arrLink.appendChild(arrIcon); container.appendChild(arrLink); } return container; } export function initHistoryControls() { const daysInput = document.getElementById('history-days'); const refreshBtn = document.getElementById('history-refresh-btn'); const ignoreToggle = document.getElementById('ignore-available-toggle'); if (daysInput) { daysInput.addEventListener('change', () => { const v = parseInt(daysInput.value, 10); if (v > 0 && v <= 90) { historyDays = v; saveHistoryDays(v); loadHistory(true); } }); } if (refreshBtn) { refreshBtn.addEventListener('click', () => loadHistory(true)); } if (ignoreToggle) { ignoreToggle.checked = state.ignoreAvailable; ignoreToggle.addEventListener('change', () => { state.ignoreAvailable = ignoreToggle.checked; saveIgnoreAvailable(state.ignoreAvailable); renderHistory(state.lastHistoryItems); }); } // Listen for history reload events from other modules document.addEventListener('historyReload', () => { loadHistory(true); }); } export function startHistoryRefresh() { stopHistoryRefresh(); state.historyRefreshHandle = setInterval(() => loadHistory(), HISTORY_REFRESH_MS); } export function stopHistoryRefresh() { if (state.historyRefreshHandle) { clearInterval(state.historyRefreshHandle); state.historyRefreshHandle = null; } } export function clearHistory() { state.lastHistoryItems = []; document.getElementById('history-list').innerHTML = ''; document.getElementById('no-history').classList.add('hidden'); document.getElementById('history-error').classList.add('hidden'); } export async function loadHistory(forceRefresh = false) { const listEl = document.getElementById('history-list'); const loadingEl = document.getElementById('history-loading'); const errorEl = document.getElementById('history-error'); const noHistoryEl = document.getElementById('no-history'); loadingEl.classList.remove('hidden'); errorEl.classList.add('hidden'); noHistoryEl.classList.add('hidden'); try { const result = await apiLoadHistory(forceRefresh); loadingEl.classList.add('hidden'); if (result.success) { state.lastHistoryItems = result.history; renderHistory(state.lastHistoryItems); } else { errorEl.textContent = result.error || 'Failed to load history.'; errorEl.classList.remove('hidden'); } } catch (err) { loadingEl.classList.add('hidden'); errorEl.textContent = 'Failed to load history.'; errorEl.classList.remove('hidden'); console.error('[History] Load error:', err); } } export function renderHistory(items) { const listEl = document.getElementById('history-list'); const noHistoryEl = document.getElementById('no-history'); listEl.innerHTML = ''; const visible = state.ignoreAvailable ? items.filter(item => !(item.outcome === 'failed' && item.availableForUpgrade)) : items; if (!visible.length) { noHistoryEl.classList.remove('hidden'); return; } noHistoryEl.classList.add('hidden'); visible.forEach(item => listEl.appendChild(createHistoryCard(item))); } export function createHistoryCard(item) { const card = document.createElement('div'); card.className = `history-card ${item.type} ${item.outcome}`; if (item.coverArt) { const coverDiv = document.createElement('div'); coverDiv.className = 'history-cover'; const img = document.createElement('img'); img.src = '/api/dashboard/cover-art?url=' + encodeURIComponent(item.coverArt); img.alt = item.movieName || item.seriesName || item.title; img.loading = 'lazy'; coverDiv.appendChild(img); card.appendChild(coverDiv); } const info = document.createElement('div'); info.className = 'history-info'; // Header row: type badge + outcome badge const header = document.createElement('div'); header.className = 'history-card-header'; const typeBadge = document.createElement('span'); typeBadge.className = `history-type-badge ${item.type}`; typeBadge.textContent = item.type === 'series' ? '📺 Series' : '🎬 Movie'; header.appendChild(typeBadge); const outcomeBadge = document.createElement('span'); outcomeBadge.className = `history-outcome-badge ${item.outcome}`; outcomeBadge.textContent = item.outcome === 'imported' ? '✓ Imported' : '✗ Failed'; header.appendChild(outcomeBadge); if (item.availableForUpgrade) { const upgradeBadge = document.createElement('span'); upgradeBadge.className = 'history-upgrade-badge'; upgradeBadge.title = 'A previous version of this item is available. An upgrade download has failed.'; upgradeBadge.textContent = '⬆ Available'; header.appendChild(upgradeBadge); } if (item.instanceName) { const instBadge = document.createElement('span'); instBadge.className = 'history-instance-badge'; instBadge.textContent = item.instanceName; header.appendChild(instBadge); } const badges = renderTagBadges(item.tagBadges, state.showAll, item.matchedUserTag); header.appendChild(badges); info.appendChild(header); // Title const title = document.createElement('h3'); title.className = 'history-title'; title.textContent = item.title; info.appendChild(title); // Series/movie name with service icons if (item.seriesName) { const p = document.createElement('p'); p.className = 'history-media-name'; // Add service icons const serviceIcons = createServiceIcons(item); if (serviceIcons.hasChildNodes()) { p.appendChild(serviceIcons); p.appendChild(document.createTextNode(' ')); } // Series name is now plain text for all users (no link) const seriesText = document.createElement('span'); seriesText.textContent = 'Series: ' + item.seriesName; p.appendChild(seriesText); info.appendChild(p); const epEl = formatEpisodeInfo(item.episodes); if (epEl) info.appendChild(epEl); } if (item.movieName) { const p = document.createElement('p'); p.className = 'history-media-name'; // Add service icons const serviceIcons = createServiceIcons(item); if (serviceIcons.hasChildNodes()) { p.appendChild(serviceIcons); p.appendChild(document.createTextNode(' ')); } // Movie name is now plain text for all users (no link) const movieText = document.createElement('span'); movieText.textContent = 'Movie: ' + item.movieName; p.appendChild(movieText); info.appendChild(p); } // Detail pills const details = document.createElement('div'); details.className = 'history-details'; if (item.completedAt) { details.appendChild(createDetailItem('Completed', formatDate(item.completedAt))); } if (item.quality) { details.appendChild(createDetailItem('Quality', item.quality)); } // Failed imports: show failure message if (item.outcome === 'failed' && item.failureMessage) { const failItem = document.createElement('div'); failItem.className = 'history-failure-message'; failItem.textContent = item.failureMessage; details.appendChild(failItem); } info.appendChild(details); card.appendChild(info); return card; } function createDetailItem(label, value) { const item = document.createElement('div'); item.className = 'detail-item'; item.dataset.label = label; const labelSpan = document.createElement('span'); labelSpan.className = 'detail-label'; labelSpan.textContent = label; const valueSpan = document.createElement('span'); valueSpan.className = 'detail-value'; valueSpan.textContent = value; item.appendChild(labelSpan); item.appendChild(valueSpan); return item; }