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
|
- Added translations and data attributes for almost all user-facing text
|
||||||
- Extend i18n support: Add new translation keys for Download and Share modals
|
- 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
|
## Changes 4/12/2025
|
||||||
|
|||||||
@@ -90,6 +90,13 @@ function toggleAdvancedSearch() {
|
|||||||
renderFileTable(window.currentFolder);
|
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 ---
|
* --- Fuse.js Search Helper ---
|
||||||
* Uses Fuse.js to perform a fuzzy search on fileData.
|
* 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.
|
* 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 fileListContent = container || document.getElementById("fileList");
|
||||||
const searchTerm = (window.currentSearchTerm || "").toLowerCase();
|
const searchTerm = (window.currentSearchTerm || "").toLowerCase();
|
||||||
// Use Fuse.js search for gallery view as well.
|
|
||||||
const filteredFiles = searchFiles(searchTerm);
|
const filteredFiles = searchFiles(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;";
|
|
||||||
let galleryHTML = `<div class="gallery-container" style="${gridStyle}">`;
|
// 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) => {
|
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)) {
|
||||||
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;">`;
|
const cacheKey = folderPath + encodeURIComponent(file.name);
|
||||||
} else if (/\.(mp3|wav|m4a|ogg|flac|aac|wma|opus)$/i.test(file.name)) {
|
if (window.imageCache && window.imageCache[cacheKey]) {
|
||||||
thumbnail = `<span class="material-icons gallery-icon">audiotrack</span>`;
|
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 {
|
} 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 = "";
|
} else if (/\.(mp3|wav|m4a|ogg|flac|aac|wma|opus)$/i.test(file.name)) {
|
||||||
if (file.tags && file.tags.length > 0) {
|
thumbnail = `<span class="material-icons gallery-icon">audiotrack</span>`;
|
||||||
tagBadgesHTML = `<div class="tag-badges" style="margin-top:4px;">`;
|
} else {
|
||||||
file.tags.forEach(tag => {
|
thumbnail = `<span class="material-icons gallery-icon">insert_drive_file</span>`;
|
||||||
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>`;
|
let tagBadgesHTML = "";
|
||||||
}
|
if (file.tags && file.tags.length > 0) {
|
||||||
galleryHTML += `<div class="gallery-card" style="border: 1px solid #ccc; padding: 5px; text-align: center;">
|
tagBadgesHTML = `<div class="tag-badges" style="margin-top:4px;">`;
|
||||||
<div class="gallery-preview" style="cursor: pointer;" onclick="previewFile('${folderPath + encodeURIComponent(file.name)}?t=' + new Date().getTime(), '${file.name}')">
|
file.tags.forEach(tag => {
|
||||||
${thumbnail}
|
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>`;
|
||||||
</div>
|
});
|
||||||
<div class="gallery-info" style="margin-top: 5px;">
|
tagBadgesHTML += `</div>`;
|
||||||
<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;">
|
galleryHTML += `
|
||||||
<button type="button" class="btn btn-sm btn-success download-btn"
|
<div class="gallery-card" style="border: 1px solid #ccc; padding: 5px; text-align: center;">
|
||||||
onclick="openDownloadModal('${file.name}', '${file.folder || 'root'}')"
|
<div class="gallery-preview" style="cursor: pointer;" onclick="previewFile('${folderPath + encodeURIComponent(file.name)}?t=' + new Date().getTime(), '${file.name}')">
|
||||||
title="${t('download')}">
|
${thumbnail}
|
||||||
<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>
|
<div class="gallery-info" style="margin-top: 5px;">
|
||||||
</div>`;
|
<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;
|
fileListContent.innerHTML = galleryHTML;
|
||||||
createViewToggleButton();
|
createViewToggleButton();
|
||||||
updateFileActionButtons();
|
updateFileActionButtons();
|
||||||
|
|
||||||
|
// Attach share button event listeners.
|
||||||
document.querySelectorAll(".share-btn").forEach(btn => {
|
document.querySelectorAll(".share-btn").forEach(btn => {
|
||||||
btn.addEventListener("click", e => {
|
btn.addEventListener("click", e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const fileName = btn.getAttribute("data-file");
|
const fileName = btn.getAttribute("data-file");
|
||||||
const file = fileData.find(f => f.name === fileName);
|
const file = fileData.find(f => f.name === fileName);
|
||||||
import('./filePreview.js').then(module => {
|
if (file) {
|
||||||
module.openShareModal(file, folder);
|
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) {
|
export function sortFiles(column, folder) {
|
||||||
if (sortOrder.column === column) {
|
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.",
|
"please_save_recovery_code": "Please save this code securely. It will not be shown again and can only be used once.",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"show": "Show",
|
"show": "Show",
|
||||||
"items_per_page": "items per page"
|
"items_per_page": "items per page",
|
||||||
|
"columns":"Columns"
|
||||||
},
|
},
|
||||||
es: {
|
es: {
|
||||||
"please_log_in_to_continue": "Por favor, inicie sesión para continuar.",
|
"please_log_in_to_continue": "Por favor, inicie sesión para continuar.",
|
||||||
@@ -463,7 +464,8 @@ const translations = {
|
|||||||
"save_permissions": "Guardar permisos",
|
"save_permissions": "Guardar permisos",
|
||||||
"your_recovery_code": "Su código de recuperación",
|
"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.",
|
"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: {
|
fr: {
|
||||||
"please_log_in_to_continue": "Veuillez vous connecter pour continuer.",
|
"please_log_in_to_continue": "Veuillez vous connecter pour continuer.",
|
||||||
@@ -690,7 +692,8 @@ const translations = {
|
|||||||
"save_permissions": "Enregistrer les permissions",
|
"save_permissions": "Enregistrer les permissions",
|
||||||
"your_recovery_code": "Votre code de récupération",
|
"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.",
|
"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: {
|
de: {
|
||||||
"please_log_in_to_continue": "Bitte melden Sie sich an, um fortzufahren.",
|
"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.",
|
"please_save_recovery_code": "Bitte speichern Sie diesen Code sicher. Er wird nicht erneut angezeigt und kann nur einmal verwendet werden.",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"show": "Zeige",
|
"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;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load share folder records.
|
// Load share folder records securely.
|
||||||
$shareFile = META_DIR . "share_folder_links.json";
|
$shareFile = META_DIR . "share_folder_links.json";
|
||||||
if (!file_exists($shareFile)) {
|
if (!file_exists($shareFile)) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
@@ -139,7 +139,7 @@ $allFiles = array_values(array_filter(scandir($realFolderPath), function($item)
|
|||||||
}));
|
}));
|
||||||
sort($allFiles);
|
sort($allFiles);
|
||||||
|
|
||||||
// Pagination variables.
|
// Pagination variables — limits the number of files (and thus images) per page.
|
||||||
$itemsPerPage = 10;
|
$itemsPerPage = 10;
|
||||||
$totalFiles = count($allFiles);
|
$totalFiles = count($allFiles);
|
||||||
$totalPages = max(1, ceil($totalFiles / $itemsPerPage));
|
$totalPages = max(1, ceil($totalFiles / $itemsPerPage));
|
||||||
@@ -195,6 +195,20 @@ function formatBytes($bytes) {
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
|
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 {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
@@ -220,13 +234,12 @@ function formatBytes($bytes) {
|
|||||||
a:hover {
|
a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
/* Simple download icon style */
|
|
||||||
.download-icon {
|
.download-icon {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #007BFF;
|
color: #007BFF;
|
||||||
}
|
}
|
||||||
/* Pagination styles */
|
/* Pagination styles - placed outside the view containers. */
|
||||||
.pagination {
|
.pagination {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
@@ -242,6 +255,24 @@ function formatBytes($bytes) {
|
|||||||
.pagination span.current {
|
.pagination span.current {
|
||||||
background: #0056b3;
|
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 {
|
.upload-container {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -279,22 +310,27 @@ function formatBytes($bytes) {
|
|||||||
<h1>Shared Folder: <?php echo htmlspecialchars($folder, ENT_QUOTES, 'UTF-8'); ?></h1>
|
<h1>Shared Folder: <?php echo htmlspecialchars($folder, ENT_QUOTES, 'UTF-8'); ?></h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<?php if (empty($filesOnPage)): ?>
|
<!-- Toggle Button -->
|
||||||
<p style="text-align:center;">This folder is empty.</p>
|
<button id="toggleBtn" class="toggle-btn" onclick="toggleViewMode()">Switch to Gallery View</button>
|
||||||
<?php else: ?>
|
|
||||||
<table>
|
<!-- View Containers -->
|
||||||
<thead>
|
<div id="listViewContainer">
|
||||||
<tr>
|
<?php if (empty($filesOnPage)): ?>
|
||||||
<th>Filename</th>
|
<p style="text-align:center;">This folder is empty.</p>
|
||||||
<th>Size</th>
|
<?php else: ?>
|
||||||
</tr>
|
<table>
|
||||||
</thead>
|
<thead>
|
||||||
<tbody>
|
<tr>
|
||||||
<?php foreach ($filesOnPage as $file):
|
<th>Filename</th>
|
||||||
$filePath = $realFolderPath . DIRECTORY_SEPARATOR . $file;
|
<th>Size</th>
|
||||||
$fileSize = formatBytes(filesize($filePath));
|
</tr>
|
||||||
// Build download link using share token and file name.
|
</thead>
|
||||||
$downloadLink = "downloadSharedFile.php?token=" . urlencode($token) . "&file=" . urlencode($file);
|
<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>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@@ -305,37 +341,41 @@ function formatBytes($bytes) {
|
|||||||
</td>
|
</td>
|
||||||
<td><?php echo $fileSize; ?></td>
|
<td><?php echo $fileSize; ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<!-- Pagination Controls -->
|
<?php endif; ?>
|
||||||
<div class="pagination">
|
</div>
|
||||||
<?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>
|
<!-- 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: ?>
|
<?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 endif; ?>
|
||||||
|
<?php endfor; ?>
|
||||||
|
|
||||||
<?php
|
<?php if ($currentPage < $totalPages): ?>
|
||||||
// Display up to 5 page links centered around the current page.
|
<a href="shareFolder.php?token=<?php echo urlencode($token); ?>&page=<?php echo $currentPage + 1; ?><?php echo !empty($providedPass) ? "&pass=" . urlencode($providedPass) : ""; ?>">Next</a>
|
||||||
$startPage = max(1, $currentPage - 2);
|
<?php else: ?>
|
||||||
$endPage = min($totalPages, $currentPage + 2);
|
<span>Next</span>
|
||||||
for ($i = $startPage; $i <= $endPage; $i++): ?>
|
<?php endif; ?>
|
||||||
<?php if ($i == $currentPage): ?>
|
</div>
|
||||||
<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 ($record['allowUpload']) : ?>
|
<?php if ($record['allowUpload']) : ?>
|
||||||
<div class="upload-container">
|
<div class="upload-container">
|
||||||
<h3>Upload File (50mb max size)</h3>
|
<h3>Upload File (50mb max size)</h3>
|
||||||
@@ -352,5 +392,66 @@ function formatBytes($bytes) {
|
|||||||
<div class="footer">
|
<div class="footer">
|
||||||
© <?php echo date("Y"); ?> FileRise. All rights reserved.
|
© <?php echo date("Y"); ?> FileRise. All rights reserved.
|
||||||
</div>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -75,7 +75,7 @@ if ($fileUpload['size'] > $maxSize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Define allowed file extensions.
|
// 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']);
|
$uploadedName = basename($fileUpload['name']);
|
||||||
$ext = strtolower(pathinfo($uploadedName, PATHINFO_EXTENSION));
|
$ext = strtolower(pathinfo($uploadedName, PATHINFO_EXTENSION));
|
||||||
if (!in_array($ext, $allowedExtensions)) {
|
if (!in_array($ext, $allowedExtensions)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user