use Illuminate\Support\Facades\DB;
use Oxbow\Http\Requests;
use Oxbow\Image;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+use RegexIterator;
class ImageController extends Controller
{
*/
public function getAll($page = 0)
{
- $pageSize = 25;
+ $pageSize = 30;
$images = DB::table('images')->orderBy('created_at', 'desc')
->skip($page*$pageSize)->take($pageSize)->get();
foreach($images as $image) {
return response()->json($this->image);
}
+ /**
+ * Update image details
+ * @param $imageId
+ * @param Request $request
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function update($imageId, Request $request)
+ {
+ $this->validate($request, [
+ 'name' => 'required|min:2|string'
+ ]);
+ $image = $this->image->findOrFail($imageId);
+ $image->fill($request->all());
+ $image->save();
+ return response()->json($this->image);
+ }
+
+ /**
+ * Deletes an image and all thumbnail/image files
+ * @param $id
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function destroy($id)
+ {
+ $image = $this->image->findOrFail($id);
+
+ // Delete files
+ $folder = public_path() . dirname($image->url);
+ $pattern = '/' . preg_quote(basename($image->url)). '/';
+ $dir = new RecursiveDirectoryIterator($folder);
+ $ite = new RecursiveIteratorIterator($dir);
+ $files = new RegexIterator($ite, $pattern, RegexIterator::ALL_MATCHES);
+ foreach($files as $path => $file) {
+ unlink($path);
+ }
+ $image->delete();
+ return response()->json('Image Deleted');
+ }
+
}
// Image routes
Route::get('/images/all', 'ImageController@getAll');
+ Route::put('/images/update/{imageId}', 'ImageController@update');
+ Route::delete('/images/{imageId}', 'ImageController@destroy');
Route::get('/images/all/{page}', 'ImageController@getAll');
Route::get('/images/{any}', 'ImageController@getImage')->where('any', '.*');
class Image extends Model
{
+
+ protected $fillable = ['name'];
+
public function getFilePath()
{
return storage_path() . $this->url;
+jQuery.fn.showSuccess = function(message) {
+ var elem = $(this);
+ var success = $('<div class="text-pos" style="display:none;"><i class="zmdi zmdi-check-circle"></i>'+message+'</div>');
+ elem.after(success);
+ success.slideDown(400, function() {
+ setTimeout(function() {success.slideUp(400, function() {
+ success.remove();
+ })}, 2000);
+ });
+};
+
+jQuery.fn.showFailure = function(messageMap) {
+ var elem = $(this);
+ $.each(messageMap, function(key, messages) {
+ var input = elem.find('[name="'+key+'"]').last();
+ var fail = $('<div class="text-neg" style="display:none;"><i class="zmdi zmdi-alert-circle"></i>'+messages.join("\n")+'</div>');
+ input.after(fail);
+ fail.slideDown(400, function() {
+ setTimeout(function() {fail.slideUp(400, function() {
+ fail.remove();
+ })}, 2000);
+ });
+ });
+
+};
+
(function() {
var ImageManager = new Vue({
var dblClickTime = 380;
var cTime = (new Date()).getTime();
var timeDiff = cTime - this.cClickTime;
- if(this.cClickTime !== 0 && timeDiff < dblClickTime) {
+ if(this.cClickTime !== 0 && timeDiff < dblClickTime && this.selectedImage === image) {
// DoubleClick
if(this.callback) {
this.callback(image);
this.cClickTime = cTime;
},
+ selectButtonClick: function() {
+ if(this.callback) {
+ this.callback(this.selectedImage);
+ }
+ this.hide();
+ },
+
show: function(callback) {
this.callback = callback;
this.$$.overlay.style.display = 'block';
hide: function() {
this.$$.overlay.style.display = 'none';
+ },
+
+ saveImageDetails: function(e) {
+ e.preventDefault();
+ var _this = this;
+ var form = $(_this.$$.imageForm);
+ $.ajax('/images/update/' + _this.selectedImage.id, {
+ method: 'PUT',
+ data: form.serialize()
+ }).done(function() {
+ form.showSuccess('Image name updated');
+ }).fail(function(jqXHR) {
+ form.showFailure(jqXHR.responseJSON);
+ })
+ },
+
+ deleteImage: function(e) {
+ e.preventDefault();
+ var _this = this;
+ var form = $(_this.$$.imageDeleteForm);
+ $.ajax('/images/' + _this.selectedImage.id, {
+ method: 'DELETE',
+ data: form.serialize()
+ }).done(function() {
+ _this.images.splice(_this.images.indexOf(_this.selectedImage), 1);
+ _this.selectedImage = false;
+ $(_this.$$.imageTitle).showSuccess('Image Deleted');
+ })
}
}
--- /dev/null
+
+.anim.fadeIn {
+ opacity: 0;
+ animation-name: fadeIn;
+ animation-duration: 160ms;
+ animation-timing-function: ease-in-out;
+ animation-fill-mode: forwards;
+}
+
+@keyframes fadeIn {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
\ No newline at end of file
margin-bottom: $-s;
}
+.form-group {
+ .text-pos, .text-neg {
+ padding: $-xs 0;
+ }
+}
+
.inline-input-style {
border: 2px dotted #BBB;
display: block;
&.faded {
background-image: linear-gradient(to right, #FFF, #e3e0e0 20%, #e3e0e0 80%, #FFF);
}
- &.margin-top {
+ &.margin-top, &.even {
margin-top: $-l;
}
}
.list > * {
display: block;
+}
+
+/**
+ * Icons
+ */
+i {
+ padding-right: $-xs;
}
\ No newline at end of file
.image-manager-body {
- background-color: rgb(37, 37, 37);
- max-width: 90%;
+ background-color: #FFF;
max-height: 90%;
width: 90%;
height: 90%;
border-radius: 4px;
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.3);
overflow: hidden;
- .image-manager-list img {
- border-radius: 0;
- float: left;
- margin: 0;
- cursor: pointer;
- width: 150px;
- height: 150px;
- border: 1px solid transparent;
- &.selected {
- border: 3px solid #EEE;
- }
- }
+ max-width: 1340px;
position: fixed;
top: 0;
bottom: 0;
z-index: 999;
display: flex;
p, h1, h2, h3, h4, label, input {
- color: #EEE;
+ color: #444;
}
h1, h2, h3 {
font-weight: 300;
}
}
#image-manager .dropzone-container {
- height: 100px;
position: relative;
+ border: 3px dashed #DDD;
}
-#container {
- height: 90vh;
+.image-manager-bottom {
+ position: absolute;
+ bottom: 0;
+ right: 0;
}
+.image-manager-list img {
+ display: block;
+ border-radius: 0;
+ float: left;
+ margin: 0;
+ cursor: pointer;
+ width: (100%/6);
+ height: auto;
+ border: 1px solid #FFF;
+ transition: all cubic-bezier(.4,0,1,1) 160ms;
+ &.selected {
+ transform: scale3d(0.92, 0.92, 0.92);
+ }
+}
#image-manager .load-more {
- width: 150px;
- height: 150px;
display: block;
- float: left;
text-align: center;
- background-color: #404040;
- margin: 1px;
- color: #FFF;
- line-height: 140px;
+ background-color: #EEE;
+ padding: $-s $-m;
+ color: #AAA;
+ clear: both;
font-size: 20px;
cursor: pointer;
+ font-style: italic;
}
.image-manager-sidebar {
.image-manager-list {
overflow-y: scroll;
flex: 1;
+ border-top: 1px solid #ddd;
}
.image-manager-content {
*/
.dz-message {
- font-size: 1.6em;
+ font-size: 1.4em;
font-style: italic;
color: #aaa;
text-align: center;
- line-height: 90px;
cursor: pointer;
+ padding: $-xl $-m;
transition: all ease-in-out 120ms;
- position: absolute;
- top: 0;
- left: 50%;
- max-width: 400px;
- width: 400px;
- margin-left: -200px;
}
.dz-drag-hover .dz-message {
background-color: rgb(16, 126, 210);
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; }
.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;
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;
@import "buttons";
@import "forms";
@import "tables";
+@import "animations";
@import "tinymce";
@import "image-manager";
<div class="overlay" v-el="overlay" v-on="click: overlayClick" style="display:none;">
<div class="image-manager-body">
<div class="image-manager-content">
- <div class="dropzone-container" v-el="dropZone">
- <div class="dz-message">Drop files or click here to upload</div>
- </div>
<div class="image-manager-list">
<div v-repeat="image: images">
- <img v-class="selected: (image==selectedImage)" v-attr="src: image.thumbnail" v-on="click: imageClick(image)" alt="@{{image.name}}">
+ <img class="anim fadeIn"
+ v-class="selected: (image==selectedImage)"
+ v-attr="src: image.thumbnail, alt: image.name, title: image.name"
+ v-on="click: imageClick(image)"
+ v-style="animation-delay: ($index > 26) ? '160ms' : ($index * 25) + 'ms'">
</div>
<div class="load-more" v-show="hasMore" v-on="click: fetchData">Load More</div>
</div>
</div>
+ <button class="neg button image-manager-close" v-on="click: hide()">x</button>
<div class="image-manager-sidebar">
- <button class="neg button image-manager-close" v-on="click: hide()">x</button>
- <h2>Images</h2>
+ <h2 v-el="imageTitle">Images</h2>
+ <hr class="even">
+ <div class="dropzone-container" v-el="dropZone">
+ <div class="dz-message">Drop files or click here to upload</div>
+ </div>
+ <div class="image-manager-details anim fadeIn" v-show="selectedImage">
+ <hr class="even">
+ <form v-on="submit: saveImageDetails" v-el="imageForm">
+ {{ csrf_field() }}
+ <div class="form-group">
+ <label for="name">Image Name</label>
+ <input type="text" id="name" name="name" v-model="selectedImage.name">
+ </div>
+ </form>
+ <hr class="even">
+ <form v-on="submit: deleteImage" v-el="imageDeleteForm">
+ {{ csrf_field() }}
+ <button class="button neg"><i class="zmdi zmdi-delete"></i>Delete Image</button>
+ </form>
+ </div>
+ <div class="image-manager-bottom">
+ <button class="button pos anim fadeIn" v-show="selectedImage" v-on="click:selectButtonClick"><i class="zmdi zmdi-square-right"></i>Select Image</button>
+ </div>
</div>
</div>
</div>