Files
decypharr/pkg/web/assets/js/download.js
2025-08-01 15:27:24 +01:00

384 lines
15 KiB
JavaScript

// Download page functionality
class DownloadManager {
constructor(downloadFolder) {
this.downloadFolder = downloadFolder;
this.currentMode = 'torrent'; // Default mode
this.refs = {
downloadForm: document.getElementById('downloadForm'),
// Mode controls
torrentMode: document.getElementById('torrentMode'),
nzbMode: document.getElementById('nzbMode'),
// Torrent inputs
magnetURI: document.getElementById('magnetURI'),
torrentFiles: document.getElementById('torrentFiles'),
torrentInputs: document.getElementById('torrentInputs'),
// NZB inputs
nzbURLs: document.getElementById('nzbURLs'),
nzbFiles: document.getElementById('nzbFiles'),
nzbInputs: document.getElementById('nzbInputs'),
// Common form elements
arr: document.getElementById('arr'),
downloadAction: document.getElementById('downloadAction'),
downloadUncached: document.getElementById('downloadUncached'),
downloadFolder: document.getElementById('downloadFolder'),
downloadFolderHint: document.getElementById('downloadFolderHint'),
debrid: document.getElementById('debrid'),
submitBtn: document.getElementById('submitDownload'),
submitButtonText: document.getElementById('submitButtonText'),
activeCount: document.getElementById('activeCount'),
completedCount: document.getElementById('completedCount'),
totalSize: document.getElementById('totalSize')
};
this.init();
}
init() {
this.loadSavedOptions();
this.bindEvents();
this.handleMagnetFromURL();
this.loadModeFromURL();
}
bindEvents() {
// Form submission
this.refs.downloadForm.addEventListener('submit', (e) => this.handleSubmit(e));
// Mode switching
this.refs.torrentMode.addEventListener('click', () => this.switchMode('torrent'));
this.refs.nzbMode.addEventListener('click', () => this.switchMode('nzb'));
// 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.downloadFolder.addEventListener('change', () => this.saveOptions());
// File input enhancement
this.refs.torrentFiles.addEventListener('change', (e) => this.handleFileSelection(e));
this.refs.nzbFiles.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',
folder: localStorage.getItem('downloadFolder') || this.downloadFolder,
mode: localStorage.getItem('downloadMode') || 'torrent'
};
this.refs.arr.value = savedOptions.category;
this.refs.downloadAction.value = savedOptions.action;
this.refs.downloadUncached.checked = savedOptions.uncached;
this.refs.downloadFolder.value = savedOptions.folder;
this.currentMode = savedOptions.mode;
}
saveOptions() {
localStorage.setItem('downloadCategory', this.refs.arr.value);
localStorage.setItem('downloadAction', this.refs.downloadAction.value);
localStorage.setItem('downloadUncached', this.refs.downloadUncached.checked.toString());
localStorage.setItem('downloadFolder', this.refs.downloadFolder.value);
localStorage.setItem('downloadMode', this.currentMode);
}
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();
let urls = [];
let files = [];
let endpoint = '/api/add';
let itemType = 'torrent';
if (this.currentMode === 'torrent') {
// Get torrent URLs
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 torrent files
for (let i = 0; i < this.refs.torrentFiles.files.length; i++) {
formData.append('files', this.refs.torrentFiles.files[i]);
files.push(this.refs.torrentFiles.files[i]);
}
} else if (this.currentMode === 'nzb') {
// Get NZB URLs
urls = this.refs.nzbURLs.value
.split('\n')
.map(url => url.trim())
.filter(url => url.length > 0);
if (urls.length > 0) {
formData.append('nzbUrls', urls.join('\n'));
}
// Get NZB files
for (let i = 0; i < this.refs.nzbFiles.files.length; i++) {
formData.append('nzbFiles', this.refs.nzbFiles.files[i]);
files.push(this.refs.nzbFiles.files[i]);
}
endpoint = '/api/nzbs/add';
itemType = 'NZB';
}
// Validation
const totalItems = urls.length + files.length;
if (totalItems === 0) {
window.decypharrUtils.createToast(`Please provide at least one ${itemType}`, 'warning');
return;
}
if (totalItems > 100) {
window.decypharrUtils.createToast(`Please submit up to 100 ${itemType}s 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);
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(endpoint, {
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) {
console.log(result.errors);
let errorMessage = ` ${result.errors.join('\n')}`;
if (result.results.length > 0) {
window.decypharrUtils.createToast(
`Added ${result.results.length} ${itemType}s with ${result.errors.length} errors \n${errorMessage}`,
'warning'
);
} else {
window.decypharrUtils.createToast(`Failed to add ${itemType}s \n${errorMessage}`, 'error');
}
} else {
window.decypharrUtils.createToast(
`Successfully added ${result.results.length} ${itemType}${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);
}
}
switchMode(mode) {
this.currentMode = mode;
this.saveOptions();
this.updateURL(mode);
// Update button states
if (mode === 'torrent') {
this.refs.torrentMode.classList.remove('btn-outline');
this.refs.torrentMode.classList.add('btn-primary');
this.refs.nzbMode.classList.remove('btn-primary');
this.refs.nzbMode.classList.add('btn-outline');
// Show/hide sections
this.refs.torrentInputs.classList.remove('hidden');
this.refs.nzbInputs.classList.add('hidden');
// Update UI text
this.refs.submitButtonText.textContent = 'Add to Download Queue';
this.refs.downloadFolderHint.textContent = 'Leave empty to use default qBittorrent folder';
} else {
this.refs.nzbMode.classList.remove('btn-outline');
this.refs.nzbMode.classList.add('btn-primary');
this.refs.torrentMode.classList.remove('btn-primary');
this.refs.torrentMode.classList.add('btn-outline');
// Show/hide sections
this.refs.nzbInputs.classList.remove('hidden');
this.refs.torrentInputs.classList.add('hidden');
// Update UI text
this.refs.submitButtonText.textContent = 'Add to NZB Queue';
this.refs.downloadFolderHint.textContent = 'Leave empty to use default SABnzbd folder';
}
}
clearForm() {
if (this.currentMode === 'torrent') {
this.refs.magnetURI.value = '';
this.refs.torrentFiles.value = '';
} else {
this.refs.nzbURLs.value = '';
this.refs.nzbFiles.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;
if (this.currentMode === 'torrent') {
// 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');
}
} else {
// Filter for .nzb files
const nzbFiles = Array.from(files).filter(file =>
file.name.toLowerCase().endsWith('.nzb')
);
if (nzbFiles.length > 0) {
// Create a new FileList-like object
const dataTransfer = new DataTransfer();
nzbFiles.forEach(file => dataTransfer.items.add(file));
this.refs.nzbFiles.files = dataTransfer.files;
this.handleFileSelection({ target: { files: nzbFiles } });
} else {
window.decypharrUtils.createToast('Please drop .nzb files only', 'warning');
}
}
}
loadModeFromURL() {
const urlParams = new URLSearchParams(window.location.search);
const mode = urlParams.get('mode');
if (mode === 'nzb' || mode === 'torrent') {
this.currentMode = mode;
} else {
this.currentMode = this.currentMode || 'torrent'; // Use saved preference or default
}
// Initialize the mode without updating URL again
this.setModeUI(this.currentMode);
}
setModeUI(mode) {
if (mode === 'torrent') {
this.refs.torrentMode.classList.remove('btn-outline');
this.refs.torrentMode.classList.add('btn-primary');
this.refs.nzbMode.classList.remove('btn-primary');
this.refs.nzbMode.classList.add('btn-outline');
this.refs.torrentInputs.classList.remove('hidden');
this.refs.nzbInputs.classList.add('hidden');
this.refs.submitButtonText.textContent = 'Add to Download Queue';
this.refs.downloadFolderHint.textContent = 'Leave empty to use default qBittorrent folder';
} else {
this.refs.nzbMode.classList.remove('btn-outline');
this.refs.nzbMode.classList.add('btn-primary');
this.refs.torrentMode.classList.remove('btn-primary');
this.refs.torrentMode.classList.add('btn-outline');
this.refs.nzbInputs.classList.remove('hidden');
this.refs.torrentInputs.classList.add('hidden');
this.refs.submitButtonText.textContent = 'Add to NZB Queue';
this.refs.downloadFolderHint.textContent = 'Leave empty to use default SABnzbd folder';
}
}
updateURL(mode) {
const url = new URL(window.location);
url.searchParams.set('mode', mode);
window.history.replaceState({}, '', url);
}
}