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
@ -3,7 +3,7 @@
|
|||||||
<component name="WebServers">
|
<component name="WebServers">
|
||||||
<option name="servers">
|
<option name="servers">
|
||||||
<webServer id="b14a34b0-0127-4886-964a-7be75a2281ac" name="Development" url="http://blue-twilight-dev.andys.eu">
|
<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>
|
||||||
<advancedOptions dataProtectionLevel="Private" />
|
<advancedOptions dataProtectionLevel="Private" />
|
||||||
</advancedOptions>
|
</advancedOptions>
|
||||||
|
@ -57,9 +57,10 @@ interface IAlbumSource
|
|||||||
/**
|
/**
|
||||||
* Saves an uploaded file to the container and returns the filename.
|
* Saves an uploaded file to the container and returns the filename.
|
||||||
* @param File $uploadedFile The photo uploaded
|
* @param File $uploadedFile The photo uploaded
|
||||||
|
* @param string $overrideFilename Specific file name to use, or null to randomly generate one.
|
||||||
* @return File
|
* @return File
|
||||||
*/
|
*/
|
||||||
function saveUploadedPhoto(File $uploadedFile);
|
function saveUploadedPhoto(File $uploadedFile, $overrideFilename = null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Album $album
|
* @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);
|
$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();
|
// Only add an extension if an override filename was not given, assume this is present
|
||||||
if (!is_null($extension))
|
if (is_null($overrideFilename))
|
||||||
{
|
{
|
||||||
$tempFilename .= '.' . $extension;
|
$extension = $uploadedFile->guessExtension();
|
||||||
|
if (!is_null($extension))
|
||||||
|
{
|
||||||
|
$tempFilename .= '.' . $extension;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$uploadedFile->move(dirname($tempFilename), basename($tempFilename));
|
$uploadedFile->move(dirname($tempFilename), basename($tempFilename));
|
||||||
|
@ -165,6 +165,7 @@ class AlbumController extends Controller
|
|||||||
'flip_vertical' => trans('admin.photo_actions.flip_vertical'),
|
'flip_vertical' => trans('admin.photo_actions.flip_vertical'),
|
||||||
'flip_both' => trans('admin.photo_actions.flip_both'),
|
'flip_both' => trans('admin.photo_actions.flip_both'),
|
||||||
'--' => '-----',
|
'--' => '-----',
|
||||||
|
'change_album' => trans('admin.photo_actions.change_album'),
|
||||||
'refresh_thumbnails' => trans('admin.photo_actions.refresh_thumbnails'),
|
'refresh_thumbnails' => trans('admin.photo_actions.refresh_thumbnails'),
|
||||||
'delete' => trans('admin.photo_actions.delete')
|
'delete' => trans('admin.photo_actions.delete')
|
||||||
],
|
],
|
||||||
@ -174,7 +175,8 @@ class AlbumController extends Controller
|
|||||||
'max_post_limit' => $postLimit,
|
'max_post_limit' => $postLimit,
|
||||||
'max_post_limit_bulk' => $fileUploadOrPostLowerLimit,
|
'max_post_limit_bulk' => $fileUploadOrPostLowerLimit,
|
||||||
'photos' => $photos,
|
'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
|
* @param int $id
|
||||||
* @return \Illuminate\Http\Response
|
* @return \Illuminate\Http\Response
|
||||||
*/
|
*/
|
||||||
public function destroy($id)
|
public function destroy(Request $request, $id)
|
||||||
{
|
{
|
||||||
$this->authorize('admin-access');
|
$this->authorize('admin-access');
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ class PhotoController extends Controller
|
|||||||
$photoService = new PhotoService($photo);
|
$photoService = new PhotoService($photo);
|
||||||
$photoService->delete();
|
$photoService->delete();
|
||||||
|
|
||||||
return back();
|
$request->session()->flash('success', trans('admin.delete_photo_successful_message', ['name' => $photo->name]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function flip($photoId, $horizontal, $vertical)
|
public function flip($photoId, $horizontal, $vertical)
|
||||||
@ -122,6 +122,37 @@ class PhotoController extends Controller
|
|||||||
$photoService->flip($horizontal, $vertical);
|
$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)
|
public function regenerateThumbnails($photoId)
|
||||||
{
|
{
|
||||||
$this->authorize('admin-access');
|
$this->authorize('admin-access');
|
||||||
@ -372,6 +403,23 @@ class PhotoController extends Controller
|
|||||||
$photoService = new PhotoService($photo);
|
$photoService = new PhotoService($photo);
|
||||||
switch (strtolower($action))
|
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':
|
case 'delete':
|
||||||
$photoService->delete();
|
$photoService->delete();
|
||||||
break;
|
break;
|
||||||
|
@ -7,6 +7,7 @@ use App\AlbumSources\IAlbumSource;
|
|||||||
use App\Helpers\ImageHelper;
|
use App\Helpers\ImageHelper;
|
||||||
use App\Helpers\ThemeHelper;
|
use App\Helpers\ThemeHelper;
|
||||||
use App\Photo;
|
use App\Photo;
|
||||||
|
use Symfony\Component\HttpFoundation\File\File;
|
||||||
|
|
||||||
class PhotoService
|
class PhotoService
|
||||||
{
|
{
|
||||||
@ -118,6 +119,32 @@ class PhotoService
|
|||||||
$this->regenerateThumbnails($originalPhotoResource);
|
$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()
|
public function delete()
|
||||||
{
|
{
|
||||||
// Remove all thumbnails first - so if any fail, we don't delete the original
|
// 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;
|
var self = this;
|
||||||
|
|
||||||
self.imagesFailed = ko.observableArray();
|
self.imagesFailed = ko.observableArray();
|
||||||
@ -7,35 +6,30 @@ function AnalyseAlbumViewModel()
|
|||||||
self.numberSuccessful = ko.observable(0);
|
self.numberSuccessful = ko.observable(0);
|
||||||
self.numberFailed = ko.observable(0);
|
self.numberFailed = ko.observable(0);
|
||||||
|
|
||||||
self.numberTotal = ko.computed(function() {
|
self.numberTotal = ko.computed(function () {
|
||||||
return self.imagesToAnalyse().length;
|
return self.imagesToAnalyse().length;
|
||||||
});
|
});
|
||||||
|
|
||||||
self.failedPercentage = ko.computed(function()
|
self.failedPercentage = ko.computed(function () {
|
||||||
{
|
|
||||||
return ((self.numberFailed() / self.numberTotal()) * 100).toFixed(2) + '%';
|
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) + '%';
|
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());
|
return self.numberTotal() > 0 && (self.numberSuccessful() + self.numberFailed() >= self.numberTotal());
|
||||||
});
|
});
|
||||||
|
|
||||||
// When an image is added to the array, automatically issue it for analysis
|
// 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
|
// changes[0].value is an instance of AnalyseImageViewModel
|
||||||
var item = changes[0].value;
|
var item = changes[0].value;
|
||||||
$.ajax(
|
$.ajax(
|
||||||
item.url(),
|
item.url(),
|
||||||
{
|
{
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
error: function (xhr, textStatus, errorThrown)
|
error: function (xhr, textStatus, errorThrown) {
|
||||||
{
|
|
||||||
self.numberFailed(self.numberFailed() + 1);
|
self.numberFailed(self.numberFailed() + 1);
|
||||||
self.imagesFailed.push({
|
self.imagesFailed.push({
|
||||||
'name': item.name(),
|
'name': item.name(),
|
||||||
@ -45,16 +39,13 @@ function AnalyseAlbumViewModel()
|
|||||||
item.isPending(false);
|
item.isPending(false);
|
||||||
},
|
},
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
success: function (data)
|
success: function (data) {
|
||||||
{
|
if (data.is_successful) {
|
||||||
if (data.is_successful)
|
|
||||||
{
|
|
||||||
self.numberSuccessful(self.numberSuccessful() + 1);
|
self.numberSuccessful(self.numberSuccessful() + 1);
|
||||||
item.isSuccessful(true);
|
item.isSuccessful(true);
|
||||||
item.isPending(false);
|
item.isPending(false);
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
self.numberFailed(self.numberFailed() + 1);
|
self.numberFailed(self.numberFailed() + 1);
|
||||||
self.imagesFailed.push({
|
self.imagesFailed.push({
|
||||||
'name': item.name(),
|
'name': item.name(),
|
||||||
@ -69,8 +60,7 @@ function AnalyseAlbumViewModel()
|
|||||||
}, null, 'arrayChange');
|
}, null, 'arrayChange');
|
||||||
}
|
}
|
||||||
|
|
||||||
function AnalyseImageViewModel(image_info)
|
function AnalyseImageViewModel(image_info) {
|
||||||
{
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.isPending = ko.observable(true);
|
self.isPending = ko.observable(true);
|
||||||
@ -79,11 +69,10 @@ function AnalyseImageViewModel(image_info)
|
|||||||
self.photoID = ko.observable(image_info.photo_id);
|
self.photoID = ko.observable(image_info.photo_id);
|
||||||
self.url = ko.observable(image_info.url);
|
self.url = ko.observable(image_info.url);
|
||||||
|
|
||||||
self.iconClass = ko.computed(function() {
|
self.iconClass = ko.computed(function () {
|
||||||
var string = 'fa fa-fw ';
|
var string = 'fa fa-fw ';
|
||||||
|
|
||||||
if (!self.isPending())
|
if (!self.isPending()) {
|
||||||
{
|
|
||||||
string += (self.isSuccessful() ? 'check text-success' : 'times text-danger')
|
string += (self.isSuccessful() ? 'check text-success' : 'times text-danger')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,22 +80,183 @@ function AnalyseImageViewModel(image_info)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
var self = this;
|
||||||
|
|
||||||
self.selectedLocation = ko.observable(true);
|
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 album_id ID of the album the photos are being uploaded to
|
||||||
* @param language Array containing language strings
|
* @param language Array containing language strings
|
||||||
* @param urls Array containing URLs
|
* @param urls Array containing URLs
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function UploadPhotosViewModel(album_id, language, urls)
|
function UploadPhotosViewModel(album_id, language, urls) {
|
||||||
{
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
self.currentStatus = ko.observable('');
|
self.currentStatus = ko.observable('');
|
||||||
@ -117,32 +267,27 @@ function UploadPhotosViewModel(album_id, language, urls)
|
|||||||
self.isUploadInProgress = ko.observable(false);
|
self.isUploadInProgress = ko.observable(false);
|
||||||
self.statusMessages = ko.observableArray();
|
self.statusMessages = ko.observableArray();
|
||||||
|
|
||||||
self.failedPercentage = ko.computed(function()
|
self.failedPercentage = ko.computed(function () {
|
||||||
{
|
|
||||||
return ((self.imagesFailed() / self.imagesTotal()) * 100).toFixed(2) + '%';
|
return ((self.imagesFailed() / self.imagesTotal()) * 100).toFixed(2) + '%';
|
||||||
});
|
});
|
||||||
|
|
||||||
// This method is called when an image is uploaded - regardless if it fails or not
|
// 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
|
self.currentStatus(language.upload_status
|
||||||
.replace(':current', (self.imagesUploaded() + self.imagesFailed()))
|
.replace(':current', (self.imagesUploaded() + self.imagesFailed()))
|
||||||
.replace(':total', self.imagesTotal()));
|
.replace(':total', self.imagesTotal()));
|
||||||
|
|
||||||
if ((self.imagesFailed() + self.imagesUploaded()) >= self.imagesTotal())
|
if ((self.imagesFailed() + self.imagesUploaded()) >= self.imagesTotal()) {
|
||||||
{
|
|
||||||
self.isUploadInProgress(false);
|
self.isUploadInProgress(false);
|
||||||
|
|
||||||
if (self.imagesFailed() == 0 && self.imagesUploaded() > 0)
|
if (self.imagesFailed() == 0 && self.imagesUploaded() > 0) {
|
||||||
{
|
|
||||||
window.location = urls.analyse;
|
window.location = urls.analyse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// This method is called when an uploaded image fails
|
// 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.imagesFailed(self.imagesFailed() + 1);
|
||||||
self.statusMessages.push({
|
self.statusMessages.push({
|
||||||
'message_class': 'text-danger',
|
'message_class': 'text-danger',
|
||||||
@ -152,26 +297,22 @@ function UploadPhotosViewModel(album_id, language, urls)
|
|||||||
};
|
};
|
||||||
|
|
||||||
// This method is called when an uploaded image succeeds
|
// This method is called when an uploaded image succeeds
|
||||||
self.onUploadSuccessful = function(data, file_name)
|
self.onUploadSuccessful = function (data, file_name) {
|
||||||
{
|
if (data.is_successful) {
|
||||||
if (data.is_successful)
|
|
||||||
{
|
|
||||||
self.imagesUploaded(self.imagesUploaded() + 1);
|
self.imagesUploaded(self.imagesUploaded() + 1);
|
||||||
// Don't add to statusMessages() array so user only sees errors
|
// Don't add to statusMessages() array so user only sees errors
|
||||||
/*self.statusMessages.push({
|
/*self.statusMessages.push({
|
||||||
'message_class': 'text-success',
|
'message_class': 'text-success',
|
||||||
'message_text': language.image_uploaded.replace(':file_name', file_name)
|
'message_text': language.image_uploaded.replace(':file_name', file_name)
|
||||||
});*/
|
});*/
|
||||||
self.onUploadCompleted();
|
self.onUploadCompleted();
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
self.onUploadFailed(data, file_name);
|
self.onUploadFailed(data, file_name);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.startUpload = function(number_images)
|
self.startUpload = function (number_images) {
|
||||||
{
|
|
||||||
self.currentStatus('');
|
self.currentStatus('');
|
||||||
self.statusMessages.removeAll();
|
self.statusMessages.removeAll();
|
||||||
self.imagesUploaded(0);
|
self.imagesUploaded(0);
|
||||||
@ -180,13 +321,11 @@ function UploadPhotosViewModel(album_id, language, urls)
|
|||||||
self.isUploadInProgress(true);
|
self.isUploadInProgress(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
self.successfulPercentage = ko.computed(function()
|
self.successfulPercentage = ko.computed(function () {
|
||||||
{
|
|
||||||
return ((self.imagesUploaded() / self.imagesTotal()) * 100).toFixed(2) + '%';
|
return ((self.imagesUploaded() / self.imagesTotal()) * 100).toFixed(2) + '%';
|
||||||
});
|
});
|
||||||
|
|
||||||
self.uploadFile = function uploadImageFile(formObject, imageFile)
|
self.uploadFile = function uploadImageFile(formObject, imageFile) {
|
||||||
{
|
|
||||||
var formData = new FormData();
|
var formData = new FormData();
|
||||||
formData.append('album_id', album_id);
|
formData.append('album_id', album_id);
|
||||||
formData.append('photo[]', imageFile, imageFile.name);
|
formData.append('photo[]', imageFile, imageFile.name);
|
||||||
@ -195,12 +334,12 @@ function UploadPhotosViewModel(album_id, language, urls)
|
|||||||
{
|
{
|
||||||
contentType: false,
|
contentType: false,
|
||||||
data: formData,
|
data: formData,
|
||||||
error: function(data) {
|
error: function (data) {
|
||||||
self.onUploadFailed(data, imageFile.name);
|
self.onUploadFailed(data, imageFile.name);
|
||||||
},
|
},
|
||||||
method: $(formObject).attr('method'),
|
method: $(formObject).attr('method'),
|
||||||
processData: false,
|
processData: false,
|
||||||
success: function(data) {
|
success: function (data) {
|
||||||
self.onUploadSuccessful(data, imageFile.name);
|
self.onUploadSuccessful(data, imageFile.name);
|
||||||
},
|
},
|
||||||
url: $(formObject).attr('action')
|
url: $(formObject).attr('action')
|
||||||
|
@ -14,6 +14,8 @@ return [
|
|||||||
'bulk_photos_changed' => ':number photo was updated successfully.|:number photos were updated successfully.',
|
'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_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.',
|
'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' => '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_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.',
|
'create_album_intro2' => 'Complete the form below to create a photo album.',
|
||||||
@ -26,6 +28,11 @@ return [
|
|||||||
'delete_album' => 'Delete album :name',
|
'delete_album' => 'Delete album :name',
|
||||||
'delete_album_confirm' => 'Are you sure you want to permanently delete this album and all its contents?',
|
'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_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' => 'Delete storage location: :name',
|
||||||
'delete_storage_confirm' => 'Are you sure you want to permanently remove this storage location?',
|
'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.',
|
'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' => [
|
'manage_widget' => [
|
||||||
'panel_header' => 'Manage'
|
'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_text' => 'You have no photo albums yet. Click the button below to create one.',
|
||||||
'no_albums_title' => 'No Photo Albums',
|
'no_albums_title' => 'No Photo Albums',
|
||||||
'no_storages_text' => 'You need a storage location to store your uploaded photographs.',
|
'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',
|
'no_storages_title' => 'No storage locations defined',
|
||||||
'open_album' => 'Open album',
|
'open_album' => 'Open album',
|
||||||
'photo_actions' => [
|
'photo_actions' => [
|
||||||
|
'change_album' => 'Move to another album',
|
||||||
'delete' => 'Delete permanently',
|
'delete' => 'Delete permanently',
|
||||||
'flip_both' => 'Flip both',
|
'flip_both' => 'Flip both',
|
||||||
'flip_horizontal' => 'Flip horizontally',
|
'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>
|
<p style="margin-top: 30px;"><button id="upload-button" class="btn btn-lg btn-success">@lang('admin.album_no_photos_button')</button></p>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@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)
|
@foreach ($photos as $photo)
|
||||||
@include (Theme::viewName('partials.single_photo_admin'))
|
@include (Theme::viewName('partials.single_photo_admin'))
|
||||||
@endforeach
|
@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>
|
<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']) !!}
|
{!! Form::hidden('new-album-id', $album->id) !!}
|
||||||
<button type="submit" class="btn btn-sm btn-success" name="bulk-apply" value="clicked">@lang('forms.apply_action')</button>
|
{!! 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>
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<button type="submit" class="btn btn-success">@lang('forms.save_action')</button>
|
<button type="submit" class="btn btn-success">@lang('forms.save_action')</button>
|
||||||
@ -169,27 +170,34 @@
|
|||||||
@push('scripts')
|
@push('scripts')
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var language = [];
|
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_failed = '{!! addslashes(trans('admin.upload_file_status_failed')) !!}';
|
||||||
language.image_uploaded = '{!! addslashes(trans('admin.upload_file_status_success')) !!}';
|
language.image_uploaded = '{!! addslashes(trans('admin.upload_file_status_success')) !!}';
|
||||||
language.upload_status = '{!! addslashes(trans('admin.upload_file_status_progress')) !!}';
|
language.upload_status = '{!! addslashes(trans('admin.upload_file_status_progress')) !!}';
|
||||||
|
|
||||||
var urls = [];
|
var urls = [];
|
||||||
urls.analyse = '{{ route('albums.analyse', ['id' => $album->id]) }}';
|
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 viewModel = new UploadPhotosViewModel('{{ $album->id }}', language, urls);
|
||||||
|
var editViewModel = new EditPhotosViewModel('{{ $album->id }}', language, urls);
|
||||||
|
|
||||||
function deletePhoto(photo_id, parent)
|
// Populate the list of albums in the view model
|
||||||
{
|
@foreach ($albums as $album)
|
||||||
var url = '{{ route('photos.destroy', ['id' => 0]) }}';
|
editViewModel.albums.push({
|
||||||
url = url.replace(/\/0$/, '/' + photo_id);
|
'id': '{{ $album->id }}',
|
||||||
|
'name': '{!! addslashes($album->name) !!}'
|
||||||
$('.loading', parent).show();
|
|
||||||
|
|
||||||
$.post(url, {'_method': 'DELETE'}, function(data)
|
|
||||||
{
|
|
||||||
window.location.reload();
|
|
||||||
});
|
});
|
||||||
}
|
@endforeach
|
||||||
|
|
||||||
function flipPhoto(photo_id, horizontal, vertical, parent)
|
function flipPhoto(photo_id, horizontal, vertical, parent)
|
||||||
{
|
{
|
||||||
@ -264,32 +272,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
{{-- Photo editing tasks - the buttons beneath the photos in partials/single_photo_admin --}}
|
{{-- Photo editing tasks - the buttons beneath the photos in partials/single_photo_admin --}}
|
||||||
$('a.delete-photo').click(function() {
|
$('a.change-album').click(editViewModel.changeAlbum);
|
||||||
var parent = $(this).parents('.photo');
|
$('a.delete-photo').click(editViewModel.delete);
|
||||||
var photo_id = $(parent).data('photo-id');
|
|
||||||
|
|
||||||
$(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() {
|
$('a.flip-photo-both').click(function() {
|
||||||
var parent = $(this).parents('.photo');
|
var parent = $(this).parents('.photo');
|
||||||
var photo_id = $(parent).data('photo-id');
|
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>
|
</script>
|
||||||
@endpush
|
@endpush
|
@ -29,6 +29,7 @@
|
|||||||
<i class="fa fa-fw fa-cog"></i> <span class="caret"></span>
|
<i class="fa fa-fw fa-cog"></i> <span class="caret"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<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="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>
|
<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>
|
</ul>
|
||||||
@ -36,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p style="margin-top: 10px;">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-sm-10">
|
<div class="col-xs-12 col-sm-10">
|
||||||
|
@ -28,6 +28,7 @@ Route::group(['prefix' => 'admin'], function () {
|
|||||||
// Photo management
|
// Photo management
|
||||||
Route::post('photos/analyse/{id}', 'Admin\PhotoController@analyse')->name('photos.analyse');
|
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/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/regenerate-thumbnails/{id}', 'Admin\PhotoController@regenerateThumbnails')->name('photos.regenerateThumbnails');
|
||||||
Route::post('photos/rotate/{photoId}/{angle}', 'Admin\PhotoController@rotate')->name('photos.rotate');
|
Route::post('photos/rotate/{photoId}/{angle}', 'Admin\PhotoController@rotate')->name('photos.rotate');
|
||||||
Route::post('photos/store-bulk', 'Admin\PhotoController@storeBulk')->name('photos.storeBulk');
|
Route::post('photos/store-bulk', 'Admin\PhotoController@storeBulk')->name('photos.storeBulk');
|
||||||
|
Loading…
Reference in New Issue
Block a user