diff --git a/web/public/css/styles.css b/web/public/css/styles.css
index f1adbb2..d4f34d7 100644
--- a/web/public/css/styles.css
+++ b/web/public/css/styles.css
@@ -1 +1 @@
-/*! tailwindcss v3.4.10 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.mx-auto{margin-left:auto;margin-right:auto}.mb-4{margin-bottom:1rem}.mt-6{margin-top:1.5rem}.inline-block{display:inline-block}.flex{display:flex}.grid{display:grid}.h-48{height:12rem}.h-full{height:100%}.w-full{width:100%}.cursor-pointer{cursor:pointer}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.items-center{align-items:center}.justify-center{justify-content:center}.gap-4{gap:1rem}.overflow-hidden{overflow:hidden}.rounded-lg{border-radius:.5rem}.rounded-l{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(209 213 219/var(--tw-bg-opacity))}.bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-opacity-50{--tw-bg-opacity:0.5}.object-cover{-o-object-fit:cover;object-fit:cover}.p-4{padding:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem}.text-lg,.text-xl{line-height:1.75rem}.text-xl{font-size:1.25rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.opacity-0{opacity:0}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.lightbox{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.9);justify-content:center;align-items:center;z-index:50}.lightbox img{max-width:100%;max-height:100%;cursor:zoom-in}.lightbox.active{display:flex}.title-overlay{pointer-events:none}.original-button{position:absolute;bottom:10px;right:10px;background-color:rgba(0,0,0,.7);color:#fff;padding:10px 15px;border-radius:5px;cursor:pointer}.hover\:bg-gray-400:hover{--tw-bg-opacity:1;background-color:rgb(156 163 175/var(--tw-bg-opacity))}.group:hover .group-hover\:opacity-100{opacity:1}@media (min-width:768px){.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}
\ No newline at end of file
+/*! tailwindcss v3.4.10 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.mx-auto{margin-left:auto;margin-right:auto}.mb-4{margin-bottom:1rem}.ml-2{margin-left:.5rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.grid{display:grid}.h-48{height:12rem}.h-full{height:100%}.w-full{width:100%}.cursor-pointer{cursor:pointer}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.overflow-hidden,.truncate{overflow:hidden}.truncate{text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-l{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.border{border-width:1px}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(209 213 219/var(--tw-bg-opacity))}.bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-opacity-50{--tw-bg-opacity:0.5}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-4{padding:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.text-center{text-align:center}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.text-blue-500{--tw-text-opacity:1;color:rgb(59 130 246/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.opacity-0{opacity:0}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.lightbox{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.9);justify-content:center;align-items:center;z-index:50}.lightbox img{max-width:100%;max-height:100%;cursor:zoom-in}.lightbox-content{position:relative}.lightbox-content img{max-width:100%;max-height:100vh;display:block;margin:auto}.lightbox.active{display:flex}.lightbox-nav{position:absolute;top:50%;font-size:30px;color:#fff;text-shadow:0 0 10px rgba(0,0,0,.7);cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;z-index:10;padding:10px}.lightbox-prev{left:20px;transform:translateY(-50%)}.lightbox-next{right:20px;transform:translateY(-50%)}.title-overlay{pointer-events:none}.original-button{position:absolute;bottom:10px;right:10px;background-color:rgba(0,0,0,.7);color:#fff;padding:10px 15px;border-radius:5px;cursor:pointer}.hover\:bg-gray-400:hover{--tw-bg-opacity:1;background-color:rgb(156 163 175/var(--tw-bg-opacity))}.group:hover .group-hover\:opacity-100{opacity:1}@media (min-width:768px){.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}
\ No newline at end of file
diff --git a/web/public/gallery.php b/web/public/gallery.php
deleted file mode 100644
index 506d42d..0000000
--- a/web/public/gallery.php
+++ /dev/null
@@ -1,100 +0,0 @@
-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);
-?>
-
-
-
-
-
-
- Photo Gallery - = htmlspecialchars($album) ?>
-
-
-
-
-
= htmlspecialchars($album) ?> - Gallery
-
← Back to Albums
-
-
-
-
![<?= htmlspecialchars($image['title']) ?>](images/<?= $album ?>/thumbs/<?= $image['title'] ?>.webp)
-
-
= htmlspecialchars($image['title']) ?>
-
-
-
-
-
-
-
-
-
-
-
-
![Lightbox Image]()
-
View Original
-
-
-
-
-
diff --git a/web/public/index.php b/web/public/index.php
index e657684..ded8aa3 100644
--- a/web/public/index.php
+++ b/web/public/index.php
@@ -4,10 +4,12 @@
require_once '../vendor/autoload.php';
use Hpz\Photogal\Database;
-use Hpz\Photogal\Image;
+use Hpz\Photogal\Album;
+use Hpz\Photogal\Gallery;
$dotenv = Dotenv\Dotenv::createImmutable('../');
$dotenv->load();
+
if (!isset($_ENV['DB_FILE_PATH'])) {
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
$dbFilePath = '../' . $_ENV['DB_FILE_PATH'];
$database = new Database($dbFilePath);
-$image = new Image($database);
-// Get the list of albums
-$albumsQuery = "SELECT DISTINCT album FROM images";
-$albumsResult = $database->getConnection()->query($albumsQuery);
-$albums = [];
-while ($row = $albumsResult->fetchArray(SQLITE3_ASSOC)) {
- $albums[] = $row['album'];
-}
+$albumClass = new Album($database);
+$galleryClass = new Gallery($database);
-// Fetch the first image from each album
-$albumPreviews = [];
-foreach ($albums as $album) {
- $albumPreviews[$album] = $image->getImages($album, 1, 0)[0] ?? null;
+$requestUri = trim($_SERVER['REQUEST_URI'], '/');
+$requestParts = explode('/', $requestUri);
+
+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';
}
?>
-
-
-
-
-
-
- Photo Gallery - Select Album
-
-
-
-
-
-
diff --git a/web/public/js/app.min.js b/web/public/js/app.min.js
index 8598122..4a011c8 100644
--- a/web/public/js/app.min.js
+++ b/web/public/js/app.min.js
@@ -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)"}
\ No newline at end of file
+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?currentImageIndexfunc.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()}))}));
\ No newline at end of file
diff --git a/web/scripts/process_images.php b/web/scripts/process_images.php
index 8dcf69a..133e810 100644
--- a/web/scripts/process_images.php
+++ b/web/scripts/process_images.php
@@ -1,5 +1,5 @@
load();
-if (!isset($_ENV['DB_FILE_PATH'])) {
- die('DB_FILE_PATH is not set in the .env file');
+if (!isset($_ENV['DB_FILE_PATH']) || !isset($_ENV['IMG_THUMB_WIDTH']) || !isset($_ENV['IMG_THUMB_HEIGHT']) || !isset($_ENV['IMG_THUMB_QUALITY'])) {
+ die('Required environment variables are not set in the .env file');
}
-// Specify the paths
// Specify the paths
$albumsDir = 'public/images/';
$dbFilePath = $_ENV['DB_FILE_PATH'];
@@ -24,10 +23,32 @@ $dbFilePath = $_ENV['DB_FILE_PATH'];
$database = new Database($dbFilePath);
$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 processDirectory($albumDir, $albumName, Image $image)
{
- $dirIterator = new RecursiveDirectoryIterator($albumDir);
+ $dirIterator = new RecursiveDirectoryIterator($albumDir, RecursiveDirectoryIterator::SKIP_DOTS);
$iterator = new RecursiveIteratorIterator($dirIterator);
foreach ($iterator as $file) {
@@ -43,16 +64,18 @@ function processDirectory($albumDir, $albumName, Image $image)
$bigPath = $bigDir . $fileInfo['filename'] . '.webp';
if (!file_exists($thumbPath)) {
- if (!is_dir($thumbDir)) {
- mkdir($thumbDir, 0777, true);
+ if (!is_dir($thumbDir) && !mkdir($thumbDir, 0777, true) && !is_dir($thumbDir)) {
+ error_log("Failed to create directory: $thumbDir");
+ continue;
}
ThumbnailGenerator::createThumbnail($filePath, $thumbPath, $_ENV['IMG_THUMB_WIDTH'], $_ENV['IMG_THUMB_HEIGHT'], $_ENV['IMG_THUMB_QUALITY']);
echo "Created thumbnail for $fileName in $albumName\n";
}
if (!file_exists($bigPath)) {
- if (!is_dir($bigDir)) {
- mkdir($bigDir, 0777, true);
+ if (!is_dir($bigDir) && !mkdir($bigDir, 0777, true) && !is_dir($bigDir)) {
+ error_log("Failed to create directory: $bigDir");
+ continue;
}
ThumbnailGenerator::createBigImage($filePath, $bigPath);
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);
foreach ($albums as $album) {
if ($album !== '.' && $album !== '..' && is_dir($albumsDir . $album)) {
diff --git a/web/src/Album.php b/web/src/Album.php
new file mode 100644
index 0000000..a75e45f
--- /dev/null
+++ b/web/src/Album.php
@@ -0,0 +1,32 @@
+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;
+ }
+}
diff --git a/web/src/Gallery.php b/web/src/Gallery.php
new file mode 100644
index 0000000..e4315c5
--- /dev/null
+++ b/web/src/Gallery.php
@@ -0,0 +1,27 @@
+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);
+ }
+}
diff --git a/web/src/Image.php b/web/src/Image.php
index 5021e21..350a27d 100644
--- a/web/src/Image.php
+++ b/web/src/Image.php
@@ -43,13 +43,13 @@ class Image
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";
if ($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);
if ($album) {
@@ -88,8 +88,7 @@ class Image
$stmt->bindValue(':album', $album, SQLITE3_TEXT);
}
$result = $stmt->execute();
- $row = $result->fetchArray(SQLITE3_ASSOC);
+ $row = $stmt->execute()->fetchArray(SQLITE3_ASSOC);
return $row['count'];
}
}
-
diff --git a/web/src/js/app.js b/web/src/js/app.js
index 44d2f3e..948b3f2 100644
--- a/web/src/js/app.js
+++ b/web/src/js/app.js
@@ -1,17 +1,65 @@
+let currentImageIndex = 0;
+let images = [];
+
function openLightbox(element) {
- const lightbox = document.getElementById('lightbox');
- const lightboxImg = document.getElementById('lightbox-img');
- lightboxImg.src = element.dataset.big;
- lightboxImg.setAttribute('data-original', element.dataset.original);
- lightbox.classList.add('active');
+ 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);
+
+ 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() {
- const lightbox = document.getElementById('lightbox');
- lightbox.classList.remove('active');
+function updateLightbox() {
const lightboxImg = document.getElementById('lightbox-img');
- lightboxImg.src = ''; // Unload the current image
+ 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');
+ lightbox.classList.remove('active');
+ const lightboxImg = document.getElementById('lightbox-img');
+ lightboxImg.src = ''; // Unload the current image
+ lightboxImg.style.transform = 'scale(1)';
+
+ // Remove the keyboard event listener
+ document.removeEventListener('keydown', handleKeydown);
+ }
}
function zoomImage(event) {
@@ -37,4 +85,80 @@ function loadOriginal(event) {
const originalSrc = lightboxImg.getAttribute('data-original');
lightboxImg.src = originalSrc;
lightboxImg.style.transform = 'scale(1)';
-}
\ No newline at end of file
+
+ // 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();
+ }
+});
\ No newline at end of file
diff --git a/web/src/styles/tailwind.css b/web/src/styles/tailwind.css
index 6aa511a..cfab0bc 100644
--- a/web/src/styles/tailwind.css
+++ b/web/src/styles/tailwind.css
@@ -21,10 +21,44 @@
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 {
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 {
pointer-events: none;
}
diff --git a/web/src/views/albums.phtml b/web/src/views/albums.phtml
new file mode 100644
index 0000000..b216870
--- /dev/null
+++ b/web/src/views/albums.phtml
@@ -0,0 +1,37 @@
+
+
+
+
+
+ Photo Gallery - Select Album
+
+
+
+
+
Select an Album
+
+ $image): ?>
+
+
+
+
+
+ No images available
+
+
+
= htmlspecialchars($album) ?>
+
+
+
+
+
+
+
+
diff --git a/web/src/views/gallery.phtml b/web/src/views/gallery.phtml
new file mode 100644
index 0000000..0d516a6
--- /dev/null
+++ b/web/src/views/gallery.phtml
@@ -0,0 +1,86 @@
+
+
+
+
+
+ Photo Gallery - = htmlspecialchars($album) ?>
+
+
+
+
+
= htmlspecialchars($album) ?> - Gallery
+
← Back to Albums
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![<?= htmlspecialchars($image['title']) ?>](/images/<?= $album ?>/thumbs/<?= $image['title'] ?>.webp)
+
+
= htmlspecialchars($image['title']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
![Lightbox Image]()
+
View Original
+
❮
+
❯
+
+
+
+
+
+
diff --git a/web/tailwind.config.js b/web/tailwind.config.js
index 916a6dd..cbe548e 100644
--- a/web/tailwind.config.js
+++ b/web/tailwind.config.js
@@ -1,6 +1,6 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
- content: ["./public/gallery.php","./public/index.php"],
+ content: ["./src/views/**/*.phtml"],
theme: {
extend: {},
},