// Download page functionality class DownloadManager { constructor(downloadFolder) { this.downloadFolder = downloadFolder; this.refs = { downloadForm: document.getElementById('downloadForm'), magnetURI: document.getElementById('magnetURI'), torrentFiles: document.getElementById('torrentFiles'), arr: document.getElementById('arr'), downloadAction: document.getElementById('downloadAction'), downloadUncached: document.getElementById('downloadUncached'), rmTrackerUrls: document.getElementById('rmTrackerUrls'), downloadFolder: document.getElementById('downloadFolder'), debrid: document.getElementById('debrid'), submitBtn: document.getElementById('submitDownload'), activeCount: document.getElementById('activeCount'), completedCount: document.getElementById('completedCount'), totalSize: document.getElementById('totalSize') }; this.init(); } init() { this.loadSavedOptions(); this.bindEvents(); this.handleMagnetFromURL(); } bindEvents() { // Form submission this.refs.downloadForm.addEventListener('submit', (e) => this.handleSubmit(e)); // Save options on change this.refs.arr.addEventListener('change', () => this.saveOptions()); this.refs.downloadAction.addEventListener('change', () => this.saveOptions()); this.refs.downloadUncached.addEventListener('change', () => this.saveOptions()); this.refs.rmTrackerUrls.addEventListener('change', () => this.saveOptions()); this.refs.downloadFolder.addEventListener('change', () => this.saveOptions()); // File input enhancement this.refs.torrentFiles.addEventListener('change', (e) => this.handleFileSelection(e)); // Drag and drop this.setupDragAndDrop(); } loadSavedOptions() { const savedOptions = { category: localStorage.getItem('downloadCategory') || '', action: localStorage.getItem('downloadAction') || 'symlink', uncached: localStorage.getItem('downloadUncached') === 'true', rmTrackerUrls: localStorage.getItem('rmTrackerUrls') === 'true', folder: localStorage.getItem('downloadFolder') || this.downloadFolder }; this.refs.arr.value = savedOptions.category; this.refs.downloadAction.value = savedOptions.action; this.refs.downloadUncached.checked = savedOptions.uncached; this.refs.rmTrackerUrls.checked = savedOptions.rmTrackerUrls; this.refs.downloadFolder.value = savedOptions.folder; } saveOptions() { localStorage.setItem('downloadCategory', this.refs.arr.value); localStorage.setItem('downloadAction', this.refs.downloadAction.value); localStorage.setItem('downloadUncached', this.refs.downloadUncached.checked.toString()); // Only save rmTrackerUrls if not disabled (i.e., not forced by config) if (!this.refs.rmTrackerUrls.disabled) { localStorage.setItem('rmTrackerUrls', this.refs.rmTrackerUrls.checked.toString()); } localStorage.setItem('downloadFolder', this.refs.downloadFolder.value); } handleMagnetFromURL() { const urlParams = new URLSearchParams(window.location.search); const magnetURI = urlParams.get('magnet'); if (magnetURI) { this.refs.magnetURI.value = magnetURI; history.replaceState({}, document.title, window.location.pathname); // Show notification window.decypharrUtils.createToast('Magnet link loaded from URL', 'info'); } } async handleSubmit(e) { e.preventDefault(); const formData = new FormData(); // Get URLs const urls = this.refs.magnetURI.value .split('\n') .map(url => url.trim()) .filter(url => url.length > 0); if (urls.length > 0) { formData.append('urls', urls.join('\n')); } // Get files for (let i = 0; i < this.refs.torrentFiles.files.length; i++) { formData.append('files', this.refs.torrentFiles.files[i]); } // Validation const totalItems = urls.length + this.refs.torrentFiles.files.length; if (totalItems === 0) { window.decypharrUtils.createToast('Please provide at least one torrent', 'warning'); return; } if (totalItems > 100) { window.decypharrUtils.createToast('Please submit up to 100 torrents at a time', 'warning'); return; } // Add other form data formData.append('arr', this.refs.arr.value); formData.append('downloadFolder', this.refs.downloadFolder.value); formData.append('action', this.refs.downloadAction.value); formData.append('downloadUncached', this.refs.downloadUncached.checked); formData.append('rmTrackerUrls', this.refs.rmTrackerUrls.checked); if (this.refs.debrid) { formData.append('debrid', this.refs.debrid.value); } try { // Set loading state window.decypharrUtils.setButtonLoading(this.refs.submitBtn, true); const response = await window.decypharrUtils.fetcher('/api/add', { method: 'POST', body: formData, headers: {} // Remove Content-Type to let browser set it for FormData }); const result = await response.json(); if (!response.ok) { throw new Error(result.error || 'Unknown error'); } // Handle partial success if (result.errors && result.errors.length > 0) { if (result.results.length > 0) { window.decypharrUtils.createToast( `Added ${result.results.length} torrents with ${result.errors.length} errors`, 'warning' ); this.showErrorDetails(result.errors); } else { window.decypharrUtils.createToast('Failed to add torrents', 'error'); this.showErrorDetails(result.errors); } } else { window.decypharrUtils.createToast( `Successfully added ${result.results.length} torrent${result.results.length > 1 ? 's' : ''}!` ); this.clearForm(); } } catch (error) { console.error('Error adding downloads:', error); window.decypharrUtils.createToast(`Error adding downloads: ${error.message}`, 'error'); } finally { window.decypharrUtils.setButtonLoading(this.refs.submitBtn, false); } } showErrorDetails(errors) { // Create a modal or detailed view for errors const errorList = errors.map(error => `• ${error}`).join('\n'); console.error('Download errors:', errorList); window.decypharrUtils.createToast( `Errors occurred while adding torrents:\n${errorList}`, 'error' ); } clearForm() { this.refs.magnetURI.value = ''; this.refs.torrentFiles.value = ''; } handleFileSelection(e) { const files = e.target.files; if (files.length > 0) { const fileNames = Array.from(files).map(f => f.name).join(', '); window.decypharrUtils.createToast( `Selected ${files.length} file${files.length > 1 ? 's' : ''}: ${fileNames}`, 'info' ); } } setupDragAndDrop() { const dropZone = this.refs.downloadForm; ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, this.preventDefaults, false); }); ['dragenter', 'dragover'].forEach(eventName => { dropZone.addEventListener(eventName, () => this.highlight(dropZone), false); }); ['dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, () => this.unhighlight(dropZone), false); }); dropZone.addEventListener('drop', (e) => this.handleDrop(e), false); } preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } highlight(element) { element.classList.add('border-primary', 'border-2', 'border-dashed', 'bg-primary/5'); } unhighlight(element) { element.classList.remove('border-primary', 'border-2', 'border-dashed', 'bg-primary/5'); } handleDrop(e) { const dt = e.dataTransfer; const files = dt.files; // Filter for .torrent files const torrentFiles = Array.from(files).filter(file => file.name.toLowerCase().endsWith('.torrent') ); if (torrentFiles.length > 0) { // Create a new FileList-like object const dataTransfer = new DataTransfer(); torrentFiles.forEach(file => dataTransfer.items.add(file)); this.refs.torrentFiles.files = dataTransfer.files; this.handleFileSelection({ target: { files: torrentFiles } }); } else { window.decypharrUtils.createToast('Please drop .torrent files only', 'warning'); } } }