Add single photo pages, navigateable by arrow keys and touch gestures.
This commit is contained in:
parent
e6b0c13354
commit
f86a3ce358
1
TODO.md
1
TODO.md
@ -1,6 +1,5 @@
|
||||
TODO:
|
||||
|
||||
* Pagina om één foto te bekijken
|
||||
* Taggen door gebruikers
|
||||
* Uploaden door gebruikers
|
||||
* Album management
|
||||
|
32
controllers/ViewPhoto.php
Normal file
32
controllers/ViewPhoto.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/*****************************************************************************
|
||||
* ViewPhoto.php
|
||||
* Contains the view photo controller
|
||||
*
|
||||
* Kabuki CMS (C) 2013-2016, Aaron van Geffen
|
||||
*****************************************************************************/
|
||||
|
||||
class ViewPhoto extends HTMLController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$photo = Asset::fromSlug($_GET['slug']);
|
||||
if (empty($photo))
|
||||
throw new NotFoundException();
|
||||
|
||||
parent::__construct($photo->getTitle() . ' - ' . SITE_TITLE);
|
||||
$page = new PhotoPage($photo->getImage());
|
||||
|
||||
// Exif data?
|
||||
$exif = EXIF::fromFile($photo->getFullPath());
|
||||
if ($exif)
|
||||
$page->setExif($exif);
|
||||
|
||||
$this->page->adopt($page);
|
||||
$this->page->setCanonicalUrl($photo->getPageUrl());
|
||||
|
||||
// Add an edit button to the admin bar.
|
||||
if (Registry::get('user')->isAdmin())
|
||||
$this->admin_bar->appendItem(BASEURL . '/editasset/?id=' . $photo->getId(), 'Edit this photo');
|
||||
}
|
||||
}
|
@ -32,9 +32,7 @@ class Asset
|
||||
|
||||
public static function fromId($id_asset, $return_format = 'object')
|
||||
{
|
||||
$db = Registry::get('db');
|
||||
|
||||
$row = $db->queryAssoc('
|
||||
$row = Registry::get('db')->queryAssoc('
|
||||
SELECT *
|
||||
FROM assets
|
||||
WHERE id_asset = {int:id_asset}',
|
||||
@ -42,16 +40,31 @@ class Asset
|
||||
'id_asset' => $id_asset,
|
||||
]);
|
||||
|
||||
// Asset not found?
|
||||
if (empty($row))
|
||||
return false;
|
||||
return empty($row) ? false : self::byRow($row, $return_format);
|
||||
}
|
||||
|
||||
$row['meta'] = $db->queryPair('
|
||||
public static function fromSlug($slug, $return_format = 'object')
|
||||
{
|
||||
$row = Registry::get('db')->queryAssoc('
|
||||
SELECT *
|
||||
FROM assets
|
||||
WHERE slug = {string:slug}',
|
||||
[
|
||||
'slug' => $slug,
|
||||
]);
|
||||
|
||||
return empty($row) ? false : self::byRow($row, $return_format);
|
||||
}
|
||||
|
||||
public static function byRow(array $row, $return_format = 'object')
|
||||
{
|
||||
// Supplement with metadata.
|
||||
$row['meta'] = Registry::get('db')->queryPair('
|
||||
SELECT variable, value
|
||||
FROM assets_meta
|
||||
WHERE id_asset = {int:id_asset}',
|
||||
[
|
||||
'id_asset' => $id_asset,
|
||||
'id_asset' => $row['id_asset'],
|
||||
]);
|
||||
|
||||
return $return_format == 'object' ? new Asset($row) : $row;
|
||||
@ -254,6 +267,11 @@ class Asset
|
||||
return $this->meta;
|
||||
}
|
||||
|
||||
public function getFullPath()
|
||||
{
|
||||
return ASSETSDIR . '/' . $this->subdir . '/' . $this->filename;
|
||||
}
|
||||
|
||||
public function getPath()
|
||||
{
|
||||
return $this->subdir;
|
||||
@ -282,6 +300,11 @@ class Asset
|
||||
return BASEURL . '/assets/' . $this->subdir . '/' . $this->filename;
|
||||
}
|
||||
|
||||
public function getPageUrl()
|
||||
{
|
||||
return BASEURL . '/' . $this->slug . '/';
|
||||
}
|
||||
|
||||
public function getType()
|
||||
{
|
||||
return substr($this->mimetype, 0, strpos($this->mimetype, '/'));
|
||||
|
@ -53,6 +53,12 @@ class Dispatcher
|
||||
$_GET = array_merge($_GET, $path);
|
||||
return new ViewPhotoAlbum();
|
||||
}
|
||||
// A photo for sure, then, right?
|
||||
elseif (preg_match('~^/(?<slug>.+?)/?$~', $_SERVER['PATH_INFO'], $path))
|
||||
{
|
||||
$_GET = array_merge($_GET, $path);
|
||||
return new ViewPhoto();
|
||||
}
|
||||
// No idea, then?
|
||||
else
|
||||
throw new NotFoundException();
|
||||
|
@ -246,7 +246,7 @@ class Tag
|
||||
|
||||
public function getUrl()
|
||||
{
|
||||
return BASEURL . '/tag/' . $this->slug . '/';
|
||||
return BASEURL . '/' . $this->slug . '/';
|
||||
}
|
||||
|
||||
public function save()
|
||||
|
@ -474,6 +474,103 @@ textarea {
|
||||
}
|
||||
|
||||
|
||||
/* Styling for the photo pages
|
||||
--------------------------------*/
|
||||
#photo_frame {
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
#photo_frame a {
|
||||
background: #fff;
|
||||
border: 0.9em solid #fff;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
cursor: -moz-zoom-in;
|
||||
display: inline-block;
|
||||
}
|
||||
#photo_frame a img {
|
||||
border: none;
|
||||
display: block;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#previous_photo, #next_photo {
|
||||
background: #fff;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
color: #678FA4;
|
||||
font-size: 3em;
|
||||
line-height: 0.5;
|
||||
padding: 32px 8px;
|
||||
position: fixed;
|
||||
text-decoration: none;
|
||||
top: 45%;
|
||||
}
|
||||
#previous_photo em, #next_photo em {
|
||||
position: absolute;
|
||||
top: -1000em;
|
||||
left: -1000em;
|
||||
}
|
||||
span#previous_photo, span#next_photo {
|
||||
opacity: 0.25;
|
||||
}
|
||||
a#previous_photo:hover, a#next_photo:hover {
|
||||
background: #eee;
|
||||
color: #000;
|
||||
}
|
||||
#previous_photo {
|
||||
left: 0;
|
||||
}
|
||||
#previous_photo:before {
|
||||
content: '←';
|
||||
}
|
||||
#next_photo {
|
||||
right: 0;
|
||||
}
|
||||
#next_photo:before {
|
||||
content: '→';
|
||||
}
|
||||
|
||||
#sub_photo h2, #sub_photo h3, #photo_exif_box h3 {
|
||||
font: 600 20px/30px "Open Sans", sans-serif;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
#sub_photo h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#sub_photo {
|
||||
background: #fff;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
float: left;
|
||||
padding: 2%;
|
||||
margin: 25px 3.5% 25px 0;
|
||||
width: 68.5%;
|
||||
}
|
||||
|
||||
#photo_exif_box {
|
||||
background: #fff;
|
||||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
margin: 25px 0 25px 0;
|
||||
overflow: auto;
|
||||
padding: 2%;
|
||||
float: right;
|
||||
width: 20%;
|
||||
}
|
||||
#photo_exif_box dt {
|
||||
font-weight: bold;
|
||||
float: left;
|
||||
clear: left;
|
||||
width: 120px;
|
||||
}
|
||||
#photo_exif_box dt:after {
|
||||
content: ':';
|
||||
}
|
||||
#photo_exif_box dd {
|
||||
float: left;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
/* Responsive: smartphone in portrait
|
||||
---------------------------------------*/
|
||||
@media only screen and (max-width: 895px) {
|
||||
|
72
public/js/photonav.js
Normal file
72
public/js/photonav.js
Normal file
@ -0,0 +1,72 @@
|
||||
function enableKeyDownNavigation() {
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (event.keyCode == 37) {
|
||||
var target = document.getElementById("previous_photo").href;
|
||||
if (target) {
|
||||
event.preventDefault();
|
||||
document.location.href = target;
|
||||
}
|
||||
}
|
||||
else if (event.keyCode == 39) {
|
||||
var target = document.getElementById("next_photo").href;
|
||||
if (target) {
|
||||
event.preventDefault();
|
||||
document.location.href = target;
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
function disableKeyDownPropagation(obj) {
|
||||
for (var x = 0; x < obj.length; x++) {
|
||||
obj[x].addEventListener("keydown", function (event) {
|
||||
if (event.keyCode == 37 || event.keyCode == 39) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function enableTouchNavigation() {
|
||||
var x_down = null;
|
||||
var y_down = null;
|
||||
|
||||
document.addEventListener('touchstart', function(event) {
|
||||
x_down = event.touches[0].clientX;
|
||||
y_down = event.touches[0].clientY;
|
||||
}, false);
|
||||
|
||||
document.addEventListener('touchmove', function(event) {
|
||||
if (!x_down || !y_down) {
|
||||
return;
|
||||
}
|
||||
|
||||
var x_diff = x_down - event.touches[0].clientX;
|
||||
var y_diff = y_down - event.touches[0].clientY;
|
||||
|
||||
if (Math.abs(y_diff) > 50) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Math.abs(x_diff) > Math.abs(y_diff)) {
|
||||
if (x_diff > 0) {
|
||||
var target = document.getElementById("previous_photo").href;
|
||||
if (target) {
|
||||
event.preventDefault();
|
||||
document.location.href = target;
|
||||
}
|
||||
} else {
|
||||
var target = document.getElementById("next_photo").href;
|
||||
if (target) {
|
||||
event.preventDefault();
|
||||
document.location.href = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
enableKeyDownNavigation();
|
||||
enableTouchNavigation();
|
||||
disableKeyDownPropagation(document.getElementsByTagName("textarea"));
|
||||
disableKeyDownPropagation(document.getElementsByTagName("input"));
|
138
templates/PhotoPage.php
Normal file
138
templates/PhotoPage.php
Normal file
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
/*****************************************************************************
|
||||
* PhotoPage.php
|
||||
* Contains the photo page template.
|
||||
*
|
||||
* Kabuki CMS (C) 2013-2016, Aaron van Geffen
|
||||
*****************************************************************************/
|
||||
|
||||
class PhotoPage extends SubTemplate
|
||||
{
|
||||
private $photo;
|
||||
private $exif;
|
||||
|
||||
public function __construct(Image $photo)
|
||||
{
|
||||
$this->photo = $photo;
|
||||
}
|
||||
|
||||
protected function html_content()
|
||||
{
|
||||
$this->photoNav();
|
||||
$this->photo();
|
||||
|
||||
echo '
|
||||
<div id="sub_photo">
|
||||
<h2 class="entry-title">', $this->photo->getTitle(), '</h2>';
|
||||
|
||||
$this->taggedPeople();
|
||||
|
||||
echo '
|
||||
</div>';
|
||||
|
||||
$this->photoMeta();
|
||||
|
||||
echo '
|
||||
<script type="text/javascript" src="', BASEURL, '/js/photonav.js"></script>';
|
||||
}
|
||||
|
||||
private function photo()
|
||||
{
|
||||
echo '
|
||||
<div id="photo_frame">
|
||||
<a href="', $this->photo->getUrl(), '">';
|
||||
|
||||
if ($this->photo->isPortrait())
|
||||
echo '
|
||||
<img src="', $this->photo->getThumbnailUrl(null, 960), '" alt="">';
|
||||
else
|
||||
echo '
|
||||
<img src="', $this->photo->getThumbnailUrl(1280, null), '" alt="">';
|
||||
|
||||
echo '
|
||||
</a>
|
||||
</div>';
|
||||
}
|
||||
|
||||
private function photoNav()
|
||||
{
|
||||
if (false) // $previous_post = $this->post->getPreviousPostUrl())
|
||||
echo '
|
||||
<a href="', $previous_post, '" id="previous_photo"><em>Previous photo</em></a>';
|
||||
else
|
||||
echo '
|
||||
<span id="previous_photo"><em>Previous photo</em></span>';
|
||||
|
||||
if (false) //$this->post->getNextPostUrl())
|
||||
echo '
|
||||
<a href="', $next_post, '" id="next_photo"><em>Next photo</em></a>';
|
||||
else
|
||||
echo '
|
||||
<span id="next_photo"><em>Next photo</em></span>';
|
||||
}
|
||||
|
||||
private function photoMeta()
|
||||
{
|
||||
echo '
|
||||
<div id="photo_exif_box">
|
||||
<h3>EXIF</h3>
|
||||
<dl class="photo_meta">';
|
||||
|
||||
if (!empty($this->exif->created_timestamp))
|
||||
echo '
|
||||
<dt>Date Taken</dt>
|
||||
<dd>', date("j M Y, H:i:s", $this->exif->created_timestamp), '</dd>';
|
||||
|
||||
if (!empty($this->exif->camera))
|
||||
echo '
|
||||
<dt>Model</dt>
|
||||
<dd>', $this->exif->camera, '</dd>';
|
||||
|
||||
if (!empty($this->exif->shutter_speed))
|
||||
echo '
|
||||
<dt>Shutter Speed</dt>
|
||||
<dd>', $this->exif->shutterSpeedFraction(), '</dd>';
|
||||
|
||||
if (!empty($this->exif->aperture))
|
||||
echo '
|
||||
<dt>Aperture</dt>
|
||||
<dd>f/', number_format($this->exif->aperture, 1), '</dd>';
|
||||
|
||||
if (!empty($this->exif->focal_length))
|
||||
echo '
|
||||
<dt>Focal Length</dt>
|
||||
<dd>', $this->exif->focal_length, ' mm</dd>';
|
||||
|
||||
if (!empty($this->exif->iso))
|
||||
echo '
|
||||
<dt>ISO Speed</dt>
|
||||
<dd>', $this->exif->iso, '</dd>';
|
||||
|
||||
echo '
|
||||
</dl>
|
||||
</div>';
|
||||
}
|
||||
|
||||
private function taggedPeople()
|
||||
{
|
||||
echo '
|
||||
<h3>Tags</h3>
|
||||
<ul>';
|
||||
|
||||
foreach ($this->photo->getTags() as $tag)
|
||||
{
|
||||
echo '
|
||||
<li>
|
||||
<a rel="tag" title="View all posts tagged ', $tag->tag, '" href="', $tag->getUrl(), '" class="entry-tag">', $tag->tag, '</a>
|
||||
</li>';
|
||||
}
|
||||
|
||||
echo '
|
||||
</ul>';
|
||||
}
|
||||
|
||||
public function setExif(EXIF $exif)
|
||||
{
|
||||
$this->exif = $exif;
|
||||
}
|
||||
}
|
@ -94,7 +94,7 @@ class PhotosIndex extends SubTemplate
|
||||
<a class="edit" href="', BASEURL, '/editasset/?id=', $image->getId(), '">Edit</a>';
|
||||
|
||||
echo '
|
||||
<a href="', $image->getUrl(), '">
|
||||
<a href="', $image->getPageUrl(), '">
|
||||
<img src="', $image->getThumbnailUrl($width, $height, $crop, $fit), '" alt="" title="', $image->getTitle(), '">';
|
||||
|
||||
if ($this->show_labels)
|
||||
|
Loading…
Reference in New Issue
Block a user