From 8201f1df8b69f5c0e6fec63914f220aec4bc6af5 Mon Sep 17 00:00:00 2001 From: Andy Heathershaw Date: Mon, 27 Mar 2017 14:04:09 +0100 Subject: [PATCH] #9: Started updating the "show album" page (easily the most complex in the application!) to use Bootstrap 4 and VueJS. --- public/js/blue-twilight.js | 128 ++++++++ resources/assets/js/albums.js | 127 +++++++ .../themes/base/admin/create_album.blade.php | 79 +++-- .../themes/base/admin/list_albums.blade.php | 19 +- .../themes/base/admin/list_groups.blade.php | 2 +- .../themes/base/admin/list_users.blade.php | 2 +- .../themes/base/admin/show_album.blade.php | 309 ++++++------------ .../base/partials/album_upload_tab.blade.php | 95 ++++++ 8 files changed, 495 insertions(+), 266 deletions(-) create mode 100644 resources/assets/js/albums.js create mode 100644 resources/views/themes/base/partials/album_upload_tab.blade.php diff --git a/public/js/blue-twilight.js b/public/js/blue-twilight.js index 9a96d9d..65ccc09 100644 --- a/public/js/blue-twilight.js +++ b/public/js/blue-twilight.js @@ -22961,3 +22961,131 @@ 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; + } + }; +} \ No newline at end of file diff --git a/resources/assets/js/albums.js b/resources/assets/js/albums.js new file mode 100644 index 0000000..d0e191e --- /dev/null +++ b/resources/assets/js/albums.js @@ -0,0 +1,127 @@ +/** + * 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; + } + }; +} \ No newline at end of file diff --git a/resources/views/themes/base/admin/create_album.blade.php b/resources/views/themes/base/admin/create_album.blade.php index 69fa3a2..718b0e1 100644 --- a/resources/views/themes/base/admin/create_album.blade.php +++ b/resources/views/themes/base/admin/create_album.blade.php @@ -2,61 +2,60 @@ @section('title', 'Gallery Admin') @section('breadcrumb') - + + + + @endsection @section('content')
-
+

@lang('admin.create_album')

@lang('admin.create_album_intro')

@lang('admin.create_album_intro2')


- {!! Form::open(['route' => 'albums.store', 'method' => 'POST']) !!} -
- {!! Form::label('name', trans('forms.name_label'), ['class' => 'control-label']) !!} - {!! Form::text('name', old('name'), ['class' => 'form-control']) !!} +
+ {{ csrf_field() }} +
+ + - @if ($errors->has('name')) - - {{ $errors->first('name') }} - - @endif -
+ @if ($errors->has('name')) + + @endif +
-
- {!! Form::label('description', trans('forms.description_label'), ['class' => 'control-label']) !!} - {!! Form::textarea('description', old('description'), ['class' => 'form-control']) !!} -
+
+ + +
-
- {!! Form::label('storage_id', trans('forms.album_source_label'), ['class' => 'control-label']) !!} - {!! Form::select('storage_id', $album_sources, old('storage_id', $default_storage_id), ['class' => 'form-control']) !!} -
+
+ + +
-
- -
+
+ +
-
- @lang('forms.cancel_action') - -
- {!! Form::close() !!} +
+ @lang('forms.cancel_action') + +
+
diff --git a/resources/views/themes/base/admin/list_albums.blade.php b/resources/views/themes/base/admin/list_albums.blade.php index 9c80b27..64eb16f 100644 --- a/resources/views/themes/base/admin/list_albums.blade.php +++ b/resources/views/themes/base/admin/list_albums.blade.php @@ -2,24 +2,18 @@ @section('title', 'Gallery Admin') @section('breadcrumb') - + + + @endsection @section('content')
-
+

@lang('admin.list_albums_title')

-

@lang('admin.list_albums_intro')

+ @lang('admin.list_albums_intro')
@if (count($albums) == 0) @@ -48,9 +42,6 @@

{{ $album->photos_count }} {{ trans_choice('admin.stats_widget.photos', $album->photos_count) }}

