#9: The progress bar when uploading is now working correctly. The delete album screen has been updated to Bootstrap v4. Alerts are now dismissable.

This commit is contained in:
Andy Heathershaw 2017-04-08 09:41:41 +01:00
parent 8b9e8f0229
commit c1740be802
9 changed files with 231 additions and 175 deletions

View File

@ -94,7 +94,7 @@ class AlbumController extends Controller
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
public function destroy(Request $request, $id)
{
$this->authorizeAccessToAdminPanel();
@ -111,6 +111,8 @@ class AlbumController extends Controller
$album->getAlbumSource()->deleteAlbumContents();
$album->delete();
$request->session()->flash('success', trans('admin.delete_album_success_message', ['album' => $album->name]));
return redirect(route('albums.index'));
}
@ -134,14 +136,15 @@ class AlbumController extends Controller
*
* @return \Illuminate\Http\Response
*/
public function index()
public function index(Request $request)
{
$this->authorizeAccessToAdminPanel();
$albums = DbHelper::getAlbumsForCurrentUser();
return Theme::render('admin.list_albums', [
'albums' => $albums
'albums' => $albums,
'success' => $request->session()->get('success'),
]);
}

View File

@ -1,3 +1,148 @@
/**
* 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
* @param queue_token Unique token of the upload queue to save the photos to
* @param language Array containing language strings
* @param urls Array containing URLs
* @constructor
*/
function UploadPhotosViewModel(album_id, queue_token, language, urls) {
this.el = '#upload-tab';
this.data = {
currentStatus: '',
imagesFailed: 0,
imagesTotal: 0,
imagesUploaded: 0,
isBulkUploadInProgress: false,
isUploadInProgress: false,
statusMessages: []
};
this.computed = {
failedPercentage: function () {
var result = 0;
if (this.imagesTotal > 0)
{
result = (this.imagesFailed / this.imagesTotal) * 100;
}
return result.toFixed(2) + '%';
},
successfulPercentage: function () {
var result = 0;
if (this.imagesTotal > 0)
{
result = (this.imagesUploaded / this.imagesTotal) * 100;
}
return result.toFixed(2) + '%';
}
};
this.methods = {
// This method is called when an image is uploaded - regardless if it fails or not
onUploadCompleted: function () {
this.currentStatus = language.upload_status
.replace(':current', (this.imagesUploaded + this.imagesFailed))
.replace(':total', this.imagesTotal);
if ((this.imagesFailed + this.imagesUploaded) >= this.imagesTotal) {
this.isUploadInProgress = false;
if (this.imagesFailed === 0 && this.imagesUploaded > 0) {
window.location = urls.analyse;
}
}
},
// This method is called when an uploaded image fails
onUploadFailed: function (data, file_name) {
this.imagesFailed++;
this.statusMessages.push({
'message_class': 'text-danger',
'message_text': language.image_failed.replace(':file_name', file_name)
});
this.onUploadCompleted();
},
// This method is called when an uploaded image succeeds
onUploadSuccessful: function (data, file_name) {
if (data.is_successful) {
this.imagesUploaded++;
// Don't add to statusMessages() array so user only sees errors
/*self.statusMessages.push({
'message_class': 'text-success',
'message_text': language.image_uploaded.replace(':file_name', file_name)
});*/
this.onUploadCompleted();
}
else {
this.onUploadFailed(data, file_name);
}
},
uploadFile: function uploadImageFile(formObject, imageFile) {
var self = this;
var formData = new FormData();
formData.append('album_id', album_id);
formData.append('queue_token', queue_token);
formData.append('photo[]', imageFile, imageFile.name);
$.ajax(
{
contentType: false,
data: formData,
error: function (data) {
self.onUploadFailed(data, imageFile.name);
},
method: $(formObject).attr('method'),
processData: false,
success: function (data) {
self.onUploadSuccessful(data, imageFile.name);
},
url: $(formObject).attr('action')
}
);
this.isUploadInProgress = true;
this.currentStatus = language.upload_status
.replace(':current', 0)
.replace(':total', this.imagesTotal);
},
uploadIndividualFiles: function(event) {
var fileSelect = $('input[type=file]', event.target);
// Get the selected files
var files = fileSelect[0].files;
// Reset statistics
this.currentStatus = '';
this.statusMessages = [];
this.imagesUploaded = 0;
this.imagesFailed = 0;
this.imagesTotal = files.length;
this.isUploadInProgress = true;
// Loop through each of the selected files and upload them individually
for (var i = 0; i < files.length; i++)
{
var file = files[i];
// We're only interested in image files
if (!file.type.match('image.*'))
{
alert(language.not_an_image_file.replace(':file_name', file.name));
this.onUploadFailed(null, file.name);
continue;
}
// Upload the file
this.uploadFile(event.target, file);
}
// Prevent standard form upload
event.preventDefault();
return false;
}
};
}
/*!
* jQuery JavaScript Library v3.2.0
* https://jquery.com/
@ -22961,131 +23106,3 @@ var Popover = function ($) {
}(jQuery);
}();
/**
* 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
* @param queue_token Unique token of the upload queue to save the photos to
* @param language Array containing language strings
* @param urls Array containing URLs
* @constructor
*/
function UploadPhotosViewModel(album_id, queue_token, language, urls) {
this.el = '#upload-tab';
this.data = {
currentStatus: '',
imagesFailed: 0,
imagesTotal: 0,
isBulkUploadInProgress: false,
isUploadInProgress: false,
statusMessages: []
};
this.computed = {
// a computed getter
failedPercentage: function () {
return ((this.imagesFailed() / this.imagesTotal()) * 100).toFixed(2) + '%';
},
successfulPercentage: function () {
return ((self.imagesUploaded() / self.imagesTotal()) * 100).toFixed(2) + '%';
}
};
this.methods = {
// This method is called when an image is uploaded - regardless if it fails or not
onUploadCompleted: function () {
this.currentStatus = language.upload_status
.replace(':current', (self.imagesUploaded + self.imagesFailed))
.replace(':total', self.imagesTotal);
if ((this.imagesFailed + this.imagesUploaded) >= this.imagesTotal) {
this.isUploadInProgress = false;
if (this.imagesFailed == 0 && this.imagesUploaded > 0) {
window.location = urls.analyse;
}
}
},
// This method is called when an uploaded image fails
onUploadFailed: function (data, file_name) {
this.imagesFailed++;
this.statusMessages.push({
'message_class': 'text-danger',
'message_text': language.image_failed.replace(':file_name', file_name)
});
this.onUploadCompleted();
},
// This method is called when an uploaded image succeeds
onUploadSuccessful: function (data, file_name) {
if (data.is_successful) {
self.imagesUploaded++;
// Don't add to statusMessages() array so user only sees errors
/*self.statusMessages.push({
'message_class': 'text-success',
'message_text': language.image_uploaded.replace(':file_name', file_name)
});*/
self.onUploadCompleted();
}
else {
self.onUploadFailed(data, file_name);
}
},
uploadFile: function uploadImageFile(formObject, imageFile) {
var self = this;
var formData = new FormData();
formData.append('album_id', album_id);
formData.append('queue_token', queue_token);
formData.append('photo[]', imageFile, imageFile.name);
$.ajax(
{
contentType: false,
data: formData,
error: function (data) {
self.onUploadFailed(data, imageFile.name);
},
method: $(formObject).attr('method'),
processData: false,
success: function (data) {
self.onUploadSuccessful(data, imageFile.name);
},
url: $(formObject).attr('action')
}
);
this.isUploadInProgress = true;
},
uploadIndividualFiles: function(event) {
var fileSelect = $('input[type=file]', event.target);
// Get the selected files
var files = fileSelect[0].files;
// Reset statistics
this.currentStatus = '';
this.statusMessages = [];
this.imagesUploaded = 0;
this.imagesFailed = 0;
this.imagesTotal = files.length;
this.isUploadInProgress = true;
// Loop through each of the selected files and upload them individually
for (var i = 0; i < files.length; i++)
{
var file = files[i];
// We're only interested in image files
if (!file.type.match('image.*'))
{
alert(language.not_an_image_file.replace(':file_name', file.name));
this.onUploadFailed(null, file.name);
continue;
}
// Upload the file
this.uploadFile($(this), file);
}
// Prevent standard form upload
event.preventDefault();
return false;
}
};
}

