Add multi-select download client filter with client type display
All checks were successful
All checks were successful
This commit is contained in:
176
public/app.js
176
public/app.js
@@ -2,12 +2,34 @@
|
||||
let currentUser = null;
|
||||
let downloads = [];
|
||||
let downloadClients = []; // List of download clients from server (for ordering/filtering)
|
||||
let selectedDownloadClient = localStorage.getItem('sofarr-download-client') || 'all'; // Selected client filter
|
||||
let selectedDownloadClients = []; // Array of selected client IDs for multi-select filter
|
||||
let isAdmin = false;
|
||||
let showAll = false;
|
||||
let csrfToken = null; // double-submit CSRF token, sent as X-CSRF-Token on mutating requests
|
||||
const SPLASH_MIN_MS = 1200; // minimum splash display time
|
||||
|
||||
// Migration from old single-select to new multi-select format
|
||||
(function migrateDownloadClientFilter() {
|
||||
const oldSelection = localStorage.getItem('sofarr-download-client');
|
||||
if (oldSelection && oldSelection !== 'all') {
|
||||
try {
|
||||
selectedDownloadClients = [oldSelection];
|
||||
localStorage.setItem('sofarr-download-clients', JSON.stringify(selectedDownloadClients));
|
||||
localStorage.removeItem('sofarr-download-client');
|
||||
} catch (e) {
|
||||
console.error('[Migration] Failed to migrate download client filter:', e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const newSelection = localStorage.getItem('sofarr-download-clients');
|
||||
selectedDownloadClients = newSelection ? JSON.parse(newSelection) : [];
|
||||
} catch (e) {
|
||||
console.error('[Migration] Failed to load download client filter:', e);
|
||||
selectedDownloadClients = [];
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// History section state
|
||||
let historyDays = parseInt(localStorage.getItem('sofarr-history-days'), 10) || 7;
|
||||
let historyRefreshHandle = null;
|
||||
@@ -361,10 +383,10 @@ function renderDownloads() {
|
||||
const downloadsList = document.getElementById('downloads-list');
|
||||
const noDownloads = document.getElementById('no-downloads');
|
||||
|
||||
// Filter downloads by selected client
|
||||
// Filter downloads by selected clients
|
||||
let filteredDownloads = downloads;
|
||||
if (selectedDownloadClient !== 'all') {
|
||||
filteredDownloads = downloads.filter(d => d.instanceId === selectedDownloadClient);
|
||||
if (selectedDownloadClients.length > 0) {
|
||||
filteredDownloads = downloads.filter(d => selectedDownloadClients.includes(d.instanceId));
|
||||
}
|
||||
|
||||
// Sort downloads by client order (matching the order in downloadClients)
|
||||
@@ -1051,43 +1073,141 @@ function initHistoryControls() {
|
||||
// =============================================================================
|
||||
|
||||
function initDownloadClientFilter() {
|
||||
const filterSelect = document.getElementById('download-client-filter');
|
||||
if (filterSelect) {
|
||||
// Set initial value from localStorage
|
||||
filterSelect.value = selectedDownloadClient;
|
||||
filterSelect.addEventListener('change', () => {
|
||||
selectedDownloadClient = filterSelect.value;
|
||||
localStorage.setItem('sofarr-download-client', selectedDownloadClient);
|
||||
const dropdownBtn = document.getElementById('download-client-dropdown-btn');
|
||||
const dropdown = document.getElementById('download-client-dropdown');
|
||||
const selectAllBtn = document.getElementById('download-client-select-all');
|
||||
const deselectAllBtn = document.getElementById('download-client-deselect-all');
|
||||
|
||||
if (dropdownBtn && dropdown) {
|
||||
// Toggle dropdown
|
||||
dropdownBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const isOpen = dropdown.classList.toggle('open');
|
||||
dropdownBtn.classList.toggle('open', isOpen);
|
||||
dropdownBtn.setAttribute('aria-expanded', isOpen);
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!dropdown.contains(e.target) && !dropdownBtn.contains(e.target)) {
|
||||
dropdown.classList.remove('open');
|
||||
dropdownBtn.classList.remove('open');
|
||||
dropdownBtn.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
});
|
||||
|
||||
// Close dropdown on Escape key
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
dropdown.classList.remove('open');
|
||||
dropdownBtn.classList.remove('open');
|
||||
dropdownBtn.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (selectAllBtn) {
|
||||
selectAllBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
selectedDownloadClients = downloadClients.map(c => c.id);
|
||||
localStorage.setItem('sofarr-download-clients', JSON.stringify(selectedDownloadClients));
|
||||
updateDownloadClientFilter();
|
||||
renderDownloads();
|
||||
});
|
||||
}
|
||||
|
||||
if (deselectAllBtn) {
|
||||
deselectAllBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
selectedDownloadClients = [];
|
||||
localStorage.setItem('sofarr-download-clients', JSON.stringify(selectedDownloadClients));
|
||||
updateDownloadClientFilter();
|
||||
renderDownloads();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateDownloadClientFilter() {
|
||||
const filterSelect = document.getElementById('download-client-filter');
|
||||
if (!filterSelect || downloadClients.length === 0) return;
|
||||
const optionsContainer = document.getElementById('download-client-options');
|
||||
if (!optionsContainer) return;
|
||||
|
||||
// Save current selection
|
||||
const currentValue = filterSelect.value;
|
||||
// Clear existing options
|
||||
optionsContainer.innerHTML = '';
|
||||
|
||||
// Clear existing options (except "All clients")
|
||||
filterSelect.innerHTML = '<option value="all">All clients</option>';
|
||||
if (downloadClients.length === 0) {
|
||||
optionsContainer.innerHTML = '<div class="download-client-empty">No clients available</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Add options for each download client
|
||||
// Add checkboxes for each download client
|
||||
downloadClients.forEach(client => {
|
||||
const option = document.createElement('option');
|
||||
option.value = client.id;
|
||||
option.textContent = client.name;
|
||||
filterSelect.appendChild(option);
|
||||
const option = document.createElement('div');
|
||||
option.className = 'download-client-option';
|
||||
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.className = 'download-client-checkbox';
|
||||
checkbox.value = client.id;
|
||||
checkbox.checked = selectedDownloadClients.includes(client.id);
|
||||
checkbox.id = `client-${client.id}`;
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.className = 'download-client-option-label';
|
||||
label.htmlFor = `client-${client.id}`;
|
||||
label.textContent = client.name;
|
||||
|
||||
const typeBadge = document.createElement('span');
|
||||
typeBadge.className = 'download-client-type';
|
||||
typeBadge.textContent = client.type;
|
||||
|
||||
option.appendChild(checkbox);
|
||||
option.appendChild(label);
|
||||
option.appendChild(typeBadge);
|
||||
|
||||
// Toggle selection when clicking the row
|
||||
option.addEventListener('click', (e) => {
|
||||
if (e.target !== checkbox) {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
}
|
||||
toggleClientSelection(client.id, checkbox.checked);
|
||||
});
|
||||
|
||||
// Toggle selection when checkbox changes
|
||||
checkbox.addEventListener('change', (e) => {
|
||||
toggleClientSelection(client.id, e.target.checked);
|
||||
});
|
||||
|
||||
optionsContainer.appendChild(option);
|
||||
});
|
||||
|
||||
// Restore selection if still valid, otherwise default to 'all'
|
||||
if (currentValue && (currentValue === 'all' || downloadClients.some(c => c.id === currentValue))) {
|
||||
filterSelect.value = currentValue;
|
||||
// Update button text
|
||||
updateSelectedCountDisplay();
|
||||
}
|
||||
|
||||
function toggleClientSelection(clientId, isSelected) {
|
||||
if (isSelected) {
|
||||
if (!selectedDownloadClients.includes(clientId)) {
|
||||
selectedDownloadClients.push(clientId);
|
||||
}
|
||||
} else {
|
||||
filterSelect.value = 'all';
|
||||
selectedDownloadClient = 'all';
|
||||
localStorage.setItem('sofarr-download-client', 'all');
|
||||
selectedDownloadClients = selectedDownloadClients.filter(id => id !== clientId);
|
||||
}
|
||||
localStorage.setItem('sofarr-download-clients', JSON.stringify(selectedDownloadClients));
|
||||
updateSelectedCountDisplay();
|
||||
renderDownloads();
|
||||
}
|
||||
|
||||
function updateSelectedCountDisplay() {
|
||||
const selectedText = document.getElementById('download-client-selected-text');
|
||||
if (!selectedText) return;
|
||||
|
||||
if (selectedDownloadClients.length === 0) {
|
||||
selectedText.textContent = 'All clients';
|
||||
} else if (selectedDownloadClients.length === 1) {
|
||||
const client = downloadClients.find(c => c.id === selectedDownloadClients[0]);
|
||||
selectedText.textContent = client ? client.name : '1 selected';
|
||||
} else {
|
||||
selectedText.textContent = `${selectedDownloadClients.length} selected`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user