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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$seed = 'abcdefghijklmnopqrstuvwxyz01234567890';
|
||||
|
@ -14,7 +14,7 @@ use Illuminate\Support\Facades\DB;
|
||||
|
||||
class AlbumController extends Controller
|
||||
{
|
||||
public function index($albumUrlAlias)
|
||||
public function index(Request $request, $albumUrlAlias)
|
||||
{
|
||||
$album = DbHelper::loadAlbumByUrlAlias($albumUrlAlias);
|
||||
if (is_null($album))
|
||||
@ -23,14 +23,31 @@ class AlbumController extends Controller
|
||||
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);
|
||||
|
||||
// The slideshow view needs access to all photos, not paged
|
||||
$photos = $album->photos()
|
||||
->orderBy(DB::raw('COALESCE(taken_at, created_at)'))
|
||||
->paginate(UserConfig::get('items_per_page'));
|
||||
->orderBy(DB::raw('COALESCE(taken_at, created_at)'));
|
||||
|
||||
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,
|
||||
'current_view' => $requestedView,
|
||||
'photos' => $photos
|
||||
]);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use App\Album;
|
||||
use App\Facade\Theme;
|
||||
use App\Facade\UserConfig;
|
||||
use App\Helpers\DbHelper;
|
||||
use App\Helpers\MiscHelper;
|
||||
use app\Http\Controllers\Admin\AlbumController;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Middleware\VerifyCsrfToken;
|
||||
@ -58,7 +59,7 @@ class PhotoController extends Controller
|
||||
return response()->file($album->getAlbumSource()->getPathToPhoto($photo, $thumbnail));
|
||||
}
|
||||
|
||||
public function show($albumUrlAlias, $photoFilename)
|
||||
public function show(Request $request, $albumUrlAlias, $photoFilename)
|
||||
{
|
||||
$album = DbHelper::loadAlbumByUrlAlias($albumUrlAlias);
|
||||
if (is_null($album))
|
||||
@ -73,10 +74,18 @@ class PhotoController extends Controller
|
||||
|
||||
$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', [
|
||||
'album' => $album,
|
||||
'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 {
|
||||
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() {
|
||||
var self = this;
|
||||
|
||||
|
@ -3,6 +3,7 @@ return [
|
||||
'activate_user_label' => 'Manually activate this account',
|
||||
'admin_user_label' => 'User is an administrator',
|
||||
'album_source_label' => 'Storage location:',
|
||||
'album_view_label' => 'View as:',
|
||||
'apply_action' => 'Apply',
|
||||
'bulk_edit_photos_label' => 'Bulk edit selected photos:',
|
||||
'bulk_edit_photos_placeholder' => 'Select an action',
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
return [
|
||||
'album_views' => [
|
||||
'default' => 'Default',
|
||||
'slideshow' => 'Slideshow'
|
||||
],
|
||||
'back_to_album' => 'Back to :name',
|
||||
'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.'
|
||||
];
|
@ -16,6 +16,10 @@
|
||||
<div class="container album-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/>
|
@ -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">
|
||||
<ol class="breadcrumb">
|
||||
<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>
|
||||
</ol>
|
||||
</div>
|
||||
@ -85,5 +85,11 @@
|
||||
</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>
|
||||
@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",
|
||||
"height": 112,
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"name": "slideshow-tiny",
|
||||
"height": 75,
|
||||
"width": 100
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user