Files
2026-03-01 13:01:46 +00:00

198 lines
7.5 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends 'base.html' %}
{% block content %}
<section class="card">
<h1>{{ t('gallery') }}</h1>
{% if images %}
<div class="gallery-grid">
{% for image in images %}
<figure class="gallery-item gallery-card">
<div class="gallery-media">
<a
href="{{ url_for('serve_upload', filename=image['filename']) }}"
class="gallery-open"
data-image-index="{{ loop.index0 }}"
>
<img src="{{ url_for('serve_upload', filename=image['filename']) }}" alt="{{ t('gallery_image_alt').format(name=image['uploaded_by_name']) }}" loading="lazy" />
</a>
{% if is_host or guest_id == image['uploaded_by'] %}
<form class="gallery-delete-form" method="post" action="{{ url_for('delete_image', image_id=image['id']) }}">
<button class="gallery-delete-btn" type="submit" aria-label="{{ t('delete') }}" title="{{ t('delete') }}">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M9 3h6l1 2h4v2H4V5h4l1-2zm1 6h2v8h-2V9zm4 0h2v8h-2V9zM7 9h2v8H7V9z" />
</svg>
</button>
</form>
{% endif %}
</div>
<figcaption>{{ t('gallery_uploaded_by').format(name=image['uploaded_by_name']) }}</figcaption>
</figure>
{% endfor %}
</div>
<div class="lightbox" id="gallery-lightbox" aria-hidden="true">
<button class="lightbox-close" type="button" aria-label="Close">×</button>
<div class="lightbox-counter" id="lightbox-counter">1 / 1</div>
<a class="lightbox-download" id="lightbox-download" href="#" aria-label="{{ t('download') }}" title="{{ t('download') }}">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M12 3a1 1 0 0 1 1 1v8.59l2.3-2.29a1 1 0 1 1 1.4 1.41l-4 4a1 1 0 0 1-1.4 0l-4-4a1 1 0 1 1 1.4-1.41L11 12.59V4a1 1 0 0 1 1-1zm-7 14a1 1 0 0 1 1 1v1h12v-1a1 1 0 1 1 2 0v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1z"/>
</svg>
</a>
<button class="lightbox-nav lightbox-prev" type="button" aria-label="Previous image"></button>
<img class="lightbox-image" id="lightbox-image" alt="" />
<button class="lightbox-nav lightbox-next" type="button" aria-label="Next image"></button>
</div>
<script>
(() => {
const lightbox = document.getElementById("gallery-lightbox");
const lightboxImage = document.getElementById("lightbox-image");
const lightboxDownload = document.getElementById("lightbox-download");
const lightboxCounter = document.getElementById("lightbox-counter");
const closeBtn = lightbox.querySelector(".lightbox-close");
const prevBtn = lightbox.querySelector(".lightbox-prev");
const nextBtn = lightbox.querySelector(".lightbox-next");
const openButtons = Array.from(document.querySelectorAll(".gallery-open"));
const images = [
{% for image in images %}
{
src: {{ url_for('serve_upload', filename=image['filename'])|tojson }},
download: {{ url_for('serve_upload', filename=image['filename'], download=1)|tojson }},
alt: {{ t('gallery_image_alt').format(name=image['uploaded_by_name'])|tojson }}
}{% if not loop.last %},{% endif %}
{% endfor %}
];
let currentIndex = 0;
let touchStartX = 0;
let touchStartY = 0;
let didSwipe = false;
let controlsTimer = null;
const scheduleHideControls = () => {
clearTimeout(controlsTimer);
controlsTimer = window.setTimeout(() => {
lightbox.classList.add("lightbox-controls-hidden");
}, 3000);
};
const showControlsTemporarily = () => {
lightbox.classList.remove("lightbox-controls-hidden");
scheduleHideControls();
};
const setImage = (index, options = {}) => {
const { revealControls = true } = options;
if (!images.length) {
return;
}
currentIndex = (index + images.length) % images.length;
const item = images[currentIndex];
lightboxImage.classList.remove("is-fading");
void lightboxImage.offsetWidth;
lightboxImage.classList.add("is-fading");
lightboxImage.src = item.src;
lightboxImage.alt = item.alt;
lightboxDownload.href = item.download;
lightboxCounter.textContent = `${currentIndex + 1} / ${images.length}`;
if (revealControls) {
showControlsTemporarily();
}
};
const openLightbox = (index) => {
setImage(index);
lightbox.classList.add("is-open");
lightbox.setAttribute("aria-hidden", "false");
document.body.classList.add("no-scroll");
showControlsTemporarily();
};
const closeLightbox = () => {
lightbox.classList.remove("is-open");
lightbox.setAttribute("aria-hidden", "true");
document.body.classList.remove("no-scroll");
lightbox.classList.remove("lightbox-controls-hidden");
clearTimeout(controlsTimer);
};
openButtons.forEach((link) => {
link.addEventListener("click", (event) => {
event.preventDefault();
openLightbox(Number(link.dataset.imageIndex || 0));
});
});
prevBtn.addEventListener("click", () => setImage(currentIndex - 1, { revealControls: true }));
nextBtn.addEventListener("click", () => setImage(currentIndex + 1, { revealControls: true }));
closeBtn.addEventListener("click", closeLightbox);
lightbox.addEventListener("mousemove", () => {
if (lightbox.classList.contains("is-open")) {
showControlsTemporarily();
}
});
lightbox.addEventListener("click", (event) => {
if (event.target === lightbox) {
closeLightbox();
} else if (
lightbox.classList.contains("is-open") &&
event.target.closest(".lightbox-close, .lightbox-download, .lightbox-nav")
) {
showControlsTemporarily();
}
});
lightboxImage.addEventListener("touchstart", (event) => {
const point = event.changedTouches[0];
touchStartX = point.clientX;
touchStartY = point.clientY;
didSwipe = false;
}, { passive: true });
lightboxImage.addEventListener("touchend", (event) => {
const point = event.changedTouches[0];
const deltaX = point.clientX - touchStartX;
const deltaY = point.clientY - touchStartY;
const absX = Math.abs(deltaX);
const absY = Math.abs(deltaY);
if (absX < 40 || absX <= absY) {
return;
}
didSwipe = true;
if (deltaX < 0) {
setImage(currentIndex + 1, { revealControls: false });
} else {
setImage(currentIndex - 1, { revealControls: false });
}
}, { passive: true });
lightboxImage.addEventListener("click", (event) => {
if (didSwipe) {
event.preventDefault();
event.stopPropagation();
didSwipe = false;
}
});
document.addEventListener("keydown", (event) => {
if (!lightbox.classList.contains("is-open")) {
return;
}
showControlsTemporarily();
if (event.key === "Escape") {
closeLightbox();
} else if (event.key === "ArrowLeft") {
setImage(currentIndex - 1, { revealControls: true });
} else if (event.key === "ArrowRight") {
setImage(currentIndex + 1, { revealControls: true });
}
});
})();
</script>
{% else %}
<p>{{ t('gallery_empty') }}</p>
{% endif %}
</section>
{% endblock %}