- @can('edit', $album) - @lang('forms.edit_action') - @endcan @can('delete', $album) @lang('forms.delete_action') @endcan diff --git a/resources/views/themes/base/admin/list_groups.blade.php b/resources/views/themes/base/admin/list_groups.blade.php index c8a5375..31b35d2 100644 --- a/resources/views/themes/base/admin/list_groups.blade.php +++ b/resources/views/themes/base/admin/list_groups.blade.php @@ -13,7 +13,7 @@

@yield('title')

-

@lang('admin.list_groups_intro')

+ @lang('admin.list_groups_intro')
@if (count($groups) == 0) diff --git a/resources/views/themes/base/admin/list_users.blade.php b/resources/views/themes/base/admin/list_users.blade.php index 06b9eec..431836b 100644 --- a/resources/views/themes/base/admin/list_users.blade.php +++ b/resources/views/themes/base/admin/list_users.blade.php @@ -13,7 +13,7 @@

@lang('admin.list_users_title')

-

@lang('admin.list_users_intro')

+ @lang('admin.list_users_intro')
diff --git a/resources/views/themes/base/admin/show_album.blade.php b/resources/views/themes/base/admin/show_album.blade.php index fc70302..8130fd1 100644 --- a/resources/views/themes/base/admin/show_album.blade.php +++ b/resources/views/themes/base/admin/show_album.blade.php @@ -2,22 +2,16 @@ @section('title', $album->name) @section('breadcrumb') - + + + + @endsection @section('content')
-
+
@lang('admin.open_album')

{{ $album->name }}

@@ -42,34 +36,39 @@

@else - {!! Form::open(['route' => ['photos.updateBulk', $album->id], 'method' => 'PUT', 'id' => 'bulk-modify-form']) !!} +
+ {{ csrf_field() }} + {{ method_field('PUT') }} - @foreach ($photos as $photo) - @include (Theme::viewName('partials.single_photo_admin')) - @endforeach + @foreach ($photos as $photo) + @include (Theme::viewName('partials.single_photo_admin')) + @endforeach -
-

- - -

- +
+

+ + +

+ -
-

@lang('admin.select_all_album_active')

+
+

@lang('admin.select_all_album_active')

+
+ +

+ + +
- -

{!! Form::label('bulk-action', trans('forms.bulk_edit_photos_label'), ['class' => 'control-label']) !!}

- {!! Form::hidden('new-album-id', $album->id) !!} - {!! Form::select('bulk-action', $bulk_actions, null, ['placeholder' => trans('forms.bulk_edit_photos_placeholder'), 'id' => 'bulk-action-apply', 'data-bind' => 'value: bulkModifyMethod, enable: photoIDs().length > 0']) !!} - -
-
- -
-
- - {!! Form::close() !!} +
+ +
+
{{ $photos->links() }} @@ -78,93 +77,7 @@
{{-- Upload --}} -
- @if (!$is_upload_enabled) -
-
-

@lang('admin.upload_disabled_heading')

-

@lang('admin.upload_disabled_text')

-
-
- @else -

@lang('admin.upload_single_file_heading')

-

@lang('admin.upload_single_file_text')

-
-

@lang('admin.upload_single_file_text2', [ - 'file_size' => sprintf('%s%s', round($file_upload_limit, 2), trans('global.units.megabytes')), - 'max_upload_size' => sprintf('%s%s', round($max_post_limit, 2), trans('global.units.megabytes')) - ])

-
- -
-
- {!! Form::open(['route' => 'photos.store', 'method' => 'POST', 'files' => true, 'id' => 'single-upload-form']) !!} - {!! Form::hidden('album_id', $album->id) !!} - {!! Form::hidden('queue_token', $queue_token) !!} - -
- {!! Form::file('photo[]', ['class' => 'control-label', 'multiple' => 'multiple', 'id' => 'single-upload-files']) !!} -
- -
- -
- {!! Form::close() !!} -
- -
-
-

@lang('admin.is_uploading')

-
-
- -
-
- -
-
-

-
- -
-

- @lang('admin.upload_file_number_failed') -

-

- @lang('admin.upload_file_failed_continue')

- @lang('forms.continue_action') -

- -
    -
  • -
-
-
-
- -
-

@lang('admin.upload_bulk_heading')

-

@lang('admin.upload_bulk_text')

-
-

@lang('admin.upload_bulk_text2', [ - 'max_upload_size' => sprintf('%s%s', round($max_post_limit_bulk, 2), trans('global.units.megabytes')) - ])

