add routing, move code to classes; misc changes
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,100 +0,0 @@
|
|||||||
<?php
|
|
||||||
// public/gallery.php
|
|
||||||
|
|
||||||
use Hpz\Photogal\Database;
|
|
||||||
use Hpz\Photogal\Image;
|
|
||||||
|
|
||||||
require_once '../vendor/autoload.php';
|
|
||||||
|
|
||||||
$dotenv = Dotenv\Dotenv::createImmutable('../');
|
|
||||||
$dotenv->load();
|
|
||||||
if (!isset($_ENV['DB_FILE_PATH'])) {
|
|
||||||
die('DB_FILE_PATH is not set in the .env file');
|
|
||||||
}
|
|
||||||
if (!file_exists('../' . $_ENV['DB_FILE_PATH'])) {
|
|
||||||
die('Database file does not exist');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the selected album
|
|
||||||
$album = $_GET['album'] ?? '';
|
|
||||||
$album = urldecode($album);
|
|
||||||
|
|
||||||
if (empty($album)) {
|
|
||||||
header("Location: index.php");
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pagination settings
|
|
||||||
$limit = 24; // Number of images per page
|
|
||||||
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
|
||||||
$offset = ($page - 1) * $limit;
|
|
||||||
|
|
||||||
|
|
||||||
// Create a new Database connection
|
|
||||||
$dbFilePath = '../' . $_ENV['DB_FILE_PATH'];
|
|
||||||
$database = new Database($dbFilePath);
|
|
||||||
$image = new Image($database);
|
|
||||||
|
|
||||||
// Get images and total count for the selected album
|
|
||||||
$images = $image->getImages($album, $limit, $offset);
|
|
||||||
$totalImages = $image->countImages($album);
|
|
||||||
$totalPages = ceil($totalImages / $limit);
|
|
||||||
|
|
||||||
// Limit the number of pages displayed in the pagination
|
|
||||||
$paginationRange = $_ENV['PAGINATION_RANGE'] ?? 5;
|
|
||||||
$startPage = max(1, $page - floor($paginationRange / 2));
|
|
||||||
$endPage = min($totalPages, $startPage + $paginationRange - 1);
|
|
||||||
?>
|
|
||||||
|
|
||||||
<!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="index.php" class="text-blue-500 mb-4 inline-block">← Back to Albums</a>
|
|
||||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
||||||
<?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="?album=<?= urlencode($album) ?>&page=<?= $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="?album=<?= urlencode($album) ?>&page=<?= $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="?album=<?= urlencode($album) ?>&page=<?= $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>
|
|
||||||
|
|
||||||
<!-- Lightbox -->
|
|
||||||
<div id="lightbox" class="lightbox" onclick="closeLightbox()">
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="/js/app.min.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -4,10 +4,12 @@
|
|||||||
require_once '../vendor/autoload.php';
|
require_once '../vendor/autoload.php';
|
||||||
|
|
||||||
use Hpz\Photogal\Database;
|
use Hpz\Photogal\Database;
|
||||||
use Hpz\Photogal\Image;
|
use Hpz\Photogal\Album;
|
||||||
|
use Hpz\Photogal\Gallery;
|
||||||
|
|
||||||
$dotenv = Dotenv\Dotenv::createImmutable('../');
|
$dotenv = Dotenv\Dotenv::createImmutable('../');
|
||||||
$dotenv->load();
|
$dotenv->load();
|
||||||
|
|
||||||
if (!isset($_ENV['DB_FILE_PATH'])) {
|
if (!isset($_ENV['DB_FILE_PATH'])) {
|
||||||
die('DB_FILE_PATH is not set in the .env file');
|
die('DB_FILE_PATH is not set in the .env file');
|
||||||
}
|
}
|
||||||
@@ -18,48 +20,46 @@ if (!file_exists('../' . $_ENV['DB_FILE_PATH'])) {
|
|||||||
// Create a new Database connection
|
// Create a new Database connection
|
||||||
$dbFilePath = '../' . $_ENV['DB_FILE_PATH'];
|
$dbFilePath = '../' . $_ENV['DB_FILE_PATH'];
|
||||||
$database = new Database($dbFilePath);
|
$database = new Database($dbFilePath);
|
||||||
$image = new Image($database);
|
|
||||||
|
|
||||||
// Get the list of albums
|
$albumClass = new Album($database);
|
||||||
$albumsQuery = "SELECT DISTINCT album FROM images";
|
$galleryClass = new Gallery($database);
|
||||||
$albumsResult = $database->getConnection()->query($albumsQuery);
|
|
||||||
$albums = [];
|
|
||||||
while ($row = $albumsResult->fetchArray(SQLITE3_ASSOC)) {
|
|
||||||
$albums[] = $row['album'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch the first image from each album
|
$requestUri = trim($_SERVER['REQUEST_URI'], '/');
|
||||||
$albumPreviews = [];
|
$requestParts = explode('/', $requestUri);
|
||||||
foreach ($albums as $album) {
|
|
||||||
$albumPreviews[$album] = $image->getImages($album, 1, 0)[0] ?? null;
|
if ($requestParts[0] === 'gallery' && isset($requestParts[1])) {
|
||||||
|
// Gallery view
|
||||||
|
$album = urldecode($requestParts[1]);
|
||||||
|
$page = isset($requestParts[2]) ? (int)$requestParts[2] : 1;
|
||||||
|
|
||||||
|
// Retrieve limit, sort order, and sort direction from cookies or defaults
|
||||||
|
$limit = $_COOKIE['limit'] ?? 24;
|
||||||
|
$sortOrder = $_COOKIE['sortOrder'] ?? 'modified_date';
|
||||||
|
$sortDirection = $_COOKIE['sortDirection'] ?? 'DESC';
|
||||||
|
|
||||||
|
// Get images and total count for the selected album
|
||||||
|
$offset = ($page - 1) * $limit;
|
||||||
|
$images = $galleryClass->getGalleryImages($album, $limit, $offset, $sortOrder, $sortDirection);
|
||||||
|
$totalImages = $galleryClass->countImages($album);
|
||||||
|
$totalPages = ceil($totalImages / $limit);
|
||||||
|
|
||||||
|
// Limit the number of pages displayed in the pagination
|
||||||
|
$paginationRange = $_ENV['PAGINATION_RANGE'] ?? 5;
|
||||||
|
$startPage = max(1, $page - floor($paginationRange / 2));
|
||||||
|
$endPage = min($totalPages, $startPage + $paginationRange - 1);
|
||||||
|
|
||||||
|
// Render gallery view
|
||||||
|
include '../src/views/gallery.phtml';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Album view
|
||||||
|
$albums = $albumClass->getAlbums();
|
||||||
|
$albumPreviews = [];
|
||||||
|
foreach ($albums as $album) {
|
||||||
|
$albumPreviews[$album] = $albumClass->getAlbumPreview($album);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render album view
|
||||||
|
include '../src/views/albums.phtml';
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!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 overflow-hidden">
|
|
||||||
<a href="gallery.php?album=<?= urlencode($album) ?>">
|
|
||||||
<img src="images/<?= $album ?>/thumbs/<?= $image['title'] ?>.webp" alt="<?= htmlspecialchars($album) ?>" class="w-full h-48 object-cover">
|
|
||||||
<div class="p-4">
|
|
||||||
<h2 class="text-xl font-semibold"><?= htmlspecialchars($album) ?></h2>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|||||||
2
web/public/js/app.min.js
vendored
2
web/public/js/app.min.js
vendored
@@ -1 +1 @@
|
|||||||
function openLightbox(element){const lightbox=document.getElementById("lightbox"),lightboxImg=document.getElementById("lightbox-img");lightboxImg.src=element.dataset.big,lightboxImg.setAttribute("data-original",element.dataset.original),lightbox.classList.add("active")}function closeLightbox(){document.getElementById("lightbox").classList.remove("active");const lightboxImg=document.getElementById("lightbox-img");lightboxImg.src="",lightboxImg.style.transform="scale(1)"}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)"}
|
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()}))}));
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
// web/process_images.php
|
// scripts/process_images.php
|
||||||
|
|
||||||
require_once 'vendor/autoload.php';
|
require_once 'vendor/autoload.php';
|
||||||
|
|
||||||
@@ -11,11 +11,10 @@ ini_set('memory_limit', '512M');
|
|||||||
|
|
||||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
|
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
|
||||||
$dotenv->load();
|
$dotenv->load();
|
||||||
if (!isset($_ENV['DB_FILE_PATH'])) {
|
if (!isset($_ENV['DB_FILE_PATH']) || !isset($_ENV['IMG_THUMB_WIDTH']) || !isset($_ENV['IMG_THUMB_HEIGHT']) || !isset($_ENV['IMG_THUMB_QUALITY'])) {
|
||||||
die('DB_FILE_PATH is not set in the .env file');
|
die('Required environment variables are not set in the .env file');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify the paths
|
|
||||||
// Specify the paths
|
// Specify the paths
|
||||||
$albumsDir = 'public/images/';
|
$albumsDir = 'public/images/';
|
||||||
$dbFilePath = $_ENV['DB_FILE_PATH'];
|
$dbFilePath = $_ENV['DB_FILE_PATH'];
|
||||||
@@ -24,10 +23,32 @@ $dbFilePath = $_ENV['DB_FILE_PATH'];
|
|||||||
$database = new Database($dbFilePath);
|
$database = new Database($dbFilePath);
|
||||||
$image = new Image($database);
|
$image = new Image($database);
|
||||||
|
|
||||||
|
// Function to check for albums in the database and remove those that don't exist in the images folder
|
||||||
|
function cleanUpAlbums(Database $database, $albumsDir)
|
||||||
|
{
|
||||||
|
$db = $database->getConnection();
|
||||||
|
$albumsQuery = "SELECT DISTINCT album FROM images";
|
||||||
|
$albumsResult = $db->query($albumsQuery);
|
||||||
|
|
||||||
|
while ($row = $albumsResult->fetchArray(SQLITE3_ASSOC)) {
|
||||||
|
$album = $row['album'];
|
||||||
|
$albumPath = $albumsDir . $album;
|
||||||
|
|
||||||
|
if (!is_dir($albumPath)) {
|
||||||
|
// Remove all images related to this album from the database
|
||||||
|
$deleteQuery = $db->prepare("DELETE FROM images WHERE album = :album");
|
||||||
|
$deleteQuery->bindValue(':album', $album, SQLITE3_TEXT);
|
||||||
|
$deleteQuery->execute();
|
||||||
|
|
||||||
|
echo "Removed album '$album' from database as it does not exist in the images folder\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Function to process a directory
|
// Function to process a directory
|
||||||
function processDirectory($albumDir, $albumName, Image $image)
|
function processDirectory($albumDir, $albumName, Image $image)
|
||||||
{
|
{
|
||||||
$dirIterator = new RecursiveDirectoryIterator($albumDir);
|
$dirIterator = new RecursiveDirectoryIterator($albumDir, RecursiveDirectoryIterator::SKIP_DOTS);
|
||||||
$iterator = new RecursiveIteratorIterator($dirIterator);
|
$iterator = new RecursiveIteratorIterator($dirIterator);
|
||||||
|
|
||||||
foreach ($iterator as $file) {
|
foreach ($iterator as $file) {
|
||||||
@@ -43,16 +64,18 @@ function processDirectory($albumDir, $albumName, Image $image)
|
|||||||
$bigPath = $bigDir . $fileInfo['filename'] . '.webp';
|
$bigPath = $bigDir . $fileInfo['filename'] . '.webp';
|
||||||
|
|
||||||
if (!file_exists($thumbPath)) {
|
if (!file_exists($thumbPath)) {
|
||||||
if (!is_dir($thumbDir)) {
|
if (!is_dir($thumbDir) && !mkdir($thumbDir, 0777, true) && !is_dir($thumbDir)) {
|
||||||
mkdir($thumbDir, 0777, true);
|
error_log("Failed to create directory: $thumbDir");
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
ThumbnailGenerator::createThumbnail($filePath, $thumbPath, $_ENV['IMG_THUMB_WIDTH'], $_ENV['IMG_THUMB_HEIGHT'], $_ENV['IMG_THUMB_QUALITY']);
|
ThumbnailGenerator::createThumbnail($filePath, $thumbPath, $_ENV['IMG_THUMB_WIDTH'], $_ENV['IMG_THUMB_HEIGHT'], $_ENV['IMG_THUMB_QUALITY']);
|
||||||
echo "Created thumbnail for $fileName in $albumName\n";
|
echo "Created thumbnail for $fileName in $albumName\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file_exists($bigPath)) {
|
if (!file_exists($bigPath)) {
|
||||||
if (!is_dir($bigDir)) {
|
if (!is_dir($bigDir) && !mkdir($bigDir, 0777, true) && !is_dir($bigDir)) {
|
||||||
mkdir($bigDir, 0777, true);
|
error_log("Failed to create directory: $bigDir");
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
ThumbnailGenerator::createBigImage($filePath, $bigPath);
|
ThumbnailGenerator::createBigImage($filePath, $bigPath);
|
||||||
echo "Created big image for $fileName in $albumName\n";
|
echo "Created big image for $fileName in $albumName\n";
|
||||||
@@ -74,6 +97,10 @@ function processDirectory($albumDir, $albumName, Image $image)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up albums that no longer exist in the images directory
|
||||||
|
cleanUpAlbums($database, $albumsDir);
|
||||||
|
|
||||||
|
// Process existing albums and images
|
||||||
$albums = scandir($albumsDir);
|
$albums = scandir($albumsDir);
|
||||||
foreach ($albums as $album) {
|
foreach ($albums as $album) {
|
||||||
if ($album !== '.' && $album !== '..' && is_dir($albumsDir . $album)) {
|
if ($album !== '.' && $album !== '..' && is_dir($albumsDir . $album)) {
|
||||||
|
|||||||
32
web/src/Album.php
Normal file
32
web/src/Album.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hpz\Photogal;
|
||||||
|
|
||||||
|
class Album
|
||||||
|
{
|
||||||
|
private $db;
|
||||||
|
private $database;
|
||||||
|
|
||||||
|
public function __construct(Database $database)
|
||||||
|
{
|
||||||
|
$this->db = $database->getConnection();
|
||||||
|
$this->database = $database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAlbums()
|
||||||
|
{
|
||||||
|
$albumsQuery = "SELECT DISTINCT album FROM images";
|
||||||
|
$albumsResult = $this->db->query($albumsQuery);
|
||||||
|
$albums = [];
|
||||||
|
while ($row = $albumsResult->fetchArray(SQLITE3_ASSOC)) {
|
||||||
|
$albums[] = $row['album'];
|
||||||
|
}
|
||||||
|
return $albums;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAlbumPreview($album)
|
||||||
|
{
|
||||||
|
$image = new Image($this->database);
|
||||||
|
return $image->getImages($album, 1, 0)[0] ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
web/src/Gallery.php
Normal file
27
web/src/Gallery.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hpz\Photogal;
|
||||||
|
|
||||||
|
class Gallery
|
||||||
|
{
|
||||||
|
private $db;
|
||||||
|
private $database;
|
||||||
|
|
||||||
|
public function __construct(Database $database)
|
||||||
|
{
|
||||||
|
$this->db = $database->getConnection();
|
||||||
|
$this->database = $database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGalleryImages($album, $limit, $offset, $sortOrder, $sortDirection)
|
||||||
|
{
|
||||||
|
$image = new Image($this->database);
|
||||||
|
return $image->getImages($album, $limit, $offset, $sortOrder, $sortDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countImages($album)
|
||||||
|
{
|
||||||
|
$image = new Image($this->database);
|
||||||
|
return $image->countImages($album);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,13 +43,13 @@ class Image
|
|||||||
return $stmt->execute();
|
return $stmt->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getImages($album = null, $limit = 24, $offset = 0)
|
public function getImages($album = null, $limit = 24, $offset = 0, $sortOrder = 'modified_date', $sortDirection = 'DESC')
|
||||||
{
|
{
|
||||||
$query = "SELECT * FROM images";
|
$query = "SELECT * FROM images";
|
||||||
if ($album) {
|
if ($album) {
|
||||||
$query .= " WHERE album = :album";
|
$query .= " WHERE album = :album";
|
||||||
}
|
}
|
||||||
$query .= " ORDER BY modified_date DESC LIMIT :limit OFFSET :offset";
|
$query .= " ORDER BY $sortOrder $sortDirection LIMIT :limit OFFSET :offset";
|
||||||
|
|
||||||
$stmt = $this->db->prepare($query);
|
$stmt = $this->db->prepare($query);
|
||||||
if ($album) {
|
if ($album) {
|
||||||
@@ -88,8 +88,7 @@ class Image
|
|||||||
$stmt->bindValue(':album', $album, SQLITE3_TEXT);
|
$stmt->bindValue(':album', $album, SQLITE3_TEXT);
|
||||||
}
|
}
|
||||||
$result = $stmt->execute();
|
$result = $stmt->execute();
|
||||||
$row = $result->fetchArray(SQLITE3_ASSOC);
|
$row = $stmt->execute()->fetchArray(SQLITE3_ASSOC);
|
||||||
return $row['count'];
|
return $row['count'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,65 @@
|
|||||||
|
let currentImageIndex = 0;
|
||||||
|
let images = [];
|
||||||
|
|
||||||
function openLightbox(element) {
|
function openLightbox(element) {
|
||||||
const lightbox = document.getElementById('lightbox');
|
images = Array.from(document.querySelectorAll('#gallery img')).map(img => ({
|
||||||
const lightboxImg = document.getElementById('lightbox-img');
|
big: img.dataset.big,
|
||||||
lightboxImg.src = element.dataset.big;
|
original: img.dataset.original
|
||||||
lightboxImg.setAttribute('data-original', element.dataset.original);
|
}));
|
||||||
lightbox.classList.add('active');
|
|
||||||
|
currentImageIndex = images.findIndex(img => img.big === element.dataset.big);
|
||||||
|
|
||||||
|
if (currentImageIndex === -1) {
|
||||||
|
console.error('Error: Image not found in the array');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLightbox();
|
||||||
|
|
||||||
|
// Add event listener for keyboard navigation
|
||||||
|
document.addEventListener('keydown', handleKeydown);
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeLightbox() {
|
function updateLightbox() {
|
||||||
|
const lightboxImg = document.getElementById('lightbox-img');
|
||||||
|
lightboxImg.src = images[currentImageIndex].big;
|
||||||
|
lightboxImg.setAttribute('data-original', images[currentImageIndex].original);
|
||||||
|
|
||||||
|
// Reset zoom on the image
|
||||||
|
lightboxImg.style.transform = 'scale(1)';
|
||||||
|
lightboxImg.style.maxWidth = '100%';
|
||||||
|
lightboxImg.style.maxHeight = '100vh';
|
||||||
|
|
||||||
|
const lightbox = document.getElementById('lightbox');
|
||||||
|
lightbox.classList.add('active');
|
||||||
|
|
||||||
|
// Reset the "View Original" button visibility
|
||||||
|
const originalButton = document.getElementById('original-button');
|
||||||
|
originalButton.style.display = 'block';
|
||||||
|
|
||||||
|
updateNavigationButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNavigationButtons() {
|
||||||
|
const prevButton = document.getElementById('lightbox-prev');
|
||||||
|
const nextButton = document.getElementById('lightbox-next');
|
||||||
|
|
||||||
|
// Disable or enable buttons based on current index
|
||||||
|
prevButton.style.display = currentImageIndex === 0 ? 'none' : 'block';
|
||||||
|
nextButton.style.display = currentImageIndex === images.length - 1 ? 'none' : 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeLightbox(event) {
|
||||||
|
if (event.type === 'keydown' || event.target.id === 'lightbox') {
|
||||||
const lightbox = document.getElementById('lightbox');
|
const lightbox = document.getElementById('lightbox');
|
||||||
lightbox.classList.remove('active');
|
lightbox.classList.remove('active');
|
||||||
const lightboxImg = document.getElementById('lightbox-img');
|
const lightboxImg = document.getElementById('lightbox-img');
|
||||||
lightboxImg.src = ''; // Unload the current image
|
lightboxImg.src = ''; // Unload the current image
|
||||||
lightboxImg.style.transform = 'scale(1)';
|
lightboxImg.style.transform = 'scale(1)';
|
||||||
|
|
||||||
|
// Remove the keyboard event listener
|
||||||
|
document.removeEventListener('keydown', handleKeydown);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function zoomImage(event) {
|
function zoomImage(event) {
|
||||||
@@ -37,4 +85,80 @@ function loadOriginal(event) {
|
|||||||
const originalSrc = lightboxImg.getAttribute('data-original');
|
const originalSrc = lightboxImg.getAttribute('data-original');
|
||||||
lightboxImg.src = originalSrc;
|
lightboxImg.src = originalSrc;
|
||||||
lightboxImg.style.transform = 'scale(1)';
|
lightboxImg.style.transform = 'scale(1)';
|
||||||
|
|
||||||
|
// Hide the "View Original" button when the original is shown
|
||||||
|
const originalButton = document.getElementById('original-button');
|
||||||
|
originalButton.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function navigateLightbox(direction) {
|
||||||
|
currentImageIndex += direction;
|
||||||
|
|
||||||
|
if (currentImageIndex < 0) {
|
||||||
|
currentImageIndex = 0;
|
||||||
|
} else if (currentImageIndex >= images.length) {
|
||||||
|
currentImageIndex = images.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLightbox();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeydown(event) {
|
||||||
|
if (event.key === 'ArrowLeft') {
|
||||||
|
if (currentImageIndex > 0) {
|
||||||
|
navigateLightbox(-1);
|
||||||
|
}
|
||||||
|
} else if (event.key === 'ArrowRight') {
|
||||||
|
if (currentImageIndex < images.length - 1) {
|
||||||
|
navigateLightbox(1);
|
||||||
|
}
|
||||||
|
} else if (event.key === 'Escape') {
|
||||||
|
closeLightbox({ type: 'keydown' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the lightbox only closes when clicking on the background
|
||||||
|
document.getElementById('lightbox').addEventListener('click', function(event) {
|
||||||
|
if (event.target.id === 'lightbox') {
|
||||||
|
closeLightbox(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Debounce function to limit rapid firing of functions
|
||||||
|
function debounce(func, wait = 200) {
|
||||||
|
let timeout;
|
||||||
|
return function(...args) {
|
||||||
|
const context = this;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => func.apply(context, args), wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
const album = document.getElementById('gallery').dataset.album;
|
||||||
|
|
||||||
|
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=/`;
|
||||||
|
document.cookie = `limit=${limit}; path=/`;
|
||||||
|
window.location.href = `/gallery/${album}/1`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSortDirection() {
|
||||||
|
const sortButton = document.getElementById('sortDirection');
|
||||||
|
if (sortButton.innerText.includes('Descending')) {
|
||||||
|
sortButton.innerText = 'Ascending ▲';
|
||||||
|
} else {
|
||||||
|
sortButton.innerText = 'Descending ▼';
|
||||||
|
}
|
||||||
|
applyFilters();
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -21,10 +21,44 @@
|
|||||||
cursor: zoom-in;
|
cursor: zoom-in;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lightbox-content {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-content img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100vh; /* Ensures the image doesn't exceed the viewport height */
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.lightbox.active {
|
.lightbox.active {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lightbox-nav {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
font-size: 30px;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0px 0px 10px rgba(0, 0, 0, 0.7);
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
z-index: 10;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-prev {
|
||||||
|
left: 20px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-next {
|
||||||
|
right: 20px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
.title-overlay {
|
.title-overlay {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|||||||
37
web/src/views/albums.phtml
Normal file
37
web/src/views/albums.phtml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<!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>
|
||||||
|
</div>
|
||||||
|
<div class="p-4">
|
||||||
|
<h2 class="text-xl font-semibold text-center truncate"><?= htmlspecialchars($album) ?></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
86
web/src/views/gallery.phtml
Normal file
86
web/src/views/gallery.phtml
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<!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>
|
||||||
|
|
||||||
|
<!-- 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 -->
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<!-- 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)">❮</div>
|
||||||
|
<div id="lightbox-next" class="lightbox-nav lightbox-next" onclick="navigateLightbox(1)">❯</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/js/app.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: ["./public/gallery.php","./public/index.php"],
|
content: ["./src/views/**/*.phtml"],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user