File diff suppressed because one or more lines are too long

View File

@ -12,17 +12,31 @@ function UploadPhotosViewModel(album_id, queue_token, language, urls) {
currentStatus: '',
imagesFailed: 0,
imagesTotal: 0,
imagesUploaded: 0,
isBulkUploadInProgress: false,
isUploadInProgress: false,
statusMessages: []
};
this.computed = {
// a computed getter
failedPercentage: function () {
return ((this.imagesFailed / this.imagesTotal) * 100).toFixed(2) + '%';
var result = 0;
if (this.imagesTotal > 0)
{
result = (this.imagesFailed / this.imagesTotal) * 100;
}
return result.toFixed(2) + '%';
},
successfulPercentage: function () {
return ((self.imagesUploaded / self.imagesTotal) * 100).toFixed(2) + '%';
var result = 0;
if (this.imagesTotal > 0)
{
result = (this.imagesUploaded / this.imagesTotal) * 100;
}
return result.toFixed(2) + '%';
}
};
this.methods = {
@ -35,7 +49,7 @@ function UploadPhotosViewModel(album_id, queue_token, language, urls) {
if ((this.imagesFailed + this.imagesUploaded) >= this.imagesTotal) {
this.isUploadInProgress = false;
if (this.imagesFailed == 0 && this.imagesUploaded > 0) {
if (this.imagesFailed === 0 && this.imagesUploaded > 0) {
window.location = urls.analyse;
}
}
@ -87,6 +101,10 @@ function UploadPhotosViewModel(album_id, queue_token, language, urls) {
}
);
this.isUploadInProgress = true;
this.currentStatus = language.upload_status
.replace(':current', 0)
.replace(':total', this.imagesTotal);
},
uploadIndividualFiles: function(event) {
var fileSelect = $('input[type=file]', event.target);

View File

@ -43,6 +43,7 @@ return [
'default_storage_legend' => 'Default storage location for new albums.',
'delete_album' => 'Delete album :name',
'delete_album_confirm' => 'Are you sure you want to permanently delete this album and all its contents?',
'delete_album_success_message' => 'The album ":album" was deleted successfully.',
'delete_album_warning' => 'This is a permanent action that cannot be undone!',
'delete_bulk_photos_message' => 'Are you sure you want to delete the selected photos? This action cannot be undone!',
'delete_bulk_photos_title' => 'Delete selected photos',

View File

@ -2,33 +2,32 @@
@section('title', trans('admin.delete_album', ['name' => $album->name]))
@section('breadcrumb')
<div class="breadcrumb">
<div class="container">
<ol class="breadcrumb">
<li><a href="{{ route('home') }}"><i class="fa fa-fw fa-home"></i></a></li>
<li><a href="{{ route('admin') }}">@lang('navigation.breadcrumb.admin')</a></li>
<li><a href="{{ route('albums.index') }}">@lang('navigation.breadcrumb.albums')</a></li>
<li><a href="{{ route('albums.show', ['id' => $album->id]) }}">{{ $album->name }}</a></li>
<li class="active">@lang('navigation.breadcrumb.delete_album')</li>
</ol>
</div>
</div>
<li class="breadcrumb-item"><a href="{{ route('home') }}"><i class="fa fa-fw fa-home"></i></a></li>
<li class="breadcrumb-item"><a href="{{ route('admin') }}">@lang('navigation.breadcrumb.admin')</a></li>
<li class="breadcrumb-item"><a href="{{ route('albums.index') }}">@lang('navigation.breadcrumb.albums')</a></li>
<li class="breadcrumb-item"><a href="{{ route('albums.show', ['id' => $album->id]) }}">{{ $album->name }}</a></li>
<li class="breadcrumb-item active">@lang('navigation.breadcrumb.delete_album')</li>
@endsection
@section('content')
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>@yield('title')</h1>
<p>@lang('admin.delete_album_confirm', ['name' => $album->name])</p>
<div class="alert alert-danger">
@lang('admin.delete_album_warning')
</div>
<div class="form-actions">
{!! Form::open(['route' => ['albums.destroy', $album->id], 'method' => 'DELETE']) !!}
<a href="{{ route('albums.show', ['id' => $album->id]) }}" class="btn btn-default">@lang('forms.cancel_action')</a>
<button type="submit" class="btn btn-danger"><i class="fa fa-fw fa-trash"></i> @lang('forms.delete_action')</button>
{!! Form::close() !!}
<div class="col-md-8 offset-md-2">
<div class="card card-outline-danger">
<div class="card-header card-danger">@yield('title')</div>
<div class="card-block">
<p>@lang('admin.delete_album_confirm', ['name' => $album->name])</p>
<p class="text-danger"><b>@lang('admin.delete_album_warning')</b></p>
<div class="text-right">
<form action="{{ route('albums.destroy', [$album->id]) }}" method="POST">
{{ csrf_field() }}
{{ method_field('DELETE') }}
<a href="{{ route('albums.show', ['id' => $album->id]) }}" class="btn btn-link">@lang('forms.cancel_action')</a>
<button type="submit" class="btn btn-danger"><i class="fa fa-fw fa-trash"></i> @lang('forms.delete_action')</button>
</form>
</div>
</pdiv>
</div>
</div>
</div>

View File

@ -359,18 +359,26 @@
// Bind the view models to the relevant tab
//ko.applyBindings(editViewModel, document.getElementById('photos-tab'));
//ko.applyBindings(viewModel, document.getElementById('upload-tab'));
var appUpload = new Vue(viewModel);
appUpload.$watch('isUploadInProgress', function(value) {
if (value)
{
$('#upload-progress-modal').modal('show');
}
else if (this.statusMessages.length == 0)
else if (this.statusMessages.length === 0)
{
$('#upload-progress-modal').modal('hide');
}
});
{{-- Whatever I try, I could not get VueJS to adjust the progress bars automatically --}}
/*appUpload.$watch('failedPercentage', function(value) {
alert('failed: ' + value);
$('.progress-bar.bg-danger', '#upload-progress-modal').width(value);
});
appUpload.$watch('successfulPercentage', function(value) {
alert('success: ' + value);
$('.progress-bar.bg-success', '#upload-progress-modal').width(value);
});*/
})
</script>
@endpush

View File

@ -39,7 +39,10 @@
@if (isset($error))
<div class="container">
<div class="alert alert-danger">
<div class="alert alert-danger alert-dismissable fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><i class="fa fa-exclamation-circle fa-fw"></i></strong> {{ $error }}
</div>
</div>
@ -47,7 +50,10 @@
@if (isset($warning))
<div class="container">
<div class="alert alert-warning">
<div class="alert alert-warning alert-dismissable fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><i class="fa fa-warning fa-fw"></i></strong> {{ $warning }}
</div>
</div>
@ -55,7 +61,10 @@
@if (isset($success))
<div class="container">
<div class="alert alert-success">
<div class="alert alert-success alert-dismissable fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><i class="fa fa-info-circle fa-fw"></i></strong> {{ $success }}
</div>
</div>
@ -63,7 +72,10 @@
@if (isset($info))
<div class="container">
<div class="alert alert-info">
<div class="alert alert-info alert-dismissable fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<strong><i class="fa fa-info-circle fa-fw"></i></strong> {{ $info }}
</div>
</div>

View File

@ -72,11 +72,9 @@
<div class="modal-body">
<div class="text-center">
<div class="progress">
<div class="progress-bar progress-bar-success" v-bind:style="{ width: successfulPercentage }">
<span class="sr-only"><span class="percentage-success" v-text="successfulPercentage"></span></span>
<div class="progress-bar bg-success" role="progressbar" v-bind:style="{ width: successfulPercentage }">
</div>
<div class="progress-bar progress-bar-danger" v-bind:style="{ width: failedPercentage }">
<span class="sr-only"><span class="percentage-danger" v-text="failedPercentage"></span></span>
<div class="progress-bar bg-danger" role="progressbar" v-bind:style="{ width: failedPercentage }">
</div>
</div>
<p v-text="currentStatus"></p>