Initial Git Commit

This commit is contained in:
seiichiro 2022-01-08 17:22:16 +01:00
commit aa20a83284
12 changed files with 696 additions and 0 deletions

0
cache/.gitkeep vendored Normal file
View file

25
conf/config.php Normal file
View file

@ -0,0 +1,25 @@
<?php
// Basic Settings
// Full Filesystem Path to the Base Image Directory
$conf['fs_imagedir'] = '/data0/Private/Pictures/Gallery';
// Full Filesystem Path to the Thumbnails Storage Directory
$conf['fs_thumbdir'] = '/var/cache/gallery/thumbs';
// Base URL for the Images Directory
$conf['web_imagedir'] = '/images';
// Base URL for the Thumbnails Directory
$conf['web_thumbdir'] = '/thumbs';
// Lifetime Filelist Cache (Filelist will be Re-Read from Disc after this time)
$conf['flcache'] = 3600;
// Defaults
// Start on this Page if none given
$conf['defpage'] = 1;
// Images per Page if not specified
$conf['defslice'] = 34;
// Default Subdirectory if none given
$conf['defdir'] = 'r34';
?>

91
css/style.css Normal file
View file

@ -0,0 +1,91 @@
body {
background-color: #000000;
font-family: sans;
}
.content {
padding: 4px;
position: relative;
width: 92vw;
height: 92vh;
background-color: #000000;
}
.single {
height: 92vh;
}
.single-image {
position: absolute;
}
.multi-image {
float: left;
padding: 6px;
width: 200px;
height: 200px;
}
.clickable {
display: block;
width: 39%;
height: 80%;
position: relative;
z-index: 9;
opacity: 0;
background-color: #000000;
float: left;
}
.clickable-center {
width: 20%;
height: 50%;
margin-left: 1%;
margin-right: 1%;
float: center;
}
.clickable-right {
float: right;
height: 80%;
}
.single-image img {
object-fit: contain;
width: 98vw;
height: 92vh;
}
.multi-image img {
object-fit: contain;
width: 200px;
height: 200px;
}
.footer {
width: 100%;
display: block;
clear: both;
text-align: center;
}
.footer a {
margin-left: 0.5em;
margin-right: 0.5em;
font-size: 3em;
font-weight: bold;
color: #ffffff;
}
.footer .info {
margin-left: 0.5em;
margin-right: 0.5em;
font-size: 3em;
font-weight: bold;
color: #ffffff;
}
.footer .tag {
margin-left: 0.5em;
margin-right: 0.5em;
font-size: 0.4em !important;
font-weight: normal !important;
color: #00ff00;
}
.search {
text-align: center;
font-weight: bold;
color: #ffffff;
}

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

94
index.php Normal file
View file

