Albums are now displayed on the index page. Logged in users now get a gravatar displayed. Thumbnails are now also served and displayed on the index page.
This commit is contained in:
parent
c2a65accdf
commit
b08a0e4710
@ -49,6 +49,28 @@ class Album extends Model
|
||||
public function getAlbumSource()
|
||||
{
|
||||
// TODO allow albums to specify different storage locations - e.g. Amazon S3, SFTP/FTP, OpenStack
|
||||
return new LocalFilesystemSource(dirname(__DIR__) . '/storage/app');
|
||||
return new LocalFilesystemSource(dirname(__DIR__) . '/storage/app/albums');
|
||||
}
|
||||
|
||||
public function photos()
|
||||
{
|
||||
return $this->hasMany(Photo::class);
|
||||
}
|
||||
|
||||
public function thumbnailUrl($thumbnailName)
|
||||
{
|
||||
$photo = Photo::where('album_id', $this->id)->first();
|
||||
|
||||
if (!is_null($photo))
|
||||
{
|
||||
return $this->getAlbumSource()->getUrlToPhoto($this, $photo, $thumbnailName);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function url()
|
||||
{
|
||||
return sprintf('/%s', urlencode($this->url_alias));
|
||||
}
|
||||
}
|
@ -8,15 +8,35 @@ use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
interface IAlbumSource
|
||||
{
|
||||
function getOriginalsFolder();
|
||||
|
||||
/**
|
||||
* Gets the absolute path or URL to the given photo file.
|
||||
* @param Album $album
|
||||
* @param Photo $photo
|
||||
* Gets the absolute path to the given photo file.
|
||||
* @param Album $album Album containing the photo.
|
||||
* @param Photo $photo Photo to get the path to.
|
||||
* @param string $thumbnail Thumbnail to get the image to.
|
||||
* @return string
|
||||
*/
|
||||
function getPathToPhoto(Album $album, Photo $photo);
|
||||
function getPathToPhoto(Album $album, Photo $photo, $thumbnail);
|
||||
|
||||
function saveThumbnail(Album $album, Photo $photo, $thumbnailImageResource, $thumbnailInfo);
|
||||
/**
|
||||
* Gets the absolute URL to the given photo file.
|
||||
* @param Album $album Album containing the photo.
|
||||
* @param Photo $photo Photo to get the URL to.
|
||||
* @param string $thumbnail Thumbnail to get the image to.
|
||||
* @return string
|
||||
*/
|
||||
function getUrlToPhoto(Album $album, Photo $photo, $thumbnail);
|
||||
|
||||
/**
|
||||
* Saves a generated thumbnail to its permanent location.
|
||||
* @param Album $album Album containing the photo.
|
||||
* @param Photo $photo Photo the thumbnail relates to.
|
||||
* @param string $thumbnailInfo Information about the thumbnail.
|
||||
* @param string $tempFilename Filename containing the thumbnail.
|
||||
* @return mixed
|
||||
*/
|
||||
function saveThumbnail(Album $album, Photo $photo, $thumbnailInfo, $tempFilename);
|
||||
|
||||
/**
|
||||
* Saves an uploaded file to the container and returns the filename.
|
||||
|
@ -7,6 +7,10 @@ use App\Helpers\MiscHelper;
|
||||
use App\Photo;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
/**
|
||||
* Driver for managing files on the local filesystem.
|
||||
* @package App\AlbumSources
|
||||
*/
|
||||
class LocalFilesystemSource implements IAlbumSource
|
||||
{
|
||||
private $parentFolder;
|
||||
@ -16,18 +20,42 @@ class LocalFilesystemSource implements IAlbumSource
|
||||
$this->parentFolder = $parentFolder;
|
||||
}
|
||||
|
||||
public function getPathToPhoto(Album $album, Photo $photo)
|
||||
public function getOriginalsFolder()
|
||||
{
|
||||
return sprintf('%s/%s', $this->getPathToAlbum($album), $photo->file_name);
|
||||
return '_originals';
|
||||
}
|
||||
|
||||
public function saveThumbnail(Album $album, Photo $photo, $thumbnailImageResource, $thumbnailInfo)
|
||||
public function getPathToPhoto(Album $album, Photo $photo, $thumbnail = null)
|
||||
{
|
||||
if (is_null($thumbnail))
|
||||
{
|
||||
$thumbnail = $this->getOriginalsFolder();
|
||||
}
|
||||
|
||||
return sprintf('%s/%s/%s', $this->getPathToAlbum($album), $thumbnail, $photo->file_name);
|
||||
}
|
||||
|
||||
public function getUrlToPhoto(Album $album, Photo $photo, $thumbnail = null)
|
||||
{
|
||||
$photoUrl = sprintf('%s/%s', urlencode($album->url_alias), urlencode($photo->file_name));
|
||||
|
||||
if (!is_null($thumbnail))
|
||||
{
|
||||
$photoUrl .= sprintf('?t=%s', urlencode($thumbnail));
|
||||
}
|
||||
|
||||
return url($photoUrl);
|
||||
}
|
||||
|
||||
public function saveThumbnail(Album $album, Photo $photo, $thumbnailInfo, $tempFilename)
|
||||
{
|
||||
$fileInfo = new File($tempFilename);
|
||||
$fileInfo->move(sprintf('%s/%s', $this->getPathToAlbum($album), $thumbnailInfo['name']), $photo->file_name);
|
||||
}
|
||||
|
||||
public function saveUploadedPhoto(Album $album, File $uploadedFile)
|
||||
{
|
||||
$tempFilename = sprintf('%s/photo_%s', $this->getPathToAlbum($album), MiscHelper::randomString(20));
|
||||
$tempFilename = sprintf('%s/%s/%s', $this->getPathToAlbum($album), $this->getOriginalsFolder(), MiscHelper::randomString(20));
|
||||
|
||||
$extension = $uploadedFile->guessExtension();
|
||||
if (!is_null($extension))
|
||||
@ -41,6 +69,6 @@ class LocalFilesystemSource implements IAlbumSource
|
||||
|
||||
private function getPathToAlbum(Album $album)
|
||||
{
|
||||
return sprintf('%s/albums/%s', $this->parentFolder, $album->url_alias);
|
||||
return sprintf('%s/%s', $this->parentFolder, $album->url_alias);
|
||||
}
|
||||
}
|
@ -117,19 +117,16 @@ class ProcessUploadCommand extends Command
|
||||
$photo->camera_software = $this->metadataCameraSoftware($exifData);
|
||||
$photo->save();
|
||||
|
||||
// Generate thumbnails
|
||||
// Generate and save thumbnails
|
||||
$this->output->writeln('Generating thumbnails');
|
||||
$themeInfo = $this->themeHelper->info();
|
||||
$thumbnailsRequired = $themeInfo['thumbnails'];
|
||||
|
||||
/** @var mixed $thumbnail */
|
||||
foreach ($thumbnailsRequired as $thumbnail)
|
||||
{
|
||||
$this->imageHelper->generateThumbnail(
|
||||
$originalPhotoResource,
|
||||
$photo,
|
||||
$albumSource,
|
||||
$thumbnail
|
||||
);
|
||||
$generatedThumbnailPath = $this->imageHelper->generateThumbnail($originalPhotoResource, $photo, $thumbnail);
|
||||
$albumSource->saveThumbnail($album, $photo, $thumbnail, $generatedThumbnailPath);
|
||||
|
||||
$this->output->writeln(sprintf('Thumbnail \'%s\' (%dx%d) created', $thumbnail['name'], $thumbnail['width'], $thumbnail['height']));
|
||||
}
|
||||
|
@ -7,12 +7,7 @@ use App\Photo;
|
||||
|
||||
class ImageHelper
|
||||
{
|
||||
public function generateThumbnail(
|
||||
$gdImageResource,
|
||||
Photo $photo,
|
||||
IAlbumSource $storage,
|
||||
$thumbnailInfo
|
||||
)
|
||||
public function generateThumbnail($gdImageResource, Photo $photo, $thumbnailInfo)
|
||||
{
|
||||
$thumbnailWidth = intval($thumbnailInfo['width']);
|
||||
$thumbnailHeight = intval($thumbnailInfo['height']);
|
||||
@ -30,11 +25,16 @@ class ImageHelper
|
||||
$photo->width,
|
||||
$photo->height
|
||||
);
|
||||
$tempName = tempnam('/tmp', 'blue_twilight_thumbnail_');
|
||||
rename($tempName, $tempName . '.jpg');
|
||||
|
||||
// TODO make the /tmp folder configurable
|
||||
$tempName = tempnam('/tmp', 'btw_thumb_');
|
||||
$tempNameWithExtension = ($tempName . '.jpg');
|
||||
rename($tempName, $tempNameWithExtension);
|
||||
|
||||
// TODO make thumbnail quality configurable
|
||||
imagejpeg($thumbnailImageResource, $tempName . '.jpg', 90);
|
||||
imagejpeg($thumbnailImageResource, $tempNameWithExtension, 90);
|
||||
|
||||
return $tempNameWithExtension;
|
||||
}
|
||||
|
||||
public function openImage($imagePath, &$imageInfo)
|
||||
|
@ -4,6 +4,12 @@ namespace App\Helpers;
|
||||
|
||||
class MiscHelper
|
||||
{
|
||||
public static function gravatarUrl($emailAddress, $size = 48, $default = 'identicon')
|
||||
{
|
||||
$hash = md5(strtolower(trim($emailAddress)));
|
||||
return sprintf('https://www.gravatar.com/avatar/%s?s=%d&d=%s', $hash, $size, $default);
|
||||
}
|
||||
|
||||
public static function randomString($length = 10)
|
||||
{
|
||||
$seed = 'abcdefghijklmnopqrstuvwxyz01234567890';
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Gallery;
|
||||
|
||||
use App\Album;
|
||||
use App\Facade\Theme;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
@ -9,6 +10,10 @@ class DefaultController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return Theme::render('gallery.index');
|
||||
$albums = Album::all()->sortBy('name');
|
||||
|
||||
return Theme::render('gallery.index', [
|
||||
'albums' => $albums
|
||||
]);
|
||||
}
|
||||
}
|
60
app/Http/Controllers/Gallery/PhotoController.php
Normal file
60
app/Http/Controllers/Gallery/PhotoController.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Gallery;
|
||||
|
||||
use App\Album;
|
||||
use app\Http\Controllers\Admin\AlbumController;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Photo;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class PhotoController extends Controller
|
||||
{
|
||||
public function view(Request $request, $albumUrlAlias, $photoFilename)
|
||||
{
|
||||
$album = PhotoController::loadAlbumByAlias($albumUrlAlias);
|
||||
$albumSource = $album->getAlbumSource();
|
||||
|
||||
$thumbnail = $request->get('t', $albumSource->getOriginalsFolder());
|
||||
$photo = PhotoController::loadPhotoByAlbumAndFilename($album, $photoFilename);
|
||||
|
||||
return response()->file($albumSource->getPathToPhoto($album, $photo, $thumbnail));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $id
|
||||
* @return Album
|
||||
*/
|
||||
public static function loadAlbumByAlias($alias)
|
||||
{
|
||||
$album = Album::where('url_alias', $alias)->first();
|
||||
if (is_null($album))
|
||||
{
|
||||
App::abort(404);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $album;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $id
|
||||
* @return Photo
|
||||
*/
|
||||
public static function loadPhotoByAlbumAndFilename(Album $album, $filename)
|
||||
{
|
||||
$photo = Photo::where([
|
||||
['album_id', $album->id],
|
||||
['file_name', $filename]
|
||||
])->first();
|
||||
|
||||
if (is_null($photo))
|
||||
{
|
||||
App::abort(404);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $photo;
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Album;
|
||||
use App\Facade\Theme;
|
||||
use App\Helpers\ImageHelper;
|
||||
use App\Helpers\ThemeHelper;
|
||||
@ -27,13 +28,11 @@ class AppServiceProvider extends ServiceProvider
|
||||
return new ThemeHelper();
|
||||
});
|
||||
|
||||
try
|
||||
// When running migrations or CLI tasks, don't need to add things to the view
|
||||
if (php_sapi_name() != 'cli')
|
||||
{
|
||||
$this->addThemeInfoToView();
|
||||
}
|
||||
catch (QueryException $ex)
|
||||
{
|
||||
// When running migrations, the configuration table may not exist - so ignore and continue
|
||||
$this->addAlbumsToView();
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,6 +46,12 @@ class AppServiceProvider extends ServiceProvider
|
||||
//
|
||||
}
|
||||
|
||||
private function addAlbumsToView()
|
||||
{
|
||||
$albums = Album::all()->sortBy('name');
|
||||
View::share('albums', $albums);
|
||||
}
|
||||
|
||||
private function addThemeInfoToView()
|
||||
{
|
||||
$themeInfo = Theme::info();
|
||||
|
15
public/themes/base/css/app.css
vendored
15
public/themes/base/css/app.css
vendored
@ -1,15 +0,0 @@
|
||||
@import url(https://fonts.googleapis.com/css?family=Raleway:300,400,600);
|
||||
|
||||
* {
|
||||
font-family: Raleway, sans-serif;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
border: solid 1px #ddd;
|
||||
border-top: 0;
|
||||
padding: 10px;
|
||||
}
|
25
public/themes/bootstrap3/theme.css
vendored
Normal file
25
public/themes/bootstrap3/theme.css
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
@import url(https://fonts.googleapis.com/css?family=Raleway:300,400,600);
|
||||
|
||||
* {
|
||||
font-family: Raleway, sans-serif;
|
||||
}
|
||||
|
||||
.album-index img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.navbar .avatar {
|
||||
left: -28px;
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
border: solid 1px #ddd;
|
||||
border-top: 0;
|
||||
padding: 10px;
|
||||
}
|
@ -1,2 +1,25 @@
|
||||
@extends('themes.base.layout')
|
||||
@section('title', 'Welcome')
|
||||
@section('title', 'Welcome')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
@foreach ($albums as $album)
|
||||
<div class="col-xs-12 col-sm-4">
|
||||
<div class="panel panel-default album-index">
|
||||
<div class="panel-heading"><a href="{{ $album->url() }}">{{ $album->name }}</a></div>
|
||||
<div class="panel-body">
|
||||
<p class="text-center">
|
||||
<img src="{{ $album->thumbnailUrl('preview') }}"/>
|
||||
</p>
|
||||
<p>{{ $album->description }}</p>
|
||||
</div>
|
||||
<div class="panel-footer text-right">
|
||||
<a href="{{ $album->url() }}" class="btn btn-sm btn-default">Open album</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
@ -14,18 +14,12 @@
|
||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
|
||||
<li><a href="#">Link</a></li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Albums <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#">Action</a></li>
|
||||
<li><a href="#">Another action</a></li>
|
||||
<li><a href="#">Something else here</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="#">Separated link</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="#">One more separated link</a></li>
|
||||
@foreach ($albums as $album)
|
||||
<li><a href="{{ $album->url() }}">{{ $album->name }}</a></li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@ -40,12 +34,7 @@
|
||||
</li>
|
||||
@endcan
|
||||
</ul>
|
||||
<form class="navbar-form navbar-left">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" placeholder="Search">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">Submit</button>
|
||||
</form>
|
||||
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{{-- Authentication Links --}}
|
||||
@if (Auth::guest())
|
||||
@ -54,16 +43,13 @@
|
||||
@else
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
|
||||
<img class="avatar" src="{{ \App\Helpers\MiscHelper::gravatarUrl(Auth::user()->email, 32) }}" alt="{{ Auth::user()->name }}" title="{{ Auth::user()->name }}" />
|
||||
{{ Auth::user()->name }} <span class="caret"></span>
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li>
|
||||
<a href="{{ url('/logout') }}"
|
||||
onclick="event.preventDefault();
|
||||
document.getElementById('logout-form').submit();">
|
||||
Logout
|
||||
</a>
|
||||
<a href="{{ url('/logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">Logout</a>
|
||||
|
||||
<form id="logout-form" action="{{ url('/logout') }}" method="POST" style="display: none;">
|
||||
{{ csrf_field() }}
|
||||
|
@ -13,9 +13,6 @@
|
||||
|
||||
Auth::routes();
|
||||
|
||||
// Gallery
|
||||
Route::get('/', 'Gallery\DefaultController@index')->name('home');
|
||||
|
||||
// Administration
|
||||
Route::group(['prefix' => 'admin'], function () {
|
||||
Route::get('/', 'Admin\DefaultController@index')->name('admin');
|
||||
@ -23,4 +20,8 @@ Route::group(['prefix' => 'admin'], function () {
|
||||
Route::get('albums/{id}/delete', 'Admin\AlbumController@delete')->name('albums.delete');
|
||||
Route::resource('albums', 'Admin\AlbumController');
|
||||
Route::resource('photos', 'Admin\PhotoController');
|
||||
});
|
||||
});
|
||||
|
||||
// Gallery
|
||||
Route::get('/', 'Gallery\DefaultController@index')->name('home');
|
||||
Route::get('/{albumUrlAlias}/{photoFilename}', 'Gallery\PhotoController@view')->name('viewPhoto');
|
Loading…
Reference in New Issue
Block a user