document.addEventListener('DOMContentLoaded', () => { const fileList = document.getElementById('file-list'); const backBtn = document.getElementById('back-btn'); const forwardBtn = document.getElementById('forward-btn'); const upBtn = document.getElementById('up-btn'); const addressInput = document.getElementById('address-input'); const searchInput = document.getElementById('search-input'); const statusText = document.getElementById('status-text'); let history = ['/']; let historyIndex = 0; let forwardStack = []; let selectedIndex = -1; let currentItems = []; function getCurrentPath() { return history[historyIndex]; } function setAddressBar() { let p = getCurrentPath(); addressInput.value = p === '/' ? 'Computer' : p; } function updateSelection() { const items = fileList.querySelectorAll('.file-item'); items.forEach((item, index) => { item.classList.toggle('focused', index === selectedIndex); }); } function selectItem(index) { const items = fileList.querySelectorAll('.file-item'); if (index >= 0 && index < items.length) { selectedIndex = index; updateSelection(); // Scroll into view items[selectedIndex].scrollIntoView({ block: 'nearest' }); // Update status if (currentItems[selectedIndex]) { const item = currentItems[selectedIndex]; statusText.textContent = `Selected: ${item.name} (${item.isDir ? 'Folder' : 'File'})`; } } } function openSelectedItem() { if (selectedIndex >= 0 && selectedIndex < currentItems.length) { const item = currentItems[selectedIndex]; if (item.isDir) { let cur = getCurrentPath(); let next = cur.endsWith('/') ? cur + item.name : cur + '/' + item.name; history = history.slice(0, historyIndex + 1); history.push(next); historyIndex++; forwardStack = []; selectedIndex = -1; fetchFiles(); } else { // Download file let cur = getCurrentPath(); let filePath = cur.endsWith('/') ? cur + item.name : cur + '/' + item.name; window.location.href = `/api/download?path=${encodeURIComponent(filePath)}`; } } } function fetchFiles(searchTerm = '') { setAddressBar(); statusText.textContent = 'Loading...'; let path = getCurrentPath(); let url = `/api/list-isos?path=${encodeURIComponent(path)}`; if (searchTerm) { url += `&search=${encodeURIComponent(searchTerm)}`; } fetch(url) .then(res => { if (!res.ok) throw new Error('Failed to fetch file list'); return res.json(); }) .then(items => { fileList.innerHTML = ''; currentItems = []; selectedIndex = -1; if (!items.length) { fileList.innerHTML = '
No files or folders found.
'; statusText.textContent = 'No items found'; return; } currentItems = items; items.forEach((item, index) => { const div = document.createElement('div'); div.className = 'file-item'; div.dataset.index = index; // Icon const icon = document.createElement('img'); icon.className = 'icon'; icon.src = item.isDir ? 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIgM0MxLjQ0NzcyIDMgMSAzLjQ0NzcyIDEgNFYxMkMxIDEyLjU1MjMgMS40NDc3MiAxMyAyIDEzSDE0QzE0LjU1MjMgMTMgMTUgMTIuNTUyMyAxNSAxMlY2QzE1IDUuNDQ3NzIgMTQuNTUyMyA1IDE0IDVIOEw2IDNIMloiIGZpbGw9IiNGRkQ3MDAiLz4KPC9zdmc+' : 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTMgM0MyLjQ0NzcyIDMgMiAzLjQ0NzcyIDIgNFYxMkMyIDEyLjU1MjMgMi40NDc3MiAxMyAzIDEzSDEzQzEzLjU1MjMgMTMgMTQgMTIuNTUyMyAxNCAxMlY0QzE0IDMuNDQ3NzIgMTMuNTUyMyAzIDEzIDNIM1oiIGZpbGw9IiM4ODg4ODgiLz4KPC9zdmc+'; icon.onerror = () => icon.style.display = 'none'; div.appendChild(icon); // Name (show relative path if searching) const nameSpan = document.createElement('span'); nameSpan.className = 'file-name'; if (searchTerm && item.path) { nameSpan.textContent = item.path; } else { nameSpan.textContent = item.name; } div.appendChild(nameSpan); // Type const typeSpan = document.createElement('span'); typeSpan.className = 'file-type'; typeSpan.textContent = item.isDir ? 'Folder' : 'File'; div.appendChild(typeSpan); // Size const sizeSpan = document.createElement('span'); sizeSpan.className = 'file-size'; sizeSpan.textContent = item.isDir ? '' : '-'; div.appendChild(sizeSpan); // Date Modified const dateSpan = document.createElement('span'); dateSpan.className = 'file-date'; dateSpan.textContent = ''; div.appendChild(dateSpan); // Action const actionDiv = document.createElement('div'); if (!item.isDir) { const dlBtn = document.createElement('button'); dlBtn.className = 'download-btn'; dlBtn.textContent = 'Download'; dlBtn.onclick = (e) => { e.stopPropagation(); let cur = getCurrentPath(); let filePath = cur.endsWith('/') ? cur + item.name : cur + '/' + item.name; window.location.href = `/api/download?path=${encodeURIComponent(filePath)}`; }; actionDiv.appendChild(dlBtn); } div.appendChild(actionDiv); // Click handlers div.onclick = () => { selectItem(index); if (item.isDir) { setTimeout(() => openSelectedItem(), 200); } }; fileList.appendChild(div); }); statusText.textContent = `${items.length} item${items.length !== 1 ? 's' : ''}`; // Update button states backBtn.disabled = historyIndex <= 0; forwardBtn.disabled = historyIndex >= history.length - 1; upBtn.disabled = getCurrentPath() === '/'; }) .catch(err => { fileList.innerHTML = `
${err.message}
`; statusText.textContent = 'Error loading files'; }); } // Navigation event handlers backBtn.addEventListener('click', () => { if (historyIndex > 0) { historyIndex--; selectedIndex = -1; fetchFiles(); } }); forwardBtn.addEventListener('click', () => { if (historyIndex < history.length - 1) { historyIndex++; selectedIndex = -1; fetchFiles(); } }); upBtn.addEventListener('click', () => { let cur = getCurrentPath(); if (cur === '/' || cur === '') return; let parts = cur.split('/').filter(Boolean); parts.pop(); let upPath = '/' + parts.join('/'); if (upPath === '') upPath = '/'; history = history.slice(0, historyIndex + 1); history.push(upPath); historyIndex++; selectedIndex = -1; fetchFiles(); }); addressInput.addEventListener('click', () => { addressInput.select(); }); searchInput.addEventListener('input', (e) => { selectedIndex = -1; fetchFiles(e.target.value); }); // Keyboard navigation document.addEventListener('keydown', (e) => { if (e.target.tagName === 'INPUT') return; switch(e.key) { case 'ArrowUp': e.preventDefault(); if (selectedIndex > 0) { selectItem(selectedIndex - 1); } else if (currentItems.length > 0) { selectItem(currentItems.length - 1); } break; case 'ArrowDown': e.preventDefault(); if (selectedIndex < currentItems.length - 1) { selectItem(selectedIndex + 1); } else if (currentItems.length > 0) { selectItem(0); } break; case 'Enter': e.preventDefault(); openSelectedItem(); break; case 'Escape': e.preventDefault(); selectedIndex = -1; updateSelection(); statusText.textContent = `${currentItems.length} item${currentItems.length !== 1 ? 's' : ''}`; break; case 'Backspace': e.preventDefault(); upBtn.click(); break; case 'F5': e.preventDefault(); selectedIndex = -1; fetchFiles(searchInput.value); break; case '/': e.preventDefault(); searchInput.focus(); break; } }); // Initial load fetchFiles(); });