@ -0,0 +1,94 @@
<?php
// Classes
require_once('lib/helpers.class.php');
require_once('lib/thumbs.class.php');
require_once('lib/template.class.php');
// Settings
require('conf/config.php');
// Prepare Some Basic Variables
$imagedir = Helpers::end_dir($conf['fs_imagedir']);
$thumbdir = Helpers::end_dir($conf['fs_thumbdir']);
$imageurl = Helpers::end_dir($conf['web_imagedir']);
$thumburl = Helpers::end_dir($conf['web_thumbdir']);
// Get Parameters or Set Defaults
$page = $_GET['p'] ?? $conf['defpage'];
$slice = $_GET['s'] ?? $conf['defslice'];
$gslice = $_GET['gs'] ?? $conf['defslice'];
$dir = $_GET['d'] ?? $conf['defdir'];
// Create or Load Session
session_start();
// Initialize Thumbnail Handler
$t = new Thumb(Helpers::end_dir($imagedir.$dir), $thumbdir, 200, 200);
// Filelist Cache Handling
if (isset($_SESSION['files'])
&& $_SESSION['dir'] == $dir
&& (time() - $_SESSION['cachetime']) < $conf['flcache']
&& !isset($_GET['refcache']))
{
$files = $_SESSION['files'];
} else {
$files = array_diff(scandir($imagedir.$dir, SCANDIR_SORT_DESCENDING), array('..', '.'));
$_SESSION['files'] = $files;
$_SESSION['dir'] = $dir;
$_SESSION['cachetime'] = time();
}
// Apply Filters to Filelist
$filter='';
if (isset($_GET['f'])) {
if ($_GET['f'] != '') {
$files = array_values(preg_grep('/.*'.$_GET['f'].'.*/i', $files));
$filter='&f='.$_GET['f'];
}
}
// Sort and Reverse Filelist
natcasesort($files);
$files = array_reverse($files);
// Get Files for current Page
$curfiles = array_slice($files, ($page-1)*$slice ,$slice, true);
// Prepare Generic Data for Template
$data['dir'] = $dir;
$data['slice'] = $slice;
$data['filter'] = $filter;
$data['page'] = $page;
$data['prev_page'] = $page - 1;
if ($data['prev_page'] < 1)
$data['prev_page'] = 1;
$data['next_page'] = $page + 1;
if ($data['next_page'] > ceil(count($files)/$slice))
$data['next_page'] = $page;
$data['last_page'] = ceil(count($files)/$slice);
// Prepare Mode Specific Data and Render Template
if ($slice > 1) {
foreach ($curfiles as $index => $image) {
$tmp['n'] = $image;
$tmp['i'] = $index+1;
$tmp['t'] = $thumburl.$t->get_thumb($image);
$data['images'][] = $tmp;
}
Template::view('tpl/gallery.html', $data);
} else {
$data['gallery_page'] = ceil($page/$gslice);
$data['gallery_slice'] = $gslice;
foreach ($curfiles as $index => $image) {
$data['imageurl'] = $imageurl.$dir.'/'.$image;
$data['imagename'] = $image;
$data['tags'] = explode(" ", pathinfo(trim(strstr(strtolower($image), ' - '), " -"))['filename']);
}
Template::view('tpl/single.html', $data);
}
?>

223
js/shortcut.js Normal file
View file

