@page
@model FolderModel
@{
ViewData["Title"] = Model.Folder?.Title ?? "Folder";
}
<div class="mt-3">
<div class="d-flex flex-wrap gap-2 align-items-center mb-3">
<div class="d-flex flex-wrap gap-2 align-items-center flex-grow-1">
<h2 class="h5 mb-0">@Model.Folder?.Title</h2>
@if (Model.Folder is not null)
{
<span class="text-muted small">@Model.Folder.Date.ToString("yyyy-MM-dd") · @Model.Folder.Author · @((Model.Folder.FileCount ?? 0).ToString()) files</span>
}
@if (Model.Keywords.Count > 0)
{
<div class="d-flex flex-wrap gap-1">
@foreach (var tag in Model.Keywords)
{
<a class="badge text-bg-light text-decoration-none" href="@Url.Page("Index", null, new { keyword = tag })">@tag</a>
}
</div>
}
</div>
<a class="btn btn-outline-secondary" href="@Model.BackUrl">Back</a>
</div>
@if (Model.Folder is null)
{
<div class="alert alert-secondary">Folder not found.</div>
}
else if (Model.Images.Count == 0)
{
<div class="alert alert-secondary">No images found.</div>
}
else
{
<div id="thumbs" class="row g-3">
@foreach (var image in Model.Images)
{
<div class="col-6 col-md-4 col-lg-3">
<button class="card text-start border-0 shadow-sm w-100"
type="button"
data-thumb="true"
data-url="@image.Url"
data-name="@image.FileName">
<img class="card-img-top" src="@image.ThumbnailUrl" alt="@image.FileName" loading="lazy" style="aspect-ratio: 1 / 1; object-fit: cover;" />
</button>
</div>
}
</div>
}
</div>
<div class="modal fade" id="folderImageModal" tabindex="-1" aria-labelledby="folderImageModalLabel" aria-hidden="true">
<div class="modal-dialog modal-fullscreen modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="folderImageModalLabel">Image</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<img id="folderModalImage" class="img-fluid d-block mx-auto" alt="image" />
</div>
</div>
</div>
</div>
@section Scripts {
<script>
const modalEl = document.getElementById("folderImageModal");
const modalImage = document.getElementById("folderModalImage");
const modalTitle = document.getElementById("folderImageModalLabel");
const modalBody = modalEl?.querySelector(".modal-body");
const modal = new bootstrap.Modal(modalEl);
const thumbs = Array.from(document.querySelectorAll("[data-thumb='true']"));
let currentIndex = 0;
function setModalByIndex(index) {
if (thumbs.length === 0) {
return;
}
const clamped = Math.max(0, Math.min(index, thumbs.length - 1));
const target = thumbs[clamped];
const url = target.dataset.url;
const name = target.dataset.name || "image";
if (url) {
modalImage.src = url;
modalImage.alt = name;
modalTitle.textContent = name;
currentIndex = clamped;
}
}
thumbs.forEach((button, index) => {
button.addEventListener("click", () => {
currentIndex = index;
setModalByIndex(currentIndex);
modal.show();
});
});
document.addEventListener("keydown", event => {
if (!modalEl?.classList.contains("show")) {
return;
}
if (event.key === "ArrowLeft") {
event.preventDefault();
setModalByIndex(currentIndex - 1);
} else if (event.key === "ArrowRight") {
event.preventDefault();
setModalByIndex(currentIndex + 1);
}
});
if (modalBody) {
let touchStartX = 0;
let touchStartY = 0;
modalBody.addEventListener("touchstart", event => {
const touch = event.touches[0];
if (!touch) {
return;
}
touchStartX = touch.clientX;
touchStartY = touch.clientY;
}, { passive: true });
modalBody.addEventListener("touchend", event => {
const touch = event.changedTouches[0];
if (!touch) {
return;
}
const dx = touch.clientX - touchStartX;
const dy = touch.clientY - touchStartY;
if (Math.abs(dx) < 40 || Math.abs(dx) < Math.abs(dy)) {
return;
}
if (dx > 0) {
setModalByIndex(currentIndex - 1);
} else {
setModalByIndex(currentIndex + 1);
}
}, { passive: true });
}
</script>
}