Gallery view enhancements
This commit is contained in:
35
CHANGELOG.md
35
CHANGELOG.md
@@ -10,6 +10,41 @@
|
||||
- Added translations and data attributes for almost all user-facing text
|
||||
- Extend i18n support: Add new translation keys for Download and Share modals
|
||||
|
||||
- **Slider Integration:**
|
||||
- Added a slider UI (range input, label, and value display) directly above the gallery grid.
|
||||
- The slider allows users to adjust the number of columns in the gallery from 1 to 6.
|
||||
- **Dynamic Grid Updates:**
|
||||
- The gallery grid’s CSS is updated in real time via the slider’s value by setting the grid-template-columns property.
|
||||
- As the slider value changes, the layout instantly reflects the new column count.
|
||||
- **Dynamic Image Resizing:**
|
||||
- Introduced a helper function (getMaxImageHeight) that calculates the maximum image height based on the current column count.
|
||||
- The max height of each image is updated immediately when the slider is adjusted to create a more dynamic display.
|
||||
- **Image Caching:**
|
||||
- Implemented an image caching mechanism using a global window.imageCache object.
|
||||
- Images are cached on load (via an onload event) to prevent unnecessary reloading, improving performance.
|
||||
- **Event Handling:**
|
||||
- The slider’s event listener is set up to update both the gallery grid layout and the dimensions of the thumbnails dynamically.
|
||||
- Share button event listeners remain attached for proper functionality across the updated gallery view.
|
||||
|
||||
- **Input Validation & Security:**
|
||||
- Used `filter_input()` to sanitize and validate incoming GET parameters (token, pass, page).
|
||||
- Validated file system paths using `realpath()` and ensured the shared folder lies within `UPLOAD_DIR`.
|
||||
- Escaped all dynamic outputs with `htmlspecialchars()` to prevent XSS.
|
||||
- **Share Link Verification:**
|
||||
- Loaded and validated share records from the JSON file.
|
||||
- Handled expiration and password protection (with proper HTTP status codes for errors).
|
||||
- **Pagination:**
|
||||
- Implemented pagination by slicing the full file list into a limited number of files per page (default of 10).
|
||||
- Calculated total pages and current page to create navigation links.
|
||||
- **View Toggle (List vs. Gallery):**
|
||||
- Added a toggle button that switches between a traditional list view and a gallery view.
|
||||
- Maintained two separate view containers (`#listViewContainer` and `#galleryViewContainer`) to support this switching.
|
||||
- **Gallery View with Image Caching:**
|
||||
- For the gallery view, implemented a JavaScript function that creates a grid of image thumbnails.
|
||||
- Each image uses a cache-busting query string on first load and caches its URL in a global `window.imageCache` for subsequent renders.
|
||||
- **Persistent Pagination Controls:**
|
||||
- Moved the pagination controls outside the individual view containers so that they remain visible regardless of the selected view.
|
||||
|
||||
---
|
||||
|
||||
## Changes 4/12/2025
|
||||
|
||||
@@ -90,6 +90,13 @@ function toggleAdvancedSearch() {
|
||||
renderFileTable(window.currentFolder);
|
||||
}
|
||||
|
||||
window.imageCache = window.imageCache || {};
|
||||
function cacheImage(imgElem, key) {
|
||||
// Save the current src for future renders.
|
||||
window.imageCache[key] = imgElem.src;
|
||||
}
|
||||
window.cacheImage = cacheImage;
|
||||
|
||||
/**
|
||||
* --- Fuse.js Search Helper ---
|
||||
* Uses Fuse.js to perform a fuzzy search on fileData.
|
||||
@@ -388,76 +395,142 @@ export function renderFileTable(folder, container) {
|
||||
/**
|
||||
* Similarly, update renderGalleryView to accept an optional container.
|
||||
*/
|
||||
export function renderGalleryView(folder, container) {
|
||||
// A helper to compute the max image height based on the current column count.
|
||||
function getMaxImageHeight() {
|
||||
// Use the slider value (default to 3 if undefined).
|
||||
const columns = parseInt(window.galleryColumns || 3, 10);
|
||||
// Simple scaling: fewer columns yield bigger images.
|
||||
// For instance, if columns === 6, max-height is 150px,
|
||||
// and if columns === 1, max-height could be 150 * 6 = 900px.
|
||||
return 150 * (7 - columns); // adjust the multiplier as needed.
|
||||
}
|
||||
|
||||
export function renderGalleryView(folder, container) {
|
||||
const fileListContent = container || document.getElementById("fileList");
|
||||
const searchTerm = (window.currentSearchTerm || "").toLowerCase();
|
||||
// Use Fuse.js search for gallery view as well.
|
||||
const filteredFiles = searchFiles(searchTerm);
|
||||
const folderPath = folder === "root"
|
||||
? "uploads/"
|
||||
: "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/";
|
||||
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}">`;
|
||||
? "uploads/"
|
||||
: "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/";
|
||||
|
||||
// Use the current global column value (default to 3).
|
||||
const numColumns = window.galleryColumns || 3;
|
||||
|
||||
// --- Insert slider controls ---
|
||||
// Build the slider HTML.
|
||||
const sliderHTML = `
|
||||
<div class="gallery-slider" style="margin: 10px; text-align: center;">
|
||||
<label for="galleryColumnsSlider" style="margin-right: 5px;">${t('columns')}:</label>
|
||||
<input type="range" id="galleryColumnsSlider" min="1" max="6" value="${numColumns}" style="vertical-align: middle;">
|
||||
<span id="galleryColumnsValue">${numColumns}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Set up the grid container using the slider's value.
|
||||
const gridStyle = `display: grid; grid-template-columns: repeat(${numColumns}, 1fr); gap: 10px; padding: 10px;`;
|
||||
|
||||
// Build the gallery container HTML and include the slider above it.
|
||||
let galleryHTML = sliderHTML;
|
||||
galleryHTML += `<div class="gallery-container" style="${gridStyle}">`;
|
||||
|
||||
filteredFiles.forEach((file) => {
|
||||
let thumbnail;
|
||||
if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(file.name)) {
|
||||
thumbnail = `<img src="${folderPath + encodeURIComponent(file.name)}?t=${new Date().getTime()}" class="gallery-thumbnail" alt="${escapeHTML(file.name)}" style="max-width: 100%; max-height: 150px; display: block; margin: 0 auto;">`;
|
||||
} else if (/\.(mp3|wav|m4a|ogg|flac|aac|wma|opus)$/i.test(file.name)) {
|
||||
thumbnail = `<span class="material-icons gallery-icon">audiotrack</span>`;
|
||||
let thumbnail;
|
||||
if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(file.name)) {
|
||||
const cacheKey = folderPath + encodeURIComponent(file.name);
|
||||
if (window.imageCache && window.imageCache[cacheKey]) {
|
||||
thumbnail = `<img src="${window.imageCache[cacheKey]}" class="gallery-thumbnail" alt="${escapeHTML(file.name)}" style="max-width: 100%; max-height: ${getMaxImageHeight()}px; display: block; margin: 0 auto;">`;
|
||||
} else {
|
||||
thumbnail = `<span class="material-icons gallery-icon">insert_drive_file</span>`;
|
||||
const imageUrl = folderPath + encodeURIComponent(file.name) + "?t=" + new Date().getTime();
|
||||
thumbnail = `<img src="${imageUrl}" onload="cacheImage(this, '${cacheKey}')" class="gallery-thumbnail" alt="${escapeHTML(file.name)}" style="max-width: 100%; max-height: ${getMaxImageHeight()}px; display: block; margin: 0 auto;">`;
|
||||
}
|
||||
let tagBadgesHTML = "";
|
||||
if (file.tags && file.tags.length > 0) {
|
||||
tagBadgesHTML = `<div class="tag-badges" style="margin-top:4px;">`;
|
||||
file.tags.forEach(tag => {
|
||||
tagBadgesHTML += `<span style="background-color: ${tag.color}; color: #fff; padding: 2px 4px; border-radius: 3px; margin-right: 2px; font-size: 0.8em;">${escapeHTML(tag.name)}</span>`;
|
||||
});
|
||||
tagBadgesHTML += `</div>`;
|
||||
}
|
||||
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}')">
|
||||
${thumbnail}
|
||||
</div>
|
||||
<div class="gallery-info" style="margin-top: 5px;">
|
||||
<span class="gallery-file-name" style="display: block; white-space: normal; overflow-wrap: break-word; word-wrap: break-word;">${escapeHTML(file.name)}</span>
|
||||
${tagBadgesHTML}
|
||||
<div class="button-wrap" style="display: flex; justify-content: center; gap: 5px;">
|
||||
<button type="button" class="btn btn-sm btn-success download-btn"
|
||||
onclick="openDownloadModal('${file.name}', '${file.folder || 'root'}')"
|
||||
title="${t('download')}">
|
||||
<i class="material-icons">file_download</i>
|
||||
</button>
|
||||
${file.editable ? `
|
||||
<button class="btn btn-sm edit-btn" onclick='editFile(${JSON.stringify(file.name)}, ${JSON.stringify(file.folder || "root")})' title="${t('Edit')}">
|
||||
<i class="material-icons">edit</i>
|
||||
</button>
|
||||
` : ""}
|
||||
<button class="btn btn-sm btn-warning rename-btn" onclick='renameFile(${JSON.stringify(file.name)}, ${JSON.stringify(file.folder || "root")})' title="${t('rename')}">
|
||||
<i class="material-icons">drive_file_rename_outline</i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary share-btn" data-file="${escapeHTML(file.name)}" title="${t('share')}">
|
||||
<i class="material-icons">share</i>
|
||||
</button>
|
||||
} else if (/\.(mp3|wav|m4a|ogg|flac|aac|wma|opus)$/i.test(file.name)) {
|
||||
thumbnail = `<span class="material-icons gallery-icon">audiotrack</span>`;
|
||||
} else {
|
||||
thumbnail = `<span class="material-icons gallery-icon">insert_drive_file</span>`;
|
||||
}
|
||||
|
||||
let tagBadgesHTML = "";
|
||||
if (file.tags && file.tags.length > 0) {
|
||||
tagBadgesHTML = `<div class="tag-badges" style="margin-top:4px;">`;
|
||||
file.tags.forEach(tag => {
|
||||
tagBadgesHTML += `<span style="background-color: ${tag.color}; color: #fff; padding: 2px 4px; border-radius: 3px; margin-right: 2px; font-size: 0.8em;">${escapeHTML(tag.name)}</span>`;
|
||||
});
|
||||
tagBadgesHTML += `</div>`;
|
||||
}
|
||||
|
||||
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}')">
|
||||
${thumbnail}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
<div class="gallery-info" style="margin-top: 5px;">
|
||||
<span class="gallery-file-name" style="display: block; white-space: normal; overflow-wrap: break-word; word-wrap: break-word;">
|
||||
${escapeHTML(file.name)}
|
||||
</span>
|
||||
${tagBadgesHTML}
|
||||
<div class="button-wrap" style="display: flex; justify-content: center; gap: 5px;">
|
||||
<button type="button" class="btn btn-sm btn-success download-btn"
|
||||
onclick="openDownloadModal('${file.name}', '${file.folder || 'root'}')"
|
||||
title="${t('download')}">
|
||||
<i class="material-icons">file_download</i>
|
||||
</button>
|
||||
${file.editable ? `
|
||||
<button class="btn btn-sm edit-btn" onclick='editFile(${JSON.stringify(file.name)}, ${JSON.stringify(file.folder || "root")})' title="${t('Edit')}">
|
||||
<i class="material-icons">edit</i>
|
||||
</button>
|
||||
` : ""}
|
||||
<button class="btn btn-sm btn-warning rename-btn" onclick='renameFile(${JSON.stringify(file.name)}, ${JSON.stringify(file.folder || "root")})' title="${t('rename')}">
|
||||
<i class="material-icons">drive_file_rename_outline</i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary share-btn" data-file="${escapeHTML(file.name)}" title="${t('share')}">
|
||||
<i class="material-icons">share</i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
galleryHTML += "</div>";
|
||||
galleryHTML += "</div>"; // End gallery container.
|
||||
|
||||
fileListContent.innerHTML = galleryHTML;
|
||||
createViewToggleButton();
|
||||
updateFileActionButtons();
|
||||
|
||||
// Attach share button event listeners.
|
||||
document.querySelectorAll(".share-btn").forEach(btn => {
|
||||
btn.addEventListener("click", e => {
|
||||
e.stopPropagation();
|
||||
const fileName = btn.getAttribute("data-file");
|
||||
const file = fileData.find(f => f.name === fileName);
|
||||
import('./filePreview.js').then(module => {
|
||||
module.openShareModal(file, folder);
|
||||
});
|
||||
});
|
||||
btn.addEventListener("click", e => {
|
||||
e.stopPropagation();
|
||||
const fileName = btn.getAttribute("data-file");
|
||||
const file = fileData.find(f => f.name === fileName);
|
||||
if (file) {
|
||||
import('./filePreview.js').then(module => {
|
||||
module.openShareModal(file, folder);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// --- Slider Event Listener ---
|
||||
const slider = document.getElementById("galleryColumnsSlider");
|
||||
if (slider) {
|
||||
slider.addEventListener("input", function() {
|
||||
const value = this.value;
|
||||
// Update the slider display.
|
||||
document.getElementById("galleryColumnsValue").textContent = value;
|
||||
// Update global value so new renders use the correct value.
|
||||
window.galleryColumns = value;
|
||||
// Update the grid columns.
|
||||
const galleryContainer = document.querySelector(".gallery-container");
|
||||
if (galleryContainer) {
|
||||
galleryContainer.style.gridTemplateColumns = `repeat(${value}, 1fr)`;
|
||||
}
|
||||
// Compute the new max image height.
|
||||
const newMaxHeight = getMaxImageHeight();
|
||||
document.querySelectorAll(".gallery-thumbnail").forEach(img => {
|
||||
img.style.maxHeight = newMaxHeight + "px";
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function sortFiles(column, folder) {
|
||||
if (sortOrder.column === column) {
|
||||
|
||||
12
js/i18n.js
12
js/i18n.js
@@ -236,7 +236,8 @@ const translations = {
|
||||
"please_save_recovery_code": "Please save this code securely. It will not be shown again and can only be used once.",
|
||||
"ok": "OK",
|
||||
"show": "Show",
|
||||
"items_per_page": "items per page"
|
||||
"items_per_page": "items per page",
|
||||
"columns":"Columns"
|
||||
},
|
||||
es: {
|
||||
"please_log_in_to_continue": "Por favor, inicie sesión para continuar.",
|
||||
@@ -463,7 +464,8 @@ const translations = {
|
||||
"save_permissions": "Guardar permisos",
|
||||
"your_recovery_code": "Su código de recuperación",
|
||||
"please_save_recovery_code": "Por favor, guarde este código de forma segura. No se mostrará de nuevo y solo podrá usarse una vez.",
|
||||
"ok": "OK"
|
||||
"ok": "OK",
|
||||
"columns": "Columnas"
|
||||
},
|
||||
fr: {
|
||||
"please_log_in_to_continue": "Veuillez vous connecter pour continuer.",
|
||||
@@ -690,7 +692,8 @@ const translations = {
|
||||
"save_permissions": "Enregistrer les permissions",
|
||||
"your_recovery_code": "Votre code de récupération",
|
||||
"please_save_recovery_code": "Veuillez sauvegarder ce code en toute sécurité. Il ne sera plus affiché et ne pourra être utilisé qu'une seule fois.",
|
||||
"ok": "OK"
|
||||
"ok": "OK",
|
||||
"columns": "Colonnes"
|
||||
},
|
||||
de: {
|
||||
"please_log_in_to_continue": "Bitte melden Sie sich an, um fortzufahren.",
|
||||
@@ -928,7 +931,8 @@ const translations = {
|
||||
"please_save_recovery_code": "Bitte speichern Sie diesen Code sicher. Er wird nicht erneut angezeigt und kann nur einmal verwendet werden.",
|
||||
"ok": "OK",
|
||||
"show": "Zeige",
|
||||
"items_per_page": "elemente pro seite"
|
||||
"items_per_page": "elemente pro seite",
|
||||
"columns": "Spalten"
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
197
shareFolder.php
197
shareFolder.php
@@ -17,7 +17,7 @@ if (empty($token)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Load share folder records.
|
||||
// Load share folder records securely.
|
||||
$shareFile = META_DIR . "share_folder_links.json";
|
||||
if (!file_exists($shareFile)) {
|
||||
http_response_code(404);
|
||||
@@ -139,7 +139,7 @@ $allFiles = array_values(array_filter(scandir($realFolderPath), function($item)
|
||||
}));
|
||||
sort($allFiles);
|
||||
|
||||
// Pagination variables.
|
||||
// Pagination variables — limits the number of files (and thus images) per page.
|
||||
$itemsPerPage = 10;
|
||||
$totalFiles = count($allFiles);
|
||||
$totalPages = max(1, ceil($totalFiles / $itemsPerPage));
|
||||
@@ -195,6 +195,20 @@ function formatBytes($bytes) {
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
/* Toggle button */
|
||||
.toggle-btn {
|
||||
margin-bottom: 20px;
|
||||
padding: 8px 16px;
|
||||
background: #007BFF;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.toggle-btn:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
/* List view table styles */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
@@ -220,13 +234,12 @@ function formatBytes($bytes) {
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
/* Simple download icon style */
|
||||
.download-icon {
|
||||
margin-left: 8px;
|
||||
font-weight: bold;
|
||||
color: #007BFF;
|
||||
}
|
||||
/* Pagination styles */
|
||||
/* Pagination styles - placed outside the view containers. */
|
||||
.pagination {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
@@ -242,6 +255,24 @@ function formatBytes($bytes) {
|
||||
.pagination span.current {
|
||||
background: #0056b3;
|
||||
}
|
||||
/* Gallery view styles */
|
||||
.shared-gallery-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 10px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.shared-gallery-card {
|
||||
border: 1px solid #ccc;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
.shared-gallery-card img {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
/* Upload container */
|
||||
.upload-container {
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
@@ -279,22 +310,27 @@ function formatBytes($bytes) {
|
||||
<h1>Shared Folder: <?php echo htmlspecialchars($folder, ENT_QUOTES, 'UTF-8'); ?></h1>
|
||||
</div>
|
||||
<div class="container">
|
||||
<?php if (empty($filesOnPage)): ?>
|
||||
<p style="text-align:center;">This folder is empty.</p>
|
||||
<?php else: ?>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Filename</th>
|
||||
<th>Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($filesOnPage as $file):
|
||||
$filePath = $realFolderPath . DIRECTORY_SEPARATOR . $file;
|
||||
$fileSize = formatBytes(filesize($filePath));
|
||||
// Build download link using share token and file name.
|
||||
$downloadLink = "downloadSharedFile.php?token=" . urlencode($token) . "&file=" . urlencode($file);
|
||||
<!-- Toggle Button -->
|
||||
<button id="toggleBtn" class="toggle-btn" onclick="toggleViewMode()">Switch to Gallery View</button>
|
||||
|
||||
<!-- View Containers -->
|
||||
<div id="listViewContainer">
|
||||
<?php if (empty($filesOnPage)): ?>
|
||||
<p style="text-align:center;">This folder is empty.</p>
|
||||
<?php else: ?>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Filename</th>
|
||||
<th>Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($filesOnPage as $file):
|
||||
$filePath = $realFolderPath . DIRECTORY_SEPARATOR . $file;
|
||||
$fileSize = formatBytes(filesize($filePath));
|
||||
// Build download link using share token and file name.
|
||||
$downloadLink = "downloadSharedFile.php?token=" . urlencode($token) . "&file=" . urlencode($file);
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
@@ -305,37 +341,41 @@ function formatBytes($bytes) {
|
||||
</td>
|
||||
<td><?php echo $fileSize; ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- Pagination Controls -->
|
||||
<div class="pagination">
|
||||
<?php if ($currentPage > 1): ?>
|
||||
<a href="shareFolder.php?token=<?php echo urlencode($token); ?>&page=<?php echo $currentPage - 1; ?><?php echo !empty($providedPass) ? "&pass=" . urlencode($providedPass) : ""; ?>">Prev</a>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Gallery View Container (hidden by default) -->
|
||||
<div id="galleryViewContainer" style="display:none;"></div>
|
||||
|
||||
<!-- Pagination Controls (always visible) -->
|
||||
<div class="pagination">
|
||||
<?php if ($currentPage > 1): ?>
|
||||
<a href="shareFolder.php?token=<?php echo urlencode($token); ?>&page=<?php echo $currentPage - 1; ?><?php echo !empty($providedPass) ? "&pass=" . urlencode($providedPass) : ""; ?>">Prev</a>
|
||||
<?php else: ?>
|
||||
<span>Prev</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$startPage = max(1, $currentPage - 2);
|
||||
$endPage = min($totalPages, $currentPage + 2);
|
||||
for ($i = $startPage; $i <= $endPage; $i++): ?>
|
||||
<?php if ($i == $currentPage): ?>
|
||||
<span class="current"><?php echo $i; ?></span>
|
||||
<?php else: ?>
|
||||
<span>Prev</span>
|
||||
<a href="shareFolder.php?token=<?php echo urlencode($token); ?>&page=<?php echo $i; ?><?php echo !empty($providedPass) ? "&pass=" . urlencode($providedPass) : ""; ?>"><?php echo $i; ?></a>
|
||||
<?php endif; ?>
|
||||
<?php endfor; ?>
|
||||
|
||||
<?php
|
||||
// Display up to 5 page links centered around the current page.
|
||||
$startPage = max(1, $currentPage - 2);
|
||||
$endPage = min($totalPages, $currentPage + 2);
|
||||
for ($i = $startPage; $i <= $endPage; $i++): ?>
|
||||
<?php if ($i == $currentPage): ?>
|
||||
<span class="current"><?php echo $i; ?></span>
|
||||
<?php else: ?>
|
||||
<a href="shareFolder.php?token=<?php echo urlencode($token); ?>&page=<?php echo $i; ?><?php echo !empty($providedPass) ? "&pass=" . urlencode($providedPass) : ""; ?>"><?php echo $i; ?></a>
|
||||
<?php endif; ?>
|
||||
<?php endfor; ?>
|
||||
|
||||
<?php if ($currentPage < $totalPages): ?>
|
||||
<a href="shareFolder.php?token=<?php echo urlencode($token); ?>&page=<?php echo $currentPage + 1; ?><?php echo !empty($providedPass) ? "&pass=" . urlencode($providedPass) : ""; ?>">Next</a>
|
||||
<?php else: ?>
|
||||
<span>Next</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($currentPage < $totalPages): ?>
|
||||
<a href="shareFolder.php?token=<?php echo urlencode($token); ?>&page=<?php echo $currentPage + 1; ?><?php echo !empty($providedPass) ? "&pass=" . urlencode($providedPass) : ""; ?>">Next</a>
|
||||
<?php else: ?>
|
||||
<span>Next</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($record['allowUpload']) : ?>
|
||||
<div class="upload-container">
|
||||
<h3>Upload File (50mb max size)</h3>
|
||||
@@ -352,5 +392,66 @@ function formatBytes($bytes) {
|
||||
<div class="footer">
|
||||
© <?php echo date("Y"); ?> FileRise. All rights reserved.
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Global variables
|
||||
var viewMode = 'list';
|
||||
window.imageCache = window.imageCache || {};
|
||||
|
||||
// File data for the gallery view (current page file names)
|
||||
// Since the server-side pagination limits the files per page, gallery view shows the same files.
|
||||
var filesData = <?php echo json_encode($filesOnPage); ?>;
|
||||
// Define the relative URL for this shared folder's files.
|
||||
var filesUrlBase = "uploads/<?php echo htmlspecialchars($folder, ENT_QUOTES, 'UTF-8'); ?>";
|
||||
|
||||
// Helper function to cache image URLs.
|
||||
function cacheImage(imgElem, key) {
|
||||
window.imageCache[key] = imgElem.src;
|
||||
}
|
||||
|
||||
// Render gallery view using filesData.
|
||||
function renderGalleryView() {
|
||||
var galleryContainer = document.getElementById("galleryViewContainer");
|
||||
var html = '<div class="shared-gallery-container">';
|
||||
filesData.forEach(function(file) {
|
||||
var fileUrl = filesUrlBase + "/" + encodeURIComponent(file);
|
||||
var ext = file.split('.').pop().toLowerCase();
|
||||
var thumbnail = "";
|
||||
if (['jpg','jpeg','png','gif','bmp','webp','svg','ico'].indexOf(ext) >= 0) {
|
||||
var cacheKey = fileUrl;
|
||||
if (window.imageCache[cacheKey]) {
|
||||
thumbnail = '<img src="'+window.imageCache[cacheKey]+'" alt="'+file+'">';
|
||||
} else {
|
||||
var imageUrl = fileUrl + '?t=' + new Date().getTime();
|
||||
thumbnail = '<img src="'+imageUrl+'" onload="cacheImage(this, \''+cacheKey+'\')" alt="'+file+'">';
|
||||
}
|
||||
} else {
|
||||
thumbnail = '<span class="material-icons">insert_drive_file</span>';
|
||||
}
|
||||
html += '<div class="shared-gallery-card">';
|
||||
html += '<div class="gallery-preview" onclick="window.location.href=\''+fileUrl+'\'" style="cursor:pointer;">'+ thumbnail +'</div>';
|
||||
html += '<div class="gallery-info"><span class="gallery-file-name">'+file+'</span></div>';
|
||||
html += '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
galleryContainer.innerHTML = html;
|
||||
}
|
||||
|
||||
// Toggle between list and gallery views.
|
||||
function toggleViewMode() {
|
||||
if (viewMode === 'list') {
|
||||
viewMode = 'gallery';
|
||||
document.getElementById("listViewContainer").style.display = "none";
|
||||
renderGalleryView();
|
||||
document.getElementById("galleryViewContainer").style.display = "block";
|
||||
document.getElementById("toggleBtn").textContent = "Switch to List View";
|
||||
} else {
|
||||
viewMode = 'list';
|
||||
document.getElementById("galleryViewContainer").style.display = "none";
|
||||
document.getElementById("listViewContainer").style.display = "block";
|
||||
document.getElementById("toggleBtn").textContent = "Switch to Gallery View";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -75,7 +75,7 @@ if ($fileUpload['size'] > $maxSize) {
|
||||
}
|
||||
|
||||
// Define allowed file extensions.
|
||||
$allowedExtensions = ['jpg','jpeg','png','gif','pdf','doc','docx','txt','xls','xlsx','ppt','pptx','mp4','webm','mp3'];
|
||||
$allowedExtensions = ['jpg','jpeg','png','gif','pdf','doc','docx','txt','xls','xlsx','ppt','pptx','mp4','webm','mp3','mkv'];
|
||||
$uploadedName = basename($fileUpload['name']);
|
||||
$ext = strtolower(pathinfo($uploadedName, PATHINFO_EXTENSION));
|
||||
if (!in_array($ext, $allowedExtensions)) {
|
||||
|
||||
Reference in New Issue
Block a user