@ -0,0 +1,223 @@
/**
* http://www.openjs.com/scripts/events/keyboard_shortcuts/
* Version : 2.01.B
* By Binny V A
* License : BSD
*/
shortcut = {
'all_shortcuts':{},//All the shortcuts are stored in this array
'add': function(shortcut_combination,callback,opt) {
//Provide a set of default options
var default_options = {
'type':'keydown',
'propagate':false,
'disable_in_input':false,
'target':document,
'keycode':false
}
if(!opt) opt = default_options;
else {
for(var dfo in default_options) {
if(typeof opt[dfo] == 'undefined') opt[dfo] = default_options[dfo];
}
}
var ele = opt.target;
if(typeof opt.target == 'string') ele = document.getElementById(opt.target);
var ths = this;
shortcut_combination = shortcut_combination.toLowerCase();
//The function to be called at keypress
var func = function(e) {
e = e || window.event;
if(opt['disable_in_input']) { //Don't enable shortcut keys in Input, Textarea fields
var element;
if(e.target) element=e.target;
else if(e.srcElement) element=e.srcElement;
if(element.nodeType==3) element=element.parentNode;
if(element.tagName == 'INPUT' || element.tagName == 'TEXTAREA') return;
}
//Find Which key is pressed
if (e.keyCode) code = e.keyCode;
else if (e.which) code = e.which;
var character = String.fromCharCode(code).toLowerCase();
if(code == 188) character=","; //If the user presses , when the type is onkeydown
if(code == 190) character="."; //If the user presses , when the type is onkeydown
var keys = shortcut_combination.split("+");
//Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked
var kp = 0;
//Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken
var shift_nums = {
"`":"~",
"1":"!",
"2":"@",
"3":"#",
"4":"$",
"5":"%",
"6":"^",
"7":"&",
"8":"*",
"9":"(",
"0":")",
"-":"_",
"=":"+",
";":":",
"'":"\"",
",":"<",
".":">",
"/":"?",
"\\":"|"
}
//Special Keys - and their codes
var special_keys = {
'esc':27,
'escape':27,
'tab':9,
'space':32,
'return':13,
'enter':13,
'backspace':8,
'scrolllock':145,
'scroll_lock':145,
'scroll':145,
'capslock':20,
'caps_lock':20,
'caps':20,
'numlock':144,
'num_lock':144,
'num':144,
'pause':19,
'break':19,
'insert':45,
'home':36,
'delete':46,
'end':35,
'pageup':33,
'page_up':33,
'pu':33,
'pagedown':34,
'page_down':34,
'pd':34,
'left':37,
'up':38,
'right':39,
'down':40,
'f1':112,
'f2':113,
'f3':114,
'f4':115,
'f5':116,
'f6':117,
'f7':118,
'f8':119,
'f9':120,
'f10':121,
'f11':122,
'f12':123
}
var modifiers = {
shift: { wanted:false, pressed:false},
ctrl : { wanted:false, pressed:false},
alt : { wanted:false, pressed:false},
meta : { wanted:false, pressed:false} //Meta is Mac specific
};
if(e.ctrlKey) modifiers.ctrl.pressed = true;
if(e.shiftKey) modifiers.shift.pressed = true;
if(e.altKey) modifiers.alt.pressed = true;
if(e.metaKey) modifiers.meta.pressed = true;
for(var i=0; k=keys[i],i<keys.length; i++) {
//Modifiers
if(k == 'ctrl' || k == 'control') {
kp++;
modifiers.ctrl.wanted = true;
} else if(k == 'shift') {
kp++;
modifiers.shift.wanted = true;
} else if(k == 'alt') {
kp++;
modifiers.alt.wanted = true;
} else if(k == 'meta') {
kp++;
modifiers.meta.wanted = true;
} else if(k.length > 1) { //If it is a special key
if(special_keys[k] == code) kp++;
} else if(opt['keycode']) {
if(opt['keycode'] == code) kp++;
} else { //The special keys did not match
if(character == k) kp++;
else {
if(shift_nums[character] && e.shiftKey) { //Stupid Shift key bug created by using lowercase
character = shift_nums[character];
if(character == k) kp++;
}
}
}
}
if(kp == keys.length &&
modifiers.ctrl.pressed == modifiers.ctrl.wanted &&
modifiers.shift.pressed == modifiers.shift.wanted &&
modifiers.alt.pressed == modifiers.alt.wanted &&
modifiers.meta.pressed == modifiers.meta.wanted) {
callback(e);
if(!opt['propagate']) { //Stop the event
//e.cancelBubble is supported by IE - this will kill the bubbling process.
e.cancelBubble = true;
e.returnValue = false;
//e.stopPropagation works in Firefox.
if (e.stopPropagation) {
e.stopPropagation();
e.preventDefault();
}
return false;
}
}
}
this.all_shortcuts[shortcut_combination] = {
'callback':func,
'target':ele,
'event': opt['type']
};
//Attach the function with the event
if(ele.addEventListener) ele.addEventListener(opt['type'], func, false);
else if(ele.attachEvent) ele.attachEvent('on'+opt['type'], func);
else ele['on'+opt['type']] = func;
},
//Remove the shortcut - just specify the shortcut and I will remove the binding
'remove':function(shortcut_combination) {
shortcut_combination = shortcut_combination.toLowerCase();
var binding = this.all_shortcuts[shortcut_combination];
delete(this.all_shortcuts[shortcut_combination])
if(!binding) return;
var type = binding['event'];
var ele = binding['target'];
var callback = binding['callback'];
if(ele.detachEvent) ele.detachEvent('on'+type, callback);
else if(ele.removeEventListener) ele.removeEventListener(type, callback, false);
else ele['on'+type] = false;
}
}

16
lib/helpers.class.php Normal file
View file

@ -0,0 +1,16 @@
<?php
class Helpers {
// Add trailing / to path if missing
public static function end_dir($dir) {
if (mb_substr($dir, -1) == '/' ) {
return $dir;
} else {
return $dir.'/';
}
}
}
?>

87
lib/template.class.php Normal file
View file

