Initial commit
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
web/public/images/*
|
||||
web/vendor/*
|
||||
web/node_modules/*
|
||||
*.db
|
||||
2
bin/buildcss
Executable file
2
bin/buildcss
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
docker-compose exec app npm run build:css
|
||||
2
bin/init_db
Executable file
2
bin/init_db
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
docker-compose exec app php scripts/init_db.php
|
||||
2
bin/minifyjs
Executable file
2
bin/minifyjs
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
docker-compose exec app npm run minify:js
|
||||
2
bin/process_images
Executable file
2
bin/process_images
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
docker-compose exec app php scripts/process_images.php
|
||||
32
docker-compose.yml
Normal file
32
docker-compose.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
version: '3.3'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: ./php
|
||||
container_name: photo_gallery_php
|
||||
volumes:
|
||||
- ./web:/var/www/html
|
||||
user: "1000:1000"
|
||||
#ports:
|
||||
# - "9000:9000"
|
||||
networks:
|
||||
- photo-gallery-network
|
||||
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
container_name: photo_gallery_nginx
|
||||
volumes:
|
||||
- ./web:/var/www/html
|
||||
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
|
||||
ports:
|
||||
- "8080:80"
|
||||
depends_on:
|
||||
- app
|
||||
networks:
|
||||
- photo-gallery-network
|
||||
|
||||
networks:
|
||||
photo-gallery-network:
|
||||
driver: bridge
|
||||
|
||||
24
nginx/default.conf
Normal file
24
nginx/default.conf
Normal file
@@ -0,0 +1,24 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
root /var/www/html/public;
|
||||
index index.php index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
#include snippets/fastcgi-php.conf;
|
||||
fastcgi_pass app:9000;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
|
||||
23
php/Dockerfile
Normal file
23
php/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM php:8.2-fpm
|
||||
|
||||
# Install dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libfreetype6-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
libpng-dev \
|
||||
libwebp-dev \
|
||||
libsqlite3-dev \
|
||||
&& docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \
|
||||
&& docker-php-ext-install gd \
|
||||
&& docker-php-ext-install mysqli
|
||||
|
||||
# Install Composer
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||
|
||||
RUN apt-get install npm vim git -y && \
|
||||
useradd -M -d /var/www -s /usr/sbin/nologin www & \
|
||||
chown -R www /var/www
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /var/www/html
|
||||
|
||||
5
web/.env
Normal file
5
web/.env
Normal file
@@ -0,0 +1,5 @@
|
||||
DB_FILE_PATH=database/gallery.db
|
||||
PAGINATION_RANGE=7
|
||||
IMG_THUMB_WIDTH=364
|
||||
IMG_THUMB_HEIGHT=364
|
||||
IMG_THUMB_QUALITY=40
|
||||
12
web/composer.json
Normal file
12
web/composer.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "hpz/photogal",
|
||||
"type": "project",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Hpz\\Photogal\\": "src/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"vlucas/phpdotenv": "^5.6"
|
||||
}
|
||||
}
|
||||
479
web/composer.lock
generated
Normal file
479
web/composer.lock
generated
Normal file
@@ -0,0 +1,479 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "328949c3f92d87a4170180a4a7e683fe",
|
||||
"packages": [
|
||||
{
|
||||
"name": "graham-campbell/result-type",
|
||||
"version": "v1.1.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GrahamCampbell/Result-Type.git",
|
||||
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945",
|
||||
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpoption/phpoption": "^1.9.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GrahamCampbell\\ResultType\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
}
|
||||
],
|
||||
"description": "An Implementation Of The Result Type",
|
||||
"keywords": [
|
||||
"Graham Campbell",
|
||||
"GrahamCampbell",
|
||||
"Result Type",
|
||||
"Result-Type",
|
||||
"result"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
|
||||
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-20T21:45:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"version": "1.9.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/schmittjoh/php-option.git",
|
||||
"reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54",
|
||||
"reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "1.9-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpOption\\": "src/PhpOption/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Johannes M. Schmitt",
|
||||
"email": "schmittjoh@gmail.com",
|
||||
"homepage": "https://github.com/schmittjoh"
|
||||
},
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
}
|
||||
],
|
||||
"description": "Option Type for PHP",
|
||||
"keywords": [
|
||||
"language",
|
||||
"option",
|
||||
"php",
|
||||
"type"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/schmittjoh/php-option/issues",
|
||||
"source": "https://github.com/schmittjoh/php-option/tree/1.9.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-20T21:41:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.30.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "0424dff1c58f028c451efff2045f5d92410bd540"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540",
|
||||
"reference": "0424dff1c58f028c451efff2045f5d92410bd540",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"provide": {
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Ctype\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gert de Pagter",
|
||||
"email": "BackEndTea@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for ctype functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"ctype",
|
||||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T15:07:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.30.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c",
|
||||
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"provide": {
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for the Mbstring extension",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"mbstring",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-06-19T12:30:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.30.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "77fa7995ac1b21ab60769b7323d600a991a90433"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433",
|
||||
"reference": "77fa7995ac1b21ab60769b7323d600a991a90433",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php80\\": ""
|
||||
},
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ion Bazan",
|
||||
"email": "ion.bazan@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-31T15:07:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "vlucas/phpdotenv",
|
||||
"version": "v5.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vlucas/phpdotenv.git",
|
||||
"reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2",
|
||||
"reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pcre": "*",
|
||||
"graham-campbell/result-type": "^1.1.3",
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpoption/phpoption": "^1.9.3",
|
||||
"symfony/polyfill-ctype": "^1.24",
|
||||
"symfony/polyfill-mbstring": "^1.24",
|
||||
"symfony/polyfill-php80": "^1.24"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"ext-filter": "*",
|
||||
"phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-filter": "Required to use the boolean validator."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "5.6-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Dotenv\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Vance Lucas",
|
||||
"email": "vance@vancelucas.com",
|
||||
"homepage": "https://github.com/vlucas"
|
||||
}
|
||||
],
|
||||
"description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
|
||||
"keywords": [
|
||||
"dotenv",
|
||||
"env",
|
||||
"environment"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/vlucas/phpdotenv/issues",
|
||||
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-20T21:52:34+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
1496
web/package-lock.json
generated
Normal file
1496
web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
web/package.json
Normal file
19
web/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "html",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build:css": "npx tailwindcss -i ./src/styles/tailwind.css -o ./public/css/styles.css --minify",
|
||||
"minify:js": "terser ./src/js/app.js -o ./public/js/app.min.js --compress"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.41",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"terser": "^5.31.6"
|
||||
}
|
||||
}
|
||||
8
web/postcss.config.js
Normal file
8
web/postcss.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// postcss.config.js
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
}
|
||||
}
|
||||
|
||||
1
web/public/css/styles.css
Normal file
1
web/public/css/styles.css
Normal file
File diff suppressed because one or more lines are too long
100
web/public/gallery.php
Normal file
100
web/public/gallery.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?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>
|
||||
65
web/public/index.php
Normal file
65
web/public/index.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
// public/index.php
|
||||
|
||||
require_once '../vendor/autoload.php';
|
||||
|
||||
use Hpz\Photogal\Database;
|
||||
use Hpz\Photogal\Image;
|
||||
|
||||
$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');
|
||||
}
|
||||
|
||||
// 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'];
|
||||
}
|
||||
|
||||
// Fetch the first image from each album
|
||||
$albumPreviews = [];
|
||||
foreach ($albums as $album) {
|
||||
$albumPreviews[$album] = $image->getImages($album, 1, 0)[0] ?? null;
|
||||
}
|
||||
?>
|
||||
|
||||
<!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>
|
||||
1
web/public/js/app.min.js
vendored
Normal file
1
web/public/js/app.min.js
vendored
Normal file
@@ -0,0 +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)"}
|
||||
28
web/scripts/init_db.php
Normal file
28
web/scripts/init_db.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
// .php
|
||||
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use Hpz\Photogal\Database;
|
||||
use Hpz\Photogal\Image;
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
|
||||
$dotenv->load();
|
||||
if (!isset($_ENV['DB_FILE_PATH'])) {
|
||||
die('DB_FILE_PATH is not set in the .env file');
|
||||
}
|
||||
|
||||
// Specify the path to the SQLite database file
|
||||
$dbFilePath = $_ENV['DB_FILE_PATH'];
|
||||
|
||||
// Create a new Database connection
|
||||
$database = new Database($dbFilePath);
|
||||
|
||||
// Create the Image table
|
||||
$image = new Image($database);
|
||||
if ($image->createTable()) {
|
||||
echo "Database and table initialized successfully.";
|
||||
} else {
|
||||
echo "Failed to initialize database and table.";
|
||||
}
|
||||
|
||||
84
web/scripts/process_images.php
Normal file
84
web/scripts/process_images.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
// web/process_images.php
|
||||
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use Hpz\Photogal\Database;
|
||||
use Hpz\Photogal\Image;
|
||||
use Hpz\Photogal\ThumbnailGenerator;
|
||||
|
||||
ini_set('memory_limit', '512M');
|
||||
|
||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
|
||||
$dotenv->load();
|
||||
if (!isset($_ENV['DB_FILE_PATH'])) {
|
||||
die('DB_FILE_PATH is not set in the .env file');
|
||||
}
|
||||
|
||||
// Specify the paths
|
||||
// Specify the paths
|
||||
$albumsDir = 'public/images/';
|
||||
$dbFilePath = $_ENV['DB_FILE_PATH'];
|
||||
|
||||
// Create a new Database connection
|
||||
$database = new Database($dbFilePath);
|
||||
$image = new Image($database);
|
||||
|
||||
// Function to process a directory
|
||||
function processDirectory($albumDir, $albumName, Image $image)
|
||||
{
|
||||
$dirIterator = new RecursiveDirectoryIterator($albumDir);
|
||||
$iterator = new RecursiveIteratorIterator($dirIterator);
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile()) {
|
||||
$filePath = $file->getPathname();
|
||||
$fileInfo = pathinfo($filePath);
|
||||
$fileName = $fileInfo['basename'];
|
||||
|
||||
if (in_array(strtolower($fileInfo['extension']), ['jpg', 'jpeg', 'png', 'gif'])) {
|
||||
$thumbDir = 'public/images/' . $albumName . '/thumbs/';
|
||||
$bigDir = 'public/images/' . $albumName . '/big/';
|
||||
$thumbPath = $thumbDir . $fileInfo['filename'] . '.webp';
|
||||
$bigPath = $bigDir . $fileInfo['filename'] . '.webp';
|
||||
|
||||
if (!file_exists($thumbPath)) {
|
||||
if (!is_dir($thumbDir)) {
|
||||
mkdir($thumbDir, 0777, true);
|
||||
}
|
||||
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);
|
||||
}
|
||||
ThumbnailGenerator::createBigImage($filePath, $bigPath);
|
||||
echo "Created big image for $fileName in $albumName\n";
|
||||
}
|
||||
|
||||
$relativePath = $albumName . '/photos';
|
||||
$modifiedDate = filemtime($filePath);
|
||||
$addedDate = time();
|
||||
$title = $fileInfo['filename'];
|
||||
|
||||
$exists = $image->imageExists($albumName, $fileName, $relativePath);
|
||||
|
||||
if (!$exists) {
|
||||
$image->addImage($albumName, $fileName, $relativePath, $modifiedDate, $addedDate, $title);
|
||||
echo "Added $fileName to database in album $albumName\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$albums = scandir($albumsDir);
|
||||
foreach ($albums as $album) {
|
||||
if ($album !== '.' && $album !== '..' && is_dir($albumsDir . $album)) {
|
||||
if (strpos($album, 'videos') === false) {
|
||||
processDirectory($albumsDir . $album . '/photos/', $album, $image);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
web/src/Database.php
Normal file
27
web/src/Database.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
// src/Database.php
|
||||
|
||||
namespace Hpz\Photogal;
|
||||
|
||||
use SQLite3;
|
||||
|
||||
class Database
|
||||
{
|
||||
private $connection;
|
||||
|
||||
public function __construct($dbFilePath)
|
||||
{
|
||||
$this->connection = new SQLite3($dbFilePath);
|
||||
}
|
||||
|
||||
public function getConnection()
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->connection->close();
|
||||
}
|
||||
}
|
||||
|
||||
95
web/src/Image.php
Normal file
95
web/src/Image.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
// src/Image.php
|
||||
|
||||
namespace Hpz\Photogal;
|
||||
|
||||
class Image
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct(Database $database)
|
||||
{
|
||||
$this->db = $database->getConnection();
|
||||
}
|
||||
|
||||
public function createTable()
|
||||
{
|
||||
$query = "
|
||||
CREATE TABLE IF NOT EXISTS images (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
album TEXT NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
modified_date INTEGER NOT NULL,
|
||||
added_date INTEGER NOT NULL,
|
||||
title TEXT NOT NULL
|
||||
);
|
||||
";
|
||||
return $this->db->exec($query);
|
||||
}
|
||||
|
||||
public function addImage($album, $filename, $path, $modifiedDate, $addedDate, $title)
|
||||
{
|
||||
$stmt = $this->db->prepare("
|
||||
INSERT INTO images (album, filename, path, modified_date, added_date, title)
|
||||
VALUES (:album, :filename, :path, :modified_date, :added_date, :title)
|
||||
");
|
||||
$stmt->bindValue(':album', $album, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':filename', $filename, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':path', $path, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':modified_date', $modifiedDate, SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':added_date', $addedDate, SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':title', $title, SQLITE3_TEXT);
|
||||
return $stmt->execute();
|
||||
}
|
||||
|
||||
public function getImages($album = null, $limit = 24, $offset = 0)
|
||||
{
|
||||
$query = "SELECT * FROM images";
|
||||
if ($album) {
|
||||
$query .= " WHERE album = :album";
|
||||
}
|
||||
$query .= " ORDER BY modified_date DESC LIMIT :limit OFFSET :offset";
|
||||
|
||||
$stmt = $this->db->prepare($query);
|
||||
if ($album) {
|
||||
$stmt->bindValue(':album', $album, SQLITE3_TEXT);
|
||||
}
|
||||
$stmt->bindValue(':limit', $limit, SQLITE3_INTEGER);
|
||||
$stmt->bindValue(':offset', $offset, SQLITE3_INTEGER);
|
||||
$result = $stmt->execute();
|
||||
|
||||
$images = [];
|
||||
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
|
||||
$images[] = $row;
|
||||
}
|
||||
return $images;
|
||||
}
|
||||
|
||||
public function imageExists($album, $filename, $path)
|
||||
{
|
||||
$stmt = $this->db->prepare("SELECT COUNT(*) as count FROM images WHERE album = :album AND filename = :filename AND path = :path");
|
||||
$stmt->bindValue(':album', $album, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':filename', $filename, SQLITE3_TEXT);
|
||||
$stmt->bindValue(':path', $path, SQLITE3_TEXT);
|
||||
$result = $stmt->execute();
|
||||
$row = $result->fetchArray(SQLITE3_ASSOC);
|
||||
return $row['count'] > 0;
|
||||
}
|
||||
|
||||
public function countImages($album = null)
|
||||
{
|
||||
$query = "SELECT COUNT(*) as count FROM images";
|
||||
if ($album) {
|
||||
$query .= " WHERE album = :album";
|
||||
}
|
||||
$stmt = $this->db->prepare($query);
|
||||
if ($album) {
|
||||
$stmt->bindValue(':album', $album, SQLITE3_TEXT);
|
||||
}
|
||||
$result = $stmt->execute();
|
||||
$row = $result->fetchArray(SQLITE3_ASSOC);
|
||||
return $row['count'];
|
||||
}
|
||||
}
|
||||
|
||||
91
web/src/ThumbnailGenerator.php
Normal file
91
web/src/ThumbnailGenerator.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
// src/ThumbnailGenerator.php
|
||||
|
||||
namespace Hpz\Photogal;
|
||||
|
||||
class ThumbnailGenerator
|
||||
{
|
||||
public static function createThumbnail($sourcePath, $destPath, $thumbWidth = 200, $thumbHeight = 200, $quality = 40)
|
||||
{
|
||||
list($width, $height, $type) = getimagesize($sourcePath);
|
||||
|
||||
$thumb = imagecreatetruecolor($thumbWidth, $thumbHeight);
|
||||
|
||||
switch ($type) {
|
||||
case IMAGETYPE_JPEG:
|
||||
$source = imagecreatefromjpeg($sourcePath);
|
||||
break;
|
||||
case IMAGETYPE_PNG:
|
||||
$source = imagecreatefrompng($sourcePath);
|
||||
break;
|
||||
case IMAGETYPE_GIF:
|
||||
$source = imagecreatefromgif($sourcePath);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
$srcAspect = $width / $height;
|
||||
$thumbAspect = $thumbWidth / $thumbHeight;
|
||||
|
||||
if ($srcAspect >= $thumbAspect) {
|
||||
$newHeight = (int)$thumbHeight;
|
||||
$newWidth = (int)($width / ($height / $thumbHeight));
|
||||
} else {
|
||||
$newWidth = (int)$thumbWidth;
|
||||
$newHeight = (int)($height / ($width / $thumbWidth));
|
||||
}
|
||||
|
||||
$xOffset = (int)(($thumbWidth - $newWidth) / 2);
|
||||
$yOffset = (int)(($thumbHeight - $newHeight) / 2);
|
||||
|
||||
imagecopyresampled(
|
||||
$thumb,
|
||||
$source,
|
||||
$xOffset,
|
||||
$yOffset,
|
||||
0,
|
||||
0,
|
||||
$newWidth,
|
||||
$newHeight,
|
||||
$width,
|
||||
$height
|
||||
);
|
||||
|
||||
return imagewebp($thumb, $destPath, $quality);
|
||||
}
|
||||
|
||||
public static function createBigImage($sourcePath, $destPath, $maxDimension = 2000, $quality = 80)
|
||||
{
|
||||
list($width, $height, $type) = getimagesize($sourcePath);
|
||||
|
||||
switch ($type) {
|
||||
case IMAGETYPE_JPEG:
|
||||
$source = imagecreatefromjpeg($sourcePath);
|
||||
break;
|
||||
case IMAGETYPE_PNG:
|
||||
$source = imagecreatefrompng($sourcePath);
|
||||
break;
|
||||
case IMAGETYPE_GIF:
|
||||
$source = imagecreatefromgif($sourcePath);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
$srcAspect = $width / $height;
|
||||
|
||||
if ($width > $height) {
|
||||
$newWidth = $maxDimension;
|
||||
$newHeight = (int)($maxDimension / $srcAspect);
|
||||
} else {
|
||||
$newHeight = $maxDimension;
|
||||
$newWidth = (int)($maxDimension * $srcAspect);
|
||||
}
|
||||
|
||||
$bigImage = imagecreatetruecolor($newWidth, $newHeight);
|
||||
imagecopyresampled($bigImage, $source, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
|
||||
|
||||
return imagewebp($bigImage, $destPath, $quality);
|
||||
}
|
||||
}
|
||||
40
web/src/js/app.js
Normal file
40
web/src/js/app.js
Normal file
@@ -0,0 +1,40 @@
|
||||
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');
|
||||
}
|
||||
|
||||
function closeLightbox() {
|
||||
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)';
|
||||
}
|
||||
|
||||
function zoomImage(event) {
|
||||
event.preventDefault();
|
||||
const img = event.target;
|
||||
let scale = img.style.transform ? parseFloat(img.style.transform.replace('scale(', '').replace(')', '')) : 1;
|
||||
|
||||
if (event.deltaY < 0) {
|
||||
scale += 0.1; // Zoom in
|
||||
} else {
|
||||
scale -= 0.1; // Zoom out
|
||||
}
|
||||
|
||||
if (scale < 1) scale = 1; // Prevent zoom out below original size
|
||||
|
||||
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');
|
||||
const originalSrc = lightboxImg.getAttribute('data-original');
|
||||
lightboxImg.src = originalSrc;
|
||||
lightboxImg.style.transform = 'scale(1)';
|
||||
}
|
||||
41
web/src/styles/tailwind.css
Normal file
41
web/src/styles/tailwind.css
Normal file
@@ -0,0 +1,41 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.lightbox {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 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, 0.7);
|
||||
color: white;
|
||||
padding: 10px 15px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
8
web/tailwind.config.js
Normal file
8
web/tailwind.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./public/gallery.php","./public/index.php"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
Reference in New Issue
Block a user