update elements to tailwindcss ui

This commit is contained in:
2024-08-29 20:18:25 -05:00
parent 67af774f47
commit 26c19849ad
12 changed files with 291 additions and 130 deletions

29
web/package-lock.json generated
View File

@@ -9,6 +9,8 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.8",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.41",
"tailwindcss": "^3.4.10",
@@ -135,6 +137,25 @@
"node": ">=14"
}
},
"node_modules/@tailwindcss/aspect-ratio": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@tailwindcss/aspect-ratio/-/aspect-ratio-0.4.2.tgz",
"integrity": "sha512-8QPrypskfBa7QIMuKHg2TA7BqES6vhBrDLOv8Unb6FcFyd3TjKbc6lcmb9UPQHxfl24sXoJ41ux/H7qQQvfaSQ==",
"peerDependencies": {
"tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1"
}
},
"node_modules/@tailwindcss/forms": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.8.tgz",
"integrity": "sha512-DJs7B7NPD0JH7BVvdHWNviWmunlFhuEkz7FyFxE4japOWYMLl9b1D6+Z9mivJJPWr6AEbmlPqgiFRyLwFB1SgQ==",
"dependencies": {
"mini-svg-data-uri": "^1.2.3"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20"
}
},
"node_modules/acorn": {
"version": "8.12.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
@@ -698,6 +719,14 @@
"node": ">=8.6"
}
},
"node_modules/mini-svg-data-uri": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
"bin": {
"mini-svg-data-uri": "cli.js"
}
},
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",

View File