@ -0,0 +1,87 @@
<?php
class Template {
static $blocks = array();
static $cache_path = 'cache/';
static $cache_enabled = TRUE;
static function view($file, $data = array()) {
$cached_file = self::cache($file);
extract($data, EXTR_SKIP);
require $cached_file;
}
static function cache($file) {
if (!file_exists(self::$cache_path)) {
mkdir(self::$cache_path, 0744);
}
$cached_file = self::$cache_path . str_replace(array('/', '.html'), array('_', ''), $file . '.php');
if (!self::$cache_enabled || !file_exists($cached_file) || filemtime($cached_file) < filemtime($file)) {
$code = self::includeFiles($file);
$code = self::compileCode($code);
file_put_contents($cached_file, '<?php class_exists(\'' . __CLASS__ . '\') or exit; ?>' . PHP_EOL . $code);
}
return $cached_file;
}
static function clearCache() {
foreach(glob(self::$cache_path . '*') as $file) {
unlink($file);
}
}
static function compileCode($code) {
$code = self::compileBlock($code);
$code = self::compileYield($code);
$code = self::compileEscapedEchos($code);
$code = self::compileEchos($code);
$code = self::compilePHP($code);
return $code;
}
static function includeFiles($file) {
$code = file_get_contents($file);
preg_match_all('/{% ?(extends|include) ?\'?(.*?)\'? ?%}/i', $code, $matches, PREG_SET_ORDER);
foreach ($matches as $value) {
$code = str_replace($value[0], self::includeFiles($value[2]), $code);
}
$code = preg_replace('/{% ?(extends|include) ?\'?(.*?)\'? ?%}/i', '', $code);
return $code;
}
static function compilePHP($code) {
return preg_replace('~\{%\s*(.+?)\s*\%}~is', '<?php $1 ?>', $code);
}
static function compileEchos($code) {
return preg_replace('~\{{\s*(.+?)\s*\}}~is', '<?php echo $1 ?>', $code);
}
static function compileEscapedEchos($code) {
return preg_replace('~\{{{\s*(.+?)\s*\}}}~is', '<?php echo htmlentities($1, ENT_QUOTES, \'UTF-8\') ?>', $code);
}
static function compileBlock($code) {
preg_match_all('/{% ?block ?(.*?) ?%}(.*?){% ?endblock ?%}/is', $code, $matches, PREG_SET_ORDER);
foreach ($matches as $value) {
if (!array_key_exists($value[1], self::$blocks)) self::$blocks[$value[1]] = '';
if (strpos($value[2], '@parent') === false) {
self::$blocks[$value[1]] = $value[2];
} else {
self::$blocks[$value[1]] = str_replace('@parent', self::$blocks[$value[1]], $value[2]);
}
$code = str_replace($value[0], '', $code);
}
return $code;
}
static function compileYield($code) {
foreach(self::$blocks as $block => $value) {
$code = preg_replace('/{% ?yield ?' . $block . ' ?%}/', $value, $code);
}
$code = preg_replace('/{% ?yield ?(.*?) ?%}/i', '', $code);
return $code;
}
}
?>

78
lib/thumbs.class.php Normal file
View file

@ -0,0 +1,78 @@
<?php
class thumb {
protected string $imagedir;
protected string $thumbdir;
protected int $w = 200;
protected int $h = 200;
const IMAGE_HANDLERS = [
IMAGETYPE_JPEG => [
'load' => 'imagecreatefromjpeg',
'save' => 'imagejpeg',
'quality' => 100
],
IMAGETYPE_PNG => [
'load' => 'imagecreatefrompng',
'save' => 'imagepng',
'quality' => 0
],
IMAGETYPE_GIF => [
'load' => 'imagecreatefromgif',
'save' => 'imagegif',
'quality' => 0
],
IMAGETYPE_WEBP => [
'load' => 'imagecreatefromwebp',
'save' => 'imagecreatewebp',
'quality' => 90
]
];
public function __construct($idir, $tdir, $width, $heigth) {
$this->imagedir = $idir;
$this->thumbdir = $tdir;
$this->w = $width;
$this->h = $heigth;
}
public function get_thumb($iname) {
$src = $this->imagedir.$iname;
$ext = pathinfo($iname, PATHINFO_EXTENSION);
$dstname = hash('sha256', $iname);
$dstpath = $this->thumbdir.$dstname.'.'.$ext;
if (!file_exists($dstpath)) {
$type = exif_imagetype($src);
$image = call_user_func(self::IMAGE_HANDLERS[$type]['load'], $src);
$srcwidth = imagesx($image);
$srcheight = imagesy($image);
$ratio = min($this->w / $srcwidth, $this->h / $srcheight);
$width = round($srcwidth*$ratio);
$height = round($srcheight*$ratio);
$thumbnail = imagecreatetruecolor($width, $height);
if ($type == IMAGETYPE_GIF || $type == IMAGETYPE_PNG) {
imagecolortransparent($thumbnail, imagecolorallocate($thumbnail, 0, 0, 0));
if ($type == IMAGETYPE_PNG) {
imagealphablending($thumbnail, false);
imagesavealpha($thumbnail, true);
}
}
imagecopyresampled($thumbnail, $image, 0, 0, 0, 0, $width, $height, $srcwidth, $srcheight);
call_user_func(self::IMAGE_HANDLERS[$type]['save'],$thumbnail,$dstpath,self::IMAGE_HANDLERS[$type]['quality']);
imagedestroy($image);
imagedestroy($thumbnail);
}
return $dstname.'.'.$ext;
}
}
?>

