resolves #2: photos can now be moved between albums. Started improving the bulk photo update to use a KnockoutJS view model to remove some of the logic from the view itself.
This commit is contained in:
parent
45277efbb8
commit
0e569562a4
2
.idea/webServers.xml
generated
2
.idea/webServers.xml
generated
@ -3,7 +3,7 @@
|
||||
<component name="WebServers">
|
||||
<option name="servers">
|
||||
<webServer id="b14a34b0-0127-4886-964a-7be75a2281ac" name="Development" url="http://blue-twilight-dev.andys.eu">
|
||||
<fileTransfer host="mickey.andys.eu" port="22" rootFolder="/srv/www/blue-twilight-dev" accessType="SFTP">
|
||||
<fileTransfer host="mickey.prod.pandy06269.uk0.bigv.io" port="22" rootFolder="/srv/www/blue-twilight-dev" accessType="SFTP">
|
||||
<advancedOptions>
|
||||
<advancedOptions dataProtectionLevel="Private" />
|
||||
</advancedOptions>
|
||||
|
@ -57,9 +57,10 @@ interface IAlbumSource
|
||||
/**
|
||||
* Saves an uploaded file to the container and returns the filename.
|
||||
* @param File $uploadedFile The photo uploaded
|
||||
* @param string $overrideFilename Specific file name to use, or null to randomly generate one.
|
||||
* @return File
|
||||
*/
|
||||
function saveUploadedPhoto(File $uploadedFile);
|
||||
function saveUploadedPhoto(File $uploadedFile, $overrideFilename = null);
|
||||
|
||||
/**
|
||||
* @param Album $album
|
||||
|
@ -68,14 +68,23 @@ class LocalFilesystemSource extends AlbumSourceBase implements IAlbumSource
|
||||
$fileInfo->move(sprintf('%s/%s', $this->getPathToAlbum(), $thumbnailInfo['name']), $photo->storage_file_name);
|
||||
}
|
||||
|
||||
public function saveUploadedPhoto(File $uploadedFile)
|
||||
public function saveUploadedPhoto(File $uploadedFile, $overrideFilename = null)
|
||||
{
|
||||
$tempFilename = sprintf('%s/%s/%s', $this->getPathToAlbum(), $this->getOriginalsFolder(), MiscHelper::randomString(20));
|
||||
$tempFilename = sprintf(
|
||||
'%s/%s/%s',
|
||||
$this->getPathToAlbum(),
|
||||
$this->getOriginalsFolder(),
|
||||
is_null($overrideFilename) ? MiscHelper::randomString(20) : basename($overrideFilename)
|
||||
);
|
||||
|
||||
$extension = $uploadedFile->guessExtension();
|
||||
if (!is_null($extension))
|
||||
// Only add an extension if an override filename was not given, assume this is present
|
||||
if (is_null($overrideFilename))
|
||||
{
|
||||
$tempFilename .= '.' . $extension;
|
||||
$extension = $uploadedFile->guessExtension();
|
||||
if (!is_null($extension))
|
||||
{
|
||||
$tempFilename .= '.' . $extension;
|
||||
}
|
||||
}
|
||||
|
||||
$uploadedFile->move(dirname($tempFilename), basename($tempFilename));
|
||||
|
@ -165,6 +165,7 @@ class AlbumController extends Controller
|
||||
'flip_vertical' => trans('admin.photo_actions.flip_vertical'),
|
||||
'flip_both' => trans('admin.photo_actions.flip_both'),
|
||||
'--' => '-----',
|
||||
'change_album' => trans('admin.photo_actions.change_album'),
|
||||
'refresh_thumbnails' => trans('admin.photo_actions.refresh_thumbnails'),
|
||||
'delete' => trans('admin.photo_actions.delete')
|
||||
],
|
||||
@ -174,7 +175,8 @@ class AlbumController extends Controller
|
||||
'max_post_limit' => $postLimit,
|
||||
'max_post_limit_bulk' => $fileUploadOrPostLowerLimit,
|
||||
'photos' => $photos,
|
||||
'success' => $request->session()->get('success')
|
||||
'success' => $request->session()->get('success'),
|
||||
'warning' => $request->session()->get('warning')
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ class PhotoController extends Controller
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy($id)
|
||||
public function destroy(Request $request, $id)
|
||||
{
|
||||
$this->authorize('admin-access');
|
||||
|
||||
@ -102,7 +102,7 @@ class PhotoController extends Controller
|
||||
$photoService = new PhotoService($photo);
|
||||
$photoService->delete();
|
||||
|
||||
return back();
|
||||
$request->session()->flash('success', trans('admin.delete_photo_successful_message', ['name' => $photo->name]));
|
||||
}
|
||||
|
||||
public function flip($photoId, $horizontal, $vertical)
|
||||
@ -122,6 +122,37 @@ class PhotoController extends Controller
|
||||
$photoService->flip($horizontal, $vertical);
|
||||
}
|
||||
|
||||
public function move(Request $request, $photoId)
|
||||
{
|
||||
$this->authorize('admin-access');
|
||||
|
||||
$photo = Photo::where('id', intval($photoId))->first();
|
||||
if (is_null($photo))
|
||||
{
|
||||
App::abort(404);
|
||||
}
|
||||
|
||||
$newAlbum = Album::where('id', intval($request->get('new_album_id')))->first();
|
||||
if (is_null($newAlbum))
|
||||
{
|
||||
App::abort(404);
|
||||
}
|
||||
|
||||
$messageData = ['name' => $photo->name, 'album' => $newAlbum->name];
|
||||
|
||||
if ($newAlbum->id == $photo->album_id)
|
||||
{
|
||||
$request->session()->flash('warning', trans('admin.move_failed_same_album', $messageData));
|
||||
}
|
||||
else
|
||||
{
|
||||
$photoService = new PhotoService($photo);
|
||||
$photoService->changeAlbum($newAlbum);
|
||||
|
||||
$request->session()->flash('success', trans('admin.move_successful_message', $messageData));
|
||||
}
|
||||
}
|
||||
|
||||
public function regenerateThumbnails($photoId)
|
||||
{
|
||||
$this->authorize('admin-access');
|
||||
@ -372,6 +403,23 @@ class PhotoController extends Controller
|
||||
$photoService = new PhotoService($photo);
|
||||
switch (strtolower($action))
|
||||
{
|
||||
case 'change_album':
|
||||
$newAlbumId = intval($request->get('new-album-id'));
|
||||
if ($newAlbumId == $photo->album_id)
|
||||
{
|
||||
// Photo already belongs to this album, don't move
|
||||
continue;
|
||||
}
|
||||
|
||||
$newAlbum = Album::where('id', $newAlbumId)->first();
|
||||
if (is_null($newAlbum))
|
||||
{
|
||||
App::abort(404);
|
||||
}
|
||||
|
||||
$photoService->changeAlbum($newAlbum);
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
$photoService->delete();
|
||||
break;
|
||||
|
@ -7,6 +7,7 @@ use App\AlbumSources\IAlbumSource;
|
||||
use App\Helpers\ImageHelper;
|
||||
use App\Helpers\ThemeHelper;
|
||||
use App\Photo;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
class PhotoService
|
||||
{
|
||||
@ -118,6 +119,32 @@ class PhotoService
|
||||
$this->regenerateThumbnails($originalPhotoResource);
|
||||
}
|
||||
|
||||
public function changeAlbum(Album $newAlbum)
|
||||
{
|
||||
/** @var IAlbumSource $currentSource */
|
||||
$currentSource = $this->photo->album->getAlbumSource();
|
||||
$newSource = $newAlbum->getAlbumSource();
|
||||
|
||||
// Get the current photo's path
|
||||
$file = new File($currentSource->getPathToPhoto($this->photo));
|
||||
|
||||
// Save to the new album
|
||||
$newSource->saveUploadedPhoto($file, $this->photo->storage_file_name);
|
||||
|
||||
// Delete the original
|
||||
$this->delete();
|
||||
|
||||
// Update the ID and new file name
|
||||
$this->photo->album_id = $newAlbum->id;
|
||||
$this->photo->save();
|
||||
|
||||
// Switch to the new album source
|
||||
$this->albumSource = $newSource;
|
||||
|
||||
// Regenerate the thumbnails in the new album
|
||||
$this->regenerateThumbnails();
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
// Remove all thumbnails first - so if any fail, we don't delete the original
|
||||
|
@ -1,5 +1,4 @@
|
||||
function AnalyseAlbumViewModel()
|
||||
{
|
||||
function AnalyseAlbumViewModel() {
|
||||
var self = this;
|
||||
|
||||
self.imagesFailed = ko.observableArray();
|
||||
@ -7,35 +6,30 @@ function AnalyseAlbumViewModel()
|
||||
self.numberSuccessful = ko.observable(0);
|
||||
self.numberFailed = ko.observable(0);
|
||||
|
||||
self.numberTotal = ko.computed(function() {
|
||||
self.numberTotal = ko.computed(function () {
|
||||
return self.imagesToAnalyse().length;
|
||||
});
|
||||
|
||||
self.failedPercentage = ko.computed(function()
|
||||
{
|
||||
self.failedPercentage = ko.computed(function () {
|
||||
return ((self.numberFailed() / self.numberTotal()) * 100).toFixed(2) + '%';
|
||||
});
|
||||
self.successfulPercentage = ko.computed(function()
|
||||
{
|
||||
self.successfulPercentage = ko.computed(function () {
|
||||
return ((self.numberSuccessful() / self.numberTotal()) * 100).toFixed(2) + '%';
|
||||
});
|
||||
|
||||
self.isCompleted = ko.computed(function()
|
||||
{
|
||||
self.isCompleted = ko.computed(function () {
|
||||
return self.numberTotal() > 0 && (self.numberSuccessful() + self.numberFailed() >= self.numberTotal());
|
||||
});
|
||||
|
||||
// When an image is added to the array, automatically issue it for analysis
|
||||
self.imagesToAnalyse.subscribe(function(changes)
|
||||
{
|
||||
self.imagesToAnalyse.subscribe(function (changes) {
|
||||
// changes[0].value is an instance of AnalyseImageViewModel
|
||||
var item = changes[0].value;
|
||||
$.ajax(
|
||||
item.url(),
|
||||
{
|
||||
dataType: 'json',
|
||||
error: function (xhr, textStatus, errorThrown)
|
||||
{
|
||||
error: function (xhr, textStatus, errorThrown) {
|
||||
self.numberFailed(self.numberFailed() + 1);
|
||||
self.imagesFailed.push({
|
||||
'name': item.name(),
|
||||
@ -45,16 +39,13 @@ function AnalyseAlbumViewModel()
|
||||
item.isPending(false);
|
||||
},
|
||||
method: 'POST',
|
||||
success: function (data)
|
||||
{
|
||||
if (data.is_successful)
|
||||
{
|
||||
success: function (data) {
|
||||
if (data.is_successful) {
|
||||
self.numberSuccessful(self.numberSuccessful() + 1);
|
||||
item.isSuccessful(true);
|
||||
item.isPending(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
self.numberFailed(self.numberFailed() + 1);
|
||||
self.imagesFailed.push({
|
||||
'name': item.name(),
|
||||
@ -69,8 +60,7 @@ function AnalyseAlbumViewModel()
|
||||
}, null, 'arrayChange');
|
||||
}
|
||||
|
||||
function AnalyseImageViewModel(image_info)
|
||||
{
|
||||
function AnalyseImageViewModel(image_info) {
|
||||
var self = this;
|
||||
|
||||
self.isPending = ko.observable(true);
|
||||
@ -79,34 +69,194 @@ function AnalyseImageViewModel(image_info)
|
||||
self.photoID = ko.observable(image_info.photo_id);
|
||||
self.url = ko.observable(image_info.url);
|
||||
|
||||
self.iconClass = ko.computed(function() {
|
||||
self.iconClass = ko.computed(function () {
|
||||
var string = 'fa fa-fw ';
|
||||
|
||||
if (!self.isPending())
|
||||
{
|
||||
if (!self.isPending()) {
|
||||
string += (self.isSuccessful() ? 'check text-success' : 'times text-danger')
|
||||
}
|
||||
|
||||
|
||||
return string;
|
||||
});
|
||||
}
|
||||
|
||||
function StorageLocationsViewModel()
|
||||
{
|
||||
/**
|
||||
* This model is used by admin/show_album.blade.php to handle photo changes.
|
||||
* @param album_id ID of the album the photos are in
|
||||
* @param language Array containing language strings
|
||||
* @param urls Array containing URLs
|
||||
* @constructor
|
||||
*/
|
||||
function EditPhotosViewModel(album_id, language, urls) {
|
||||
var self = this;
|
||||
|
||||
self.albums = ko.observableArray();
|
||||
self.bulkModifyMethod = ko.observable();
|
||||
self.photoIDs = ko.observableArray();
|
||||
|
||||
/* Called when the Apply button on the "bulk apply selected actions" form is clicked */
|
||||
self.bulkModifySelected = function()
|
||||
{
|
||||
var bulk_form = $('form#bulk-modify-form');
|
||||
|
||||
if (self.bulkModifyMethod() == 'change_album')
|
||||
{
|
||||
// Prompt for the new album to move to
|
||||
self.promptForNewAlbum(function(dialog) {
|
||||
var album_id = $('select', dialog).val();
|
||||
$('input[name="new-album-id"]', form).val(album_id);
|
||||
bulk_form.submit();
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (self.bulkModifyMethod() == 'delete')
|
||||
{
|
||||
// Prompt for a confirmation - are you sure?!
|
||||
bootbox.dialog({
|
||||
message: language.delete_bulk_confirm_message.replace(':number', self.photoIDs().length),
|
||||
title: language.delete_bulk_confirm_title.replace(':number', self.photoIDs().length),
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: language.action_cancel,
|
||||
className: "btn-default"
|
||||
},
|
||||
confirm: {
|
||||
label: language.action_delete,
|
||||
className: "btn-danger",
|
||||
callback: function() {
|
||||
bulk_form.submit();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// All other methods submit the form as normal
|
||||
return true;
|
||||
};
|
||||
|
||||
self.changeAlbum = function()
|
||||
{
|
||||
self.selectPhotoSingle(this);
|
||||
|
||||
self.promptForNewAlbum(function(dialog) {
|
||||
var album_id = $('select', dialog).val();
|
||||
$.post(urls.move_photo.replace(/\/0$/, '/' + self.photoIDs()[0]), { 'new_album_id': album_id }, function()
|
||||
{
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
self.delete = function() {
|
||||
self.selectPhotoSingle(this);
|
||||
|
||||
bootbox.dialog({
|
||||
message: language.delete_confirm_message,
|
||||
title: language.delete_confirm_title,
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: language.action_cancel,
|
||||
className: "btn-default"
|
||||
},
|
||||
confirm: {
|
||||
label: language.action_delete,
|
||||
className: "btn-danger",
|
||||
callback: function() {
|
||||
var url = urls.delete_photo;
|
||||
url = url.replace(/\/0$/, '/' + self.photoIDs()[0]);
|
||||
|
||||
$('.loading', parent).show();
|
||||
|
||||
$.post(url, {'_method': 'DELETE'}, function(data)
|
||||
{
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
self.promptForNewAlbum = function(callback_on_selected)
|
||||
{
|
||||
var albums = self.albums();
|
||||
var select = $('<select/>')
|
||||
.attr('name', 'album_id')
|
||||
.addClass('form-control');
|
||||
|
||||
for (var i = 0; i < albums.length; i++)
|
||||
{
|
||||
var option = $('<option/>')
|
||||
.attr('value', albums[i].id)
|
||||
.html(albums[i].name)
|
||||
.appendTo(select);
|
||||
|
||||
// Pre-select the current album
|
||||
if (album_id == albums[i].id)
|
||||
{
|
||||
option.attr('selected', 'selected');
|
||||
}
|
||||
}
|
||||
|
||||
bootbox.dialog({
|
||||
message: $('<p/>').html(language.change_album_message).prop('outerHTML') + select.prop('outerHTML'),
|
||||
title: language.change_album_title,
|
||||
buttons:
|
||||
{
|
||||
cancel:
|
||||
{
|
||||
label: language.action_cancel,
|
||||
className: 'btn-default'
|
||||
},
|
||||
confirm:
|
||||
{
|
||||
label: language.action_continue,
|
||||
className: 'btn-success',
|
||||
callback: function()
|
||||
{
|
||||
callback_on_selected(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
self.selectPhotoSingle = function (link_item) {
|
||||
// Get the photo ID from the clicked link
|
||||
var parent = $(link_item).parents('.photo');
|
||||
var photo_id = $(parent).data('photo-id');
|
||||
|
||||
// Save the photo ID
|
||||
self.photoIDs.removeAll();
|
||||
self.photoIDs.push(photo_id);
|
||||
|
||||
// Hide the dropdown
|
||||
$(link_item).dropdown('toggle');
|
||||
};
|
||||
}
|
||||
|
||||
function StorageLocationsViewModel() {
|
||||
var self = this;
|
||||
|
||||
self.selectedLocation = ko.observable(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* This file is used by admin/show_album.blade.php to handle photo uploads.
|
||||
* 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 language Array containing language strings
|
||||
* @param urls Array containing URLs
|
||||
* @constructor
|
||||
*/
|
||||
function UploadPhotosViewModel(album_id, language, urls)
|
||||
{
|
||||
function UploadPhotosViewModel(album_id, language, urls) {
|
||||
var self = this;
|
||||
|
||||
self.currentStatus = ko.observable('');
|
||||
@ -117,32 +267,27 @@ function UploadPhotosViewModel(album_id, language, urls)
|
||||
self.isUploadInProgress = ko.observable(false);
|
||||
self.statusMessages = ko.observableArray();
|
||||
|
||||
self.failedPercentage = ko.computed(function()
|
||||
{
|
||||
self.failedPercentage = ko.computed(function () {
|
||||
return ((self.imagesFailed() / self.imagesTotal()) * 100).toFixed(2) + '%';
|
||||
});
|
||||
|
||||
// This method is called when an image is uploaded - regardless if it fails or not
|
||||
self.onUploadCompleted = function()
|
||||
{
|
||||
self.onUploadCompleted = function () {
|
||||
self.currentStatus(language.upload_status
|
||||
.replace(':current', (self.imagesUploaded() + self.imagesFailed()))
|
||||
.replace(':total', self.imagesTotal()));
|
||||
.replace(':current', (self.imagesUploaded() + self.imagesFailed()))
|
||||
.replace(':total', self.imagesTotal()));
|
||||
|
||||
if ((self.imagesFailed() + self.imagesUploaded()) >= self.imagesTotal())
|
||||
{
|
||||
if ((self.imagesFailed() + self.imagesUploaded()) >= self.imagesTotal()) {
|
||||
self.isUploadInProgress(false);
|
||||
|
||||
if (self.imagesFailed() == 0 && self.imagesUploaded() > 0)
|
||||
{
|
||||
if (self.imagesFailed() == 0 && self.imagesUploaded() > 0) {
|
||||
window.location = urls.analyse;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// This method is called when an uploaded image fails
|
||||
self.onUploadFailed = function(data, file_name)
|
||||
{
|
||||
self.onUploadFailed = function (data, file_name) {
|
||||
self.imagesFailed(self.imagesFailed() + 1);
|
||||
self.statusMessages.push({
|
||||
'message_class': 'text-danger',
|
||||
@ -152,26 +297,22 @@ function UploadPhotosViewModel(album_id, language, urls)
|
||||
};
|
||||
|
||||
// This method is called when an uploaded image succeeds
|
||||
self.onUploadSuccessful = function(data, file_name)
|
||||
{
|
||||
if (data.is_successful)
|
||||
{
|
||||
self.onUploadSuccessful = function (data, file_name) {
|
||||
if (data.is_successful) {
|
||||
self.imagesUploaded(self.imagesUploaded() + 1);
|
||||
// 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)
|
||||
});*/
|
||||
'message_class': 'text-success',
|
||||
'message_text': language.image_uploaded.replace(':file_name', file_name)
|
||||
});*/
|
||||
self.onUploadCompleted();
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
self.onUploadFailed(data, file_name);
|
||||
}
|
||||
};
|
||||
|
||||
self.startUpload = function(number_images)
|
||||
{
|
||||
self.startUpload = function (number_images) {
|
||||
self.currentStatus('');
|
||||
self.statusMessages.removeAll();
|
||||
self.imagesUploaded(0);
|
||||
@ -180,13 +321,11 @@ function UploadPhotosViewModel(album_id, language, urls)
|
||||
self.isUploadInProgress(true);
|
||||
};
|
||||
|
||||
self.successfulPercentage = ko.computed(function()
|
||||
{
|
||||
self.successfulPercentage = ko.computed(function () {
|
||||
return ((self.imagesUploaded() / self.imagesTotal()) * 100).toFixed(2) + '%';
|
||||
});
|
||||
|
||||
self.uploadFile = function uploadImageFile(formObject, imageFile)
|
||||
{
|
||||
self.uploadFile = function uploadImageFile(formObject, imageFile) {
|
||||
var formData = new FormData();
|
||||
formData.append('album_id', album_id);
|
||||
formData.append('photo[]', imageFile, imageFile.name);
|
||||
@ -195,12 +334,12 @@ function UploadPhotosViewModel(album_id, language, urls)
|
||||
{
|
||||
contentType: false,
|
||||
data: formData,
|
||||
error: function(data) {
|
||||
error: function (data) {
|
||||
self.onUploadFailed(data, imageFile.name);
|
||||
},
|
||||
method: $(formObject).attr('method'),
|
||||
processData: false,
|
||||
success: function(data) {
|
||||
success: function (data) {
|
||||
self.onUploadSuccessful(data, imageFile.name);
|
||||
},
|
||||
url: $(formObject).attr('action')
|
||||
|
@ -14,6 +14,8 @@ return [
|
||||
'bulk_photos_changed' => ':number photo was updated successfully.|:number photos were updated successfully.',
|
||||
'cannot_delete_own_user_account' => 'It is not possible to delete your own user account. Please ask another administrator to delete it for you.',
|
||||
'cannot_remove_own_admin' => 'You cannot remove your own administrator permissions. Please ask another administrator to remove the administrator permissions for you.',
|
||||
'change_album_message' => 'Please select the album to move the photo(s) to:',
|
||||
'change_album_title' => 'Move photo(s) to another album',
|
||||
'create_album' => 'Create a photo album',
|
||||
'create_album_intro' => 'Photo albums contain individual photographs together in the same way as a physical photo album or memory book.',
|
||||
'create_album_intro2' => 'Complete the form below to create a photo album.',
|
||||
@ -26,6 +28,11 @@ return [
|
||||
'delete_album' => 'Delete album :name',
|
||||
'delete_album_confirm' => 'Are you sure you want to permanently delete this album and all its contents?',
|
||||
'delete_album_warning' => 'This is a permanent action that cannot be undone!',
|
||||
'delete_bulk_photos_message' => 'Are you sure you want to delete the :number selected photos? This action cannot be undone!',
|
||||
'delete_bulk_photos_title' => 'Delete :number photos',
|
||||
'delete_photo_message' => 'Are you sure you want to delete this photo? This action cannot be undone!',
|
||||
'delete_photo_successful_message' => 'The photo ":name" was deleted successfully.',
|
||||
'delete_photo_title' => 'Delete photo',
|
||||
'delete_storage' => 'Delete storage location: :name',
|
||||
'delete_storage_confirm' => 'Are you sure you want to permanently remove this storage location?',
|
||||
'delete_storage_existing_albums' => 'At least one album is still using the storage location. Please delete all albums before removing the storage location.',
|
||||
@ -44,6 +51,8 @@ return [
|
||||
'manage_widget' => [
|
||||
'panel_header' => 'Manage'
|
||||
],
|
||||
'move_failed_same_album' => 'The photo ":name" already belongs to the ":album" album and was not moved.',
|
||||
'move_successful_message' => 'The photo ":name" was moved successfully to the ":album" album.',
|
||||
'no_albums_text' => 'You have no photo albums yet. Click the button below to create one.',
|
||||
'no_albums_title' => 'No Photo Albums',
|
||||
'no_storages_text' => 'You need a storage location to store your uploaded photographs.',
|
||||
@ -51,6 +60,7 @@ return [
|
||||
'no_storages_title' => 'No storage locations defined',
|
||||
'open_album' => 'Open album',
|
||||
'photo_actions' => [
|
||||
'change_album' => 'Move to another album',
|
||||
'delete' => 'Delete permanently',
|
||||
'flip_both' => 'Flip both',
|
||||
'flip_horizontal' => 'Flip horizontally',
|
||||
|
@ -41,16 +41,17 @@
|
||||
<p style="margin-top: 30px;"><button id="upload-button" class="btn btn-lg btn-success">@lang('admin.album_no_photos_button')</button></p>
|
||||
</div>
|
||||
@else
|
||||
{!! Form::open(['route' => ['photos.updateBulk', $album->id], 'method' => 'PUT']) !!}
|
||||
{!! Form::open(['route' => ['photos.updateBulk', $album->id], 'method' => 'PUT', 'id' => 'bulk-modify-form']) !!}
|
||||
|
||||
@foreach ($photos as $photo)
|
||||
@include (Theme::viewName('partials.single_photo_admin'))
|
||||
@endforeach
|
||||
|
||||
<div class="pull-left">
|
||||
<div class="pull-left" style="margin-bottom: 15px;">
|
||||
<p>{!! Form::label('bulk-action', trans('forms.bulk_edit_photos_label'), ['class' => 'control-label']) !!}</p>
|
||||
{!! Form::select('bulk-action', $bulk_actions, null, ['placeholder' => trans('forms.bulk_edit_photos_placeholder'), 'id' => 'bulk-action-apply']) !!}
|
||||
<button type="submit" class="btn btn-sm btn-success" name="bulk-apply" value="clicked">@lang('forms.apply_action')</button>
|
||||
{!! 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']) !!}
|
||||
<button type="submit" class="btn btn-sm btn-primary" name="bulk-apply" value="clicked" data-bind="click: bulkModifySelected, enable: photoIDs().length > 0">@lang('forms.apply_action')</button>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<button type="submit" class="btn btn-success">@lang('forms.save_action')</button>
|
||||
@ -169,27 +170,34 @@
|
||||
@push('scripts')
|
||||
<script type="text/javascript">
|
||||
var language = [];
|
||||
language.action_cancel = '{!! addslashes(trans('forms.cancel_action')) !!}';
|
||||
language.action_continue = '{!! addslashes(trans('forms.continue_action')) !!}';
|
||||
language.action_delete = '{!! addslashes(trans('forms.delete_action')) !!}';
|
||||
language.change_album_message = '{!! addslashes(trans('admin.change_album_message')) !!}';
|
||||
language.change_album_title = '{!! addslashes(trans('admin.change_album_title')) !!}';
|
||||
language.delete_bulk_confirm_message = '{!! addslashes(trans('admin.delete_bulk_photos_message')) !!}';
|
||||
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.image_failed = '{!! addslashes(trans('admin.upload_file_status_failed')) !!}';
|
||||
language.image_uploaded = '{!! addslashes(trans('admin.upload_file_status_success')) !!}';
|
||||
language.upload_status = '{!! addslashes(trans('admin.upload_file_status_progress')) !!}';
|
||||
|
||||
var urls = [];
|
||||
urls.analyse = '{{ route('albums.analyse', ['id' => $album->id]) }}';
|
||||
urls.delete_photo = '{{ route('photos.destroy', ['id' => 0]) }}';
|
||||
urls.move_photo = '{{ route('photos.move', ['photoId' => 0]) }}';
|
||||
|
||||
var viewModel = new UploadPhotosViewModel('{{ $album->id }}', language, urls);
|
||||
var editViewModel = new EditPhotosViewModel('{{ $album->id }}', language, urls);
|
||||
|
||||
function deletePhoto(photo_id, parent)
|
||||
{
|
||||
var url = '{{ route('photos.destroy', ['id' => 0]) }}';
|
||||
url = url.replace(/\/0$/, '/' + photo_id);
|
||||
|
||||
$('.loading', parent).show();
|
||||
|
||||
$.post(url, {'_method': 'DELETE'}, function(data)
|
||||
{
|
||||
window.location.reload();
|
||||
// Populate the list of albums in the view model
|
||||
@foreach ($albums as $album)
|
||||
editViewModel.albums.push({
|
||||
'id': '{{ $album->id }}',
|
||||
'name': '{!! addslashes($album->name) !!}'
|
||||
});
|
||||
}
|
||||
@endforeach
|
||||
|
||||
function flipPhoto(photo_id, horizontal, vertical, parent)
|
||||
{
|
||||
@ -264,32 +272,9 @@
|
||||
});
|
||||
|
||||
{{-- Photo editing tasks - the buttons beneath the photos in partials/single_photo_admin --}}
|
||||
$('a.delete-photo').click(function() {
|
||||
var parent = $(this).parents('.photo');
|
||||
var photo_id = $(parent).data('photo-id');
|
||||
$('a.change-album').click(editViewModel.changeAlbum);
|
||||
$('a.delete-photo').click(editViewModel.delete);
|
||||
|
||||
$(this).dropdown('toggle');
|
||||
|
||||
bootbox.dialog({
|
||||
message: 'Are you sure you want to delete this photo? This cannot be undone!',
|
||||
title: 'Delete photo',
|
||||
buttons: {
|
||||
cancel: {
|
||||
label: "Cancel",
|
||||
className: "btn-default"
|
||||
},
|
||||
confirm: {
|
||||
label: "Delete",
|
||||
className: "btn-danger",
|
||||
callback: function() {
|
||||
deletePhoto(photo_id, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
$('a.flip-photo-both').click(function() {
|
||||
var parent = $(this).parents('.photo');
|
||||
var photo_id = $(parent).data('photo-id');
|
||||
@ -383,7 +368,9 @@
|
||||
});
|
||||
}
|
||||
|
||||
ko.applyBindings(viewModel);
|
||||
// Bind the view models to the relevant tab
|
||||
ko.applyBindings(editViewModel, document.getElementById('photos-tab'));
|
||||
ko.applyBindings(viewModel, document.getElementById('upload-tab'));
|
||||
})
|
||||
</script>
|
||||
@endpush
|
@ -29,6 +29,7 @@
|
||||
<i class="fa fa-fw fa-cog"></i> <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#" class="change-album"><i class="fa fa-fw fa-share"></i> @lang('admin.photo_actions.change_album')</a></li>
|
||||
<li><a href="#" class="regenerate-thumbnails"><i class="fa fa-fw fa-picture-o"></i> @lang('admin.photo_actions.refresh_thumbnails')</a></li>
|
||||
<li><a href="#" class="delete-photo"><i class="fa fa-fw fa-trash text-danger"></i> <span class="text-danger">@lang('admin.photo_actions.delete')</span></a></li>
|
||||
</ul>
|
||||
@ -36,7 +37,7 @@
|
||||
</div>
|
||||
|
||||
<p style="margin-top: 10px;">
|
||||
<input type="checkbox" id="select-photo-{{ $photo->id }}" name="select-photo[]" value="{{ $photo->id }}" /> <label for="select-photo-{{ $photo->id }}">@lang('forms.select')</label>
|
||||
<input type="checkbox" id="select-photo-{{ $photo->id }}" name="select-photo[]" value="{{ $photo->id }}" data-bind="checked: photoIDs" /> <label for="select-photo-{{ $photo->id }}">@lang('forms.select')</label>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-10">
|
||||
|
@ -28,6 +28,7 @@ Route::group(['prefix' => 'admin'], function () {
|
||||
// Photo management
|
||||
Route::post('photos/analyse/{id}', 'Admin\PhotoController@analyse')->name('photos.analyse');
|
||||
Route::post('photos/flip/{photoId}/{horizontal}/{vertical}', 'Admin\PhotoController@flip')->name('photos.flip');
|
||||
Route::post('photos/move/{photoId}', 'Admin\PhotoController@move')->name('photos.move');
|
||||
Route::post('photos/regenerate-thumbnails/{id}', 'Admin\PhotoController@regenerateThumbnails')->name('photos.regenerateThumbnails');
|
||||
Route::post('photos/rotate/{photoId}/{angle}', 'Admin\PhotoController@rotate')->name('photos.rotate');
|
||||
Route::post('photos/store-bulk', 'Admin\PhotoController@storeBulk')->name('photos.storeBulk');
|
||||
|
Loading…
Reference in New Issue
Block a user