From ce0d888f864cf1e3785f5d75df4dc3df10b22808 Mon Sep 17 00:00:00 2001 From: Ryan Date: Sat, 8 Mar 2025 06:32:31 -0500 Subject: [PATCH] auth fix, file search & drag drop --- auth.js | 5 +- fileManager.js | 268 ++++++++++++++++++++++++++++++------------------- index.html | 10 +- styles.css | 27 ++++- upload.js | 126 ++++++++++++++++++----- 5 files changed, 303 insertions(+), 133 deletions(-) diff --git a/auth.js b/auth.js index 7e5bda4..0e37cf9 100644 --- a/auth.js +++ b/auth.js @@ -21,9 +21,8 @@ export function initAuth() { .then(data => { console.log("Login response:", data); if (data.success) { - console.log("✅ Login successful."); - updateUIOnLogin(data.isAdmin); - checkAuthentication(); // Double-check session persistence. + console.log("✅ Login successful. Reloading page."); + window.location.reload(); } else { alert("Login failed: " + (data.error || "Unknown error")); } diff --git a/fileManager.js b/fileManager.js index c5dfe75..68b653e 100644 --- a/fileManager.js +++ b/fileManager.js @@ -85,90 +85,161 @@ export function loadFileList(folderParam) { }); } -// Render the file table with pagination controls. export function renderFileTable(folder) { const fileListContainer = document.getElementById("fileList"); const folderPath = (folder === "root") ? "uploads/" : "uploads/" + encodeURIComponent(folder) + "/"; - // Pagination variables: + // Get current search term from the search input, if it exists. + let searchInputElement = document.getElementById("searchInput"); + const searchHadFocus = searchInputElement && (document.activeElement === searchInputElement); + let searchTerm = searchInputElement ? searchInputElement.value : ""; + + // Filter fileData using the search term (case-insensitive). + const filteredFiles = fileData.filter(file => + file.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + // Pagination variables. const itemsPerPage = window.itemsPerPage || 10; const currentPage = window.currentPage || 1; - const totalFiles = fileData.length; + const totalFiles = filteredFiles.length; const totalPages = Math.ceil(totalFiles / itemsPerPage); - // Build table header. - let tableHTML = ` - - - - - - - - - - - `; + // 1. Top controls: Responsive row with search box on the left and Prev/Next on the right. + const topControlsHTML = ` +
+ +
+
+
+ + search + +
+ +
+
+ +
+ + Page ${currentPage} of ${totalPages || 1} + +
+
+ `; + + // 2. Build the File Table with Bootstrap styling. + let tableHTML = ` +
- File Name ${sortOrder.column === "name" ? (sortOrder.ascending ? "▲" : "▼") : ""} - - Date Modified ${sortOrder.column === "modified" ? (sortOrder.ascending ? "▲" : "▼") : ""} - - Upload Date ${sortOrder.column === "uploaded" ? (sortOrder.ascending ? "▲" : "▼") : ""} - - File Size ${sortOrder.column === "size" ? (sortOrder.ascending ? "▲" : "▼") : ""} - - Uploader ${sortOrder.column === "uploader" ? (sortOrder.ascending ? "▲" : "▼") : ""} - Actions
+ + + + + + + + + + + + `; // Calculate slice for current page. const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = Math.min(startIndex + itemsPerPage, totalFiles); let tableBody = ``; - fileData.slice(startIndex, endIndex).forEach(file => { - const isEditable = canEditFile(file.name); - const safeFileName = escapeHTML(file.name); - const safeModified = escapeHTML(file.modified); - const safeUploaded = escapeHTML(file.uploaded); - const safeSize = escapeHTML(file.size); - const safeUploader = escapeHTML(file.uploader || "Unknown"); - - tableBody += ` - - - - - - - - `; - }); + if (totalFiles > 0) { + filteredFiles.slice(startIndex, endIndex).forEach(file => { + const isEditable = canEditFile(file.name); + const safeFileName = escapeHTML(file.name); + const safeModified = escapeHTML(file.modified); + const safeUploaded = escapeHTML(file.uploaded); + const safeSize = escapeHTML(file.size); + const safeUploader = escapeHTML(file.uploader || "Unknown"); + tableBody += ` + + + + + + + + + + `; + }); + } else { + tableBody += ``; + } tableBody += `
+ + + File Name ${sortOrder.column === "name" ? (sortOrder.ascending ? "▲" : "▼") : ""} + + Date Modified ${sortOrder.column === "modified" ? (sortOrder.ascending ? "▲" : "▼") : ""} + + Upload Date ${sortOrder.column === "uploaded" ? (sortOrder.ascending ? "▲" : "▼") : ""} + + File Size ${sortOrder.column === "size" ? (sortOrder.ascending ? "▲" : "▼") : ""} + + Uploader ${sortOrder.column === "uploader" ? (sortOrder.ascending ? "▲" : "▼") : ""} + Actions
${safeFileName}${safeModified}${safeUploaded}${safeSize}${safeUploader} -
- Download - ${isEditable ? `` : ""} - -
-
+ + ${safeFileName}${safeModified}${safeUploaded}${safeSize}${safeUploader} +
+ Download + ${isEditable ? ` + + ` : ""} + +
+
No files found.
`; - // Build pagination controls. - let paginationHTML = `
`; - paginationHTML += `
`; - paginationHTML += ` `; - paginationHTML += `Page ${currentPage} of ${totalPages} `; - paginationHTML += ``; - paginationHTML += `
`; - paginationHTML += `
Show items per page
`; - paginationHTML += `
`; + // 3. Bottom controls: "Show [dropdown] items per page" with consistent 16px font. + const bottomControlsHTML = ` +
+ + + items per page +
+ `; - fileListContainer.innerHTML = tableHTML + tableBody + paginationHTML; + // Combine top controls, table, and bottom controls. + fileListContainer.innerHTML = topControlsHTML + tableHTML + tableBody + bottomControlsHTML; + + // Re-focus the search input if it was previously focused. + const newSearchInput = document.getElementById("searchInput"); + if (searchHadFocus && newSearchInput) { + newSearchInput.focus(); + newSearchInput.setSelectionRange(newSearchInput.value.length, newSearchInput.value.length); + } + + // Attach event listener for search input. + newSearchInput.addEventListener("input", function () { + window.currentPage = 1; + renderFileTable(folder); + }); // Attach sorting event listeners on header cells. const headerCells = document.querySelectorAll("table.table thead th[data-column]"); @@ -179,52 +250,45 @@ export function renderFileTable(folder) { }); }); - // After rendering the table, reattach the file checkbox event listener. + // Reattach event listeners for file checkboxes. document.querySelectorAll('#fileList .file-checkbox').forEach(checkbox => { - checkbox.addEventListener('change', updateFileActionButtons); + checkbox.addEventListener('change', function (e) { + updateRowHighlight(e.target); + updateFileActionButtons(); + }); }); - // Finally, call updateFileActionButtons so the buttons show (or are disabled) correctly. updateFileActionButtons(); } -// Sort files and re-render the table. -export function sortFiles(column, folder) { - if (sortOrder.column === column) { - sortOrder.ascending = !sortOrder.ascending; - } else { - sortOrder.column = column; - sortOrder.ascending = true; +/** + * Toggles row selection when the user clicks any part of the row (except buttons/links). + */ +window.toggleRowSelection = function (event, fileName) { + const targetTag = event.target.tagName.toLowerCase(); + if (targetTag === 'a' || targetTag === 'button' || targetTag === 'input') { + return; } + const row = event.currentTarget; + const checkbox = row.querySelector('.file-checkbox'); + if (!checkbox) return; + checkbox.checked = !checkbox.checked; + updateRowHighlight(checkbox); + updateFileActionButtons(); +}; - fileData.sort((a, b) => { - let valA = a[column] || ""; - let valB = b[column] || ""; - - if (column === "modified" || column === "uploaded") { - // Log the raw date strings. - console.log(`Sorting ${column}: raw values ->`, valA, valB); - - const parsedA = parseCustomDate(valA); - const parsedB = parseCustomDate(valB); - - // Log the parsed numeric timestamps. - console.log(`Sorting ${column}: parsed values ->`, parsedA, parsedB); - - valA = parsedA; - valB = parsedB; - } else if (typeof valA === "string") { - valA = valA.toLowerCase(); - valB = valB.toLowerCase(); - } - - if (valA < valB) return sortOrder.ascending ? -1 : 1; - if (valA > valB) return sortOrder.ascending ? 1 : -1; - return 0; - }); - - renderFileTable(folder); -} +/** + * Updates row highlight based on whether the checkbox is checked. + */ +window.updateRowHighlight = function (checkbox) { + const row = checkbox.closest('tr'); + if (!row) return; + if (checkbox.checked) { + row.classList.add('row-selected'); + } else { + row.classList.remove('row-selected'); + } +}; // Delete selected files. export function handleDeleteSelected(e) { diff --git a/index.html b/index.html index a2339f0..b64d5a4 100644 --- a/index.html +++ b/index.html @@ -10,6 +10,7 @@ + @@ -57,10 +58,13 @@
- - +
+ Drop files here or click 'Choose files' +
+ +
- +
diff --git a/styles.css b/styles.css index 07d234b..5b68d76 100644 --- a/styles.css +++ b/styles.css @@ -132,6 +132,12 @@ header { gap: 5px; } +#uploadBtn { + font-size: 16px; + padding: 10px 22px; + align-items: center; +} + /* UPLOAD PROGRESS */ #uploadProgressContainer ul { list-style: none; @@ -376,4 +382,23 @@ label { #fileListContainer { margin-top: 40px !important; -} \ No newline at end of file +} + +.row-selected { + background-color: #f2f2f2 !important; +} + +.custom-prev-next-btn { + background-color: #e0e0e0; + color: #000; + border: none; + padding: 6px 12px; + font-size: 14px; + border-radius: 4px; + margin: 0 4px; + cursor: pointer; +} +.custom-prev-next-btn:hover:not(:disabled) { + background-color: #d5d5d5; +} + diff --git a/upload.js b/upload.js index 5e615b8..95510f2 100644 --- a/upload.js +++ b/upload.js @@ -6,12 +6,100 @@ export function initUpload() { const fileInput = document.getElementById("file"); const progressContainer = document.getElementById("uploadProgressContainer"); const uploadForm = document.getElementById("uploadFileForm"); + const dropArea = document.getElementById("uploadDropArea"); - // Build progress list when files are selected. + // Helper function: set the drop area's default layout. + function setDropAreaDefault() { + if (dropArea) { + dropArea.innerHTML = ` +
+ Drop files here or click 'Choose files' +
+
+ +
+ No files selected +
+
+ `; + // Wire up the custom button. + /* const customChooseBtn = document.getElementById("customChooseBtn"); + if (customChooseBtn) { + customChooseBtn.addEventListener("click", function () { + fileInput.click(); + }); + }*/ + } + } + + // Initialize drop area. + if (dropArea) { + dropArea.style.border = "2px dashed #ccc"; + dropArea.style.padding = "20px"; + dropArea.style.textAlign = "center"; + dropArea.style.marginBottom = "15px"; + dropArea.style.cursor = "pointer"; + // Set default content once. + setDropAreaDefault(); + + // Prevent default behavior for drag events. + dropArea.addEventListener("dragover", function (e) { + e.preventDefault(); + dropArea.style.backgroundColor = "#f8f8f8"; + }); + dropArea.addEventListener("dragleave", function (e) { + e.preventDefault(); + dropArea.style.backgroundColor = ""; + }); + dropArea.addEventListener("drop", function (e) { + e.preventDefault(); + dropArea.style.backgroundColor = ""; + const dt = e.dataTransfer; + if (dt && dt.files && dt.files.length > 0) { + fileInput.files = dt.files; + fileInput.dispatchEvent(new Event("change")); + } + }); + + // Clicking the drop area triggers file selection. + dropArea.addEventListener("click", function () { + fileInput.click(); + }); + } + + // When files are selected, update only the file info container. if (fileInput) { fileInput.addEventListener("change", function () { - progressContainer.innerHTML = ""; const files = fileInput.files; + // Update the file info container without replacing the entire drop area. + const fileInfoContainer = document.getElementById("fileInfoContainer"); + if (fileInfoContainer) { + if (files.length > 0) { + if (files.length === 1) { + fileInfoContainer.innerHTML = ` +
+ ${files[0].name} + `; + } else { + fileInfoContainer.innerHTML = ` +
+ ${files.length} files selected + `; + } + const previewContainer = document.getElementById("filePreviewContainer"); + if (previewContainer) { + previewContainer.innerHTML = ""; + displayFilePreview(files[0], previewContainer); + } + } else { + fileInfoContainer.innerHTML = `No files selected`; + } + } + + // Build progress list as before. + progressContainer.innerHTML = ""; if (files.length > 0) { const allFiles = Array.from(files); const maxDisplay = 10; @@ -22,7 +110,6 @@ export function initUpload() { const li = document.createElement("li"); li.style.paddingTop = "20px"; li.style.marginBottom = "10px"; - // Only display progress items for the first maxDisplay files. li.style.display = (index < maxDisplay) ? "flex" : "none"; li.style.alignItems = "center"; li.style.flexWrap = "wrap"; @@ -51,12 +138,10 @@ export function initUpload() { li.appendChild(preview); li.appendChild(nameDiv); li.appendChild(progDiv); - // Save references for later updates. li.progressBar = progBar; li.startTime = Date.now(); list.appendChild(li); }); - // If more than maxDisplay files, add a note. if (allFiles.length > maxDisplay) { const extra = document.createElement("li"); extra.style.paddingTop = "20px"; @@ -70,7 +155,7 @@ export function initUpload() { }); } - // Submit handler: upload all files and then check the server's file list. + // Submit handler remains unchanged. if (uploadForm) { uploadForm.addEventListener("submit", function (e) { e.preventDefault(); @@ -80,22 +165,21 @@ export function initUpload() { return; } const allFiles = Array.from(files); - const maxDisplay = 10; // Only show progress for first 10 items. + const maxDisplay = 10; const folderToUse = window.currentFolder || "root"; const listItems = progressContainer.querySelectorAll("li"); let finishedCount = 0; let allSucceeded = true; - // Array to track each file's upload result. const uploadResults = new Array(allFiles.length).fill(false); - + allFiles.forEach((file, index) => { const formData = new FormData(); formData.append("file[]", file); formData.append("folder", folderToUse); - + const xhr = new XMLHttpRequest(); let currentPercent = 0; - + xhr.upload.addEventListener("progress", function (e) { if (e.lengthComputable) { currentPercent = Math.round((e.loaded / e.total) * 100); @@ -113,7 +197,7 @@ export function initUpload() { } } }); - + xhr.addEventListener("load", function () { let jsonResponse; try { @@ -136,11 +220,10 @@ export function initUpload() { finishedCount++; console.log("Upload response for file", file.name, xhr.responseText); if (finishedCount === allFiles.length) { - // Immediately refresh the file list. refreshFileList(); } }); - + xhr.addEventListener("error", function () { if (index < maxDisplay && listItems[index]) { listItems[index].progressBar.innerText = "Error"; @@ -153,7 +236,7 @@ export function initUpload() { refreshFileList(); } }); - + xhr.addEventListener("abort", function () { if (index < maxDisplay && listItems[index]) { listItems[index].progressBar.innerText = "Aborted"; @@ -166,20 +249,16 @@ export function initUpload() { refreshFileList(); } }); - + xhr.open("POST", "upload.php", true); xhr.send(formData); }); - + function refreshFileList() { - // Call loadFileList immediately (with a timestamp added inside loadFileList, if needed). loadFileList(folderToUse) .then(serverFiles => { initFileActions(); - // Normalize server file names to lowercase. serverFiles = (serverFiles || []).map(item => item.name.trim().toLowerCase()); - // For each file, if it was successful and is present on the server, leave its progress item; - // if not, mark it as "Error". allFiles.forEach((file, index) => { const fileName = file.name.trim().toLowerCase(); if (index < maxDisplay && listItems[index]) { @@ -189,11 +268,10 @@ export function initUpload() { } } }); - // Now, the file list is refreshed instantly. - // However, we want the progress list to remain visible for 10 seconds. setTimeout(() => { progressContainer.innerHTML = ""; fileInput.value = ""; + if (dropArea) setDropAreaDefault(); }, 10000); if (!allSucceeded) { alert("Some files failed to upload. Please check the list."); @@ -206,4 +284,4 @@ export function initUpload() { } }); } -} +} \ No newline at end of file