Compare commits

..

8 Commits

Author SHA1 Message Date
8d180466a2 public/index.php aktualisiert
All checks were successful
release-tag / release-image (push) Successful in 26s
2025-01-01 23:02:44 +00:00
72e7158aa3 config.php.example aktualisiert
All checks were successful
release-tag / release-image (push) Successful in 28s
2025-01-01 22:54:05 +00:00
55ae125cab public/index.php aktualisiert
Some checks failed
release-tag / release-image (push) Has been cancelled
2025-01-01 22:53:46 +00:00
32eb679d9a Dockerfile aktualisiert
All checks were successful
release-tag / release-image (push) Successful in 26s
2025-01-01 22:48:57 +00:00
63a1d8423e .gitea/workflows/registry.yml hinzugefügt
All checks were successful
release-tag / release-image (push) Successful in 42s
2025-01-01 22:38:02 +00:00
b7068375e8 Dockerfile hinzugefügt 2025-01-01 22:36:14 +00:00
e39b25e0d4 Merge pull request 'init' (#1) from master into main
Reviewed-on: #1
2025-01-01 22:31:50 +00:00
82ce2594ac init 2025-01-01 23:30:28 +01:00
15 changed files with 536 additions and 2 deletions

View File

@@ -0,0 +1,51 @@
name: release-tag
on:
push:
branches:
- 'main'
jobs:
release-image:
runs-on: ubuntu-latest
env:
DOCKER_ORG: sendnrw
DOCKER_LATEST: latest
RUNNER_TOOL_CACHE: /toolcache
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v2
with: # replace it with your local IP
config-inline: |
[registry."git.send.nrw"]
http = true
insecure = true
- name: Login to DockerHub
uses: docker/login-action@v2
with:
registry: git.send.nrw # replace it with your local IP
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Get Meta
id: meta
run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
platforms: |
linux/amd64
push: true
tags: | # replace it with your local IP and tags
git.send.nrw/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
git.send.nrw/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.idea/
.DS_Store
config.php

3
Dockerfile Normal file
View File

@@ -0,0 +1,3 @@
FROM git.send.nrw/sendnrw/docker_php_84:latest
COPY public /usr/share/nginx/html
COPY config.php.example /usr/share/nginx/config.php

View File

@@ -1,2 +1 @@
# docker_image_gallery
# docker_image_gallery

12
config.php.example Normal file
View File

@@ -0,0 +1,12 @@
<?php
return [
'base_url' => 'http://localhost:8080',
'title' => 'Danb Gallery',
'author_text' => 'Dan Brown',
'author_link' => 'https://danb.me',
'license_link' => 'https://creativecommons.org/publicdomain/zero/1.0/',
'license_text' => 'CC0 - Public Domain',
];
?>

21
license Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Dan Brown
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

2
public/images/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

270
public/index.php Normal file
View File

@@ -0,0 +1,270 @@
<?php
declare(strict_types=1);
//ini_set('display_errors', '1');
//error_reporting(E_ALL);
// Global constants
const IMAGES_DIR = __DIR__ . DIRECTORY_SEPARATOR . 'images';
const THUMBS_DIR = __DIR__ . DIRECTORY_SEPARATOR . 'thumbs';
// Load configuration
$config = loadConfig();
// Path management
$uri = $_SERVER['REQUEST_URI'];
$routes = [
'categories' => function() {
sendPage("categories", [
'categories' => getCategories(),
]);
},
'category' => function(string $category) {
sendPage("category", [
'category' => $category,
'images' => getCategoryImages($category),
]);
},
'404' => function(string $uri) {
sendPage("404", [
'uri' => $uri,
], 404);
}
];
if ($uri === '/') {
$routes['categories']();
} else {
$category = urldecode(trim($uri, '/'));
if (categoryExists($category)) {
$routes['category']($category);
} else {
$routes['404']($uri);
}
}
/**
* Get details of all available categories.
* @returns Category[]
*/
function getCategories(): array {
$names = getCategoryFolderNames();
$categories = [];
foreach ($names as $name) {
$category = new Category(
name: $name,
thumb: getCategoryThumbnail($name)
);
if ($category->thumb) {
$categories[] = $category;
}
}
return $categories;
}
/**
* Get the thumbnail image uri for the given category.
*/
function getCategoryThumbnail(string $category): string {
$categoryImages = getCategoryImageFiles($category);
$firstImage = $categoryImages[0] ?? '';
if (empty($firstImage)) {
return '';
}
generateImageThumbnail($category, $firstImage);
return "thumbs/{$category}/{$firstImage}";
}
/**
* Generated a thumbnail for the given image filename withing
* the given category folder.
*/
function generateImageThumbnail(string $category, string $image): void {
$imagePath = buildPath(IMAGES_DIR, $category, $image);
$thumbPath = buildPath(THUMBS_DIR, $category, $image);
if (file_exists($thumbPath)) {
return;
}
if (!file_exists($imagePath)) {
error("Could not find image at {$imagePath}");
}
$thumbDir = dirname($thumbPath);
if (!file_exists($thumbDir)) {
mkdir($thumbDir, 0777, true);
}
$imageExtension = strtolower(pathinfo($imagePath, PATHINFO_EXTENSION));
switch ($imageExtension) {
case 'webp':
$originalImage = imagecreatefromwebp($imagePath);
break;
case 'jpg':
case 'jpeg':
$originalImage = imagecreatefromjpeg($imagePath);
break;
case 'png':
$originalImage = imagecreatefrompng($imagePath);
break;
default:
error("Unsupported image format: {$imageExtension}");
}
$thumbImage = imagescale($originalImage, 1200);
switch ($imageExtension) {
case 'webp':
imagewebp($thumbImage, $thumbPath, 50);
break;
case 'jpg':
case 'jpeg':
imagejpeg($thumbImage, $thumbPath, 50);
break;
case 'png':
imagepng($thumbImage, $thumbPath, 6); // Compression level for PNG: 0-9
break;
}
imagedestroy($originalImage);
imagedestroy($thumbImage);
}
/**
* Get the categorized folder names within the image directory.
* @return string[]
*/
function getCategoryFolderNames(): array {
$dirs = glob(buildPath(IMAGES_DIR, '*'), GLOB_ONLYDIR);
$dirNames = array_map(fn(string $dir) => basename($dir), $dirs);
return array_reverse($dirNames);
}
/**
* Check that a given category exists.
*/
function categoryExists(string $category): bool {
$expectedPath = buildPath(IMAGES_DIR, $category);
return is_dir($expectedPath);
}
/**
* Get the image filenames for the images within the given
* category folder.
* @return string[]
*/
function getCategoryImageFiles(string $category): array {
$extensions = ['webp', 'jpg', 'jpeg', 'png'];
$images = [];
foreach ($extensions as $extension) {
$images = array_merge($images, glob(buildPath(IMAGES_DIR, $category, "*.{$extension}")));
}
return array_map(fn(string $dir) => basename($dir), $images);
}
/**
* Get the images within the given category.
* @return Image[]
*/
function getCategoryImages(string $category): array {
$files = getCategoryImageFiles($category);
$images = array_map(function(string $file) use ($category) {
$imagePath = buildPath('images', $category, $file);
[$width, $height] = getimagesize($imagePath);
return new Image(
name: $file,
width: $width,
height: $height,
uri: "./images/{$category}/{$file}",
thumb: "./thumbs/{$category}/{$file}"
);
}, $files);
foreach ($files as $fileName) {
generateImageThumbnail($category, $fileName);
}
return $images;
}
/**
* Build a directory path from the given path parts.
*/
function buildPath(...$parts): string {
return implode(DIRECTORY_SEPARATOR, $parts);
}
/**
* Render and send the page of the given name to the user.
*/
function sendPage(string $name, array $data = [], int $status = 200): void {
global $config;
$mergedData = array_merge($data, ['config' => $config]);
extract($mergedData);
header('Content-Type: text/html; charset=utf-8');
http_response_code($status);
include "templates/shared/header.php";
include "templates/{$name}.php";
include "templates/shared/footer.php";
}
/**
* Load the config file from the parent directory.
*/
function loadConfig(): array {
$configPath = buildPath(dirname(__DIR__), 'config.php');
$config = include $configPath;
return $config;
}
/**
* Error out stop the application, showing the given message.
*/
function error(string $message): never {
echo "An error occurred: {$message}";
http_response_code(500);
exit(1);
}
/**
* Dump the given arguments and exit.
* (Dump & die)
*/
function dd(...$args): never {
foreach ($args as $arg) {
print_r($arg);
}
exit(1);
}
class Category {
public function __construct(
public string $name,
public string $thumb
) {}
}
class Image {
public function __construct(
public string $name,
public int $width,
public int $height,
public string $uri,
public string $thumb,
) {}
}
?>

82
public/styles.css Normal file
View File

@@ -0,0 +1,82 @@
body {
background: #222;
color: #FFF;
font-family: "Roboto", sans-serif;
font-size: 16px;
margin: 0;
padding: 0;
}
header {
font-size: 1rem;
background: #000;
margin: 0;
display: flex;
gap: 0.5rem;
padding: .5rem;
font-weight: 700;
position: sticky;
top: 0;
z-index: 5;
}
header h1 {
margin: 0;
font-size: 1rem;
}
header a {
color: #FFF;
text-decoration: none;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(480px, 48%), 1fr));
gap: 1px;
}
.gallery-item {
position: relative;
display: block;
text-decoration: none;
color: #FFF;
aspect-ratio: 1/1;
}
.gallery-item img {
position: absolute;
object-fit: cover;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.gallery-item h3 {
position: absolute;
top: 0;
left: 0;
padding: .5rem 1rem;
margin: 0;
font-size: .9rem;
box-shadow: 0 0 2px 0 rgba(0, 0, 0, 1);
background: black;
opacity: 0.7;
}
.gallery-item:hover h3 {
opacity: 1;
}
.gallery-item h3 small {
font-weight: 400;
font-size: .8rem;
}
footer {
display: flex;
gap: 1rem;
padding: 1rem;
justify-content: center;
opacity: .6;
}
footer a {
color: #fff;
text-decoration: underline;
}

13
public/templates/404.php Normal file
View File

@@ -0,0 +1,13 @@
<?php
/**
* @var string $uri
*/
?>
<h1>Not Found</h1>
<p>
Sorry, Nothing could be found at the requested
<?php echo $uri; ?>
path;
</p>

View File

@@ -0,0 +1,18 @@
<?php
/**
* @var Category[] $categories
*/
?>
<div class="gallery-grid">
<?php foreach ($categories as $category): ?>
<a href="./<?php echo $category->name; ?>" class="gallery-item">
<img src="<?php echo $category->thumb ?>" alt="<?php echo $category->name; ?>" loading="lazy">
<h3><?php echo $category->name; ?></h3>
</a>
<?php endforeach; ?>
</div>

View File

@@ -0,0 +1,28 @@
<?php
/**
* @var string $category
* @var Image[] $images
*/
?>
<header>
<a href="./">Back</a>
<span>|</span>
<h1>Category: <?php echo $category; ?></h1>
</header>
<div class="gallery-grid">
<?php foreach ($images as $image): ?>
<a href="<?php echo $image->uri; ?>" target="_blank" class="gallery-item">
<img src="<?php echo $image->thumb; ?>" alt="<?php echo $image->name; ?>" loading="lazy">
<h3>
<?php echo $image->name; ?>
<small>[<?php echo $image->width; ?>x<?php echo $image->height; ?>]</small>
</h3>
</a>
<?php endforeach; ?>
</div>

View File

@@ -0,0 +1,18 @@
<footer>
<p>
By <a href="<?php echo $config['author_link']; ?>" target="_blank" rel="noreferrer noopener"><?php echo $config['author_text']; ?></a>
</p>
<p>|</p>
<p>
Images are licensed as
<a href="<?php echo $config['license_link']; ?>" target="_blank" rel="noreferrer noopener"><?php echo $config['license_text']; ?></a>
</p>
<p>|</p>
<p>
<a href="https://github.com/ssddanbrown/gallery" target="_blank" rel="noreferrer noopener">Gallery Source Code</a>
</p>
</footer>
</body>
</html>

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<base href="<?php echo $config['base_url']; ?>">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><?php echo $config['title']; ?></title>
<link rel="stylesheet" href="styles.css">
</head>
<body>

2
public/thumbs/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore