bethaus-app/static/gallery.js
2025-03-23 18:07:56 +01:00

278 lines
8.6 KiB
JavaScript

let isPinching = false;
let pinchZoomOccurred = false;
let initialPinchDistance = null;
let initialScale = 1;
let currentScale = 1;
// Assume that currentGalleryImages is already declared in app.js.
// Only declare currentGalleryIndex if it isn't defined already.
if (typeof currentGalleryIndex === "undefined") {
var currentGalleryIndex = -1;
}
function openGalleryModal(relUrl) {
document.body.style.overflow = 'hidden'; // Disable background scrolling.
currentGalleryIndex = currentGalleryImages.indexOf(relUrl);
showGalleryImage(relUrl);
document.getElementById('gallery-modal').style.display = 'flex';
}
function showGalleryImage(relUrl) {
const fullUrl = '/media/' + relUrl;
const modal = document.getElementById('gallery-modal');
const currentImage = document.getElementById('gallery-modal-content');
const loader = document.getElementById('loader-container');
const closeBtn = document.getElementById('gallery-close');
// Set a timeout for showing the loader after 500ms.
let loaderTimeout = setTimeout(() => {
loader.style.display = 'flex';
}, 500);
// Preload the new image.
const tempImg = new Image();
tempImg.src = fullUrl;
tempImg.onload = function () {
clearTimeout(loaderTimeout);
loader.style.display = 'none';
// Select all current images in the gallery modal
const existingImages = document.querySelectorAll('#gallery-modal img');
// Create a new image element for the transition
const newImage = document.createElement('img');
newImage.src = fullUrl;
newImage.style.position = 'absolute';
newImage.style.top = '50%'; // Center vertically
newImage.style.left = '50%'; // Center horizontally
newImage.style.transform = 'translate(-50%, -50%)'; // Ensure centering
newImage.style.maxWidth = '95vw'; // Prevent overflow on large screens
newImage.style.maxHeight = '95vh'; // Prevent overflow on tall screens
newImage.style.transition = 'opacity 0.5s ease-in-out';
newImage.style.opacity = 0; // Start fully transparent
newImage.id = 'gallery-modal-content';
// Add pinch-to-zoom event listeners
newImage.addEventListener('touchstart', handlePinchStart, { passive: false });
newImage.addEventListener('touchmove', handlePinchMove, { passive: false });
newImage.addEventListener('touchend', handlePinchEnd, { passive: false });
newImage.addEventListener('click', handleGalleryImageClick);
// Reset pinch variables for the new image
initialPinchDistance = null;
initialScale = 1;
currentScale = 1;
pinchZoomOccurred = false;
// Append new image on top of existing ones
modal.appendChild(newImage);
// Fade out all old images and fade in the new one
setTimeout(() => {
existingImages.forEach(img => {
img.style.opacity = 0; // Apply fade-out
});
newImage.style.opacity = 1; // Fade in the new image
}, 10);
// Wait for fade-out to finish, then remove all old images
setTimeout(() => {
existingImages.forEach(img => {
if (img.parentNode) {
img.parentNode.removeChild(img); // Remove old images
}
});
}, 510); // 500ms for fade-out + small buffer
// Preload the next image
preloadNextImage();
};
tempImg.onerror = function() {
clearTimeout(loaderTimeout);
loader.style.display = 'none';
console.error('Error loading image:', fullUrl);
};
}
function preloadNextImage() {
if (currentGalleryIndex < currentGalleryImages.length - 1) {
const nextImage = new Image();
nextImage.src = '/media/' + currentGalleryImages[currentGalleryIndex + 1];
}
}
function closeGalleryModal() {
document.getElementById('gallery-modal').style.display = 'none';
// remove all images
const existingImages = document.querySelectorAll('#gallery-modal img');
existingImages.forEach(img => {
if (img.parentNode) {
img.parentNode.removeChild(img);
}
});
// Restore scrolling.
document.body.style.overflow = '';
}
function handleGalleryImageClick(e) {
// If a pinch zoom occurred, don't change the image.
if (pinchZoomOccurred) {
pinchZoomOccurred = false; // Reset the flag for the next gesture.
e.stopPropagation();
return;
}
if (e.target.classList.contains('gallery-next') || e.target.classList.contains('gallery-prev')) {
return; // Prevent conflict with navigation buttons.
}
const imgRect = this.getBoundingClientRect();
if (e.clientX < imgRect.left + imgRect.width / 2) {
if (currentGalleryIndex > 0) {
currentGalleryIndex--;
showGalleryImage(currentGalleryImages[currentGalleryIndex]);
}
} else {
if (currentGalleryIndex < currentGalleryImages.length - 1) {
currentGalleryIndex++;
showGalleryImage(currentGalleryImages[currentGalleryIndex]);
}
}
}
function initGallerySwipe() {
let touchStartX = 0;
let touchEndX = 0;
const galleryModal = document.getElementById('gallery-modal');
galleryModal.addEventListener('touchstart', function(e) {
// If more than one finger is on screen, ignore swipe tracking.
if (e.touches.length > 1) {
return;
}
touchStartX = e.changedTouches[0].screenX;
}, false);
galleryModal.addEventListener('touchend', function(e) {
// If the event was part of a multi-touch (pinch) gesture or a pinch was detected, skip swipe.
if (e.changedTouches.length > 1 || isPinching || pinchZoomOccurred) {
return;
}
touchEndX = e.changedTouches[0].screenX;
handleSwipe();
}, false);
function handleSwipe() {
const deltaX = touchEndX - touchStartX;
if (Math.abs(deltaX) > 50) { // Swipe threshold.
if (deltaX > 0) { // Swipe right: previous image.
if (currentGalleryIndex > 0) {
currentGalleryIndex--;
showGalleryImage(currentGalleryImages[currentGalleryIndex]);
}
} else { // Swipe left: next image.
if (currentGalleryIndex < currentGalleryImages.length - 1) {
currentGalleryIndex++;
showGalleryImage(currentGalleryImages[currentGalleryIndex]);
}
}
}
}
}
function handlePinchStart(e) {
if (e.touches.length === 2) {
e.preventDefault();
isPinching = true;
pinchZoomOccurred = false; // Reset at the start of a pinch gesture.
const dx = e.touches[0].pageX - e.touches[1].pageX;
const dy = e.touches[0].pageY - e.touches[1].pageY;
initialPinchDistance = Math.sqrt(dx * dx + dy * dy);
}
}
function handlePinchMove(e) {
if (e.touches.length === 2 && initialPinchDistance) {
e.preventDefault();
const dx = e.touches[0].pageX - e.touches[1].pageX;
const dy = e.touches[0].pageY - e.touches[1].pageY;
const currentDistance = Math.sqrt(dx * dx + dy * dy);
let scale = currentDistance / initialPinchDistance;
scale = Math.max(0.5, Math.min(scale * initialScale, 3));
// If the scale has changed enough, mark that a pinch zoom occurred.
if (Math.abs(scale - initialScale) > 0.05) {
pinchZoomOccurred = true;
}
currentScale = scale;
e.currentTarget.style.transform = `translate(-50%, -50%) scale(${scale})`;
}
}
function handlePinchEnd(e) {
if (e.touches.length < 2) {
initialScale = currentScale;
initialPinchDistance = null;
isPinching = false;
}
}
function initGalleryControls() {
const nextButton = document.querySelector('.gallery-next');
const prevButton = document.querySelector('.gallery-prev');
const closeButton = document.getElementById('gallery-close');
if (nextButton) {
nextButton.addEventListener('click', function () {
if (currentGalleryIndex < currentGalleryImages.length - 1) {
currentGalleryIndex++;
showGalleryImage(currentGalleryImages[currentGalleryIndex]);
}
});
}
if (prevButton) {
prevButton.addEventListener('click', function () {
if (currentGalleryIndex > 0) {
currentGalleryIndex--;
showGalleryImage(currentGalleryImages[currentGalleryIndex]);
}
});
}
if (closeButton) {
closeButton.addEventListener('click', closeGalleryModal);
}
}
function initGallery() {
initGallerySwipe();
initGalleryControls();
// Close modal when clicking outside the image.
const galleryModal = document.getElementById('gallery-modal');
galleryModal.addEventListener('click', function(e) {
if (e.target === galleryModal) {
closeGalleryModal();
}
});
}
// Initialize the gallery once the DOM content is loaded.
if (document.readyState === "loading") {
document.addEventListener('DOMContentLoaded', initGallery);
} else {
initGallery();
}