Compare commits

...

4 Commits

Author SHA1 Message Date
Ryan
7cce03d092 Use create folder material icon and increase version 2025-04-10 02:59:04 -04:00
Ryan
ff92a6d26c Reduce header height & create folder material icon 2025-04-10 02:29:48 -04:00
Ryan
4fa5faa2bf Shift Key Multi‑Selection & Total Files and File Size 2025-04-10 00:45:35 -04:00
Ryan
98850a7c65 Update 2025-04-09 02:20:16 -04:00
10 changed files with 255 additions and 68 deletions

View File

@@ -1,6 +1,40 @@
# Changelog # Changelog
## Folder Sharing Feature - Changelog 4/9/2025 ## Shift Key MultiSelection Changes 4/10/2025
- **Implemented Range Selection:**
- Modified the `toggleRowSelection` function so that when the Shift key is held down, all rows between the last clicked (anchor) row (stored as `window.lastSelectedFileRow`) and the currently clicked row are selected.
- **Modifier Handling:**
- Regular clicks (or Ctrl/Cmd clicks) simply toggle the clicked row without clearing other selections.
- **Prevented Default Browser Behavior:**
- Added `event.preventDefault()` in the Shiftclick branch to avoid unwanted text selection.
- **Maintaining the Anchor:**
- The last clicked row is stored for future range selections.
## Total Files and File Size Summary
- **Size Calculation:**
- Created `parseSizeToBytes(sizeStr)` to convert file size strings (e.g. `"456.9KB"`, `"1.2 MB"`) into a numerical byte value.
- Created `formatSize(totalBytes)` to format a byte value into a humanreadable string (choosing between Bytes, KB, MB, or GB).
- Created `buildFolderSummary(filteredFiles)` to:
- Sum the sizes of all files (using `parseSizeToBytes`).
- Count the total number of files.
- **Dynamic Display in `loadFileList`:**
- Updated `loadFileList` to update a summary element (with `id="fileSummary"`) inside the `#fileListActions` container when files are present.
- When no files are found, the summary element is hidden (setting its `display` to `"none"` or clearing the container).
- **Responsive Styling:**
- Added CSS media queries to the `#fileSummary` element so that on small screens it is centered and any extra side margins are removed. Dark and light mode supported.
- **Other changes**
- `shareFolder.php` updated to display format size.
- Fix to prevent the filename text from overflowing its container in the gallery view.
- Reduced header height.
- Create Folder changed to Material Icon `edit`
---
## Folder Sharing Feature - Changelog 4/9/2025 v1.1.0
### New Endpoints ### New Endpoints

View File

@@ -26,7 +26,7 @@ Thank you for your interest in contributing to FileRise! We appreciate your help
``` ```
3. **Set Up a Local Environment** 3. **Set Up a Local Environment**
FileRise runs on a standard LAMP stack. Ensure you have PHP, Apache, and the necessary dependencies installed. For frontend development, Node.js may be required for build tasks if applicable. FileRise runs on a standard LAMP stack. Ensure you have PHP, Apache, and the necessary dependencies installed.
4. **Configuration** 4. **Configuration**
Copy any example configuration files (if provided) and adjust them as needed for your local setup. Copy any example configuration files (if provided) and adjust them as needed for your local setup.

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2024 SeNS Copyright (c) 2025 FileRise
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -12,7 +12,7 @@ Upload, organize, and share files through a sleek web interface. **FileRise** is
--- ---
## Features at a Glance or [Full Feature Wiki](https://github.com/error311/FileRise/wiki/Features) ## Features at a Glance or [Full Features Wiki](https://github.com/error311/FileRise/wiki/Features)
- 🚀 **Easy File Uploads:** Upload multiple files and folders via drag & drop or file picker. Supports large files with pause/resumable chunked uploads and shows real-time progress for each file. No more failed transfers FileRise will pick up where it left off if your connection drops. - 🚀 **Easy File Uploads:** Upload multiple files and folders via drag & drop or file picker. Supports large files with pause/resumable chunked uploads and shows real-time progress for each file. No more failed transfers FileRise will pick up where it left off if your connection drops.

View File

