/** * AniWorld - Selection Manager Module * * Handles series selection for downloads. * * Dependencies: constants.js, api-client.js, ui-utils.js, series-manager.js */ var AniWorld = window.AniWorld || {}; AniWorld.SelectionManager = (function() { 'use strict'; const API = AniWorld.Constants.API; // State let selectedSeries = new Set(); /** * Initialize the selection manager */ function init() { bindEvents(); } /** * Bind UI events */ function bindEvents() { const selectAllBtn = document.getElementById('select-all'); if (selectAllBtn) { selectAllBtn.addEventListener('click', toggleSelectAll); } const downloadBtn = document.getElementById('download-selected'); if (downloadBtn) { downloadBtn.addEventListener('click', downloadSelected); } } /** * Toggle series selection * @param {string} key - Series key * @param {boolean} selected - Whether to select or deselect */ function toggleSerieSelection(key, selected) { // Only allow selection of series with missing episodes const serie = AniWorld.SeriesManager.findByKey(key); if (!serie || serie.missing_episodes === 0) { // Uncheck the checkbox if it was checked for a complete series const checkbox = document.querySelector('input[data-key="' + key + '"]'); if (checkbox) checkbox.checked = false; return; } if (selected) { selectedSeries.add(key); } else { selectedSeries.delete(key); } updateSelectionUI(); } /** * Check if a series is selected * @param {string} key - Series key * @returns {boolean} */ function isSelected(key) { return selectedSeries.has(key); } /** * Update selection UI (buttons and card styles) */ function updateSelectionUI() { const downloadBtn = document.getElementById('download-selected'); const selectAllBtn = document.getElementById('select-all'); // Get series that can be selected (have missing episodes) const selectableSeriesData = AniWorld.SeriesManager.getFilteredSeriesData().length > 0 ? AniWorld.SeriesManager.getFilteredSeriesData() : AniWorld.SeriesManager.getSeriesData(); const selectableSeries = selectableSeriesData.filter(function(serie) { return serie.missing_episodes > 0; }); const selectableKeys = selectableSeries.map(function(serie) { return serie.key; }); downloadBtn.disabled = selectedSeries.size === 0; const allSelectableSelected = selectableKeys.every(function(key) { return selectedSeries.has(key); }); if (selectedSeries.size === 0) { selectAllBtn.innerHTML = 'Select All'; } else if (allSelectableSelected && selectableKeys.length > 0) { selectAllBtn.innerHTML = 'Deselect All'; } else { selectAllBtn.innerHTML = 'Select All'; } // Update card appearances document.querySelectorAll('.series-card').forEach(function(card) { const key = card.dataset.key; const isSelectedCard = selectedSeries.has(key); card.classList.toggle('selected', isSelectedCard); }); } /** * Toggle select all / deselect all */ function toggleSelectAll() { // Get series that can be selected (have missing episodes) const selectableSeriesData = AniWorld.SeriesManager.getFilteredSeriesData().length > 0 ? AniWorld.SeriesManager.getFilteredSeriesData() : AniWorld.SeriesManager.getSeriesData(); const selectableSeries = selectableSeriesData.filter(function(serie) { return serie.missing_episodes > 0; }); const selectableKeys = selectableSeries.map(function(serie) { return serie.key; }); const allSelectableSelected = selectableKeys.every(function(key) { return selectedSeries.has(key); }); if (allSelectableSelected && selectedSeries.size > 0) { // Deselect all selectable series selectableKeys.forEach(function(key) { selectedSeries.delete(key); }); document.querySelectorAll('.series-checkbox:not([disabled])').forEach(function(cb) { cb.checked = false; }); } else { // Select all selectable series selectableKeys.forEach(function(key) { selectedSeries.add(key); }); document.querySelectorAll('.series-checkbox:not([disabled])').forEach(function(cb) { cb.checked = true; }); } updateSelectionUI(); } /** * Clear all selections */ function clearSelection() { selectedSeries.clear(); document.querySelectorAll('.series-checkbox').forEach(function(cb) { cb.checked = false; }); updateSelectionUI(); } /** * Download selected series */ async function downloadSelected() { console.log('=== downloadSelected - Using key as primary identifier ==='); if (selectedSeries.size === 0) { AniWorld.UI.showToast('No series selected', 'warning'); return; } try { const selectedKeys = Array.from(selectedSeries); console.log('=== Starting download for selected series ==='); console.log('Selected keys:', selectedKeys); let totalEpisodesAdded = 0; let failedSeries = []; // For each selected series, get its missing episodes and add to queue for (var i = 0; i < selectedKeys.length; i++) { const key = selectedKeys[i]; const serie = AniWorld.SeriesManager.findByKey(key); if (!serie || !serie.episodeDict) { console.error('Serie not found or has no episodeDict for key:', key, serie); failedSeries.push(key); continue; } // Validate required fields if (!serie.key) { console.error('Serie missing key:', serie); failedSeries.push(key); continue; } // Convert episodeDict format {season: [episodes]} to episode identifiers const episodes = []; Object.entries(serie.episodeDict).forEach(function(entry) { const season = entry[0]; const episodeNumbers = entry[1]; if (Array.isArray(episodeNumbers)) { episodeNumbers.forEach(function(episode) { episodes.push({ season: parseInt(season), episode: episode }); }); } }); if (episodes.length === 0) { console.log('No episodes to add for serie:', serie.name); continue; } // Use folder name as fallback if serie name is empty const serieName = serie.name && serie.name.trim() ? serie.name : serie.folder; // Add episodes to download queue const requestBody = { serie_id: serie.key, serie_folder: serie.folder, serie_name: serieName, episodes: episodes, priority: 'NORMAL' }; console.log('Sending queue add request:', requestBody); const response = await AniWorld.ApiClient.post(API.QUEUE_ADD, requestBody); if (!response) { failedSeries.push(key); continue; } const data = await response.json(); console.log('Queue add response:', response.status, data); // Log validation errors in detail if (data.detail && Array.isArray(data.detail)) { console.error('Validation errors:', JSON.stringify(data.detail, null, 2)); } if (response.ok && data.status === 'success') { totalEpisodesAdded += episodes.length; } else { console.error('Failed to add to queue:', data); failedSeries.push(key); } } // Show result message console.log('=== Download request complete ==='); console.log('Total episodes added:', totalEpisodesAdded); console.log('Failed series (keys):', failedSeries); if (totalEpisodesAdded > 0) { const message = failedSeries.length > 0 ? 'Added ' + totalEpisodesAdded + ' episode(s) to queue (' + failedSeries.length + ' series failed)' : 'Added ' + totalEpisodesAdded + ' episode(s) to download queue'; AniWorld.UI.showToast(message, 'success'); } else { const errorDetails = failedSeries.length > 0 ? 'Failed series (keys): ' + failedSeries.join(', ') : 'No episodes were added. Check browser console for details.'; console.error('Failed to add episodes. Details:', errorDetails); AniWorld.UI.showToast('Failed to add episodes to queue. Check console for details.', 'error'); } } catch (error) { console.error('Download error:', error); AniWorld.UI.showToast('Failed to start download', 'error'); } } /** * Get selected series count * @returns {number} */ function getSelectionCount() { return selectedSeries.size; } // Public API return { init: init, toggleSerieSelection: toggleSerieSelection, isSelected: isSelected, updateSelectionUI: updateSelectionUI, toggleSelectAll: toggleSelectAll, clearSelection: clearSelection, downloadSelected: downloadSelected, getSelectionCount: getSelectionCount }; })();