session persistent Items Per Page & dark/light modes

This commit is contained in:
Ryan
2025-03-15 00:51:42 -04:00
committed by GitHub
parent eee3611da9
commit 5e50304295
6 changed files with 342 additions and 247 deletions

50
auth.js
View File

@@ -1,11 +1,10 @@
// auth.js
import { sendRequest } from './networkUtils.js'; import { sendRequest } from './networkUtils.js';
import { toggleVisibility, showToast } from './domUtils.js'; import { toggleVisibility, showToast } from './domUtils.js';
// Import loadFileList from fileManager.js to refresh the file list upon login. // Import loadFileList and renderFileTable from fileManager.js to refresh the file list upon login.
import { loadFileList } from './fileManager.js'; import { loadFileList, renderFileTable, displayFilePreview, initFileActions } from './fileManager.js';
import { loadFolderTree } from './folderManager.js';
export function initAuth() { function initAuth() {
// First, check if the user is already authenticated. // First, check if the user is already authenticated.
checkAuthentication(); checkAuthentication();
@@ -31,7 +30,6 @@ export function initAuth() {
}) })
.catch(error => console.error("❌ Error logging in:", error)); .catch(error => console.error("❌ Error logging in:", error));
}); });
}
// Set up the logout button. // Set up the logout button.
document.getElementById("logoutBtn").addEventListener("click", function () { document.getElementById("logoutBtn").addEventListener("click", function () {
@@ -122,8 +120,9 @@ document.getElementById("deleteUserBtn").addEventListener("click", function () {
document.getElementById("cancelRemoveUserBtn").addEventListener("click", function () { document.getElementById("cancelRemoveUserBtn").addEventListener("click", function () {
closeRemoveUserModal(); closeRemoveUserModal();
}); });
}
export function checkAuthentication() { function checkAuthentication() {
// Return the promise from sendRequest // Return the promise from sendRequest
return sendRequest("checkAuth.php") return sendRequest("checkAuth.php")
.then(data => { .then(data => {
@@ -157,6 +156,12 @@ export function checkAuthentication() {
if (removeUserBtn) removeUserBtn.style.display = "none"; if (removeUserBtn) removeUserBtn.style.display = "none";
} }
document.querySelector(".header-buttons").style.visibility = "visible"; document.querySelector(".header-buttons").style.visibility = "visible";
// Update persistent items-per-page select once main operations are visible.
const selectElem = document.querySelector(".form-control.bottom-select");
if (selectElem) {
const stored = localStorage.getItem("itemsPerPage") || "10";
selectElem.value = stored;
}
return true; return true;
} else { } else {
showToast("Please log in to continue."); showToast("Please log in to continue.");
@@ -175,7 +180,34 @@ export function checkAuthentication() {
} }
window.checkAuthentication = checkAuthentication; window.checkAuthentication = checkAuthentication;
// Helper functions for auth modals. /* ------------------------------
Persistent Items-Per-Page Setting
------------------------------ */
// When the select value changes, save it to localStorage and refresh the file list.
window.changeItemsPerPage = function (value) {
console.log("Saving itemsPerPage:", value);
localStorage.setItem("itemsPerPage", value);
// Refresh the file list automatically.
const folder = window.currentFolder || "root";
if (typeof renderFileTable === "function") {
renderFileTable(folder);
}
};
// On DOMContentLoaded, set the select to the persisted value.
document.addEventListener("DOMContentLoaded", function () {
const selectElem = document.querySelector(".form-control.bottom-select");
if (selectElem) {
const stored = localStorage.getItem("itemsPerPage") || "10";
console.log("Loaded itemsPerPage from localStorage:", stored);
selectElem.value = stored;
}
});
/* ------------------------------
Helper functions for modals and user list
------------------------------ */
function resetUserForm() { function resetUserForm() {
document.getElementById("newUsername").value = ""; document.getElementById("newUsername").value = "";
document.getElementById("newPassword").value = ""; document.getElementById("newPassword").value = "";
@@ -215,3 +247,5 @@ function loadUserList() {
}) })
.catch(error => console.error("Error loading user list:", error)); .catch(error => console.error("Error loading user list:", error));
} }
export { initAuth, checkAuthentication };

View File

@@ -128,16 +128,21 @@ export function renderFileTable(folder) {
const folderPath = (folder === "root") const folderPath = (folder === "root")
? "uploads/" ? "uploads/"
: "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/"; : "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/";
let searchInputElement = document.getElementById("searchInput");
// Attempt to get the search input element.
const searchInputElement = document.getElementById("searchInput");
const searchHadFocus = searchInputElement && (document.activeElement === searchInputElement); const searchHadFocus = searchInputElement && (document.activeElement === searchInputElement);
let searchTerm = searchInputElement ? searchInputElement.value : ""; const searchTerm = searchInputElement ? searchInputElement.value : "";
const filteredFiles = fileData.filter(file => const filteredFiles = fileData.filter(file =>
file.name.toLowerCase().includes(searchTerm.toLowerCase()) file.name.toLowerCase().includes(searchTerm.toLowerCase())
); );
const itemsPerPage = window.itemsPerPage || 10;
// Read persistent items per page from localStorage, default to 10.
const itemsPerPageSetting = parseInt(localStorage.getItem('itemsPerPage') || '10', 10);
const currentPage = window.currentPage || 1; const currentPage = window.currentPage || 1;
const totalFiles = filteredFiles.length; const totalFiles = filteredFiles.length;
const totalPages = Math.ceil(totalFiles / itemsPerPage); const totalPages = Math.ceil(totalFiles / itemsPerPageSetting);
const safeSearchTerm = escapeHTML(searchTerm); const safeSearchTerm = escapeHTML(searchTerm);
const topControlsHTML = ` const topControlsHTML = `
@@ -177,8 +182,8 @@ export function renderFileTable(folder) {
</thead> </thead>
`; `;
const startIndex = (currentPage - 1) * itemsPerPage; const startIndex = (currentPage - 1) * itemsPerPageSetting;
const endIndex = Math.min(startIndex + itemsPerPage, totalFiles); const endIndex = Math.min(startIndex + itemsPerPageSetting, totalFiles);
let tableBody = `<tbody>`; let tableBody = `<tbody>`;
if (totalFiles > 0) { if (totalFiles > 0) {
@@ -190,10 +195,9 @@ export function renderFileTable(folder) {
const safeSize = escapeHTML(file.size); const safeSize = escapeHTML(file.size);
const safeUploader = escapeHTML(file.uploader || "Unknown"); const safeUploader = escapeHTML(file.uploader || "Unknown");
// Check if the file is an image using a regex // Check if file is an image.
const isImage = /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name); const isImage = /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name);
// Build the preview button HTML string using the file's properties directly.
const previewButton = isImage const previewButton = isImage
? `<button class="btn btn-sm btn-info ml-2" onclick="event.stopPropagation(); previewImage('${folderPath + encodeURIComponent(file.name)}', '${safeFileName}')"> ? `<button class="btn btn-sm btn-info ml-2" onclick="event.stopPropagation(); previewImage('${folderPath + encodeURIComponent(file.name)}', '${safeFileName}')">
<i class="material-icons">image</i> <i class="material-icons">image</i>
@@ -230,7 +234,9 @@ export function renderFileTable(folder) {
<div class="d-flex align-items-center mt-3 bottom-controls"> <div class="d-flex align-items-center mt-3 bottom-controls">
<label class="label-inline mr-2 mb-0">Show</label> <label class="label-inline mr-2 mb-0">Show</label>
<select class="form-control bottom-select" onchange="changeItemsPerPage(this.value)"> <select class="form-control bottom-select" onchange="changeItemsPerPage(this.value)">
${[10, 20, 50, 100].map(num => `<option value="${num}" ${num === itemsPerPage ? "selected" : ""}>${num}</option>`).join("")} ${[10, 20, 50, 100]
.map(num => `<option value="${num}" ${num === itemsPerPageSetting ? "selected" : ""}>${num}</option>`)
.join("")}
</select> </select>
<span class="items-per-page-text ml-2 mb-0">items per page</span> <span class="items-per-page-text ml-2 mb-0">items per page</span>
</div> </div>
@@ -238,28 +244,28 @@ export function renderFileTable(folder) {
fileListContainer.innerHTML = topControlsHTML + tableHTML + tableBody + bottomControlsHTML; fileListContainer.innerHTML = topControlsHTML + tableHTML + tableBody + bottomControlsHTML;
const newSearchInput = document.getElementById("searchInput"); // Only add event listeners if searchInputElement exists.
if (searchHadFocus && newSearchInput) { if (searchInputElement) {
newSearchInput.focus(); searchInputElement.addEventListener("input", function () {
newSearchInput.setSelectionRange(newSearchInput.value.length, newSearchInput.value.length);
}
newSearchInput.addEventListener("input", function () {
window.currentPage = 1; window.currentPage = 1;
renderFileTable(folder); renderFileTable(folder);
}); });
const headerCells = document.querySelectorAll("table.table thead th[data-column]"); }
headerCells.forEach(cell => {
document.querySelectorAll("table.table thead th[data-column]").forEach(cell => {
cell.addEventListener("click", function () { cell.addEventListener("click", function () {
const column = this.getAttribute("data-column"); const column = this.getAttribute("data-column");
sortFiles(column, folder); sortFiles(column, folder);
}); });
}); });
document.querySelectorAll('#fileList .file-checkbox').forEach(checkbox => { document.querySelectorAll('#fileList .file-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', function (e) { checkbox.addEventListener('change', function (e) {
updateRowHighlight(e.target); updateRowHighlight(e.target);
updateFileActionButtons(); updateFileActionButtons();
}); });
}); });
updateFileActionButtons(); updateFileActionButtons();
} }

View File

@@ -70,25 +70,24 @@
</div> </div>
<!-- Main Operations: Upload and Folder Management --> <!-- Main Operations: Upload and Folder Management -->
<div id="mainOperations" style="display: none;"> <div id="mainOperations">
<div class="container" style="max-width: 1400px; margin: 0 auto;">
<div class="row align-items-stretch" id="uploadFolderRow"> <div class="row align-items-stretch" id="uploadFolderRow">
<!-- Upload Card: 50% width on medium, 58% on large --> <!-- Upload Card: 50% width on medium, 58% on large -->
<div class="container" style="max-width: 1400px; margin: 0 auto;">
<div class="row">
<div class="col-md-6 col-lg-7 d-flex"> <div class="col-md-6 col-lg-7 d-flex">
<div class="card flex-fill" style="max-width: 900px; width: 100%;"> <div id="uploadCard" class="card flex-fill" style="max-width: 900px; width: 100%;">
<div class="card-header">Upload Files</div> <div class="card-header">Upload Files/Folders</div>
<!-- Card body is a flex container in column direction -->
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form id="uploadFileForm" method="post" enctype="multipart/form-data" class="d-flex flex-column" <form id="uploadFileForm" method="post" enctype="multipart/form-data" class="d-flex flex-column"
style="height: 100%;"> style="height: 100%;">
<div class="form-group flex-grow-1" style="margin-bottom: 1rem;"> <div class="form-group flex-grow-1" style="margin-bottom: 1rem;">
<div id="uploadDropArea" <div id="uploadDropArea"
style="border:2px dashed #ccc; padding:20px; cursor:pointer; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;"> style="border:2px dashed #ccc; padding:20px; cursor:pointer; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;">
<span>Drop files here or click 'Choose files'</span> <span>Drop files/folders here or click 'Choose files'</span>
<br /> <br />
<input type="file" id="file" name="file[]" class="form-control-file" multiple required <input type="file" id="file" name="file[]" class="form-control-file" multiple required
style="display:none;" /> webkitdirectory directory mozdirectory style="opacity:0; position:absolute; z-index:-1;" />
<button type="button" onclick="document.getElementById('file').click();">Choose Folder</button>
</div> </div>
</div> </div>
<button type="submit" id="uploadBtn" class="btn btn-primary d-block mx-auto">Upload</button> <button type="submit" id="uploadBtn" class="btn btn-primary d-block mx-auto">Upload</button>
@@ -100,12 +99,12 @@
<!-- Folder Management Card: 50% width on medium, 42% on large --> <!-- Folder Management Card: 50% width on medium, 42% on large -->
<div class="col-md-6 col-lg-5 d-flex"> <div class="col-md-6 col-lg-5 d-flex">
<div class="card flex-fill" style="max-width: 900px; width: 100%;"> <div class="card flex-fill" style="max-width: 900px; width: 100%;">
<div class="card-header">Folder Navigation & Management</div> <div class="card-header">Folder Navigation &amp; Management</div>
<div class="card-body custom-folder-card-body"> <div class="card-body custom-folder-card-body">
<div class="form-group d-flex align-items-top" style="padding-top:0; margin-bottom:0;"> <div class="form-group d-flex align-items-top" style="padding-top:0; margin-bottom:0;">
<div id="folderTreeContainer"></div> <div id="folderTreeContainer"></div>
</div> </div>
<!-- Folder actions --> <!-- Folder actions (create, rename, delete) -->
<div class="folder-actions mt-3"> <div class="folder-actions mt-3">
<button id="createFolderBtn" class="btn btn-primary">Create Folder</button> <button id="createFolderBtn" class="btn btn-primary">Create Folder</button>
<!-- Create Folder Modal --> <!-- Create Folder Modal -->
@@ -170,7 +169,6 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- File List Section --> <!-- File List Section -->
<div id="fileListContainer" style="display: none;"> <div id="fileListContainer" style="display: none;">
<h2 id="fileListTitle">Files in (Root)</h2> <h2 id="fileListTitle">Files in (Root)</h2>
@@ -278,14 +276,6 @@
<!-- JavaScript Files --> <!-- JavaScript Files -->
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script type="module" src="main.js"></script> <script type="module" src="main.js"></script>
<script>
// Dark mode toggle script
const darkModeToggle = document.getElementById('darkModeToggle');
darkModeToggle.addEventListener('click', () => {
document.body.classList.toggle('dark-mode');
darkModeToggle.innerText = document.body.classList.contains('dark-mode') ? 'Light Mode' : 'Dark Mode';
});
</script>
</body> </body>
</html> </html>

64
main.js
View File

@@ -1,5 +1,3 @@
// main.js
import { sendRequest } from './networkUtils.js'; import { sendRequest } from './networkUtils.js';
import { import {
toggleVisibility, toggleVisibility,
@@ -27,38 +25,66 @@ window.editFile = editFile;
window.saveFile = saveFile; window.saveFile = saveFile;
window.renameFile = renameFile; window.renameFile = renameFile;
// Global variable for the current folder. // Global variable for the current folder.
window.currentFolder = "root"; window.currentFolder = "root";
// DOMContentLoaded initialization.
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
// Call initAuth synchronously. // Call initAuth synchronously.
initAuth(); initAuth();
// Check OS theme preference & apply dark mode // --- Dark Mode Persistence ---
// Get the dark mode toggle button.
const darkModeToggle = document.getElementById("darkModeToggle");
// Retrieve stored user preference (if any).
const storedDarkMode = localStorage.getItem("darkMode");
// Apply stored preference; if none, fall back to OS setting.
if (storedDarkMode === "true") {
document.body.classList.add("dark-mode");
} else if (storedDarkMode === "false") {
document.body.classList.remove("dark-mode");
} else {
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) { if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
document.body.classList.add("dark-mode"); // Enable dark mode if OS is set to dark document.body.classList.add("dark-mode");
} else {
document.body.classList.remove("dark-mode");
}
} }
// Listen for real-time OS theme changes // Set the initial button label.
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (event) => { if (darkModeToggle) {
if (event.matches) { darkModeToggle.textContent = document.body.classList.contains("dark-mode")
document.body.classList.add("dark-mode"); // Enable dark mode ? "Light Mode"
: "Dark Mode";
// When clicked, toggle dark mode and store preference.
darkModeToggle.addEventListener("click", function () {
if (document.body.classList.contains("dark-mode")) {
document.body.classList.remove("dark-mode");
localStorage.setItem("darkMode", "false");
darkModeToggle.textContent = "Dark Mode";
} else { } else {
document.body.classList.remove("dark-mode"); // Disable dark mode document.body.classList.add("dark-mode");
localStorage.setItem("darkMode", "true");
darkModeToggle.textContent = "Light Mode";
} }
}); });
// ✅ Fix the Button Label on Page Load
const darkModeToggle = document.getElementById("darkModeToggle");
if (document.body.classList.contains("dark-mode")) {
darkModeToggle.textContent = "Light Mode";
} else {
darkModeToggle.textContent = "Dark Mode";
} }
// Listen for OS theme changes if no user preference is set.
if (localStorage.getItem("darkMode") === null && window.matchMedia) {
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (event) => {
if (event.matches) {
document.body.classList.add("dark-mode");
if (darkModeToggle) darkModeToggle.textContent = "Light Mode";
} else {
document.body.classList.remove("dark-mode");
if (darkModeToggle) darkModeToggle.textContent = "Dark Mode";
}
});
}
// --- End Dark Mode Persistence ---
const message = sessionStorage.getItem("welcomeMessage"); const message = sessionStorage.getItem("welcomeMessage");
if (message) { if (message) {
showToast(message); showToast(message);

View File

@@ -294,13 +294,13 @@ body.dark-mode header {
/* folder icon and remove file to be uploaded */ /* folder icon and remove file to be uploaded */
.material-icons.folder-icon { .material-icons.folder-icon {
color: black; color: black;
margin-right: 3px; margin-right: 5px;
/* adjust the value as needed */ /* adjust the value as needed */
} }
body.dark-mode .material-icons.folder-icon { body.dark-mode .material-icons.folder-icon {
color: white; color: white;
margin-right: 3px; margin-right: 5px;
} }
/* Remove button default styling */ /* Remove button default styling */
@@ -1514,6 +1514,13 @@ body.dark-mode .file-icon {
margin-left: 5px; margin-left: 5px;
} }
.upload-progress-wrapper {
max-height: 300px;
/* Adjust the value as needed */
overflow-y: auto;
/* Optional: Add some padding or border if desired */
}
.upload-progress-list { .upload-progress-list {
list-style: none; list-style: none;
padding: 0; padding: 0;

View File

@@ -45,6 +45,25 @@ function getFilesFromDataTransferItems(items) {
return Promise.all(promises).then(results => results.flat()); return Promise.all(promises).then(results => results.flat());
} }
function adjustFolderHelpExpansion() {
const uploadCard = document.getElementById("uploadCard");
const folderHelpDetails = document.querySelector(".folder-help-details");
if (uploadCard && folderHelpDetails) {
if (uploadCard.offsetHeight > 400) {
folderHelpDetails.setAttribute("open", "");
} else {
folderHelpDetails.removeAttribute("open");
}
}
}
function adjustFolderHelpExpansionClosed() {
const folderHelpDetails = document.querySelector(".folder-help-details");
if (folderHelpDetails) {
folderHelpDetails.removeAttribute("open");
}
}
// Helper: Update file info container count/preview. // Helper: Update file info container count/preview.
function updateFileInfoCount() { function updateFileInfoCount() {
const fileInfoContainer = document.getElementById("fileInfoContainer"); const fileInfoContainer = document.getElementById("fileInfoContainer");
@@ -226,9 +245,20 @@ function processFiles(filesInput) {
list.appendChild(extra); list.appendChild(extra);
} }
} }
progressContainer.appendChild(list); const listWrapper = document.createElement("div");
listWrapper.classList.add("upload-progress-wrapper");
// Set a maximum height (adjust as needed) and enable vertical scrolling.
listWrapper.style.maxHeight = "300px"; // for example, 300px
listWrapper.style.overflowY = "auto";
listWrapper.appendChild(list);
progressContainer.appendChild(listWrapper);
} }
// Call once on page load:
adjustFolderHelpExpansion();
// Also call on window resize:
window.addEventListener("resize", adjustFolderHelpExpansion);
// Store files globally for submission. // Store files globally for submission.
window.selectedFiles = files; window.selectedFiles = files;
updateFileInfoCount(); updateFileInfoCount();
@@ -367,6 +397,8 @@ function submitFiles(allFiles) {
removeBtns.forEach(btn => btn.style.display = "none"); removeBtns.forEach(btn => btn.style.display = "none");
progressContainer.innerHTML = ""; progressContainer.innerHTML = "";
window.selectedFiles = []; window.selectedFiles = [];
adjustFolderHelpExpansionClosed();
window.addEventListener("resize", adjustFolderHelpExpansionClosed);
const fileInfoContainer = document.getElementById("fileInfoContainer"); const fileInfoContainer = document.getElementById("fileInfoContainer");
if (fileInfoContainer) { if (fileInfoContainer) {
fileInfoContainer.innerHTML = `<span id="fileInfoDefault">No files selected</span>`; fileInfoContainer.innerHTML = `<span id="fileInfoDefault">No files selected</span>`;