#9: Started working on converting the analysis and album screens to Bootstrap v4

This commit is contained in:
Andy Heathershaw 2017-04-09 09:07:18 +01:00
parent c1740be802
commit 3c2d50f373
5 changed files with 172 additions and 33 deletions

View File

@ -60,4 +60,11 @@ class DbHelper
->withCount('photos')
->paginate(UserConfig::get('items_per_page'));
}
public static function getAlbumByAliasForCurrentUser($urlAlias)
{
$album = Album::where('url_alias', $urlAlias)->first();
return $album;
}
}

View File

@ -20,7 +20,7 @@ class PhotoController extends Controller
{
public function download(Request $request, $albumUrlAlias, $photoFilename)
{
$album = DbHelper::loadAlbumByUrlAlias($albumUrlAlias);
$album = DbHelper::getAlbumByAliasForCurrentUser($albumUrlAlias);
if (is_null($album))
{
App::abort(404);
@ -75,7 +75,7 @@ class PhotoController extends Controller
public function show(Request $request, $albumUrlAlias, $photoFilename)
{
$album = DbHelper::loadAlbumByUrlAlias($albumUrlAlias);
$album = DbHelper::getAlbumByAliasForCurrentUser($albumUrlAlias);
if (is_null($album))
{
App::abort(404);

View File

@ -1,3 +1,130 @@
/**
* This model is used by admin/analyse_album.blade.php, to analyse all images.
* @constructor
*/
function AnalyseAlbumViewModel() {
this.el = '#analyse-album';
this.data = {
imagesFailed: [],
imagesToAnalyse: [],
imagesInProgress: [],
imagesRecentlyCompleted: [],
numberSuccessful: 0,
numberFailed: 0
};
this.computed = {
failedPercentage: function () {
var result = 0;
if (this.numberTotal > 0)
{
result = (this.numberFailed / this.numberTotal) * 100;
}
return result.toFixed(2) + '%';
},
isCompleted: function () {
return this.numberTotal > 0 && (this.numberSuccessful + this.numberFailed >= this.numberTotal);
},
latestCompletedImages: function() {
return this.imagesRecentlyCompleted.slice(
(this.imagesRecentlyCompleted.length - 3 < 0
? 0
: this.imagesRecentlyCompleted.length - 3)
, 3);
},
numberTotal: function () {
return this.imagesToAnalyse.length;
},
successfulPercentage: function () {
var result = 0;
if (this.numberTotal > 0)
{
result = (this.numberSuccessful / this.numberTotal) * 100;
}
return result.toFixed(2) + '%';
}
};
this.methods = {
// This method is called when an image is added to the array, automatically issue it for analysis
// item is an instance of AnalyseImageViewModel
analyseImage: function (item) {
var self = this;
this.imagesToAnalyse.push(item);
$.ajax(
item.url,
{
beforeSend: function() {
self.imagesInProgress.push(item);
},
dataType: 'json',
error: function (xhr, textStatus, errorThrown) {
self.numberFailed++;
self.imagesFailed.push({
'name': item.name,
'reason': textStatus
});
item.isSuccessful = false;
item.isPending = false;
},
method: 'POST',
success: function (data) {
if (data.is_successful) {
self.numberSuccessful++;
item.isSuccessful = true;
item.isPending = false;
// Push into our "recently completed" array
self.imagesRecentlyCompleted.push(item);
var indexToRemove = self.imagesInProgress.indexOf(item);
if (indexToRemove > -1)
{
self.imagesInProgress.splice(indexToRemove, 1);
}
// Remove it again after a few seconds
/*window.setTimeout(function() {
var indexToRemove = self.imagesRecentlyCompleted.indexOf(item);
if (indexToRemove > -1) {
self.imagesRecentlyCompleted.splice(indexToRemove, 1);
}
}, 2000);*/
}
else {
self.numberFailed++;
self.imagesFailed.push({
'name': item.name,
'reason': data.message
});
item.isSuccessful = false;
item.isPending = false;
}
}
}
);
}
};
}
/**
* This model is used by admin/analyse_album.blade.php, as a sub-model of AnalyseAlbumViewModel.
* @param image_info Array of information about the image
* @constructor
*/
function AnalyseImageViewModel(image_info) {
this.isPending = true;
this.isSuccessful = false;
this.name = image_info.name;
this.photoID = image_info.photo_id;
this.url = image_info.url;
}
/**
* This model is used by admin/show_album.blade.php to handle photo uploads.
* @param album_id ID of the album the photos are being uploaded to
@ -8,6 +135,7 @@
*/
function UploadPhotosViewModel(album_id, queue_token, language, urls) {
this.el = '#upload-tab';
this.data = {
currentStatus: '',
imagesFailed: 0,
@ -17,6 +145,7 @@ function UploadPhotosViewModel(album_id, queue_token, language, urls) {
isUploadInProgress: false,
statusMessages: []
};
this.computed = {
failedPercentage: function () {
var result = 0;
@ -39,6 +168,7 @@ function UploadPhotosViewModel(album_id, queue_token, language, urls) {
return result.toFixed(2) + '%';
}
};
this.methods = {
// This method is called when an image is uploaded - regardless if it fails or not
onUploadCompleted: function () {

View File

@ -4,50 +4,53 @@
@section('content')
<div class="container" style="margin-top: 40px;">
<div class="row">
<div class="col-xs-12 col-sm-8 col-sm-offset-2">
<div class="panel panel-default" data-bind="visible: !isCompleted()">
<div class="panel-heading">Analysing...</div>
<div class="panel-body">
<div class="col-sm-8 offset-sm-2" id="analyse-album">
<div class="card" v-if="!isCompleted">
<div class="card-header">Analysing...</div>
<div class="card-block">
<p>Your uploaded photos are now being analysed.</p>
<div class="progress">
<div class="progress-bar progress-bar-success" data-bind="style: { width: successfulPercentage() }">
<span class="sr-only"><span class="percentage-success" data-bind="text: successfulPercentage"></span></span>
<div class="progress-bar bg-success" v-bind:style="{ width: successfulPercentage }">
</div>
<div class="progress-bar progress-bar-danger" data-bind="style: { width: failedPercentage() }">
<span class="sr-only"><span class="percentage-danger" data-bind="text: failedPercentage"></span></span>
<div class="progress-bar bg-danger" v-bind:style="{ width: failedPercentage }">
</div>
</div>
{{-- We display a queue of 3 recently completed items, and 5 up-next items, as well as a counter saying "... and XYZ more" --}}
{{-- That's what the 3's and 5's are in the next couple of blocks --}}
<div data-bind="foreach: imagesRecentlyCompleted.slice((imagesRecentlyCompleted().length - 3 < 0) ? 0 : imagesRecentlyCompleted().length - 3, 3)" style="margin-top: 20px;">
<p class="text-success"><span data-bind="text: name"></span> ... <i class="fa fa-fw fa-check"></i></p>
<div v-for="image in latestCompletedImages" style="margin-top: 20px;">
<p class="text-success"><span v-text="image.name"></span> ... <i class="fa fa-fw fa-check"></i></p>
</div>
<div data-bind="foreach: imagesInProgress.slice(0, 5)" style="margin-top: 20px;">
<p><span data-bind="text: name"></span> ... <i data-bind="css: iconClass()"></i></p>
<div v-for="image in imagesInProgress.slice(0, 5)" style="margin-top: 20px;">
<p><span v-text="image.name"></span> ... <i class="fa fa-fw fa-refresh"></i></p>
</div>
<div data-bind="visible: imagesInProgress().length > 5">
<p>@lang('admin.analyse_and_more.and') <span data-bind="text: imagesInProgress().length - 5"></span> @lang('admin.analyse_and_more.others')</p>
<div v-if="imagesInProgress.length > 5">
<p>@lang('admin.analyse_and_more.and') <span v-text="imagesInProgress.length - 5"></span> @lang('admin.analyse_and_more.others')</p>
</div>
<div v-for="image in imagesFailed" style="margin-top: 20px;">
<p class="text-danger"><span v-text="image.name"></span> ... <i class="fa fa-fw fa-times"></i></p>
</div>
</div>
</div>
<div class="panel panel-default" data-bind="visible: isCompleted">
<div class="panel-heading">Upload completed</div>
<div class="panel-body">
<div class="card" v-if="isCompleted">
<div class="card-header">Upload completed</div>
<div class="card-block">
<p>Your upload has completed.</p>
<div data-bind="visible: numberFailed() > 0">
<div v-if="numberFailed > 0">
<p class="text-danger">@lang('admin.analyse_photos_failed')</p>
<ul class="text-danger" data-bind="foreach: imagesFailed">
<li><span data-bind="text: name"></span>: <span data-bind="text: reason"></span></li>
<ul class="text-danger" v-for="image in imagesFailed">
<li><span v-text="image.name"></span>: <span v-text="image.reason"></span></li>
</ul>
</div>
<div class="btn-toolbar btn-group-sm pull-right">
<a class="btn btn-default" href="{{ $album->url() }}">View album</a>
<a class="btn btn-primary" href="{{ route('albums.show', ['id' => $album->id]) }}">Back to album settings</a>
<div class="text-right">
<a class="btn btn-link" href="{{ $album->url() }}">View album</a>
<a class="btn btn-primary" href="{{ route('albums.show', ['id' => $album->id]) }}"><i class="fa fa-fw fa-chevron-left"></i> Back to album settings</a>
</div>
</div>
</div>
@ -59,16 +62,15 @@
@push('scripts')
<script type="text/javascript">
var viewModel = new AnalyseAlbumViewModel();
var app = new Vue(viewModel);
{{-- For each photo to analyse, push an instance of AnalyseImageViewModel to our master view model --}}
@foreach ($photos as $photo)
viewModel.imagesToAnalyse.push(new AnalyseImageViewModel({
app.analyseImage(new AnalyseImageViewModel({
'id': '{{ $photo->id }}',
'name': '{!! addslashes($photo->name) !!}',
'url': '{{ route('photos.analyse', ['id' => $photo->id, 'queue_token' => $queue_token]) }}'
}));
@endforeach
ko.applyBindings(viewModel);
</script>
@endpush

View File

@ -1,7 +1,7 @@
@php ($field_prefix = sprintf('photo[%d]', $photo->id))
<hr/>
<div class="photo row" data-photo-id="{{ $photo->id }}">
<div class="col-xs-12 col-sm-2">
<div class="col-sm-2">
<div class="loading"><img src="{{ asset('ripple.svg') }}" /></div>
<a href="{{ $photo->thumbnailUrl() }}" target="_blank">
@ -43,8 +43,8 @@
<div class="col-xs-12 col-sm-10">
@php($validation_field_name = ('photo.' . $photo->id . '.name'))
<div class="form-group{{ $errors->has($validation_field_name) ? ' has-error' : '' }}">
{!! Form::label($field_prefix . '[name]', trans('forms.name_label'), ['class' => 'control-label']) !!}
{!! Form::text($field_prefix . '[name]', old('name', $photo->name), ['class' => 'form-control']) !!}
<label class="control-label" name="{{ $field_prefix }}[name]">@lang('forms.name_label')</label>
<input class="form-control" type="text" name="{{ $field_prefix }}[name]" value="{{ old('name', $photo->name) }}"/>
@if ($errors->has($validation_field_name))
<span class="help-block">
@ -54,8 +54,8 @@
</div>
<div class="form-group">
{!! Form::label($field_prefix . '[description]', trans('forms.description_label'), ['class' => 'control-label']) !!}
{!! Form::textarea($field_prefix . '[description]', old('name', $photo->description), ['class' => 'form-control', 'rows' => 4]) !!}
<label class="control-label" name="{{ $field_prefix }}[description]">@lang('forms.description_label')</label>
<textarea name="{{ $field_prefix }}[description]" class="form-control" rows="4">{{ old('name', $photo->description) }}</textarea>
</div>
</div>
</div>