277 lines
14 KiB
Plaintext
277 lines
14 KiB
Plaintext
<div id="panel-kiwix" class="app-panel hidden-section position-relative pb-5">
|
|
<h2 class="mb-4">Kiwix Repository - Wikipedia</h2>
|
|
|
|
<div id="kiwix-status" class="alert alert-info d-flex align-items-center">
|
|
<span class="spinner-border spinner-border-sm me-3" role="status" aria-hidden="true"></span>
|
|
<span id="kiwix-status-text">Connecting to local server...</span>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<input type="text" id="zim-search" class="form-control bg-dark text-light border-secondary form-control-lg" placeholder="🔍 Search (e.g., es all nopic...)">
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-2">
|
|
<button id="btn-downloaded-filter" class="btn filter-inactive border-secondary px-3" style="border-radius: 8px;">
|
|
💾 Local: <span id="downloaded-counter" class="badge bg-secondary ms-1">0</span>
|
|
</button>
|
|
|
|
<div class="text-end">
|
|
<span class="text-secondary me-2 d-none d-sm-inline">
|
|
Selected: <strong id="selection-counter" class="text-warning">0</strong> / <span id="ui-limit"></span>
|
|
</span>
|
|
<button id="btn-start-download" class="btn btn-success px-4" disabled>Download</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="terminal-container" class="hidden-section position-relative">
|
|
<div id="kiwix-terminal" style="background-color: #000; color: #0f0; height: 250px; overflow-y: auto; padding: 10px; font-family: monospace; border-radius: 5px; white-space: pre-wrap; margin-bottom: 15px;">Waiting for commands...</div>
|
|
|
|
<div class="collapse-btn-container d-flex justify-content-center gap-3">
|
|
<button id="btn-toggle-terminal" class="btn btn-sm btn-dark border-secondary text-secondary" style="border-radius: 20px; padding: 2px 15px;">▲ Hide Terminal</button>
|
|
<button id="btn-cancel-download" class="btn btn-sm btn-danger text-light hidden-section shadow" style="border-radius: 20px; padding: 2px 15px;">🛑 Cancel Download</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3 mb-4" id="zims-container"></div>
|
|
|
|
<div class="text-center mb-4">
|
|
<button id="btn-load-more" class="btn btn-outline-primary hidden-section">Load more results ↓</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal fade" id="solidarityModal" tabindex="-1" data-bs-theme="dark"><div class="modal-dialog"><div class="modal-content bg-dark text-light border-warning"><div class="modal-header border-secondary"><h5 class="modal-title text-warning">⚠️ Suggested Download Limit</h5><button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button></div><div class="modal-body"><p>Please complete the downloads in the queue first. Help use Kiwix's bandwidth responsibly.</p></div><div class="modal-footer border-secondary"><button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">Got it</button></div></div></div></div>
|
|
|
|
<div class="modal fade" id="ariaMissingModal" tabindex="-1" data-bs-theme="dark"><div class="modal-dialog"><div class="modal-content bg-dark text-light border-danger"><div class="modal-header border-secondary"><h5 class="modal-title text-danger">❌ Error: Aria2 Not Found</h5><button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button></div><div class="modal-body"><p>The binary <code>/usr/bin/aria2c</code> was not detected.</p></div><div class="modal-footer border-secondary"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button></div></div></div></div>
|
|
|
|
<div class="modal fade" id="indexerMissingModal" tabindex="-1" data-bs-theme="dark"><div class="modal-dialog"><div class="modal-content bg-dark text-light border-warning"><div class="modal-header border-secondary"><h5 class="modal-title text-warning">⚠️ Warning: Missing Indexer</h5><button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button></div><div class="modal-body"><p><code>iiab-make-kiwix-lib</code> was not detected in the backend. Do you want to continue?</p></div><div class="modal-footer border-secondary"><button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button><button type="button" class="btn btn-warning text-dark" id="btn-force-download">Continue Download</button></div></div></div></div>
|
|
|
|
<script>
|
|
const MAX_ZIM_SELECTION = 3; const ZIMS_PER_PAGE = 9;
|
|
document.getElementById('ui-limit').textContent = MAX_ZIM_SELECTION;
|
|
|
|
let zimsInMemory = [];
|
|
let filteredZims = [];
|
|
let selectedZims = new Set();
|
|
let currentPage = 1;
|
|
let onlyDownloaded = false;
|
|
let terminalVisible = true;
|
|
|
|
const solidarityModal = new bootstrap.Modal(document.getElementById('solidarityModal'));
|
|
const ariaMissingModal = new bootstrap.Modal(document.getElementById('ariaMissingModal'));
|
|
const indexerMissingModal = new bootstrap.Modal(document.getElementById('indexerMissingModal'));
|
|
|
|
const kiwixTerminal = document.getElementById('kiwix-terminal');
|
|
const terminalContainer = document.getElementById('terminal-container');
|
|
const btnCancelDownload = document.getElementById('btn-cancel-download');
|
|
|
|
// 1. Initial Load and Refresh
|
|
socket.emit('request_kiwix_catalog');
|
|
socket.on('kiwix_status', (data) => document.getElementById('kiwix-status-text').textContent = data.message);
|
|
|
|
socket.on('kiwix_catalog_ready', (realData) => {
|
|
zimsInMemory = realData;
|
|
document.getElementById('kiwix-status').classList.add('hidden-section');
|
|
|
|
// Update downloaded counter
|
|
const totalDownloaded = zimsInMemory.filter(z => z.isDownloaded).length;
|
|
document.getElementById('downloaded-counter').textContent = totalDownloaded;
|
|
|
|
applyFilters();
|
|
});
|
|
|
|
socket.on('refresh_kiwix_catalog', () => {
|
|
selectedZims.clear();
|
|
document.getElementById('selection-counter').textContent = "0";
|
|
document.getElementById('btn-start-download').disabled = true;
|
|
socket.emit('request_kiwix_catalog');
|
|
});
|
|
|
|
// 2. Unified Filter Logic (Search + Downloaded Toggle)
|
|
function applyFilters() {
|
|
const terms = document.getElementById('zim-search').value.toLowerCase().trim().split(/\s+/);
|
|
|
|
filteredZims = zimsInMemory.filter(zim => {
|
|
const content = (zim.title + " " + zim.id).toLowerCase();
|
|
const matchesText = terms.every(term => content.includes(term));
|
|
const matchesDownloaded = onlyDownloaded ? zim.isDownloaded : true;
|
|
|
|
return matchesText && matchesDownloaded;
|
|
});
|
|
|
|
currentPage = 1;
|
|
renderGrid();
|
|
}
|
|
|
|
document.getElementById('zim-search').addEventListener('input', applyFilters);
|
|
document.getElementById('btn-load-more').addEventListener('click', () => { currentPage++; renderGrid(); });
|
|
|
|
// Toggle downloaded filter
|
|
document.getElementById('btn-downloaded-filter').addEventListener('click', (e) => {
|
|
onlyDownloaded = !onlyDownloaded;
|
|
const btn = e.currentTarget;
|
|
if(onlyDownloaded) {
|
|
btn.classList.replace('filter-inactive', 'filter-active');
|
|
} else {
|
|
btn.classList.replace('filter-active', 'filter-inactive');
|
|
}
|
|
applyFilters();
|
|
});
|
|
|
|
// 3. Card Rendering
|
|
function renderGrid() {
|
|
const container = document.getElementById('zims-container');
|
|
const visualLimit = currentPage * ZIMS_PER_PAGE;
|
|
const resultsToShow = filteredZims.slice(0, visualLimit);
|
|
|
|
container.innerHTML = '';
|
|
|
|
resultsToShow.forEach(zim => {
|
|
const isChecked = selectedZims.has(zim.id) ? 'checked' : '';
|
|
const borderClass = zim.isDownloaded ? 'border-success' : 'border-secondary';
|
|
const bgClass = zim.isDownloaded ? 'bg-dark text-success' : 'bg-dark';
|
|
|
|
// Delete button with unique ID
|
|
const trashBtn = zim.isDownloaded
|
|
? `<button id="btn-del-${zim.id}" class="btn btn-sm btn-outline-danger mt-2" onclick="deleteZim(event, '${zim.id}')">🗑️ Delete</button>`
|
|
: ``;
|
|
|
|
const card = `
|
|
<div class="col-12 col-md-6 col-lg-4">
|
|
<div class="card ${bgClass} ${borderClass} p-3 h-100 zim-card" style="cursor: pointer; transition: 0.2s;" onclick="toggleCard(event, '${zim.id}')">
|
|
<div class="form-check" style="pointer-events: none;">
|
|
<input class="form-check-input check-zim" type="checkbox" value="${zim.id}" id="chk-${zim.id}" ${isChecked}>
|
|
<label class="form-check-label w-100 fw-bold text-light">${zim.title}</label>
|
|
</div>
|
|
<div class="mt-2 text-secondary small d-flex flex-column justify-content-between h-100">
|
|
<div>
|
|
<div class="text-truncate" title="${zim.id}">📦 ${zim.id}</div>
|
|
<div>📅 ${zim.date} 💾 <span class="badge bg-secondary">${zim.size}</span></div>
|
|
</div>
|
|
<div class="text-end">${trashBtn}</div>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
container.innerHTML += card;
|
|
});
|
|
|
|
const btnLoadMore = document.getElementById('btn-load-more');
|
|
if (filteredZims.length > visualLimit) btnLoadMore.classList.remove('hidden-section');
|
|
else btnLoadMore.classList.add('hidden-section');
|
|
}
|
|
|
|
function toggleCard(event, zimId) {
|
|
if (event.target.tagName.toLowerCase() === 'button' || event.target.closest('button')) return;
|
|
const checkbox = document.getElementById(`chk-${zimId}`);
|
|
checkbox.checked = !checkbox.checked;
|
|
handleSelection(checkbox, zimId);
|
|
}
|
|
|
|
function handleSelection(checkbox, zimId) {
|
|
const zimObj = zimsInMemory.find(z => z.id === zimId);
|
|
if (checkbox.checked) {
|
|
if (zimObj && zimObj.isDownloaded) {
|
|
if (!confirm(`The file ${zimObj.id} is already downloaded.\n\nDo you want to overwrite it?`)) {
|
|
checkbox.checked = false; return;
|
|
}
|
|
}
|
|
if (selectedZims.size >= MAX_ZIM_SELECTION) {
|
|
checkbox.checked = false; solidarityModal.show(); return;
|
|
}
|
|
selectedZims.add(checkbox.value);
|
|
} else {
|
|
selectedZims.delete(checkbox.value);
|
|
}
|
|
document.getElementById('selection-counter').textContent = selectedZims.size;
|
|
document.getElementById('btn-start-download').disabled = selectedZims.size === 0;
|
|
}
|
|
|
|
// 4. Terminal Visibility Logic
|
|
function forceShowTerminal() {
|
|
terminalContainer.classList.remove('hidden-section');
|
|
kiwixTerminal.style.display = 'block';
|
|
document.getElementById('btn-toggle-terminal').textContent = '▲ Hide Terminal';
|
|
terminalVisible = true;
|
|
}
|
|
|
|
document.getElementById('btn-toggle-terminal').addEventListener('click', (e) => {
|
|
if(terminalVisible) {
|
|
kiwixTerminal.style.display = 'none';
|
|
e.target.textContent = '▼ Show Terminal';
|
|
} else {
|
|
kiwixTerminal.style.display = 'block';
|
|
e.target.textContent = '▲ Hide Terminal';
|
|
}
|
|
terminalVisible = !terminalVisible;
|
|
});
|
|
|
|
// 5. Deletion Logic
|
|
function deleteZim(event, zimId) {
|
|
event.stopPropagation();
|
|
if(confirm(`⚠️ Do you want to DELETE the file ${zimId}?\n\nThis will update the Kiwix database.`)){
|
|
|
|
// UX: Change button to "Deleting..." and disable it
|
|
const btnDel = document.getElementById(`btn-del-${zimId}`);
|
|
btnDel.textContent = "⏳ Deleting...";
|
|
btnDel.disabled = true;
|
|
btnDel.classList.replace('btn-outline-danger', 'btn-secondary');
|
|
|
|
forceShowTerminal();
|
|
kiwixTerminal.textContent += `\n[System] Requesting deletion from server...\n`;
|
|
socket.emit('delete_zim', zimId);
|
|
}
|
|
}
|
|
|
|
// Cancel Button
|
|
btnCancelDownload.addEventListener('click', () => {
|
|
if(confirm('⚠️ Do you want to CANCEL the ongoing download?\n\nIncomplete files will be wiped from your hard drive.')) {
|
|
btnCancelDownload.disabled = true;
|
|
btnCancelDownload.textContent = '⏳ Aborting...';
|
|
socket.emit('cancel_kiwix_download');
|
|
}
|
|
});
|
|
|
|
// 6. Download Logic
|
|
const btnDownload = document.getElementById('btn-start-download');
|
|
|
|
function sendArrayToBackend() {
|
|
socket.emit('start_kiwix_download', Array.from(selectedZims));
|
|
indexerMissingModal.hide();
|
|
forceShowTerminal();
|
|
}
|
|
|
|
btnDownload.addEventListener('click', () => socket.emit('check_kiwix_tools'));
|
|
|
|
socket.on('kiwix_tools_status', (status) => {
|
|
if (!status.hasAria2) return ariaMissingModal.show();
|
|
if (!status.hasIndexer) return indexerMissingModal.show();
|
|
sendArrayToBackend();
|
|
});
|
|
|
|
document.getElementById('btn-force-download').addEventListener('click', sendArrayToBackend);
|
|
|
|
socket.on('kiwix_terminal_output', (data) => {
|
|
kiwixTerminal.textContent += data;
|
|
kiwixTerminal.scrollTop = kiwixTerminal.scrollHeight;
|
|
});
|
|
|
|
socket.on('kiwix_process_status', (status) => {
|
|
btnDownload.disabled = status.isRunning;
|
|
document.getElementById('zim-search').disabled = status.isRunning;
|
|
|
|
if (status.isRunning) {
|
|
btnDownload.textContent = "Downloading...";
|
|
btnDownload.classList.replace('btn-success', 'btn-secondary');
|
|
|
|
// Show cancel button
|
|
btnCancelDownload.classList.remove('hidden-section');
|
|
btnCancelDownload.disabled = false;
|
|
btnCancelDownload.textContent = '🛑 Cancel Download';
|
|
} else {
|
|
btnDownload.textContent = "Download";
|
|
btnDownload.classList.replace('btn-secondary', 'btn-success');
|
|
|
|
// Hide cancel button
|
|
btnCancelDownload.classList.add('hidden-section');
|
|
}
|
|
});
|
|
</script> |