BLUE-13: improved the design and handling of the analysis screen. Also fixed bulk uploads to work since the storage changes in 1.1
This commit is contained in:
parent
e3d3d4d8be
commit
5b915f911e
@ -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="scar.andys.eu" port="22" privateKey="C:\Users\aheathershaw\.ssh\id_rsa" rootFolder="/srv/www/blue-twilight-dev" accessType="SFTP" keyPair="true">
|
<fileTransfer host="scar.andys.eu" port="22" privateKey="$USER_HOME$/.ssh/id_rsa" rootFolder="/srv/www/blue-twilight-dev" accessType="SFTP" keyPair="true">
|
||||||
<advancedOptions>
|
<advancedOptions>
|
||||||
<advancedOptions dataProtectionLevel="Private" />
|
<advancedOptions dataProtectionLevel="Private" />
|
||||||
</advancedOptions>
|
</advancedOptions>
|
||||||
|
@ -2,11 +2,38 @@
|
|||||||
|
|
||||||
namespace App\Helpers;
|
namespace App\Helpers;
|
||||||
|
|
||||||
use Illuminate\Http\File;
|
|
||||||
use Illuminate\Http\UploadedFile;
|
use Illuminate\Http\UploadedFile;
|
||||||
|
use Symfony\Component\HttpFoundation\File\File;
|
||||||
|
|
||||||
class FileHelper
|
class FileHelper
|
||||||
{
|
{
|
||||||
|
public static function deleteIfEmpty($folderPath)
|
||||||
|
{
|
||||||
|
// Another request may have got here first!
|
||||||
|
if (!is_dir($folderPath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$queueIterator = new \DirectoryIterator($folderPath);
|
||||||
|
$files = 0;
|
||||||
|
|
||||||
|
foreach ($queueIterator as $item)
|
||||||
|
{
|
||||||
|
if ($item->isDot())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$files++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($files == 0)
|
||||||
|
{
|
||||||
|
@rmdir($folderPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static function getQueuePath($queueUid)
|
public static function getQueuePath($queueUid)
|
||||||
{
|
{
|
||||||
$path = join(DIRECTORY_SEPARATOR, [
|
$path = join(DIRECTORY_SEPARATOR, [
|
||||||
@ -25,6 +52,27 @@ class FileHelper
|
|||||||
return $path;
|
return $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function saveExtractedFile(File $extractedFile, $destinationPath, $overrideFilename = null)
|
||||||
|
{
|
||||||
|
$tempFilename = join(DIRECTORY_SEPARATOR, [
|
||||||
|
$destinationPath,
|
||||||
|
is_null($overrideFilename) ? MiscHelper::randomString(20) : basename($overrideFilename)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Only add an extension if an override filename was not given, assume this is present
|
||||||
|
if (is_null($overrideFilename))
|
||||||
|
{
|
||||||
|
$extension = $extractedFile->guessExtension();
|
||||||
|
if (!is_null($extension))
|
||||||
|
{
|
||||||
|
$tempFilename .= '.' . $extension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$extractedFile->move(dirname($tempFilename), basename($tempFilename));
|
||||||
|
return new File($tempFilename);
|
||||||
|
}
|
||||||
|
|
||||||
public static function saveUploadedFile(UploadedFile $uploadedFile, $destinationPath, $overrideFilename = null)
|
public static function saveUploadedFile(UploadedFile $uploadedFile, $destinationPath, $overrideFilename = null)
|
||||||
{
|
{
|
||||||
$tempFilename = join(DIRECTORY_SEPARATOR, [
|
$tempFilename = join(DIRECTORY_SEPARATOR, [
|
||||||
|
@ -65,7 +65,7 @@ class ImageHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO make the /tmp folder configurable
|
// TODO make the /tmp folder configurable
|
||||||
$tempName = tempnam('/tmp', 'btw_thumb_');
|
$tempName = tempnam(sys_get_temp_dir(), 'btw_thumb_');
|
||||||
$tempNameWithExtension = ($tempName . '.jpg');
|
$tempNameWithExtension = ($tempName . '.jpg');
|
||||||
rename($tempName, $tempNameWithExtension);
|
rename($tempName, $tempNameWithExtension);
|
||||||
|
|
||||||
|
@ -37,6 +37,11 @@ class AlbumController extends Controller
|
|||||||
->orderBy('created_at')
|
->orderBy('created_at')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
|
if (count($photos) == 0)
|
||||||
|
{
|
||||||
|
return redirect(route('albums.show', ['id' => $album->id]));
|
||||||
|
}
|
||||||
|
|
||||||
return Theme::render('admin.analyse_album', ['album' => $album, 'photos' => $photos, 'queue_token' => $queue_token]);
|
return Theme::render('admin.analyse_album', ['album' => $album, 'photos' => $photos, 'queue_token' => $queue_token]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,6 +306,7 @@ class PhotoController extends Controller
|
|||||||
$zip->open($archiveFile->getPathname());
|
$zip->open($archiveFile->getPathname());
|
||||||
$zip->extractTo($queueFolder);
|
$zip->extractTo($queueFolder);
|
||||||
$zip->close();
|
$zip->close();
|
||||||
|
@unlink($archiveFile->getPathname());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -329,6 +330,13 @@ class PhotoController extends Controller
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (substr($fileInfo->getFilename(), 0, 1) == '.')
|
||||||
|
{
|
||||||
|
// Temporary/hidden file - skip
|
||||||
|
@unlink($fileInfo->getPathname());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$result = getimagesize($fileInfo->getPathname());
|
$result = getimagesize($fileInfo->getPathname());
|
||||||
if ($result === false)
|
if ($result === false)
|
||||||
{
|
{
|
||||||
@ -340,7 +348,7 @@ class PhotoController extends Controller
|
|||||||
$photoFile = new File($fileInfo->getPathname());
|
$photoFile = new File($fileInfo->getPathname());
|
||||||
|
|
||||||
/** @var File $savedFile */
|
/** @var File $savedFile */
|
||||||
$savedFile = $album->getAlbumSource()->saveUploadedPhoto($photoFile);
|
$savedFile = FileHelper::saveExtractedFile($photoFile, $queueFolder);
|
||||||
|
|
||||||
$photo = new Photo();
|
$photo = new Photo();
|
||||||
$photo->album_id = $album->id;
|
$photo->album_id = $album->id;
|
||||||
@ -354,8 +362,6 @@ class PhotoController extends Controller
|
|||||||
$photo->save();
|
$photo->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@rmdir($queueFolder);
|
|
||||||
|
|
||||||
return redirect(route('albums.analyse', [
|
return redirect(route('albums.analyse', [
|
||||||
'id' => $album->id,
|
'id' => $album->id,
|
||||||
'queue_token' => $queueUid
|
'queue_token' => $queueUid
|
||||||
|
@ -51,11 +51,11 @@ class PhotoService
|
|||||||
|
|
||||||
public function analyse($queueToken)
|
public function analyse($queueToken)
|
||||||
{
|
{
|
||||||
$photoFile = join(DIRECTORY_SEPARATOR, [
|
$queuePath = FileHelper::getQueuePath($queueToken);
|
||||||
FileHelper::getQueuePath($queueToken),
|
$photoFile = join(DIRECTORY_SEPARATOR, [$queuePath, $this->photo->storage_file_name]);
|
||||||
$this->photo->storage_file_name
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
$imageInfo = null;
|
$imageInfo = null;
|
||||||
$originalPhotoResource = $this->imageHelper->openImage($photoFile, $imageInfo);
|
$originalPhotoResource = $this->imageHelper->openImage($photoFile, $imageInfo);
|
||||||
if ($originalPhotoResource === false)
|
if ($originalPhotoResource === false)
|
||||||
@ -121,6 +121,18 @@ class PhotoService
|
|||||||
|
|
||||||
$this->regenerateThumbnails($originalPhotoResource);
|
$this->regenerateThumbnails($originalPhotoResource);
|
||||||
}
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
@unlink($photoFile);
|
||||||
|
|
||||||
|
// If the queue directory is now empty, get rid of it
|
||||||
|
FileHelper::deleteIfEmpty($queuePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function changeAlbum(Album $newAlbum)
|
public function changeAlbum(Album $newAlbum)
|
||||||
{
|
{
|
||||||
@ -213,6 +225,7 @@ class PhotoService
|
|||||||
{
|
{
|
||||||
$generatedThumbnailPath = $this->imageHelper->generateThumbnail($originalPhotoResource, $this->photo, $thumbnail);
|
$generatedThumbnailPath = $this->imageHelper->generateThumbnail($originalPhotoResource, $this->photo, $thumbnail);
|
||||||
$this->albumSource->saveThumbnail($this->photo, $generatedThumbnailPath, $thumbnail['name']);
|
$this->albumSource->saveThumbnail($this->photo, $generatedThumbnailPath, $thumbnail['name']);
|
||||||
|
@unlink($generatedThumbnailPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_null($originalPhotoResource) && !is_null($photoPath))
|
if (is_null($originalPhotoResource) && !is_null($photoPath))
|
||||||
|
@ -3,6 +3,8 @@ function AnalyseAlbumViewModel() {
|
|||||||
|
|
||||||
self.imagesFailed = ko.observableArray();
|
self.imagesFailed = ko.observableArray();
|
||||||
self.imagesToAnalyse = ko.observableArray();
|
self.imagesToAnalyse = ko.observableArray();
|
||||||
|
self.imagesInProgress = ko.observableArray();
|
||||||
|
self.imagesRecentlyCompleted = ko.observableArray();
|
||||||
self.numberSuccessful = ko.observable(0);
|
self.numberSuccessful = ko.observable(0);
|
||||||
self.numberFailed = ko.observable(0);
|
self.numberFailed = ko.observable(0);
|
||||||
|
|
||||||
@ -23,11 +25,20 @@ function AnalyseAlbumViewModel() {
|
|||||||
|
|
||||||
// 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) {
|
||||||
|
// We only care about additions
|
||||||
|
if (changes[0].status !== 'added')
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 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(),
|
||||||
{
|
{
|
||||||
|
beforeSend: function() {
|
||||||
|
self.imagesInProgress.push(item);
|
||||||
|
},
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
error: function (xhr, textStatus, errorThrown) {
|
error: function (xhr, textStatus, errorThrown) {
|
||||||
self.numberFailed(self.numberFailed() + 1);
|
self.numberFailed(self.numberFailed() + 1);
|
||||||
@ -44,6 +55,15 @@ function AnalyseAlbumViewModel() {
|
|||||||
self.numberSuccessful(self.numberSuccessful() + 1);
|
self.numberSuccessful(self.numberSuccessful() + 1);
|
||||||
item.isSuccessful(true);
|
item.isSuccessful(true);
|
||||||
item.isPending(false);
|
item.isPending(false);
|
||||||
|
|
||||||
|
// Push into our "recently completed" array
|
||||||
|
self.imagesRecentlyCompleted.push(item);
|
||||||
|
self.imagesInProgress.remove(item);
|
||||||
|
|
||||||
|
// Remove it again after a few seconds
|
||||||
|
window.setTimeout(function() {
|
||||||
|
self.imagesRecentlyCompleted.remove(item);
|
||||||
|
}, 2000);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.numberFailed(self.numberFailed() + 1);
|
self.numberFailed(self.numberFailed() + 1);
|
||||||
@ -70,9 +90,12 @@ function AnalyseImageViewModel(image_info) {
|
|||||||
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 fa-';
|
||||||
|
|
||||||
if (!self.isPending()) {
|
if (self.isPending()) {
|
||||||
|
string += 'refresh';
|
||||||
|
}
|
||||||
|
else {
|
||||||
string += (self.isSuccessful() ? 'check text-success' : 'times text-danger')
|
string += (self.isSuccessful() ? 'check text-success' : 'times text-danger')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,10 @@ return [
|
|||||||
'album_security_intro' => 'The settings below affect the visibility of this album to other users.',
|
'album_security_intro' => 'The settings below affect the visibility of this album to other users.',
|
||||||
'album_settings_tab' => 'Settings',
|
'album_settings_tab' => 'Settings',
|
||||||
'album_upload_tab' => 'Upload',
|
'album_upload_tab' => 'Upload',
|
||||||
|
'analyse_and_more' => [
|
||||||
|
'and' => '... and ',
|
||||||
|
'others' => ' others'
|
||||||
|
],
|
||||||
'analyse_photos_failed' => 'The following items could not be analysed and were removed:',
|
'analyse_photos_failed' => 'The following items could not be analysed and were removed:',
|
||||||
'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.',
|
||||||
|
@ -19,8 +19,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div data-bind="foreach: imagesToAnalyse" style="margin-top: 20px;">
|
{{-- We display a queue of 3 recently completed items, and 5 up-next items, as well as a counter saying "... and XYZ more" --}}
|
||||||
<p><span data-bind="text: name"></span> ... <i data-bind="css: iconClass"></i></p>
|
{{-- That's what the 3's and 5's are in the next couple of blocks --}}
|
||||||
|
<div data-bind="foreach: imagesRecentlyCompleted.slice((imagesRecentlyCompleted().length - 3 < 0) ? 0 : imagesRecentlyCompleted().length - 3, 3)" style="margin-top: 20px;">
|
||||||
|
<p class="text-success"><span data-bind="text: name"></span> ... <i class="fa fa-fw fa-check"></i></p>
|
||||||
|
</div>
|
||||||
|
<div data-bind="foreach: imagesInProgress.slice(0, 5)" style="margin-top: 20px;">
|
||||||
|
<p><span data-bind="text: name"></span> ... <i data-bind="css: iconClass()"></i></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-bind="visible: imagesInProgress().length > 5">
|
||||||
|
<p>@lang('admin.analyse_and_more.and') <span data-bind="text: imagesInProgress().length - 5"></span> @lang('admin.analyse_and_more.others')</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -324,7 +324,19 @@
|
|||||||
$('#bulk-upload-form').submit(function(event) {
|
$('#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
|
// 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
|
// will reload the page in some way after completion
|
||||||
|
if (!viewModel.isBulkUploadInProgress())
|
||||||
|
{
|
||||||
viewModel.isBulkUploadInProgress(true);
|
viewModel.isBulkUploadInProgress(true);
|
||||||
|
|
||||||
|
// Wait a minute to give KnockoutJS chance to update the UI
|
||||||
|
window.setTimeout(function() {
|
||||||
|
$('#bulk-upload-form').submit();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already set the flag, let the upload commence
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user