feat: Selectable torrent list items with a 'delete selected' button (#29)
This commit is contained in:
committed by
GitHub
parent
5d7ddbd208
commit
12f89b3047
@@ -4,6 +4,9 @@
|
|||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h4 class="mb-0"><i class="bi bi-table me-2"></i>Active Torrents</h4>
|
<h4 class="mb-0"><i class="bi bi-table me-2"></i>Active Torrents</h4>
|
||||||
<div>
|
<div>
|
||||||
|
<button class="btn btn-outline-danger btn-sm me-2" id="batchDeleteBtn" style="display: none;">
|
||||||
|
<i class="bi bi-trash me-1"></i>Delete Selected
|
||||||
|
</button>
|
||||||
<button class="btn btn-outline-secondary btn-sm me-2" id="refreshBtn">
|
<button class="btn btn-outline-secondary btn-sm me-2" id="refreshBtn">
|
||||||
<i class="bi bi-arrow-clockwise me-1"></i>Refresh
|
<i class="bi bi-arrow-clockwise me-1"></i>Refresh
|
||||||
</button>
|
</button>
|
||||||
@@ -17,6 +20,9 @@
|
|||||||
<table class="table table-hover mb-0">
|
<table class="table table-hover mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>
|
||||||
|
<input type="checkbox" class="form-check-input" id="selectAll">
|
||||||
|
</th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Size</th>
|
<th>Size</th>
|
||||||
<th>Progress</th>
|
<th>Progress</th>
|
||||||
@@ -36,7 +42,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
const torrentRowTemplate = (torrent) => `
|
const torrentRowTemplate = (torrent) => `
|
||||||
<tr>
|
<tr data-hash="${torrent.hash}">
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" class="form-check-input torrent-select" data-hash="${torrent.hash}">
|
||||||
|
</td>
|
||||||
<td class="text-nowrap text-truncate overflow-hidden" style="max-width: 350px;" title="${torrent.name}">${torrent.name}</td>
|
<td class="text-nowrap text-truncate overflow-hidden" style="max-width: 350px;" title="${torrent.name}">${torrent.name}</td>
|
||||||
<td class="text-nowrap">${formatBytes(torrent.size)}</td>
|
<td class="text-nowrap">${formatBytes(torrent.size)}</td>
|
||||||
<td style="min-width: 150px;">
|
<td style="min-width: 150px;">
|
||||||
@@ -83,6 +92,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let refreshInterval;
|
let refreshInterval;
|
||||||
|
let selectedTorrents = new Set();
|
||||||
|
|
||||||
async function loadTorrents() {
|
async function loadTorrents() {
|
||||||
try {
|
try {
|
||||||
@@ -92,10 +102,12 @@
|
|||||||
const tbody = document.getElementById('torrentsList');
|
const tbody = document.getElementById('torrentsList');
|
||||||
tbody.innerHTML = torrents.map(torrent => torrentRowTemplate(torrent)).join('');
|
tbody.innerHTML = torrents.map(torrent => torrentRowTemplate(torrent)).join('');
|
||||||
|
|
||||||
|
restoreSelections();
|
||||||
|
|
||||||
// Update category filter options
|
// Update category filter options
|
||||||
let category = document.getElementById('categoryFilter').value;
|
let category = document.getElementById('categoryFilter').value;
|
||||||
document.querySelectorAll('#torrentsList tr').forEach(row => {
|
document.querySelectorAll('#torrentsList tr').forEach(row => {
|
||||||
const rowCategory = row.querySelector('td:nth-child(5)').textContent;
|
const rowCategory = row.querySelector('td:nth-child(6)').textContent;
|
||||||
row.style.display = (!category || rowCategory.includes(category)) ? '' : 'none';
|
row.style.display = (!category || rowCategory.includes(category)) ? '' : 'none';
|
||||||
});
|
});
|
||||||
updateCategoryFilter(torrents);
|
updateCategoryFilter(torrents);
|
||||||
@@ -113,6 +125,21 @@
|
|||||||
categories.map(cat => `<option value="${cat}" ${cat === currentValue ? 'selected' : ''}>${cat}</option>`).join('');
|
categories.map(cat => `<option value="${cat}" ${cat === currentValue ? 'selected' : ''}>${cat}</option>`).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function restoreSelections() {
|
||||||
|
selectedTorrents.forEach(hash => {
|
||||||
|
const checkbox = document.querySelector(`input[data-hash="${hash}"]`);
|
||||||
|
if (checkbox) {
|
||||||
|
checkbox.checked = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateBatchDeleteButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBatchDeleteButton() {
|
||||||
|
const batchDeleteBtn = document.getElementById('batchDeleteBtn');
|
||||||
|
batchDeleteBtn.style.display = selectedTorrents.size > 0 ? '' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
async function deleteTorrent(hash) {
|
async function deleteTorrent(hash) {
|
||||||
if (!confirm('Are you sure you want to delete this torrent?')) return;
|
if (!confirm('Are you sure you want to delete this torrent?')) return;
|
||||||
|
|
||||||
@@ -120,6 +147,7 @@
|
|||||||
await fetch(`/internal/torrents/${hash}`, {
|
await fetch(`/internal/torrents/${hash}`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
});
|
});
|
||||||
|
selectedTorrents.delete(hash);
|
||||||
await loadTorrents();
|
await loadTorrents();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting torrent:', error);
|
console.error('Error deleting torrent:', error);
|
||||||
@@ -127,16 +155,61 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteSelectedTorrents() {
|
||||||
|
if (!confirm(`Are you sure you want to delete ${selectedTorrents.size} selected torrents?`)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deletePromises = Array.from(selectedTorrents).map(hash =>
|
||||||
|
fetch(`/internal/torrents/${hash}`, { method: 'DELETE' })
|
||||||
|
);
|
||||||
|
await Promise.all(deletePromises);
|
||||||
|
selectedTorrents.clear();
|
||||||
|
await loadTorrents();
|
||||||
|
|
||||||
|
document.getElementById('selectAll').checked = false;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting torrents:', error);
|
||||||
|
alert('Failed to delete some torrents');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
loadTorrents();
|
loadTorrents();
|
||||||
refreshInterval = setInterval(loadTorrents, 5000); // Refresh every 5 seconds
|
refreshInterval = setInterval(loadTorrents, 5000);
|
||||||
|
|
||||||
document.getElementById('refreshBtn').addEventListener('click', loadTorrents);
|
document.getElementById('refreshBtn').addEventListener('click', loadTorrents);
|
||||||
|
document.getElementById('batchDeleteBtn').addEventListener('click', deleteSelectedTorrents);
|
||||||
|
|
||||||
|
document.getElementById('selectAll').addEventListener('change', (e) => {
|
||||||
|
const checkboxes = document.querySelectorAll('.torrent-select');
|
||||||
|
checkboxes.forEach(checkbox => {
|
||||||
|
checkbox.checked = e.target.checked;
|
||||||
|
const hash = checkbox.dataset.hash;
|
||||||
|
if (e.target.checked) {
|
||||||
|
selectedTorrents.add(hash);
|
||||||
|
} else {
|
||||||
|
selectedTorrents.delete(hash);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateBatchDeleteButton();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('torrentsList').addEventListener('change', (e) => {
|
||||||
|
if (e.target.classList.contains('torrent-select')) {
|
||||||
|
const hash = e.target.dataset.hash;
|
||||||
|
if (e.target.checked) {
|
||||||
|
selectedTorrents.add(hash);
|
||||||
|
} else {
|
||||||
|
selectedTorrents.delete(hash);
|
||||||
|
}
|
||||||
|
updateBatchDeleteButton();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById('categoryFilter').addEventListener('change', (e) => {
|
document.getElementById('categoryFilter').addEventListener('change', (e) => {
|
||||||
const category = e.target.value;
|
const category = e.target.value;
|
||||||
document.querySelectorAll('#torrentsList tr').forEach(row => {
|
document.querySelectorAll('#torrentsList tr').forEach(row => {
|
||||||
const rowCategory = row.querySelector('td:nth-child(5)').textContent;
|
const rowCategory = row.querySelector('td:nth-child(6)').textContent;
|
||||||
row.style.display = (!category || rowCategory.includes(category)) ? '' : 'none';
|
row.style.display = (!category || rowCategory.includes(category)) ? '' : 'none';
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user