32
tpl/gallery.html Normal file
View file

@ -0,0 +1,32 @@
{% extends tpl/layout.html %}
{% block title %}Gallery{% endblock %}
{% block content %}
<div class="content">
<div class="page-header">
<div class="search">
<form action="index.php" method="get">
Suchen: <input type="text" name="f" />
<input type="submit" />
<input type="hidden" name="d" value="{{ $dir }}">
<input type="hidden" name="s" value="{{ $slice }}">
</form>
</div>
</div>
{% foreach($images as $image): %}
<div class="multi-image">
<a href="index.php?d={{ $dir }}&s=1&gs={{ $slice }}&p={{ $image['i'] }}{{ $filter }}">
<img src="{{ $image['t'] }}" alt="{{ $image['n'] }}"/>
</a>
</div>
{% endforeach %}
<div class="footer page-footer">
<a id="link-first" href="/index.php?d={{ $dir }}&p=1&s={{ $slice }}{{ $filter }}">1</a>
<a id="link-prev" href="/index.php?d={{ $dir }}&p={{ $prev_page }}&s={{ $slice }}{{ $filter }}">&lt;&lt;</a>
<span class="info">{{ $page }}</span>
<a id="link-next" href="/index.php?d={{ $dir }}&p={{ $next_page }}&s={{ $slice }}{{ $filter }}">&gt;&gt;</a>
<a id="link-last" href="/index.php?d={{ $dir }}&p={{ $last_page }}&s={{ $slice }}{{ $filter }}">{{ $last_page }}</a>
</div>
</div>
{% endblock %}

23
tpl/layout.html Normal file
View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="css/style.css">
<script src="js/shortcut.js"></script>
<title>{% yield title %}</title>
</head>
<body>
{% yield content %}
<script>
addEventListener("DOMContentLoaded", function(){
shortcut.add("Left", function() { document.getElementById("link-prev").click(); }, { 'disable_in_input': true, 'propagate': true });
shortcut.add("Right", function() { document.getElementById("link-next").click(); }, { 'disable_in_input': true, 'propagate': true });
shortcut.add("Home", function() { document.getElementById("link-home").click(); }, {'disable_in_input': true, 'propagate': true });
});
</script>
</body>
</html>

27
tpl/single.html Normal file
View file

@ -0,0 +1,27 @@
{% extends tpl/layout.html %}
{% block title %}{{ $page }}/{{ $last_page }} | {{ $imagename }}{% endblock %}
{% block content %}
<div class="content single">
<div class="single-image">
<img src="{{ $imageurl }}" title="{{ $page }}/{{ $last_page }}" />
</div>
<a id="link-prev" href="/index.php?d={{ $dir }}&p={{ $prev_page }}&s=1&gs={{ $gallery_slice }}{{ $filter }}">
<div class="clickable"></div>
</a>
<a id="link-home" href="/index.php?d={{ $dir }}&p={{ $gallery_page }}&s={{ $gallery_slice }}{{ $filter }}">
<div class="clickable clickable-center"></div>
</a>
<a id="link-next" href="/index.php?d={{ $dir }}&p={{ $next_page }}&s=1&gs={{ $gallery_slice }}{{ $filter }}">
<div class="clickable clickable-right"></div>
</a>
</div>
<div class="footer">
{% foreach($tags as $tag): %}
<a href="/index.php?d={{ $dir }}&s={{ $gallery_slice }}&f={{{ $tag }}}"><span class="tag">{{ $tag }}</span></a>
{% endforeach %}
</div>
{% endblock %}