-
- - {!! Form::open(['route' => 'photos.storeBulk', 'method' => 'POST', 'files' => true, 'id' => 'bulk-upload-form']) !!} - {!! Form::hidden('album_id', $album->id) !!} - {!! Form::hidden('queue_token', $queue_token) !!} - -
- {!! Form::file('archive', ['class' => 'control-label']) !!} -
- -
- -
- {!! Form::close() !!} - @endif -
+ @include(Theme::viewName('partials.album_upload_tab')) {{-- Permissions --}}
@@ -259,64 +172,71 @@ {{-- Settings --}}
- {!! Form::model($album, ['route' => ['albums.update', $album->id], 'method' => 'PUT']) !!} -

@lang('admin.album_basic_info_heading')

-

@lang('admin.album_basic_info_intro')

+
+ {{ csrf_field() }} + {{ method_field('PUT') }} -
- {!! Form::label('name', trans('forms.name_label'), ['class' => 'control-label']) !!} - {!! Form::text('name', old('name'), ['class' => 'form-control']) !!} +

@lang('admin.album_basic_info_heading')

+

@lang('admin.album_basic_info_intro')

- @if ($errors->has('name')) - - {{ $errors->first('name') }} - - @endif -
+
+ + -
- {!! Form::label('description', trans('forms.description_label'), ['class' => 'control-label']) !!} - {!! Form::textarea('description', old('description'), ['class' => 'form-control']) !!} -
+ @if ($errors->has('name')) + + @endif +
-
+
+ + +
-

@lang('admin.album_appearance_heading')

-

@lang('admin.album_appearance_intro')

+
-
- - {!! Form::select('default_view', $allowed_views, old('default_view'), ['class' => 'form-control']) !!} -
+

@lang('admin.album_appearance_heading')

+

@lang('admin.album_appearance_intro')

-
+
+ + +
-
-
-
-
@lang('admin.save_changes_heading')
-
-

@lang('admin.save_changes_intro')

-

- -

+
+ +
+
+
+
@lang('admin.save_changes_heading')
+
+

@lang('admin.save_changes_intro')

+
+ +
+
+
+
+ +
+
+
@lang('admin.danger_zone_heading')
+
+

@lang('admin.danger_zone_intro')

+ +
- -
-
-
@lang('admin.danger_zone_heading')
-
-

@lang('admin.danger_zone_intro')

-

- @lang('forms.delete_action') -

