Refactor API endpoints and modularize controllers and models
This commit is contained in:
437
public/js/filePreview.js
Normal file
437
public/js/filePreview.js
Normal file
@@ -0,0 +1,437 @@
|
||||
// filePreview.js
|
||||
import { escapeHTML, showToast } from './domUtils.js';
|
||||
import { fileData } from './fileListView.js';
|
||||
import { t } from './i18n.js';
|
||||
|
||||
export function openShareModal(file, folder) {
|
||||
const existing = document.getElementById("shareModal");
|
||||
if (existing) existing.remove();
|
||||
|
||||
const modal = document.createElement("div");
|
||||
modal.id = "shareModal";
|
||||
modal.classList.add("modal");
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content share-modal-content" style="width: 600px; max-width:90vw;">
|
||||
<div class="modal-header">
|
||||
<h3>${t("share_file")}: ${escapeHTML(file.name)}</h3>
|
||||
<span class="close-image-modal" id="closeShareModal" title="Close">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>${t("set_expiration")}</p>
|
||||
<select id="shareExpiration">
|
||||
<option value="30">30 minutes</option>
|
||||
<option value="60" selected>60 minutes</option>
|
||||
<option value="120">120 minutes</option>
|
||||
<option value="180">180 minutes</option>
|
||||
<option value="240">240 minutes</option>
|
||||
<option value="1440">1 Day</option>
|
||||
</select>
|
||||
<p>${t("password_optional")}</p>
|
||||
<input type="text" id="sharePassword" placeholder=${t("password_optional")} style="width: 100%;"/>
|
||||
<br>
|
||||
<button id="generateShareLinkBtn" class="btn btn-primary" style="margin-top:10px;">${t("generate_share_link")}</button>
|
||||
<div id="shareLinkDisplay" style="margin-top: 10px; display:none;">
|
||||
<p>${t("shareable_link")}</p>
|
||||
<input type="text" id="shareLinkInput" readonly style="width:100%;"/>
|
||||
<button id="copyShareLinkBtn" class="btn btn-primary" style="margin-top:5px;">${t("copy_link")}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(modal);
|
||||
modal.style.display = "block";
|
||||
|
||||
document.getElementById("closeShareModal").addEventListener("click", () => {
|
||||
modal.remove();
|
||||
});
|
||||
|
||||
document.getElementById("generateShareLinkBtn").addEventListener("click", () => {
|
||||
const expiration = document.getElementById("shareExpiration").value;
|
||||
const password = document.getElementById("sharePassword").value;
|
||||
fetch("api/file/createShareLink.php", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-CSRF-Token": window.csrfToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
folder: folder,
|
||||
file: file.name,
|
||||
expirationMinutes: parseInt(expiration),
|
||||
password: password
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.token) {
|
||||
let shareEndpoint = document.querySelector('meta[name="share-url"]')
|
||||
? document.querySelector('meta[name="share-url"]').getAttribute('content')
|
||||
: (window.SHARE_URL || "api/file/share.php");
|
||||
const shareUrl = `${shareEndpoint}?token=${encodeURIComponent(data.token)}`;
|
||||
const displayDiv = document.getElementById("shareLinkDisplay");
|
||||
const inputField = document.getElementById("shareLinkInput");
|
||||
inputField.value = shareUrl;
|
||||
displayDiv.style.display = "block";
|
||||
} else {
|
||||
showToast("Error generating share link: " + (data.error || "Unknown error"));
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Error generating share link:", err);
|
||||
showToast("Error generating share link.");
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("copyShareLinkBtn").addEventListener("click", () => {
|
||||
const input = document.getElementById("shareLinkInput");
|
||||
input.select();
|
||||
document.execCommand("copy");
|
||||
showToast("Link copied to clipboard!");
|
||||
});
|
||||
}
|
||||
|
||||
export function previewFile(fileUrl, fileName) {
|
||||
let modal = document.getElementById("filePreviewModal");
|
||||
if (!modal) {
|
||||
modal = document.createElement("div");
|
||||
modal.id = "filePreviewModal";
|
||||
Object.assign(modal.style, {
|
||||
position: "fixed",
|
||||
top: "0",
|
||||
left: "0",
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
backgroundColor: "rgba(0,0,0,0.7)",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
zIndex: "1000"
|
||||
});
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content image-preview-modal-content" style="position: relative; max-width: 90vw; max-height: 90vh;">
|
||||
<span id="closeFileModal" class="close-image-modal" style="position: absolute; top: 10px; right: 10px; font-size: 24px; cursor: pointer;">×</span>
|
||||
<h4 class="image-modal-header"></h4>
|
||||
<div class="file-preview-container" style="position: relative; text-align: center;"></div>
|
||||
</div>`;
|
||||
document.body.appendChild(modal);
|
||||
|
||||
function closeModal() {
|
||||
const mediaElements = modal.querySelectorAll("video, audio");
|
||||
mediaElements.forEach(media => {
|
||||
media.pause();
|
||||
if (media.tagName.toLowerCase() !== 'video') {
|
||||
try { media.currentTime = 0; } catch (e) { }
|
||||
}
|
||||
});
|
||||
modal.remove();
|
||||
}
|
||||
|
||||
document.getElementById("closeFileModal").addEventListener("click", closeModal);
|
||||
modal.addEventListener("click", function (e) {
|
||||
if (e.target === modal) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
modal.querySelector("h4").textContent = fileName;
|
||||
const container = modal.querySelector(".file-preview-container");
|
||||
container.innerHTML = "";
|
||||
|
||||
const extension = fileName.split('.').pop().toLowerCase();
|
||||
const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(fileName);
|
||||
if (isImage) {
|
||||
// Create the image element with default transform data.
|
||||
const img = document.createElement("img");
|
||||
img.src = fileUrl;
|
||||
img.className = "image-modal-img";
|
||||
img.style.maxWidth = "80vw";
|
||||
img.style.maxHeight = "80vh";
|
||||
img.style.transition = "transform 0.3s ease";
|
||||
img.dataset.scale = 1;
|
||||
img.dataset.rotate = 0;
|
||||
img.style.position = 'relative';
|
||||
img.style.zIndex = '1';
|
||||
|
||||
// Filter gallery images for navigation.
|
||||
const images = fileData.filter(file => /\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(file.name));
|
||||
|
||||
// Create a flex wrapper to hold left panel, center image, and right panel.
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'image-wrapper';
|
||||
wrapper.style.display = 'flex';
|
||||
wrapper.style.alignItems = 'center';
|
||||
wrapper.style.justifyContent = 'center';
|
||||
wrapper.style.position = 'relative';
|
||||
|
||||
// --- Left Panel: Contains Zoom controls (top) and Prev button (bottom) ---
|
||||
const leftPanel = document.createElement('div');
|
||||
leftPanel.className = 'left-panel';
|
||||
leftPanel.style.display = 'flex';
|
||||
leftPanel.style.flexDirection = 'column';
|
||||
leftPanel.style.justifyContent = 'space-between';
|
||||
leftPanel.style.alignItems = 'center';
|
||||
leftPanel.style.width = '60px';
|
||||
leftPanel.style.height = '100%';
|
||||
leftPanel.style.zIndex = '10';
|
||||
|
||||
// Top container for zoom buttons.
|
||||
const leftTop = document.createElement('div');
|
||||
leftTop.style.display = 'flex';
|
||||
leftTop.style.flexDirection = 'column';
|
||||
leftTop.style.gap = '4px';
|
||||
// Zoom In button.
|
||||
const zoomInBtn = document.createElement('button');
|
||||
zoomInBtn.className = 'material-icons zoom_in';
|
||||
zoomInBtn.title = 'Zoom In';
|
||||
zoomInBtn.style.background = 'transparent';
|
||||
zoomInBtn.style.border = 'none';
|
||||
zoomInBtn.style.cursor = 'pointer';
|
||||
zoomInBtn.textContent = 'zoom_in';
|
||||
// Zoom Out button.
|
||||
const zoomOutBtn = document.createElement('button');
|
||||
zoomOutBtn.className = 'material-icons zoom_out';
|
||||
zoomOutBtn.title = 'Zoom Out';
|
||||
zoomOutBtn.style.background = 'transparent';
|
||||
zoomOutBtn.style.border = 'none';
|
||||
zoomOutBtn.style.cursor = 'pointer';
|
||||
zoomOutBtn.textContent = 'zoom_out';
|
||||
leftTop.appendChild(zoomInBtn);
|
||||
leftTop.appendChild(zoomOutBtn);
|
||||
leftPanel.appendChild(leftTop);
|
||||
|
||||
// Bottom container for prev button.
|
||||
const leftBottom = document.createElement('div');
|
||||
leftBottom.style.display = 'flex';
|
||||
leftBottom.style.justifyContent = 'center';
|
||||
leftBottom.style.alignItems = 'center';
|
||||
leftBottom.style.width = '100%';
|
||||
if (images.length > 1) {
|
||||
const prevBtn = document.createElement("button");
|
||||
prevBtn.textContent = "‹";
|
||||
prevBtn.className = "gallery-nav-btn";
|
||||
prevBtn.style.background = 'transparent';
|
||||
prevBtn.style.border = 'none';
|
||||
prevBtn.style.color = 'white';
|
||||
prevBtn.style.fontSize = '48px';
|
||||
prevBtn.style.cursor = 'pointer';
|
||||
prevBtn.addEventListener("click", function (e) {
|
||||
e.stopPropagation();
|
||||
// Safety check:
|
||||
if (!modal.galleryImages || modal.galleryImages.length === 0) return;
|
||||
modal.galleryCurrentIndex = (modal.galleryCurrentIndex - 1 + modal.galleryImages.length) % modal.galleryImages.length;
|
||||
let newFile = modal.galleryImages[modal.galleryCurrentIndex];
|
||||
modal.querySelector("h4").textContent = newFile.name;
|
||||
img.src = ((window.currentFolder === "root")
|
||||
? "uploads/"
|
||||
: "uploads/" + window.currentFolder.split("/").map(encodeURIComponent).join("/") + "/")
|
||||
+ encodeURIComponent(newFile.name) + "?t=" + new Date().getTime();
|
||||
// Reset transforms.
|
||||
img.dataset.scale = 1;
|
||||
img.dataset.rotate = 0;
|
||||
img.style.transform = 'scale(1) rotate(0deg)';
|
||||
});
|
||||
leftBottom.appendChild(prevBtn);
|
||||
} else {
|
||||
// Insert an empty placeholder for consistent layout.
|
||||
leftBottom.innerHTML = ' ';
|
||||
}
|
||||
leftPanel.appendChild(leftBottom);
|
||||
|
||||
// --- Center Panel: Contains the image ---
|
||||
const centerPanel = document.createElement('div');
|
||||
centerPanel.className = 'center-image-container';
|
||||
centerPanel.style.flexGrow = '1';
|
||||
centerPanel.style.textAlign = 'center';
|
||||
centerPanel.style.position = 'relative';
|
||||
centerPanel.style.zIndex = '1';
|
||||
centerPanel.appendChild(img);
|
||||
|
||||
// --- Right Panel: Contains Rotate controls (top) and Next button (bottom) ---
|
||||
const rightPanel = document.createElement('div');
|
||||
rightPanel.className = 'right-panel';
|
||||
rightPanel.style.display = 'flex';
|
||||
rightPanel.style.flexDirection = 'column';
|
||||
rightPanel.style.justifyContent = 'space-between';
|
||||
rightPanel.style.alignItems = 'center';
|
||||
rightPanel.style.width = '60px';
|
||||
rightPanel.style.height = '100%';
|
||||
rightPanel.style.zIndex = '10';
|
||||
|
||||
// Top container for rotate buttons.
|
||||
const rightTop = document.createElement('div');
|
||||
rightTop.style.display = 'flex';
|
||||
rightTop.style.flexDirection = 'column';
|
||||
rightTop.style.gap = '4px';
|
||||
// Rotate Left button.
|
||||
const rotateLeftBtn = document.createElement('button');
|
||||
rotateLeftBtn.className = 'material-icons rotate_left';
|
||||
rotateLeftBtn.title = 'Rotate Left';
|
||||
rotateLeftBtn.style.background = 'transparent';
|
||||
rotateLeftBtn.style.border = 'none';
|
||||
rotateLeftBtn.style.cursor = 'pointer';
|
||||
rotateLeftBtn.textContent = 'rotate_left';
|
||||
// Rotate Right button.
|
||||
const rotateRightBtn = document.createElement('button');
|
||||
rotateRightBtn.className = 'material-icons rotate_right';
|
||||
rotateRightBtn.title = 'Rotate Right';
|
||||
rotateRightBtn.style.background = 'transparent';
|
||||
rotateRightBtn.style.border = 'none';
|
||||
rotateRightBtn.style.cursor = 'pointer';
|
||||
rotateRightBtn.textContent = 'rotate_right';
|
||||
rightTop.appendChild(rotateLeftBtn);
|
||||
rightTop.appendChild(rotateRightBtn);
|
||||
rightPanel.appendChild(rightTop);
|
||||
|
||||
// Bottom container for next button.
|
||||
const rightBottom = document.createElement('div');
|
||||
rightBottom.style.display = 'flex';
|
||||
rightBottom.style.justifyContent = 'center';
|
||||
rightBottom.style.alignItems = 'center';
|
||||
rightBottom.style.width = '100%';
|
||||
if (images.length > 1) {
|
||||
const nextBtn = document.createElement("button");
|
||||
nextBtn.textContent = "›";
|
||||
nextBtn.className = "gallery-nav-btn";
|
||||
nextBtn.style.background = 'transparent';
|
||||
nextBtn.style.border = 'none';
|
||||
nextBtn.style.color = 'white';
|
||||
nextBtn.style.fontSize = '48px';
|
||||
nextBtn.style.cursor = 'pointer';
|
||||
nextBtn.addEventListener("click", function (e) {
|
||||
e.stopPropagation();
|
||||
// Safety check:
|
||||
if (!modal.galleryImages || modal.galleryImages.length === 0) return;
|
||||
modal.galleryCurrentIndex = (modal.galleryCurrentIndex + 1) % modal.galleryImages.length;
|
||||
let newFile = modal.galleryImages[modal.galleryCurrentIndex];
|
||||
modal.querySelector("h4").textContent = newFile.name;
|
||||
img.src = ((window.currentFolder === "root")
|
||||
? "uploads/"
|
||||
: "uploads/" + window.currentFolder.split("/").map(encodeURIComponent).join("/") + "/")
|
||||
+ encodeURIComponent(newFile.name) + "?t=" + new Date().getTime();
|
||||
// Reset transforms.
|
||||
img.dataset.scale = 1;
|
||||
img.dataset.rotate = 0;
|
||||
img.style.transform = 'scale(1) rotate(0deg)';
|
||||
});
|
||||
rightBottom.appendChild(nextBtn);
|
||||
} else {
|
||||
// Insert a placeholder so that center remains properly aligned.
|
||||
rightBottom.innerHTML = ' ';
|
||||
}
|
||||
rightPanel.appendChild(rightBottom);
|
||||
|
||||
// Assemble panels into the wrapper.
|
||||
wrapper.appendChild(leftPanel);
|
||||
wrapper.appendChild(centerPanel);
|
||||
wrapper.appendChild(rightPanel);
|
||||
container.appendChild(wrapper);
|
||||
|
||||
// --- Set up zoom controls event listeners ---
|
||||
zoomInBtn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
let scale = parseFloat(img.dataset.scale) || 1;
|
||||
scale += 0.1;
|
||||
img.dataset.scale = scale;
|
||||
img.style.transform = 'scale(' + scale + ') rotate(' + img.dataset.rotate + 'deg)';
|
||||
});
|
||||
zoomOutBtn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
let scale = parseFloat(img.dataset.scale) || 1;
|
||||
scale = Math.max(0.1, scale - 0.1);
|
||||
img.dataset.scale = scale;
|
||||
img.style.transform = 'scale(' + scale + ') rotate(' + img.dataset.rotate + 'deg)';
|
||||
});
|
||||
|
||||
// Attach rotation control listeners (always present now).
|
||||
rotateLeftBtn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
let rotate = parseFloat(img.dataset.rotate) || 0;
|
||||
rotate = (rotate - 90 + 360) % 360;
|
||||
img.dataset.rotate = rotate;
|
||||
img.style.transform = 'scale(' + img.dataset.scale + ') rotate(' + rotate + 'deg)';
|
||||
});
|
||||
rotateRightBtn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
let rotate = parseFloat(img.dataset.rotate) || 0;
|
||||
rotate = (rotate + 90) % 360;
|
||||
img.dataset.rotate = rotate;
|
||||
img.style.transform = 'scale(' + img.dataset.scale + ') rotate(' + rotate + 'deg)';
|
||||
});
|
||||
|
||||
// Save gallery details if there is more than one image.
|
||||
if (images.length > 1) {
|
||||
modal.galleryImages = images;
|
||||
modal.galleryCurrentIndex = images.findIndex(f => f.name === fileName);
|
||||
}
|
||||
} else {
|
||||
// Handle non-image file previews.
|
||||
if (extension === "pdf") {
|
||||
const embed = document.createElement("embed");
|
||||
const separator = fileUrl.indexOf('?') === -1 ? '?' : '&';
|
||||
embed.src = fileUrl + separator + 't=' + new Date().getTime();
|
||||
embed.type = "application/pdf";
|
||||
embed.style.width = "80vw";
|
||||
embed.style.height = "80vh";
|
||||
embed.style.border = "none";
|
||||
container.appendChild(embed);
|
||||
} else if (/\.(mp4|mkv|webm|mov|ogv)$/i.test(fileName)) {
|
||||
const video = document.createElement("video");
|
||||
video.src = fileUrl;
|
||||
video.controls = true;
|
||||
video.className = "image-modal-img";
|
||||
|
||||
const progressKey = 'videoProgress-' + fileUrl;
|
||||
video.addEventListener("loadedmetadata", () => {
|
||||
const savedTime = localStorage.getItem(progressKey);
|
||||
if (savedTime) {
|
||||
video.currentTime = parseFloat(savedTime);
|
||||
}
|
||||
});
|
||||
video.addEventListener("timeupdate", () => {
|
||||
localStorage.setItem(progressKey, video.currentTime);
|
||||
});
|
||||
video.addEventListener("ended", () => {
|
||||
localStorage.removeItem(progressKey);
|
||||
});
|
||||
container.appendChild(video);
|
||||
} else if (/\.(mp3|wav|m4a|ogg|flac|aac|wma|opus)$/i.test(fileName)) {
|
||||
const audio = document.createElement("audio");
|
||||
audio.src = fileUrl;
|
||||
audio.controls = true;
|
||||
audio.className = "audio-modal";
|
||||
audio.style.maxWidth = "80vw";
|
||||
container.appendChild(audio);
|
||||
} else {
|
||||
container.textContent = "Preview not available for this file type.";
|
||||
}
|
||||
}
|
||||
modal.style.display = "flex";
|
||||
}
|
||||
|
||||
// Preserve original functionality.
|
||||
export function displayFilePreview(file, container) {
|
||||
const actualFile = file.file || file;
|
||||
if (!(actualFile instanceof File)) {
|
||||
console.error("displayFilePreview called with an invalid file object");
|
||||
return;
|
||||
}
|
||||
container.style.display = "inline-block";
|
||||
while (container.firstChild) {
|
||||
container.removeChild(container.firstChild);
|
||||
}
|
||||
if (/\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(actualFile.name)) {
|
||||
const img = document.createElement("img");
|
||||
img.src = URL.createObjectURL(actualFile);
|
||||
img.classList.add("file-preview-img");
|
||||
container.appendChild(img);
|
||||
} else {
|
||||
const iconSpan = document.createElement("span");
|
||||
iconSpan.classList.add("material-icons", "file-icon");
|
||||
iconSpan.textContent = "insert_drive_file";
|
||||
container.appendChild(iconSpan);
|
||||
}
|
||||
}
|
||||
|
||||
window.previewFile = previewFile;
|
||||
window.openShareModal = openShareModal;
|
||||
Reference in New Issue
Block a user