@@ -69,7 +69,7 @@ body {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
height: 80px; height: 65px;
padding: 10px 20px; padding: 10px 20px;
background-color: #2196F3; background-color: #2196F3;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
@@ -82,13 +82,13 @@ body.dark-mode .header-container {
} }
.header-logo { .header-logo {
max-height: 70px; max-height: 60px;
width: auto; width: auto;
display: block; display: block;
} }
.header-logo svg { .header-logo svg {
height: 70px; height: 60px;
width: auto; width: auto;
} }
@@ -650,12 +650,15 @@ body.dark-mode .editor-header {
} }
#uploadBtn { #uploadBtn {
margin-top: 20px;
font-size: 20px; font-size: 20px;
padding: 10px 22px; padding: 10px 22px;
align-items: center; align-items: center;
} }
.card-body.d-flex.flex-column {
padding: 0.75rem !important;
}
#customChooseBtn { #customChooseBtn {
background-color: #9E9E9E; background-color: #9E9E9E;
color: #fff; color: #fff;
@@ -1945,12 +1948,12 @@ body.dark-mode #folderContextMenu {
transition: transform 0.3s ease, opacity 0.3s ease; transition: transform 0.3s ease, opacity 0.3s ease;
width: 100%; width: 100%;
margin-bottom: 20px; margin-bottom: 20px;
min-height: 353px; min-height: 320px;
} }
#uploadFolderRow.highlight { #uploadFolderRow.highlight {
min-height: 353px; min-height: 320px;
margin-bottom: 20px; margin-bottom: 20px;
} }
@@ -2135,3 +2138,26 @@ body.dark-mode .header-drop-zone.drag-active {
font-size: 10px; font-size: 10px;
color: #aaa; color: #aaa;
} }
/* Disable text selection on rows to prevent accidental copying when shift-clicking */
#fileList tbody tr.clickable-row {
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE10+/Edge */
user-select: none; /* Standard */
}
#fileSummary {
color: black;
}
@media only screen and (max-width: 600px) {
#fileSummary {
float: none !important;
margin: 0 auto !important;
text-align: center !important;
}
}
body.dark-mode #fileSummary {
color: white;
}

View File

@@ -245,8 +245,9 @@
<div id="folderTreeContainer"></div> <div id="folderTreeContainer"></div>
</div> </div>
<div class="folder-actions mt-3"> <div class="folder-actions mt-3">
<button id="createFolderBtn" class="btn btn-primary" data-i18n-key="create_folder">Create <button id="createFolderBtn" class="btn btn-primary">
Folder</button> <i class="material-icons">create_new_folder</i>
</button>
<div id="createFolderModal" class="modal"> <div id="createFolderModal" class="modal">
<div class="modal-content"> <div class="modal-content">
<h4 data-i18n-key="create_folder_title">Create Folder</h4> <h4 data-i18n-key="create_folder_title">Create Folder</h4>
@@ -261,7 +262,7 @@
</div> </div>
</div> </div>
</div> </div>
<button id="renameFolderBtn" class="btn btn-warning ml-2" data-i18n-title="rename_folder"> <button id="renameFolderBtn" class="btn btn-warning ml-2">
<i class="material-icons">drive_file_rename_outline</i> <i class="material-icons">drive_file_rename_outline</i>
</button> </button>
<div id="renameFolderModal" class="modal"> <div id="renameFolderModal" class="modal">

View File

@@ -2,7 +2,7 @@ import { showToast, toggleVisibility, attachEnterKeyListener } from './domUtils.
import { sendRequest } from './networkUtils.js'; import { sendRequest } from './networkUtils.js';
import { t, applyTranslations, setLocale } from './i18n.js'; import { t, applyTranslations, setLocale } from './i18n.js';
const version = "v1.1.0"; const version = "v1.1.1";
const adminTitle = `Admin Panel <small style="font-size: 12px; color: gray;">${version}</small>`; const adminTitle = `Admin Panel <small style="font-size: 12px; color: gray;">${version}</small>`;
let lastLoginData = null; let lastLoginData = null;

View File