-
-
-
-
- {!! Form::close() !!} +
@@ -337,6 +257,7 @@ language.delete_bulk_confirm_title = '{!! addslashes(trans('admin.delete_bulk_photos_title')) !!}'; language.delete_confirm_message = '{!! addslashes(trans('admin.delete_photo_message')) !!}'; language.delete_confirm_title = '{!! addslashes(trans('admin.delete_photo_title')) !!}'; + language.not_an_image_file = '{!! addslashes(trans('admin.upload_file_not_image_messages')) !!}'; language.select_all_choice_all_action = '{!! addslashes(trans('admin.select_all_choice.all_action')) !!}'; language.select_all_choice_message = '{!! addslashes(trans('admin.select_all_choice.message')) !!}'; language.select_all_choice_title = '{!! addslashes(trans('admin.select_all_choice.title')) !!}'; @@ -354,7 +275,7 @@ urls.rotate_photo = '{{ route('photos.rotate', ['id' => 0, 'angle' => 1]) }}'; var viewModel = new UploadPhotosViewModel('{{ $album->id }}', '{{ $queue_token }}', language, urls); - var editViewModel = new EditPhotosViewModel('{{ $album->id }}', language, urls); + /*var editViewModel = new EditPhotosViewModel('{{ $album->id }}', language, urls); @foreach ($photos as $photo) editViewModel.photoIDsAvailable.push('{{ $photo->id }}'); @@ -366,7 +287,7 @@ 'id': '{{ $album->id }}', 'name': '{!! addslashes($album->name) !!}' }); - @endforeach + @endforeach*/ $(document).ready(function() { $('#upload-button').click(function() { @@ -374,51 +295,18 @@ }); {{-- Photo editing tasks - the buttons beneath the photos in partials/single_photo_admin --}} - $('a.change-album').click(editViewModel.changeAlbum); + /*$('a.change-album').click(editViewModel.changeAlbum); $('a.delete-photo').click(editViewModel.delete); $('a.flip-photo-both').click(editViewModel.flipBoth); $('a.flip-photo-horizontal').click(editViewModel.flipHorizontal); $('a.flip-photo-vertical').click(editViewModel.flipVertical); $('a.regenerate-thumbnails').click(editViewModel.regenerateThumbnails); $('a.rotate-photo-left').click(editViewModel.rotateLeft); - $('a.rotate-photo-right').click(editViewModel.rotateRight); + $('a.rotate-photo-right').click(editViewModel.rotateRight);*/ {{-- Photo uploads using AJAX --}} if (window.FormData) { - $('#single-upload-form').submit(function(event) { - var fileSelect = $('#single-upload-files', this); - var uploadButton = $('button[type=submit]', this); - - // Get the selected files - var notImageString = '{!! addslashes(trans('admin.upload_file_not_image_messages')) !!}'; - var files = fileSelect[0].files; - - // Reset statistics - viewModel.startUpload(files.length); - - // 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(notImageString.replace(':file_name', file.name)); - viewModel.onUploadFailed(null, file.name); - continue; - } - - // Upload the file - viewModel.uploadFile($(this), file); - } - - // Prevent standard form upload - event.preventDefault(); - return false; - }); - $('#bulk-upload-form').submit(function(event) { // Set the in progress flag - no need to unset it as this is a synchronous process so the browser // will reload the page in some way after completion @@ -450,7 +338,7 @@ }); {{-- Type-ahead support for users textbox on the permissions tab --}} - var userDataSource = new Bloodhound({ + /*var userDataSource = new Bloodhound({ datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), queryTokenizer: Bloodhound.tokenizers.whitespace, //prefetch: '../data/films/post_1960.json', @@ -467,11 +355,12 @@ }); $('#user-search-textbox').bind('typeahead:select', function(ev, suggestion) { $('#user-id-field').val(suggestion.id); - }); + });*/ // Bind the view models to the relevant tab - ko.applyBindings(editViewModel, document.getElementById('photos-tab')); - ko.applyBindings(viewModel, document.getElementById('upload-tab')); + //ko.applyBindings(editViewModel, document.getElementById('photos-tab')); + //ko.applyBindings(viewModel, document.getElementById('upload-tab')); + var appUpload = new Vue(viewModel); }) @endpush \ No newline at end of file diff --git a/resources/views/themes/base/partials/album_upload_tab.blade.php b/resources/views/themes/base/partials/album_upload_tab.blade.php new file mode 100644 index 0000000..e39335f --- /dev/null +++ b/resources/views/themes/base/partials/album_upload_tab.blade.php @@ -0,0 +1,95 @@ +
+ @if (!$is_upload_enabled) +
+
+

@lang('admin.upload_disabled_heading')

+

@lang('admin.upload_disabled_text')

+
+
+ @else +

@lang('admin.upload_single_file_heading')

+

@lang('admin.upload_single_file_text')

+
+ @lang('admin.upload_single_file_text2', [ + 'file_size' => sprintf('%s%s', round($file_upload_limit, 2), trans('global.units.megabytes')), + 'max_upload_size' => sprintf('%s%s', round($max_post_limit, 2), trans('global.units.megabytes')) + ]) +
+ +
+
+
+ {{ csrf_field() }} + + + +
+ +
+ +
+ +
+ +
+ +
+
+

@lang('admin.is_uploading')

+
+
+ +
+
+ +
+
+

+
+ +
+

+ @lang('admin.upload_file_number_failed') +

+

+ @lang('admin.upload_file_failed_continue')

+ @lang('forms.continue_action') +

+ +
    +
  • +
+
+
+
+ +
+

@lang('admin.upload_bulk_heading')

+

@lang('admin.upload_bulk_text')

+
+ @lang('admin.upload_bulk_text2', [ + 'max_upload_size' => sprintf('%s%s', round($max_post_limit_bulk, 2), trans('global.units.megabytes')) + ]) +
+ +
+ {{ csrf_field() }} + + + +
+ +
+ +
+ +
+ + @endif +
\ No newline at end of file