@@ -11,6 +11,8 @@
"author": "",
"license": "ISC",
"dependencies": {
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.8",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.41",
"tailwindcss": "^3.4.10",

File diff suppressed because one or more lines are too long

View File

@@ -21,14 +21,12 @@ if (!file_exists('../' . $_ENV['DB_FILE_PATH'])) {
$dbFilePath = '../' . $_ENV['DB_FILE_PATH'];
$database = new Database($dbFilePath);
$albumClass = new Album($database);
$galleryClass = new Gallery($database);
$requestUri = trim($_SERVER['REQUEST_URI'], '/');
$requestParts = explode('/', $requestUri);
if ($requestParts[0] === 'gallery' && isset($requestParts[1])) {
// Gallery view
$galleryClass = new Gallery($database);
$album = urldecode($requestParts[1]);
$page = isset($requestParts[2]) ? (int)$requestParts[2] : 1;
@@ -48,18 +46,29 @@ if ($requestParts[0] === 'gallery' && isset($requestParts[1])) {
$startPage = max(1, $page - floor($paginationRange / 2));
$endPage = min($totalPages, $startPage + $paginationRange - 1);
include '../src/views/header.phtml';
$pageType = 'gallery';
include '../src/views/menubar.phtml';
// Render gallery view
include '../src/views/gallery.phtml';
include '../src/views/footer.phtml';
} else {
// Album view
$albumClass = new Album($database);
$albums = $albumClass->getAlbums();
$albumPreviews = [];
foreach ($albums as $album) {
$albumPreviews[$album] = $albumClass->getAlbumPreview($album);
}
$album = 'Album List';
include '../src/views/header.phtml';
$pageType = 'album';
include '../src/views/menubar.phtml';
// Render album view
include '../src/views/albums.phtml';
include '../src/views/footer.phtml';
}
?>

View File

@@ -1 +1 @@
let currentImageIndex=0,images=[];function openLightbox(element){images=Array.from(document.querySelectorAll("#gallery img")).map((img=>({big:img.dataset.big,original:img.dataset.original}))),currentImageIndex=images.findIndex((img=>img.big===element.dataset.big)),-1!==currentImageIndex?(updateLightbox(),document.addEventListener("keydown",handleKeydown)):console.error("Error: Image not found in the array")}function updateLightbox(){const lightboxImg=document.getElementById("lightbox-img");lightboxImg.src=images[currentImageIndex].big,lightboxImg.setAttribute("data-original",images[currentImageIndex].original),lightboxImg.style.transform="scale(1)",lightboxImg.style.maxWidth="100%",lightboxImg.style.maxHeight="100vh";document.getElementById("lightbox").classList.add("active");document.getElementById("original-button").style.display="block",updateNavigationButtons()}function updateNavigationButtons(){const prevButton=document.getElementById("lightbox-prev"),nextButton=document.getElementById("lightbox-next");prevButton.style.display=0===currentImageIndex?"none":"block",nextButton.style.display=currentImageIndex===images.length-1?"none":"block"}function closeLightbox(event){if("keydown"===event.type||"lightbox"===event.target.id){document.getElementById("lightbox").classList.remove("active");const lightboxImg=document.getElementById("lightbox-img");lightboxImg.src="",lightboxImg.style.transform="scale(1)",document.removeEventListener("keydown",handleKeydown)}}function zoomImage(event){event.preventDefault();const img=event.target;let scale=img.style.transform?parseFloat(img.style.transform.replace("scale(","").replace(")","")):1;event.deltaY<0?scale+=.1:scale-=.1,scale<1&&(scale=1),img.style.transform=`scale(${scale})`,img.style.transformOrigin=`${event.offsetX}px ${event.offsetY}px`}function loadOriginal(event){event.stopPropagation();const lightboxImg=document.getElementById("lightbox-img"),originalSrc=lightboxImg.getAttribute("data-original");lightboxImg.src=originalSrc,lightboxImg.style.transform="scale(1)";document.getElementById("original-button").style.display="none"}function navigateLightbox(direction){currentImageIndex+=direction,currentImageIndex<0?currentImageIndex=0:currentImageIndex>=images.length&&(currentImageIndex=images.length-1),updateLightbox()}function handleKeydown(event){"ArrowLeft"===event.key?currentImageIndex>0&&navigateLightbox(-1):"ArrowRight"===event.key?currentImageIndex<images.length-1&&navigateLightbox(1):"Escape"===event.key&&closeLightbox({type:"keydown"})}function debounce(func,wait=200){let timeout;return function(...args){const context=this;clearTimeout(timeout),timeout=setTimeout((()=>func.apply(context,args)),wait)}}document.getElementById("lightbox").addEventListener("click",(function(event){"lightbox"===event.target.id&&closeLightbox(event)})),document.addEventListener("DOMContentLoaded",(function(){const album=document.getElementById("gallery").dataset.album;function applyFilters(){const sortOrder=document.getElementById("sortOrder").value,sortDirection=document.getElementById("sortDirection").innerText.includes("Descending")?"DESC":"ASC",limit=document.getElementById("limit").value;document.cookie=`sortOrder=${sortOrder}; path=/`,document.cookie=`sortDirection=${sortDirection}; path=/`,document.cookie=`limit=${limit}; path=/`,window.location.href=`/gallery/${album}/1`}document.getElementById("sortOrder").addEventListener("change",applyFilters),document.getElementById("limit").addEventListener("change",applyFilters),document.getElementById("sortDirection").addEventListener("click",(function(){const sortButton=document.getElementById("sortDirection");sortButton.innerText.includes("Descending")?sortButton.innerText="Ascending ▲":sortButton.innerText="Descending ▼";applyFilters()}))}));
let currentImageIndex=0,images=[];function openLightbox(element){images=Array.from(document.querySelectorAll("#gallery img")).map((img=>({big:img.dataset.big,original:img.dataset.original}))),currentImageIndex=images.findIndex((img=>img.big===element.dataset.big)),-1!==currentImageIndex?(updateLightbox(),document.addEventListener("keydown",handleKeydown)):console.error("Error: Image not found in the array")}function updateLightbox(){const lightboxImg=document.getElementById("lightbox-img");lightboxImg.src=images[currentImageIndex].big,lightboxImg.setAttribute("data-original",images[currentImageIndex].original),lightboxImg.style.transform="scale(1)",lightboxImg.style.maxWidth="100%",lightboxImg.style.maxHeight="100vh";document.getElementById("lightbox").classList.add("active");document.getElementById("original-button").style.display="block",updateNavigationButtons()}function updateNavigationButtons(){const prevButton=document.getElementById("lightbox-prev"),nextButton=document.getElementById("lightbox-next");prevButton.style.display=0===currentImageIndex?"none":"block",nextButton.style.display=currentImageIndex===images.length-1?"none":"block"}function closeLightbox(event){if("keydown"===event.type||"lightbox"===event.target.id){document.getElementById("lightbox").classList.remove("active");const lightboxImg=document.getElementById("lightbox-img");lightboxImg.src="",lightboxImg.style.transform="scale(1)",document.removeEventListener("keydown",handleKeydown)}}function zoomImage(event){event.preventDefault();const img=event.target;let scale=img.style.transform?parseFloat(img.style.transform.replace("scale(","").replace(")","")):1;event.deltaY<0?scale+=.1:scale-=.1,scale<1&&(scale=1),img.style.transform=`scale(${scale})`,img.style.transformOrigin=`${event.offsetX}px ${event.offsetY}px`}function loadOriginal(event){event.stopPropagation();const lightboxImg=document.getElementById("lightbox-img"),originalSrc=lightboxImg.getAttribute("data-original");lightboxImg.src=originalSrc,lightboxImg.style.transform="scale(1)";document.getElementById("original-button").style.display="none"}function navigateLightbox(direction){currentImageIndex+=direction,currentImageIndex<0?currentImageIndex=0:currentImageIndex>=images.length&&(currentImageIndex=images.length-1),updateLightbox()}function handleKeydown(event){"ArrowLeft"===event.key?currentImageIndex>0&&navigateLightbox(-1):"ArrowRight"===event.key?currentImageIndex<images.length-1&&navigateLightbox(1):"Escape"===event.key&&closeLightbox({type:"keydown"})}function debounce(func,wait=200){let timeout;return function(...args){const context=this;clearTimeout(timeout),timeout=setTimeout((()=>func.apply(context,args)),wait)}}document.getElementById("lightbox").addEventListener("click",(function(event){"lightbox"===event.target.id&&closeLightbox(event)})),document.addEventListener("DOMContentLoaded",(function(){const galleryElement=document.getElementById("gallery");let album=null;galleryElement&&(album=galleryElement.dataset.album);let sortDirection=document.cookie.split(";").reduce(((acc,cookie)=>{const[key,value]=cookie.split("=").map((c=>c.trim()));return acc[key]=value,acc}),{}).sortDirection||"DESC";const sortDescending=document.getElementById("sortDescending"),sortAscending=document.getElementById("sortAscending");function applyFilters(){const sortOrder=document.getElementById("sortOrder").value,limit=document.getElementById("limit").value;document.cookie=`sortOrder=${sortOrder}; path=/`,document.cookie=`sortDirection=${sortDirection}; path=/`,document.cookie=`limit=${limit}; path=/`,window.location.href=`/gallery/${album}/1`}"ASC"===sortDirection?(sortDescending.classList.add("hidden"),sortAscending.classList.remove("hidden")):(sortDescending.classList.remove("hidden"),sortAscending.classList.add("hidden")),document.getElementById("sortOrder").addEventListener("change",applyFilters),document.getElementById("limit").addEventListener("change",applyFilters),document.getElementById("sortDirection").addEventListener("click",(function(){sortDescending.classList.contains("hidden")?(sortDescending.classList.remove("hidden"),sortAscending.classList.add("hidden"),sortDirection="DESC"):(sortDescending.classList.add("hidden"),sortAscending.classList.remove("hidden"),sortDirection="ASC");applyFilters()}))}));

View File

@@ -136,15 +136,40 @@ function debounce(func, wait = 200) {
}
document.addEventListener("DOMContentLoaded", function() {
const album = document.getElementById('gallery').dataset.album;
const galleryElement = document.getElementById('gallery');
let album = null;
if (galleryElement) {
album = galleryElement.dataset.album;
}
// Retrieve cookies
const cookies = document.cookie.split(';').reduce((acc, cookie) => {
const [key, value] = cookie.split('=').map(c => c.trim());
acc[key] = value;
return acc;
}, {});
let sortDirection = cookies['sortDirection'] || 'DESC'; // Default to 'DESC'
// Set initial state based on cookie
const sortDescending = document.getElementById('sortDescending');
const sortAscending = document.getElementById('sortAscending');
if (sortDirection === 'ASC') {
sortDescending.classList.add('hidden');
sortAscending.classList.remove('hidden');
} else {
sortDescending.classList.remove('hidden');
sortAscending.classList.add('hidden');
}
document.getElementById('sortOrder').addEventListener('change', applyFilters);
document.getElementById('limit').addEventListener('change', applyFilters);
document.getElementById('sortDirection').addEventListener('click', toggleSortDirection);
function applyFilters() {
const sortOrder = document.getElementById('sortOrder').value;
const sortDirection = document.getElementById('sortDirection').innerText.includes('Descending') ? 'DESC' : 'ASC';
const limit = document.getElementById('limit').value;
document.cookie = `sortOrder=${sortOrder}; path=/`;
document.cookie = `sortDirection=${sortDirection}; path=/`;
@@ -153,12 +178,15 @@ document.addEventListener("DOMContentLoaded", function() {
}
function toggleSortDirection() {
const sortButton = document.getElementById('sortDirection');
if (sortButton.innerText.includes('Descending')) {
sortButton.innerText = 'Ascending ▲';
if (sortDescending.classList.contains('hidden')) {
sortDescending.classList.remove('hidden');
sortAscending.classList.add('hidden');
sortDirection = 'DESC';
} else {
sortButton.innerText = 'Descending ▼';
sortDescending.classList.add('hidden');
sortAscending.classList.remove('hidden');
sortDirection = 'ASC';
}
applyFilters();
}
});
});

View File

@@ -1,37 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Photo Gallery - Select Album</title>
<link href="/css/styles.css" rel="stylesheet">
</head>
<body class="bg-gray-100">
<div class="container mx-auto p-4">
<h1 class="text-3xl font-bold mb-4">Select an Album</h1>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<?php foreach ($albumPreviews as $album => $image): ?>
<?php if ($image): ?>
<div class="bg-white shadow-md rounded-lg">
<a href="/gallery/<?= urlencode($album) ?>" class="block">
<img src="images/<?= $album ?>/thumbs/<?= $image['title'] ?>.webp" alt="<?= htmlspecialchars($album) ?>" class="w-full h-full object-cover">
<div class="p-4">
<h2 class="text-xl font-semibold text-center truncate"><?= htmlspecialchars($album) ?></h2>
</div>
</a>
</div>
<?php else: ?>
<div class="bg-white shadow-md rounded-lg">
<div class="flex justify-center items-center h-48 bg-gray-200">
<span class="text-gray-500">No images available</span>
<main>
<div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
<div class="container mx-auto p-4">
<ul role="list" class="grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8">
<?php foreach ($albumPreviews as $album => $image): ?>
<li class="relative">
<div class="group aspect-h-10 aspect-w-7 block w-full overflow-hidden rounded-lg bg-gray-100 focus-within:ring-2 focus-within:ring-indigo-500 focus-within:ring-offset-2 focus-within:ring-offset-gray-100">
<img src="images/<?= $album ?>/thumbs/<?= $image['title'] ?>.webp" alt="<?= htmlspecialchars($album) ?>" alt="" class="pointer-events-none object-cover group-hover:opacity-75">
<form action="/gallery/<?= urlencode($album) ?>">
<button type="button" class="absolute inset-0 focus:outline-none" onclick="location.href='/gallery/<?= urlencode($album) ?>';">
<span class="sr-only">View details for <?= htmlspecialchars($album) ?></span>
</button>
</form>
</div>
<div class="p-4">
<h2 class="text-xl font-semibold text-center truncate"><?= htmlspecialchars($album) ?></h2>
</div>
</div>
<?php endif; ?>
<?php endforeach; ?>
<p class="pointer-events-none mt-2 block truncate text-sm font-medium text-gray-900"><?= htmlspecialchars($album) ?></p>
</li>
<?php endforeach; ?>
</ul>
</div>
</div>
</body>
</html>
</main>

View File

@@ -0,0 +1,4 @@
</div>
<script type="text/javascript" src="/js/app.min.js"></script>
</body>
</html>

View File

@@ -1,86 +1,94 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Photo Gallery - <?= htmlspecialchars($album) ?></title>
<link href="/css/styles.css" rel="stylesheet">
</head>
<body class="bg-gray-100">
<div class="container mx-auto p-4">
<h1 class="text-3xl font-bold mb-4"><?= htmlspecialchars($album) ?> - Gallery</h1>
<a href="/" class="text-blue-500 mb-4 inline-block">← Back to Albums</a>
<main>
<div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
<div class="container mx-auto p-4">
<!-- Sorting and Pagination Controls -->
<div class="flex justify-between mb-4">
<div class="flex space-x-4">
<div>
<label for="sortOrder" class="block text-sm font-medium leading-6 text-gray-900">Sort by:</label>
<select id="sortOrder" name="sortOrder" onchange="applyFilters()" class="mt-2 block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6">
<option value="title" <?= $sortOrder == 'title' ? 'selected' : '' ?>>Name</option>
<option value="modified_date" <?= $sortOrder == 'modified_date' ? 'selected' : '' ?>>Modified Date</option>
<option value="added_date" <?= $sortOrder == 'added_date' ? 'selected' : '' ?>>Added Date</option>
</select>
</div>
<div>
<label for="sortDirection" class="block text-sm font-medium leading-6 text-gray-900">Order:</label>
<button id="sortDirection" class="mt-2 flex w-full block justify-center rounded-md bg-white px-3 py-1.5 text-sm font-semibold leading-6 text-black ring-1 ring-inset ring-gray-300 shadow-sm hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">
<span id="sortDescending">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 13.5 12 21m0 0-7.5-7.5M12 21V3" />
</svg>
</span>
<span class="hidden" id="sortAscending">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 10.5 12 3m0 0 7.5 7.5M12 3v18" />
</svg>
</span>
</button>
</div>
</div>
<div>
<label for="limit" class="block text-sm font-medium leading-6 text-gray-900">Images per page:</label>
<select id="limit" name="limit" onchange="applyFilters()" class="mt-2 block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6">
<option value="12" <?= $limit == 12 ? 'selected' : '' ?>>12</option>
<option value="24" <?= $limit == 24 ? 'selected' : '' ?>>24</option>
<option value="36" <?= $limit == 36 ? 'selected' : '' ?>>36</option>
</select>
</div>
</div>
<!-- Sorting and Pagination Controls -->
<div class="flex justify-between mb-4">
<div class="flex space-x-4">
<div>
<label for="sortOrder" class="text-sm font-medium">Sort by:</label>
<select id="sortOrder" name="sortOrder" onchange="applyFilters()" class="ml-2 p-1 border rounded">
<option value="title" <?= $sortOrder == 'title' ? 'selected' : '' ?>>Name</option>
<option value="modified_date" <?= $sortOrder == 'modified_date' ? 'selected' : '' ?>>Modified Date</option>
<option value="added_date" <?= $sortOrder == 'added_date' ? 'selected' : '' ?>>Added Date</option>
</select>
</div>
<div>
<label for="sortDirection" class="text-sm font-medium">Order:</label>
<button id="sortDirection" onclick="toggleSortDirection()" class="ml-2 p-1 border rounded">
<?= $sortDirection == 'DESC' ? 'Descending ▼' : 'Ascending ▲' ?>
</button>
</div>
</div>
<div>
<label for="limit" class="text-sm font-medium">Images per page:</label>
<select id="limit" name="limit" onchange="applyFilters()" class="ml-2 p-1 border rounded">
<option value="12" <?= $limit == 12 ? 'selected' : '' ?>>12</option>
<option value="24" <?= $limit == 24 ? 'selected' : '' ?>>24</option>
<option value="36" <?= $limit == 36 ? 'selected' : '' ?>>36</option>
</select>
</div>
</div>
<!-- Image Grid -->
<ul role="list" id="gallery" class="grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8" data-album="<?= urlencode($album) ?>">
<?php foreach ($images as $image): ?>
<li class="relative">
<div class="group aspect-h-10 aspect-w-7 block w-full overflow-hidden rounded-lg bg-gray-100 focus-within:ring-2 focus-within:ring-indigo-500 focus-within:ring-offset-2 focus-within:ring-offset-gray-100">
<img src="/images/<?= $album ?>/thumbs/<?= $image['title'] ?>.webp" alt="<?= htmlspecialchars($image['title']) ?>" class="cursor-pointer object-cover group-hover:opacity-75" data-big="/images/<?= $album ?>/big/<?= $image['title'] ?>.webp" data-original="/images/<?= $album ?>/photos/<?= $image['filename'] ?>" onclick="openLightbox(this)">
</div>
<p class="pointer-events-none mt-2 block truncate text-sm font-medium text-gray-900"><?= htmlspecialchars($image['filename']) ?></p>
<!-- <p class="pointer-events-none block text-sm font-medium text-gray-500">0.0 MB</p> -->
</li>
<?php endforeach; ?>
</ul>
<!-- Image Grid -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4" id="gallery" data-album="<?= urlencode($album) ?>">
<?php foreach ($images as $image): ?>
<div class="relative bg-white shadow-md rounded-lg overflow-hidden group">
<img src="/images/<?= $album ?>/thumbs/<?= $image['title'] ?>.webp" alt="<?= htmlspecialchars($image['title']) ?>" class="w-full h-full object-cover cursor-pointer" data-big="/images/<?= $album ?>/big/<?= $image['title'] ?>.webp" data-original="/images/<?= $album ?>/photos/<?= $image['filename'] ?>" onclick="openLightbox(this)">
<div class="absolute inset-0 bg-black bg-opacity-50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center title-overlay">
<h2 class="text-lg font-semibold text-white"><?= htmlspecialchars($image['title']) ?></h2>
</div>
</div>
<?php endforeach; ?>
</div>
<!-- Pagination Controls -->
<div class="mt-6">
<?php if ($totalPages > 1): ?>
<nav class="flex justify-center">
<?php if ($page > 1): ?>
<a href="/gallery/<?= urlencode($album) ?>/<?= $page - 1 ?>" class="px-4 py-2 bg-gray-300 rounded-l hover:bg-gray-400">Previous</a>
<?php endif; ?>
<?php for ($i = $startPage; $i <= $endPage; $i++): ?>
<a href="/gallery/<?= urlencode($album) ?>/<?= $i ?>" class="px-4 py-2 <?= $i == $page ? 'bg-gray-500 text-white' : 'bg-gray-300' ?> hover:bg-gray-400"><?= $i ?></a>
<?php endfor; ?>
<?php if ($page < $totalPages): ?>
<a href="/gallery/<?= urlencode($album) ?>/<?= $page + 1 ?>" class="px-4 py-2 bg-gray-300 rounded-r hover:bg-gray-400">Next</a>
<?php endif; ?>
</nav>
<?php endif; ?>
</div>
</div>
<!-- Pagination Controls -->
<?php if ($totalPages > 1): ?>
<nav class="flex mt-2 items-center justify-between border-t border-gray-200 px-4 sm:px-0">
<div class="-mt-px flex w-0 flex-1">
<a href="<?= $page > 1 ? '/gallery/' . urlencode($album) . '/' . $page - 1 : '#'; ?>" class="inline-flex items-center border-t-2 border-transparent pr-1 pt-4 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700">
<svg class="mr-3 h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M18 10a.75.75 0 01-.75.75H4.66l2.1 1.95a.75.75 0 11-1.02 1.1l-3.5-3.25a.75.75 0 010-1.1l3.5-3.25a.75.75 0 111.02 1.1l-2.1 1.95h12.59A.75.75 0 0118 10z" clip-rule="evenodd" />
</svg>
Previous
</a>
</div>
<div class="hidden md:-mt-px md:flex">
<?php for ($i = $startPage; $i <= $endPage; $i++): ?>
<!-- Lightbox -->
<div id="lightbox" class="lightbox">
<div id="lightbox-content" class="lightbox-content">
<img id="lightbox-img" src="" alt="Lightbox Image" onwheel="zoomImage(event)">
<div id="original-button" class="original-button" onclick="loadOriginal(event)">View Original</div>
<div id="lightbox-prev" class="lightbox-nav lightbox-prev" onclick="navigateLightbox(-1)">&#10094;</div>
<div id="lightbox-next" class="lightbox-nav lightbox-next" onclick="navigateLightbox(1)">&#10095;</div>
</div>
</div>
<script type="text/javascript" src="/js/app.min.js"></script>
</body>
</html>
<a href="/gallery/<?= urlencode($album) ?>/<?= $i ?>" class="inline-flex items-center border-t-2 px-4 pt-4 text-sm font-medium <?= $i == $page ? 'border-indigo-500 text-indigo-600' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700' ?>" <?= $i == $page ? 'aria-current="page"' : '' ?>><?= $i ?></a>
<?php endfor; ?>
</div>
<div class="-mt-px flex w-0 flex-1 justify-end">
<a href="<?= $page < $totalPages ? '/gallery/' . urlencode($album) . '/' . $page + 1 : '#'; ?>" class="inline-flex items-center border-t-2 border-transparent pl-1 pt-4 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700">
Next
<svg class="ml-3 h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M2 10a.75.75 0 01.75-.75h12.59l-2.1-1.95a.75.75 0 111.02-1.1l3.5 3.25a.75.75 0 010 1.1l-3.5 3.25a.75.75 0 11-1.02-1.1l2.1-1.95H2.75A.75.75 0 012 10z" clip-rule="evenodd" />
</svg>
</a>
</div>
</nav>
<?php endif; ?>
</div>
<!-- Lightbox -->
<div id="lightbox" class="lightbox">
<div id="lightbox-content" class="lightbox-content">
<img id="lightbox-img" src="" alt="Lightbox Image" onwheel="zoomImage(event)">
<div id="original-button" class="original-button" onclick="loadOriginal(event)">View Original</div>
<div id="lightbox-prev" class="lightbox-nav lightbox-prev" onclick="navigateLightbox(-1)">&#10094;</div>
<div id="lightbox-next" class="lightbox-nav lightbox-next" onclick="navigateLightbox(1)">&#10095;</div>
</div>
</div>
</div>
</main>

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html class="h-full bg-gray-100" lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Photo Gallery - <?= htmlspecialchars($album) ?></title>
<link href="/css/styles.css" rel="stylesheet">
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
</head>
<body class="h-full">
<div class="min-h-full">

View File

@@ -0,0 +1,76 @@
<nav class="bg-indigo-600">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="flex h-16 items-center justify-between">
<div class="flex items-center">
<div class="flex-shrink-0">
<img class="h-8 w-8" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=300" alt="Your Company">
</div>
<div class="hidden md:block">
<div class="ml-10 flex items-baseline space-x-4">
<!-- Current: "bg-indigo-700 text-white", Default: "text-white hover:bg-indigo-500 hover:bg-opacity-75" -->
<?php
$menuStyle = 'rounded-md px-3 py-2 text-sm font-medium text-white';
$selected = 'bg-indigo-700';
$unSelected = 'hover:bg-indigo-500 hover:bg-opacity-75';
?>
<a href="/" class="<?= $menuStyle ?> <?= $pageType=='album' ? $selected : $unSelected ?>">Albums</a>
<a class="<?= $menuStyle ?> <?= $pageType=='gallery' ? $selected : $unSelected ?>">Gallery</a>
</div>
</div>
</div>
<div class="-mr-2 flex md:hidden">
<!-- Mobile menu button -->
<button type="button" class="relative inline-flex items-center justify-center rounded-md bg-indigo-600 p-2 text-indigo-200 hover:bg-indigo-500 hover:bg-opacity-75 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-indigo-600" aria-controls="mobile-menu" aria-expanded="false">
<span class="absolute -inset-0.5"></span>
<span class="sr-only">Open main menu</span>
<!-- Menu open: "hidden", Menu closed: "block" -->
<svg class="block h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
<!-- Menu open: "block", Menu closed: "hidden" -->
<svg class="hidden h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<!-- Mobile menu, show/hide based on menu state. -->
<div class="md:hidden" id="mobile-menu">
<div class="space-y-1 px-2 pb-3 pt-2 sm:px-3">
<!-- Current: "bg-indigo-700 text-white", Default: "text-white hover:bg-indigo-500 hover:bg-opacity-75" -->
<a href="#" class="block rounded-md px-3 py-2 text-base font-medium text-white hover:bg-indigo-500 hover:bg-opacity-75">Albums</a>
<a href="#" class="block rounded-md bg-indigo-700 px-3 py-2 text-base font-medium text-white" aria-current="page">Gallery</a>
</div>
<div class="border-t border-indigo-700 pb-3 pt-4">
<div class="flex items-center px-5">
<div class="flex-shrink-0">
<img class="h-10 w-10 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="">
</div>
<div class="ml-3">
<div class="text-base font-medium text-white">Tom Cook</div>
<div class="text-sm font-medium text-indigo-300">tom@example.com</div>
</div>
<button type="button" class="relative ml-auto flex-shrink-0 rounded-full bg-indigo-600 p-1 text-indigo-200 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-indigo-600">
<span class="absolute -inset-1.5"></span>
<span class="sr-only">View notifications</span>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" />
</svg>
</button>
</div>
<div class="mt-3 space-y-1 px-2">
<a href="#" class="block rounded-md px-3 py-2 text-base font-medium text-white hover:bg-indigo-500 hover:bg-opacity-75">Your Profile</a>
<a href="#" class="block rounded-md px-3 py-2 text-base font-medium text-white hover:bg-indigo-500 hover:bg-opacity-75">Settings</a>
<a href="#" class="block rounded-md px-3 py-2 text-base font-medium text-white hover:bg-indigo-500 hover:bg-opacity-75">Sign out</a>
</div>
</div>
</div>
</nav>
<header class="bg-white shadow-sm">
<div class="mx-auto max-w-7xl px-4 py-4 sm:px-6 lg:px-8">
<h1 class="text-lg font-semibold leading-6 text-gray-900"><?= $pageType=='gallery' ? "Gallery - " . htmlspecialchars($album) : 'Select an album' ?></h1>
</div>
</header>

View File

@@ -1,8 +1,16 @@
const defaultTheme = require('tailwindcss/defaultTheme')
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/views/**/*.phtml"],
theme: {
extend: {},
extend: {
fontFamily: {
sans: ['Inter var', ...defaultTheme.fontFamily.sans],
},
},
},
plugins: [],
plugins: [
require('@tailwindcss/forms'),
require('@tailwindcss/aspect-ratio'),
],
}