refs #3: implemented multiple independent views for an album and created a slideshow view
This commit is contained in:
parent
8990c31a5f
commit
58b055e9cd
@ -43,6 +43,19 @@ class MiscHelper
|
|||||||
return sprintf('https://www.gravatar.com/avatar/%s?s=%d&d=%s', $hash, $size, $default);
|
return sprintf('https://www.gravatar.com/avatar/%s?s=%d&d=%s', $hash, $size, $default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether the provided URL belongs to the current application (i.e. both scheme and hostname match.)
|
||||||
|
* @param $url
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isSafeUrl($url)
|
||||||
|
{
|
||||||
|
$parts = parse_url($url);
|
||||||
|
$validParts = parse_url(url('/'));
|
||||||
|
|
||||||
|
return ($parts['scheme'] == $validParts['scheme'] && $parts['host'] == $validParts['host']);
|
||||||
|
}
|
||||||
|
|
||||||
public static function randomString($length = 10)
|
public static function randomString($length = 10)
|
||||||
{
|
{
|
||||||
$seed = 'abcdefghijklmnopqrstuvwxyz01234567890';
|
$seed = 'abcdefghijklmnopqrstuvwxyz01234567890';
|
||||||
|
@ -14,7 +14,7 @@ use Illuminate\Support\Facades\DB;
|
|||||||
|
|
||||||
class AlbumController extends Controller
|
class AlbumController extends Controller
|
||||||
{
|
{
|
||||||
public function index($albumUrlAlias)
|
public function index(Request $request, $albumUrlAlias)
|
||||||
{
|
{
|
||||||
$album = DbHelper::loadAlbumByUrlAlias($albumUrlAlias);
|
$album = DbHelper::loadAlbumByUrlAlias($albumUrlAlias);
|
||||||
if (is_null($album))
|
if (is_null($album))
|
||||||
@ -23,14 +23,31 @@ class AlbumController extends Controller
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$validViews = ['default', 'slideshow'];
|
||||||
|
$requestedView = strtolower($request->get('view'));
|
||||||
|
if (!in_array($requestedView, $validViews))
|
||||||
|
{
|
||||||
|
$requestedView = $validViews[0];
|
||||||
|
}
|
||||||
|
|
||||||
$this->authorizeForUser($this->getUser(), 'album.view', $album);
|
$this->authorizeForUser($this->getUser(), 'album.view', $album);
|
||||||
|
|
||||||
|
// The slideshow view needs access to all photos, not paged
|
||||||
$photos = $album->photos()
|
$photos = $album->photos()
|
||||||
->orderBy(DB::raw('COALESCE(taken_at, created_at)'))
|
->orderBy(DB::raw('COALESCE(taken_at, created_at)'));
|
||||||
->paginate(UserConfig::get('items_per_page'));
|
|
||||||
|
|
||||||
return Theme::render('gallery.album', [
|
if ($requestedView != 'slideshow')
|
||||||
|
{
|
||||||
|
$photos = $photos->paginate(UserConfig::get('items_per_page'));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$photos = $photos->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Theme::render(sprintf('gallery.album_%s', $requestedView), [
|
||||||
'album' => $album,
|
'album' => $album,
|
||||||
|
'current_view' => $requestedView,
|
||||||
'photos' => $photos
|
'photos' => $photos
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use App\Album;
|
|||||||
use App\Facade\Theme;
|
use App\Facade\Theme;
|
||||||
use App\Facade\UserConfig;
|
use App\Facade\UserConfig;
|
||||||
use App\Helpers\DbHelper;
|
use App\Helpers\DbHelper;
|
||||||
|
use App\Helpers\MiscHelper;
|
||||||
use app\Http\Controllers\Admin\AlbumController;
|
use app\Http\Controllers\Admin\AlbumController;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Middleware\VerifyCsrfToken;
|
use App\Http\Middleware\VerifyCsrfToken;
|
||||||
@ -58,7 +59,7 @@ class PhotoController extends Controller
|
|||||||
return response()->file($album->getAlbumSource()->getPathToPhoto($photo, $thumbnail));
|
return response()->file($album->getAlbumSource()->getPathToPhoto($photo, $thumbnail));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show($albumUrlAlias, $photoFilename)
|
public function show(Request $request, $albumUrlAlias, $photoFilename)
|
||||||
{
|
{
|
||||||
$album = DbHelper::loadAlbumByUrlAlias($albumUrlAlias);
|
$album = DbHelper::loadAlbumByUrlAlias($albumUrlAlias);
|
||||||
if (is_null($album))
|
if (is_null($album))
|
||||||
@ -73,10 +74,18 @@ class PhotoController extends Controller
|
|||||||
|
|
||||||
$isOriginalAllowed = Gate::forUser($this->getUser())->allows('photo.download_original', $photo);
|
$isOriginalAllowed = Gate::forUser($this->getUser())->allows('photo.download_original', $photo);
|
||||||
|
|
||||||
|
$returnAlbumUrl = $album->url();
|
||||||
|
$referer = $request->headers->get('Referer');
|
||||||
|
if (strlen($referer) > 0 && MiscHelper::isSafeUrl($referer))
|
||||||
|
{
|
||||||
|
$returnAlbumUrl = $referer;
|
||||||
|
}
|
||||||
|
|
||||||
return Theme::render('gallery.photo', [
|
return Theme::render('gallery.photo', [
|
||||||
'album' => $album,
|
'album' => $album,
|
||||||
'is_original_allowed' => $isOriginalAllowed,
|
'is_original_allowed' => $isOriginalAllowed,
|
||||||
'photo' => $photo
|
'photo' => $photo,
|
||||||
|
'return_album_url' => $returnAlbumUrl
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
public/themes/base/css/app.css
vendored
13
public/themes/base/css/app.css
vendored
@ -1,3 +1,16 @@
|
|||||||
|
.album-slideshow-container .image-preview {
|
||||||
|
height: 600px;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.album-slideshow-container .thumbnails {
|
||||||
|
overflow-x: scroll;
|
||||||
|
overflow-y: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.no-margin-bottom {
|
.no-margin-bottom {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
@ -364,6 +364,67 @@ function EditPhotosViewModel(album_id, language, urls) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SlideShowViewModel(required_timeout_ms) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.current = ko.observable();
|
||||||
|
self.currentIndex = ko.observable(0);
|
||||||
|
self.images = ko.observableArray();
|
||||||
|
self.interval = null;
|
||||||
|
|
||||||
|
self.isRunning = ko.observable(false);
|
||||||
|
self.isPaused = ko.observable(false);
|
||||||
|
|
||||||
|
self.changeCurrentImage = function(photo_id)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < self.images().length; i++)
|
||||||
|
{
|
||||||
|
var this_image = self.images()[i];
|
||||||
|
if (this_image.id == photo_id)
|
||||||
|
{
|
||||||
|
self.current(this_image);
|
||||||
|
self.currentIndex(i);
|
||||||
|
window.clearInterval(self.interval);
|
||||||
|
self.interval = window.setInterval(self.rotateNextImage, required_timeout_ms);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.continueSlideshow = function() {
|
||||||
|
self.isPaused(false);
|
||||||
|
self.interval = window.setInterval(self.rotateNextImage, required_timeout_ms);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.pauseSlideshow = function() {
|
||||||
|
self.isPaused(true);
|
||||||
|
window.clearInterval(self.interval);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.rotateNextImage = function() {
|
||||||
|
var next_index = self.currentIndex() + 1;
|
||||||
|
if (next_index >= self.images().length)
|
||||||
|
{
|
||||||
|
next_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.current(self.images()[next_index]);
|
||||||
|
self.currentIndex(next_index);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.startSlideshow = function() {
|
||||||
|
if (self.images().length <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.interval = window.setInterval(self.rotateNextImage, required_timeout_ms);
|
||||||
|
|
||||||
|
self.current(self.images()[0]);
|
||||||
|
self.isRunning(true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function StorageLocationsViewModel() {
|
function StorageLocationsViewModel() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ return [
|
|||||||
'activate_user_label' => 'Manually activate this account',
|
'activate_user_label' => 'Manually activate this account',
|
||||||
'admin_user_label' => 'User is an administrator',
|
'admin_user_label' => 'User is an administrator',
|
||||||
'album_source_label' => 'Storage location:',
|
'album_source_label' => 'Storage location:',
|
||||||
|
'album_view_label' => 'View as:',
|
||||||
'apply_action' => 'Apply',
|
'apply_action' => 'Apply',
|
||||||
'bulk_edit_photos_label' => 'Bulk edit selected photos:',
|
'bulk_edit_photos_label' => 'Bulk edit selected photos:',
|
||||||
'bulk_edit_photos_placeholder' => 'Select an action',
|
'bulk_edit_photos_placeholder' => 'Select an action',
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
return [
|
return [
|
||||||
|
'album_views' => [
|
||||||
|
'default' => 'Default',
|
||||||
|
'slideshow' => 'Slideshow'
|
||||||
|
],
|
||||||
|
'back_to_album' => 'Back to :name',
|
||||||
'index_no_results_heading' => 'This gallery is empty!',
|
'index_no_results_heading' => 'This gallery is empty!',
|
||||||
'index_no_results_text' => 'If you are the owner of this gallery, you can create new albums and upload photos using the :admin_link.'
|
'index_no_results_text' => 'If you are the owner of this gallery, you can create new albums and upload photos using the :admin_link.'
|
||||||
];
|
];
|
@ -16,6 +16,10 @@
|
|||||||
<div class="container album-container">
|
<div class="container album-container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
|
<div class="pull-right">
|
||||||
|
@include(\App\Facade\Theme::viewName('partials.album_view_selector'))
|
||||||
|
</div>
|
||||||
|
|
||||||
<h1 class="page-title">{{ $album->name }}</h1>
|
<h1 class="page-title">{{ $album->name }}</h1>
|
||||||
<p>{{ $album->description }}</p>
|
<p>{{ $album->description }}</p>
|
||||||
<hr/>
|
<hr/>
|
@ -0,0 +1,81 @@
|
|||||||
|
@extends('themes.base.layout')
|
||||||
|
@section('title', $album->name)
|
||||||
|
|
||||||
|
@section('breadcrumb')
|
||||||
|
<div class="breadcrumb">
|
||||||
|
<div class="container">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li><a href="{{ route('home') }}">Gallery</a></li>
|
||||||
|
<li class="active">{{ $album->name }}</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container album-container album-slideshow-container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div class="pull-right">
|
||||||
|
@include(\App\Facade\Theme::viewName('partials.album_view_selector'))
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="page-title">{{ $album->name }}</h1>
|
||||||
|
<p>{{ $album->description }}</p>
|
||||||
|
<hr/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 text-center">
|
||||||
|
<div class="pull-right">
|
||||||
|
<div class="btn btn-group">
|
||||||
|
<button class="btn btn-default" data-bind="click: pauseSlideshow, disable: isPaused, visible: isRunning"><i class="fa fa-fw fa-pause"></i></button>
|
||||||
|
<button class="btn btn-default" data-bind="click: continueSlideshow, enable: isPaused, visible: isRunning"><i class="fa fa-fw fa-play"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="image-preview" data-bind="with: current()">
|
||||||
|
<a data-bind="attr: { href: info_url, title: description }">
|
||||||
|
<img class="img-rounded thumbnail img-responsive" data-bind="attr: { src: original_url, alt: name, title: description }"/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" style="margin-top: 15px;">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div class="thumbnails">
|
||||||
|
@foreach ($photos as $photo)
|
||||||
|
<a href="{{ $photo->url() }}" data-bind="click: function() { viewModel.changeCurrentImage({{ $photo->id }}); }">
|
||||||
|
<img class="ss-thumbnail" src="{{ $photo->thumbnailUrl('slideshow-tiny') }}" alt="{{ $photo->name }}" title="{{ $photo->description }}" data-photo-id="{{ $photo->id }}" data-original-url="{{ $photo->thumbnailUrl('fullsize') }}" data-info-url="{{ $photo->url() }}" />
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript">
|
||||||
|
// TODO make this timeout configurable
|
||||||
|
var viewModel = new SlideShowViewModel(5000);
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('img.ss-thumbnail').each(function(index, element)
|
||||||
|
{
|
||||||
|
viewModel.images.push({
|
||||||
|
'id': $(element).data('photo-id'),
|
||||||
|
'name': $(element).attr('alt'),
|
||||||
|
'description': $(element).attr('title'),
|
||||||
|
'original_url': $(element).data('original-url'),
|
||||||
|
'info_url': $(element).data('info-url')
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ko.applyBindings(viewModel);
|
||||||
|
viewModel.startSlideshow();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
@ -6,7 +6,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li><a href="{{ route('home') }}">Gallery</a></li>
|
<li><a href="{{ route('home') }}">Gallery</a></li>
|
||||||
<li><a href="{{ $photo->album->url() }}">{{ $photo->album->name }}</a></li>
|
<li><a href="{{ $return_album_url }}">{{ $photo->album->name }}</a></li>
|
||||||
<li class="active">{{ $photo->name }}</li>
|
<li class="active">{{ $photo->name }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
@ -85,5 +85,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<a href="{{ $return_album_url }}" class="btn btn-default">@lang('gallery.back_to_album', ['name' => $photo->album->name])</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
@ -0,0 +1,19 @@
|
|||||||
|
<form method="get" action="{{ \Request::url() }}">
|
||||||
|
<div class="field-group">
|
||||||
|
<label class="control-label">@lang('forms.album_view_label')</label>
|
||||||
|
<select name="view" class="form-control" id="album-view-selector">
|
||||||
|
<option value="default"@if (isset($current_view) && $current_view == 'default') selected="selected"@endif>@lang('gallery.album_views.default')</option>
|
||||||
|
<option value="slideshow"@if (isset($current_view) && $current_view == 'slideshow') selected="selected"@endif>@lang('gallery.album_views.slideshow')</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#album-view-selector').change(function() {
|
||||||
|
$(this).parents('form').submit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
@ -19,6 +19,11 @@
|
|||||||
"name": "admin-preview",
|
"name": "admin-preview",
|
||||||
"height": 112,
|
"height": 112,
|
||||||
"width": 150
|
"width": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "slideshow-tiny",
|
||||||
|
"height": 75,
|
||||||
|
"width": 100
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user