267 lines
10 KiB
JavaScript
Vendored
267 lines
10 KiB
JavaScript
Vendored
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 = '<div class="file-item" style="grid-column: 1 / -1; text-align: center; color: #888;">No files or folders found.</div>';
|
|
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 = `<div class="file-item" style="grid-column: 1 / -1; text-align: center; color: #d9534f;">${err.message}</div>`;
|
|
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();
|
|
}); |