@@ -223,15 +223,63 @@ export function updateRowHighlight(checkbox) {
} }
export function toggleRowSelection(event, fileName) { export function toggleRowSelection(event, fileName) {
// Prevent default text selection when shift is held.
if (event.shiftKey) {
event.preventDefault();
}
// Ignore clicks on interactive elements.
const targetTag = event.target.tagName.toLowerCase(); const targetTag = event.target.tagName.toLowerCase();
if (targetTag === 'a' || targetTag === 'button' || targetTag === 'input') { if (["a", "button", "input"].includes(targetTag)) {
return; return;
} }
// Get the clicked row and its checkbox.
const row = event.currentTarget; const row = event.currentTarget;
const checkbox = row.querySelector('.file-checkbox'); const checkbox = row.querySelector(".file-checkbox");
if (!checkbox) return; if (!checkbox) return;
// Get all rows in the current file list view.
const allRows = Array.from(document.querySelectorAll("#fileList tbody tr"));
// Helper: clear all selections (not used in this updated version).
const clearAllSelections = () => {
allRows.forEach(r => {
const cb = r.querySelector(".file-checkbox");
if (cb) {
cb.checked = false;
updateRowHighlight(cb);
}
});
};
// If the user is holding the Shift key, perform range selection.
if (event.shiftKey) {
// Use the last clicked row as the anchor.
const lastRow = window.lastSelectedFileRow || row;
const currentIndex = allRows.indexOf(row);
const lastIndex = allRows.indexOf(lastRow);
const start = Math.min(currentIndex, lastIndex);
const end = Math.max(currentIndex, lastIndex);
// If neither CTRL nor Meta is pressed, you might choose
// to clear existing selections. For this example we leave existing selections intact.
for (let i = start; i <= end; i++) {
const cb = allRows[i].querySelector(".file-checkbox");
if (cb) {
cb.checked = true;
updateRowHighlight(cb);
}
}
}
// Otherwise, for all non-shift clicks simply toggle the selected state.
else {
checkbox.checked = !checkbox.checked; checkbox.checked = !checkbox.checked;
updateRowHighlight(checkbox); updateRowHighlight(checkbox);
}
// Update the anchor row to the row that was clicked.
window.lastSelectedFileRow = row;
updateFileActionButtons(); updateFileActionButtons();
} }

View File

