From 9f95cbcbfb8dddc8b3fd4288f891ce6184a7aa9e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 9 Aug 2015 14:52:15 +0100 Subject: [PATCH] Improved image serving and uploading. Fixes #7 and #8. --- app/Http/Controllers/ImageController.php | 20 +- public/js/image-manager.js | 55 ++- resources/assets/sass/image-manager.scss | 324 ++++++++++++++++++ resources/assets/sass/styles.scss | 52 +-- resources/views/pages/image-manager.blade.php | 16 +- 5 files changed, 402 insertions(+), 65 deletions(-) create mode 100644 resources/assets/sass/image-manager.scss diff --git a/app/Http/Controllers/ImageController.php b/app/Http/Controllers/ImageController.php index a2271a547..c8a13e48b 100644 --- a/app/Http/Controllers/ImageController.php +++ b/app/Http/Controllers/ImageController.php @@ -71,13 +71,18 @@ class ImageController extends Controller */ public function getAll($page = 0) { - $pageSize = 25; + $pageSize = 13; $images = DB::table('images')->orderBy('created_at', 'desc') ->skip($page*$pageSize)->take($pageSize)->get(); foreach($images as $image) { $image->thumbnail = $this->getThumbnail($image, 150, 150); } - return response()->json($images); + $hasMore = count(DB::table('images')->orderBy('created_at', 'desc') + ->skip(($page+1)*$pageSize)->take($pageSize)->get()) > 0; + return response()->json([ + 'images' => $images, + 'hasMore' => $hasMore + ]); } /** @@ -93,18 +98,24 @@ class ImageController extends Controller array_splice($explodedPath, 3, 0, ['thumbs-' . $width . '-' . $height]); $thumbPath = implode('/', $explodedPath); $thumbFilePath = storage_path() . $thumbPath; + + // Return the thumbnail url path if already exists if(file_exists($thumbFilePath)) { return $thumbPath; } - //dd($thumbFilePath); + // Otherwise create the thumbnail $thumb = ImageTool::make(storage_path() . $image->url); $thumb->fit($width, $height); + + // Create thumbnail folder if it does not exist if(!file_exists(dirname($thumbFilePath))) { mkdir(dirname($thumbFilePath), 0775, true); } + + //Save Thumbnail $thumb->save($thumbFilePath); - return $thumbFilePath; + return $thumbPath; } /** @@ -130,6 +141,7 @@ class ImageController extends Controller $this->image->created_by = Auth::user()->id; $this->image->updated_by = Auth::user()->id; $this->image->save(); + $this->image->thumbnail = $this->getThumbnail($this->image, 150, 150); return response()->json($this->image); } diff --git a/public/js/image-manager.js b/public/js/image-manager.js index 27a0cadda..45c26d8e1 100644 --- a/public/js/image-manager.js +++ b/public/js/image-manager.js @@ -1,4 +1,19 @@ +// Dropzone config +Dropzone.options.imageUploadDropzone = { + uploadMultiple: false, + previewsContainer: '.image-manager-display .uploads', + init: function() { + this.on('success', function(event, image) { + $('.image-manager-display .uploads').empty(); + var newImage = $('').attr('data-image-id', image.id); + newImage.attr('title', image.name).attr('src', image.thumbnail); + newImage.data('imageData', image); + $('.image-manager-display .uploads').after(newImage); + }); + } +}; + (function() { var isInit = false; @@ -6,6 +21,9 @@ var overlay; var display; var imageIndexUrl = '/images/all'; + var pageIndex = 0; + var hasMore = true; + var isGettingImages = true; var ImageManager = {}; var action = false; @@ -22,23 +40,48 @@ }; ImageManager.init = function(selector) { - console.log('cat'); elem = $(selector); overlay = elem.closest('.overlay'); - display = elem.find('.image-manager-display').first() - + display = elem.find('.image-manager-display').first(); + var uploads = display.find('.uploads'); + var images = display.find('images'); + var loadMore = display.find('.load-more'); // Get recent images and show $.getJSON(imageIndexUrl, showImages); - function showImages(images) { + function showImages(data) { + var images = data.images; + hasMore = data.hasMore; + pageIndex++; + isGettingImages = false; for(var i = 0; i < images.length; i++) { var image = images[i]; var newImage = $('').attr('data-image-id', image.id); newImage.attr('title', image.name).attr('src', image.thumbnail); - display.append(newImage); + loadMore.before(newImage); newImage.data('imageData', image); } + if(hasMore) loadMore.show(); } + loadMore.click(function() { + loadMore.hide(); + if(isGettingImages === false) { + isGettingImages = true; + $.getJSON(imageIndexUrl + '/' + pageIndex, showImages); + } + }); + + // Image grabbing on scroll + display.on('scroll', function() { + var displayBottom = display.scrollTop() + display.height(); + var elemTop = loadMore.offset().top; + if(elemTop < displayBottom && hasMore && isGettingImages === false) { + isGettingImages = true; + loadMore.hide(); + $.getJSON(imageIndexUrl + '/' + pageIndex, showImages); + } + }); + elem.on('dblclick', '.image-manager-display img', function() { var imageElem = $(this); var imageData = imageElem.data('imageData'); @@ -55,7 +98,7 @@ // Set up dropzone elem.find('.image-manager-dropzone').first().dropzone({ uploadMultiple: false - }) + }); isInit = true; }; diff --git a/resources/assets/sass/image-manager.scss b/resources/assets/sass/image-manager.scss new file mode 100644 index 000000000..d892f83a9 --- /dev/null +++ b/resources/assets/sass/image-manager.scss @@ -0,0 +1,324 @@ +#image-manager { + background-color: #EEE; + max-width: 90%; + max-height: 90%; + width: 90%; + height: 90%; + margin: 2% 5%; + //border: 2px solid $primary; + border-radius: 4px; + box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.3); + overflow: hidden; + .image-manager-display img { + border-radius: 0; + float: left; + margin: 1px; + cursor: pointer; + } +} +#image-manager .dropzone { + display: table; + position: absolute; + top: 10px; + left: 300px; + width: 480px; + height: 60px; + border: 4px dashed $primary; + text-align: center; + z-index: 900; + .dz-message { + display: table-cell; + vertical-align: middle; + color: $primary; + font-size: 1.2em; + } + * { + pointer-events: none; + } +} +.image-manager-left { + background-color: #FFF; + height: 100%; + width: 100%; + text-align: left; + position: relative; + .image-manager-display-wrap { + height: 100%; + padding-top: 87px; + position: absolute; + top: 0;width: 100%; + } + .image-manager-display { + height: 100%; + width: 100%; + text-align: left; + overflow-y: scroll; + } + .image-manager-header { + z-index: 50; + position: relative; + } +} + +#image-manager .load-more { + width: 150px; + height: 150px; + display: none; + float: left; + text-align: center; + background-color: #888; + margin: 1px; + color: #FFF; + line-height: 140px; + font-size: 20px; + cursor: pointer; +} +.image-manager-title { + font-size: 2em; + text-align: left; + margin: 0 $-m; + padding: $-xl $-m; + color: #666; + border-bottom: 1px solid #DDD; +} + +.image-manager-dropzone { + background-color: lighten($primary, 40%); + height: 25%; + text-align: center; + font-size: 2em; + line-height: 2em; + padding-top: $-xl*1.2; + color: #666; + border-top: 2px solid $primary; +} + +// Dropzone +/* + * The MIT License + * Copyright (c) 2012 Matias Meno + */ + +@keyframes passing-through { + 0% { + opacity: 0; + transform: translateY(40px); } + 30%, 70% { + opacity: 1; + transform: translateY(0px); } + 100% { + opacity: 0; + transform: translateY(-40px); } } + +@keyframes slide-in { + 0% { + opacity: 0; + transform: translateY(40px); } + 30% { + opacity: 1; + transform: translateY(0px); } } +@keyframes pulse { + 0% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } + 10% { + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -ms-transform: scale(1.1); + -o-transform: scale(1.1); + transform: scale(1.1); } + 20% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } } +.dropzone, .dropzone * { + box-sizing: border-box; } + +.dropzone { + background: white; + padding: 20px 20px; } +.dropzone.dz-clickable { + cursor: pointer; } +.dropzone.dz-clickable * { + cursor: default; } +.dropzone.dz-clickable .dz-message, .dropzone.dz-clickable .dz-message * { + cursor: pointer; } +.dropzone.dz-started .dz-message { + display: none; } +.dropzone.dz-drag-hover { + border-style: solid; } +.dropzone.dz-drag-hover .dz-message { + opacity: 0.5; } +.dropzone .dz-message { + text-align: center; + margin: 2em 0; } +.dz-preview { + position: relative; + display: inline-block; + vertical-align: top; + margin: 16px; + min-height: 100px; } +.dz-preview:hover { + z-index: 1000; } +.dz-preview:hover .dz-details { + opacity: 1; } +.dz-preview.dz-file-preview .dz-image { + border-radius: 4px; + background: #999; + background: linear-gradient(to bottom, #eee, #ddd); } +.dz-preview.dz-file-preview .dz-details { + opacity: 1; } +.dz-preview.dz-image-preview { + background: white; } +.dz-preview.dz-image-preview .dz-details { + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -ms-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; } +.dz-preview .dz-remove { + font-size: 14px; + text-align: center; + display: block; + cursor: pointer; + border: none; } +.dz-preview .dz-remove:hover { + text-decoration: underline; } +.dz-preview:hover .dz-details { + opacity: 1; } +.dz-preview .dz-details { + z-index: 20; + position: absolute; + top: 0; + left: 0; + opacity: 0; + font-size: 13px; + min-width: 100%; + max-width: 100%; + padding: 2em 1em; + text-align: center; + color: rgba(0, 0, 0, 0.9); + line-height: 150%; } +.dz-preview .dz-details .dz-size { + margin-bottom: 1em; + font-size: 16px; } +.dz-preview .dz-details .dz-filename { + white-space: nowrap; } +.dz-preview .dz-details .dz-filename:hover span { + border: 1px solid rgba(200, 200, 200, 0.8); + background-color: rgba(255, 255, 255, 0.8); } +.dz-preview .dz-details .dz-filename:not(:hover) { + overflow: hidden; + text-overflow: ellipsis; } +.dz-preview .dz-details .dz-filename:not(:hover) span { + border: 1px solid transparent; } +.dz-preview .dz-details .dz-filename span, .dz-preview .dz-details .dz-size span { + background-color: rgba(255, 255, 255, 0.4); + padding: 0 0.4em; + border-radius: 3px; } +.dz-preview:hover .dz-image img { + -webkit-transform: scale(1.05, 1.05); + -moz-transform: scale(1.05, 1.05); + -ms-transform: scale(1.05, 1.05); + -o-transform: scale(1.05, 1.05); + transform: scale(1.05, 1.05); + -webkit-filter: blur(8px); + filter: blur(8px); } +.dz-preview .dz-image { + border-radius: 4px; + overflow: hidden; + width: 120px; + height: 120px; + position: relative; + display: block; + z-index: 10; } +.dz-preview .dz-image img { + display: block; } +.dz-preview.dz-success .dz-success-mark { + animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); } +.dz-preview.dz-error .dz-error-mark { + opacity: 1; + animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); } +.dz-preview .dz-success-mark, .dz-preview .dz-error-mark { + pointer-events: none; + opacity: 0; + z-index: 500; + position: absolute; + display: block; + top: 50%; + left: 50%; + margin-left: -27px; + margin-top: -27px; } +.dz-preview .dz-success-mark svg, .dz-preview .dz-error-mark svg { + display: block; + width: 54px; + height: 54px; } +.dz-preview.dz-processing .dz-progress { + opacity: 1; + transition: all 0.2s linear; } +.dz-preview.dz-complete .dz-progress { + opacity: 0; + transition: opacity 0.4s ease-in; } +.dz-preview:not(.dz-processing) .dz-progress { + animation: pulse 6s ease infinite; } +.dz-preview .dz-progress { + opacity: 1; + z-index: 1000; + pointer-events: none; + position: absolute; + height: 16px; + left: 50%; + top: 50%; + margin-top: -8px; + width: 80px; + margin-left: -40px; + background: rgba(255, 255, 255, 0.9); + -webkit-transform: scale(1); + border-radius: 8px; + overflow: hidden; } +.dz-preview .dz-progress .dz-upload { + background: #333; + background: linear-gradient(to bottom, #666, #444); + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 0; + transition: width 300ms ease-in-out; } +.dz-preview.dz-error .dz-error-message { + display: block; } +.dz-preview.dz-error:hover .dz-error-message { + opacity: 1; + pointer-events: auto; } +.dz-preview .dz-error-message { + pointer-events: none; + z-index: 1000; + position: absolute; + display: block; + display: none; + opacity: 0; + transition: opacity 0.3s ease; + border-radius: 8px; + font-size: 13px; + top: 130px; + left: -10px; + width: 140px; + background: #be2626; + background: linear-gradient(to bottom, #be2626, #a92222); + padding: 0.5em 1.2em; + color: white; } +.dz-preview .dz-error-message:after { + content: ''; + position: absolute; + top: -6px; + left: 64px; + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid #be2626; } diff --git a/resources/assets/sass/styles.scss b/resources/assets/sass/styles.scss index aece8c09a..62f0a6919 100644 --- a/resources/assets/sass/styles.scss +++ b/resources/assets/sass/styles.scss @@ -9,6 +9,7 @@ @import "forms"; @import "tables"; @import "tinymce"; +@import "image-manager"; header { display: block; @@ -192,57 +193,6 @@ ul.menu { right: 0; bottom: 0; } -#image-manager { - background-color: #EEE; - max-width: 90%; - max-height: 90%; - width: 90%; - height: 90%; - margin: 2% 5%; - //border: 2px solid $primary; - border-radius: 4px; - box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.3); - overflow: hidden; - .image-manager-display img { - width: 150px; - height: 150px; - display: inline-block; - margin: $-s 0 0 $-s; - cursor: pointer; - } -} -.image-manager-left { - background-color: #FFF; - height: 100%; - width: 100%; - text-align: left; - .image-manager-display { - height: 75%; - width: 100%; - text-align: left; - overflow-y: scroll; - } -} - -.image-manager-title { - font-size: 2em; - text-align: left; - margin: 0 $-m; - padding: $-xl $-m; - color: #666; - border-bottom: 1px solid #DDD; -} - -.image-manager-dropzone { - background-color: lighten($primary, 40%); - height: 25%; - text-align: center; - font-size: 2em; - line-height: 2em; - padding-top: $-xl*1.2; - color: #666; - border-top: 2px solid $primary; -} // Link hooks & popovers a.link-hook { diff --git a/resources/views/pages/image-manager.blade.php b/resources/views/pages/image-manager.blade.php index 7f33a98a2..ef52a43d7 100644 --- a/resources/views/pages/image-manager.blade.php +++ b/resources/views/pages/image-manager.blade.php @@ -1,15 +1,23 @@