@@ -15,6 +15,7 @@ import {
import { t } from './i18n.js'; import { t } from './i18n.js';
import { bindFileListContextMenu } from './fileMenu.js'; import { bindFileListContextMenu } from './fileMenu.js';
import { openDownloadModal } from './fileActions.js'; import { openDownloadModal } from './fileActions.js';
import { openTagModal, openMultiTagModal } from './fileTags.js';
export let fileData = []; export let fileData = [];
export let sortOrder = { column: "uploaded", ascending: true }; export let sortOrder = { column: "uploaded", ascending: true };
@@ -23,9 +24,63 @@ window.itemsPerPage = window.itemsPerPage || 10;
window.currentPage = window.currentPage || 1; window.currentPage = window.currentPage || 1;
window.viewMode = localStorage.getItem("viewMode") || "table"; // "table" or "gallery" window.viewMode = localStorage.getItem("viewMode") || "table"; // "table" or "gallery"
// ----------------------------- /**
// VIEW MODE TOGGLE BUTTON & Helpers * --- Helper Functions ---
// ----------------------------- */
/**
* Convert a file size string (e.g. "456.9KB", "1.2 MB", "1024") into bytes.
*/
function parseSizeToBytes(sizeStr) {
if (!sizeStr) return 0;
// Remove any whitespace
let s = sizeStr.trim();
// Extract the numerical part.
let value = parseFloat(s);
// Determine if there is a unit. Convert the unit to uppercase for easier matching.
let upper = s.toUpperCase();
if (upper.includes("KB")) {
value *= 1024;
} else if (upper.includes("MB")) {
value *= 1024 * 1024;
} else if (upper.includes("GB")) {
value *= 1024 * 1024 * 1024;
}
return value;
}
/**
* Format the total bytes as a human-readable string, choosing an appropriate unit.
*/
function formatSize(totalBytes) {
if (totalBytes < 1024) {
return totalBytes + " Bytes";
} else if (totalBytes < 1024 * 1024) {
return (totalBytes / 1024).toFixed(2) + " KB";
} else if (totalBytes < 1024 * 1024 * 1024) {
return (totalBytes / (1024 * 1024)).toFixed(2) + " MB";
} else {
return (totalBytes / (1024 * 1024 * 1024)).toFixed(2) + " GB";
}
}
/**
* Build the folder summary HTML using the filtered file list.
* This function sums the file sizes in bytes correctly, then formats the total.
*/
function buildFolderSummary(filteredFiles) {
const totalFiles = filteredFiles.length;
const totalBytes = filteredFiles.reduce((sum, file) => {
// file.size might be something like "456.9KB" or just "1024".
return sum + parseSizeToBytes(file.size);
}, 0);
const sizeStr = formatSize(totalBytes);
return `<strong>Total Files:</strong> ${totalFiles} &nbsp;|&nbsp; <strong>Total Size:</strong> ${sizeStr}`;
}
/**
* --- VIEW MODE TOGGLE BUTTON & Helpers ---
*/
export function createViewToggleButton() { export function createViewToggleButton() {
let toggleBtn = document.getElementById("toggleViewBtn"); let toggleBtn = document.getElementById("toggleViewBtn");
if (!toggleBtn) { if (!toggleBtn) {
@@ -58,11 +113,9 @@ export function formatFolderName(folder) {
window.toggleRowSelection = toggleRowSelection; window.toggleRowSelection = toggleRowSelection;
window.updateRowHighlight = updateRowHighlight; window.updateRowHighlight = updateRowHighlight;
import { openTagModal, openMultiTagModal } from './fileTags.js'; /**
* --- FILE LIST & VIEW RENDERING ---
// ----------------------------- */
// FILE LIST & VIEW RENDERING
// -----------------------------
export function loadFileList(folderParam) { export function loadFileList(folderParam) {
const folder = folderParam || "root"; const folder = folderParam || "root";
const fileListContainer = document.getElementById("fileList"); const fileListContainer = document.getElementById("fileList");
@@ -80,7 +133,7 @@ export function loadFileList(folderParam) {
return response.json(); return response.json();
}) })
.then(data => { .then(data => {
fileListContainer.innerHTML = ""; fileListContainer.innerHTML = ""; // Clear loading message.
if (data.files && data.files.length > 0) { if (data.files && data.files.length > 0) {
data.files = data.files.map(file => { data.files = data.files.map(file => {
file.fullName = (file.path || file.name).trim().toLowerCase(); file.fullName = (file.path || file.name).trim().toLowerCase();
@@ -92,6 +145,26 @@ export function loadFileList(folderParam) {
return file; return file;
}); });
fileData = data.files; fileData = data.files;
// Update the file list actions area without removing existing buttons.
const actionsContainer = document.getElementById("fileListActions");
if (actionsContainer) {
let summaryElem = document.getElementById("fileSummary");
if (!summaryElem) {
summaryElem = document.createElement("div");
summaryElem.id = "fileSummary";
summaryElem.style.float = "right";
summaryElem.style.marginLeft = "auto";
summaryElem.style.marginRight = "60px";
summaryElem.style.fontSize = "0.9em";
actionsContainer.appendChild(summaryElem);
} else {
summaryElem.style.display = "block";
}
summaryElem.innerHTML = buildFolderSummary(fileData);
}
// Render the view normally.
if (window.viewMode === "gallery") { if (window.viewMode === "gallery") {
renderGalleryView(folder); renderGalleryView(folder);
} else { } else {
@@ -99,6 +172,10 @@ export function loadFileList(folderParam) {
} }
} else { } else {
fileListContainer.textContent = t("no_files_found"); fileListContainer.textContent = t("no_files_found");
const summaryElem = document.getElementById("fileSummary");
if (summaryElem) {
summaryElem.style.display = "none";
}
updateFileActionButtons(); updateFileActionButtons();
} }
return data.files || []; return data.files || [];
@@ -115,8 +192,12 @@ export function loadFileList(folderParam) {
}); });
} }
export function renderFileTable(folder) { /**
const fileListContainer = document.getElementById("fileList"); * Update renderFileTable so that it writes its content into the provided container.
* If no container is provided, it defaults to the element with id "fileList".
*/
export function renderFileTable(folder, container) {
const fileListContent = container || document.getElementById("fileList");
const searchTerm = (window.currentSearchTerm || "").toLowerCase(); const searchTerm = (window.currentSearchTerm || "").toLowerCase();
const itemsPerPageSetting = parseInt(localStorage.getItem("itemsPerPage") || "10", 10); const itemsPerPageSetting = parseInt(localStorage.getItem("itemsPerPage") || "10", 10);
let currentPage = window.currentPage || 1; let currentPage = window.currentPage || 1;
@@ -126,14 +207,12 @@ export function renderFileTable(folder) {
const tagMatch = file.tags && file.tags.some(tag => tag.name.toLowerCase().includes(searchTerm)); const tagMatch = file.tags && file.tags.some(tag => tag.name.toLowerCase().includes(searchTerm));
return nameMatch || tagMatch; return nameMatch || tagMatch;
}); });
const totalFiles = filteredFiles.length; const totalFiles = filteredFiles.length;
const totalPages = Math.ceil(totalFiles / itemsPerPageSetting); const totalPages = Math.ceil(totalFiles / itemsPerPageSetting);
if (currentPage > totalPages) { if (currentPage > totalPages) {
currentPage = totalPages > 0 ? totalPages : 1; currentPage = totalPages > 0 ? totalPages : 1;
window.currentPage = currentPage; window.currentPage = currentPage;
} }
const folderPath = folder === "root" const folderPath = folder === "root"
? "uploads/" ? "uploads/"
: "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/"; : "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/";
@@ -147,7 +226,6 @@ export function renderFileTable(folder) {
const startIndex = (currentPage - 1) * itemsPerPageSetting; const startIndex = (currentPage - 1) * itemsPerPageSetting;
const endIndex = Math.min(startIndex + itemsPerPageSetting, totalFiles); const endIndex = Math.min(startIndex + itemsPerPageSetting, totalFiles);
let rowsHTML = "<tbody>"; let rowsHTML = "<tbody>";
if (totalFiles > 0) { if (totalFiles > 0) {
filteredFiles.slice(startIndex, endIndex).forEach((file, idx) => { filteredFiles.slice(startIndex, endIndex).forEach((file, idx) => {
let rowHTML = buildFileTableRow(file, folderPath); let rowHTML = buildFileTableRow(file, folderPath);
@@ -161,15 +239,12 @@ export function renderFileTable(folder) {
}); });
tagBadgesHTML += "</div>"; tagBadgesHTML += "</div>";
} }
rowHTML = rowHTML.replace(/(<td class="file-name-cell">)(.*?)(<\/td>)/, (match, p1, p2, p3) => { rowHTML = rowHTML.replace(/(<td class="file-name-cell">)(.*?)(<\/td>)/, (match, p1, p2, p3) => {
return p1 + p2 + tagBadgesHTML + p3; return p1 + p2 + tagBadgesHTML + p3;
}); });
rowHTML = rowHTML.replace(/(<\/div>\s*<\/td>\s*<\/tr>)/, `<button class="share-btn btn btn-sm btn-secondary" data-file="${escapeHTML(file.name)}" title="Share"> rowHTML = rowHTML.replace(/(<\/div>\s*<\/td>\s*<\/tr>)/, `<button class="share-btn btn btn-sm btn-secondary" data-file="${escapeHTML(file.name)}" title="Share">
<i class="material-icons">share</i> <i class="material-icons">share</i>
</button>$1`); </button>$1`);
rowsHTML += rowHTML; rowsHTML += rowHTML;
}); });
} else { } else {
@@ -177,16 +252,18 @@ export function renderFileTable(folder) {
} }
rowsHTML += "</tbody></table>"; rowsHTML += "</tbody></table>";
const bottomControlsHTML = buildBottomControls(itemsPerPageSetting); const bottomControlsHTML = buildBottomControls(itemsPerPageSetting);
fileListContainer.innerHTML = topControlsHTML + headerHTML + rowsHTML + bottomControlsHTML;
fileListContent.innerHTML = topControlsHTML + headerHTML + rowsHTML + bottomControlsHTML;
createViewToggleButton(); createViewToggleButton();
// Setup event listeners as before...
const newSearchInput = document.getElementById("searchInput"); const newSearchInput = document.getElementById("searchInput");
if (newSearchInput) { if (newSearchInput) {
newSearchInput.addEventListener("input", debounce(function () { newSearchInput.addEventListener("input", debounce(function () {
window.currentSearchTerm = newSearchInput.value; window.currentSearchTerm = newSearchInput.value;
window.currentPage = 1; window.currentPage = 1;
renderFileTable(folder); renderFileTable(folder, container);
setTimeout(() => { setTimeout(() => {
const freshInput = document.getElementById("searchInput"); const freshInput = document.getElementById("searchInput");
if (freshInput) { if (freshInput) {
@@ -197,21 +274,18 @@ export function renderFileTable(folder) {
}, 0); }, 0);
}, 300)); }, 300));
} }
document.querySelectorAll("table.table thead th[data-column]").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();
}); });
}); });
document.querySelectorAll(".share-btn").forEach(btn => { document.querySelectorAll(".share-btn").forEach(btn => {
btn.addEventListener("click", function (e) { btn.addEventListener("click", function (e) {
e.stopPropagation(); e.stopPropagation();
@@ -224,40 +298,34 @@ export function renderFileTable(folder) {
} }
}); });
}); });
updateFileActionButtons(); updateFileActionButtons();
document.querySelectorAll("#fileListContent tbody tr").forEach(row => {
// Add drag-and-drop support for each table row.
document.querySelectorAll("#fileList tbody tr").forEach(row => {
row.setAttribute("draggable", "true"); row.setAttribute("draggable", "true");
import('./fileDragDrop.js').then(module => { import('./fileDragDrop.js').then(module => {
row.addEventListener("dragstart", module.fileDragStartHandler); row.addEventListener("dragstart", module.fileDragStartHandler);
}); });
}); });
// Prevent clicks on these buttons from selecting the row
document.querySelectorAll(".download-btn, .edit-btn, .rename-btn").forEach(btn => { document.querySelectorAll(".download-btn, .edit-btn, .rename-btn").forEach(btn => {
btn.addEventListener("click", e => e.stopPropagation()); btn.addEventListener("click", e => e.stopPropagation());
}); });
// rebind context menu
bindFileListContextMenu(); bindFileListContextMenu();
} }
export function renderGalleryView(folder) { /**
const fileListContainer = document.getElementById("fileList"); * Similarly, update renderGalleryView to accept an optional container.
*/
export function renderGalleryView(folder, container) {
const fileListContent = container || document.getElementById("fileList");
const searchTerm = (window.currentSearchTerm || "").toLowerCase(); const searchTerm = (window.currentSearchTerm || "").toLowerCase();
const filteredFiles = fileData.filter(file => { const filteredFiles = fileData.filter(file => {
return file.name.toLowerCase().includes(searchTerm) || return file.name.toLowerCase().includes(searchTerm) ||
(file.tags && file.tags.some(tag => tag.name.toLowerCase().includes(searchTerm))); (file.tags && file.tags.some(tag => tag.name.toLowerCase().includes(searchTerm)));
}); });
const folderPath = folder === "root" const folderPath = folder === "root"
? "uploads/" ? "uploads/"
: "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/"; : "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/";
const gridStyle = "display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; padding: 10px;"; const gridStyle = "display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; padding: 10px;";
let galleryHTML = `<div class="gallery-container" style="${gridStyle}">`; let galleryHTML = `<div class="gallery-container" style="${gridStyle}">`;
filteredFiles.forEach((file) => { filteredFiles.forEach((file) => {
let thumbnail; let thumbnail;
if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(file.name)) { if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(file.name)) {
@@ -267,7 +335,6 @@ export function renderGalleryView(folder) {
} else { } else {
thumbnail = `<span class="material-icons gallery-icon">insert_drive_file</span>`; thumbnail = `<span class="material-icons gallery-icon">insert_drive_file</span>`;
} }
let tagBadgesHTML = ""; let tagBadgesHTML = "";
if (file.tags && file.tags.length > 0) { if (file.tags && file.tags.length > 0) {
tagBadgesHTML = `<div class="tag-badges" style="margin-top:4px;">`; tagBadgesHTML = `<div class="tag-badges" style="margin-top:4px;">`;
@@ -276,7 +343,6 @@ export function renderGalleryView(folder) {
}); });
tagBadgesHTML += `</div>`; tagBadgesHTML += `</div>`;
} }
galleryHTML += `<div class="gallery-card" style="border: 1px solid #ccc; padding: 5px; text-align: center;"> galleryHTML += `<div class="gallery-card" style="border: 1px solid #ccc; padding: 5px; text-align: center;">
<div class="gallery-preview" style="cursor: pointer;" onclick="previewFile('${folderPath + encodeURIComponent(file.name)}?t=' + new Date().getTime(), '${file.name}')"> <div class="gallery-preview" style="cursor: pointer;" onclick="previewFile('${folderPath + encodeURIComponent(file.name)}?t=' + new Date().getTime(), '${file.name}')">
${thumbnail} ${thumbnail}
@@ -305,15 +371,10 @@ export function renderGalleryView(folder) {
</div> </div>
</div>`; </div>`;
}); });
galleryHTML += "</div>"; galleryHTML += "</div>";
fileListContent.innerHTML = galleryHTML;
fileListContainer.innerHTML = galleryHTML;
createViewToggleButton(); createViewToggleButton();
updateFileActionButtons(); updateFileActionButtons();
// Bind share button clicks
document.querySelectorAll(".share-btn").forEach(btn => { document.querySelectorAll(".share-btn").forEach(btn => {
btn.addEventListener("click", e => { btn.addEventListener("click", e => {
e.stopPropagation(); e.stopPropagation();
@@ -413,7 +474,6 @@ window.changeItemsPerPage = function (newCount) {
}; };
// fileListView.js (bottom) // fileListView.js (bottom)
window.loadFileList = loadFileList; window.loadFileList = loadFileList;
window.renderFileTable = renderFileTable; window.renderFileTable = renderFileTable;
window.renderGalleryView = renderGalleryView; window.renderGalleryView = renderGalleryView;

View File

@@ -146,6 +146,24 @@ $totalPages = max(1, ceil($totalFiles / $itemsPerPage));
$currentPage = min($page, $totalPages); $currentPage = min($page, $totalPages);
$startIndex = ($currentPage - 1) * $itemsPerPage; $startIndex = ($currentPage - 1) * $itemsPerPage;
$filesOnPage = array_slice($allFiles, $startIndex, $itemsPerPage); $filesOnPage = array_slice($allFiles, $startIndex, $itemsPerPage);
/**
* Convert file size in bytes into a human-readable string.
*
* @param int $bytes The file size in bytes.
* @return string The formatted size string.
*/
function formatBytes($bytes) {
if ($bytes < 1024) {
return $bytes . " B";
} elseif ($bytes < 1024 * 1024) {
return round($bytes / 1024, 2) . " KB";
} elseif ($bytes < 1024 * 1024 * 1024) {
return round($bytes / (1024 * 1024), 2) . " MB";
} else {
return round($bytes / (1024 * 1024 * 1024), 2) . " GB";
}
}
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@@ -268,13 +286,13 @@ $filesOnPage = array_slice($allFiles, $startIndex, $itemsPerPage);
<thead> <thead>
<tr> <tr>
<th>Filename</th> <th>Filename</th>
<th>Size (MB)</th> <th>Size</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($filesOnPage as $file): <?php foreach ($filesOnPage as $file):
$filePath = $realFolderPath . DIRECTORY_SEPARATOR . $file; $filePath = $realFolderPath . DIRECTORY_SEPARATOR . $file;
$sizeMB = round(filesize($filePath) / (1024 * 1024), 2); $fileSize = formatBytes(filesize($filePath));
// Build download link using share token and file name. // Build download link using share token and file name.
$downloadLink = "downloadSharedFile.php?token=" . urlencode($token) . "&file=" . urlencode($file); $downloadLink = "downloadSharedFile.php?token=" . urlencode($token) . "&file=" . urlencode($file);
?> ?>
@@ -285,7 +303,7 @@ $filesOnPage = array_slice($allFiles, $startIndex, $itemsPerPage);
<span class="download-icon">&#x21E9;</span> <span class="download-icon">&#x21E9;</span>
</a> </a>
</td> </td>
<td><?php echo $sizeMB; ?></td> <td><?php echo $fileSize; ?></td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>