]> BookStack Code Mirror - bookstack/commitdiff
Added image name editing & deleting
authorDan Brown <redacted>
Sat, 15 Aug 2015 23:18:22 +0000 (00:18 +0100)
committerDan Brown <redacted>
Sat, 15 Aug 2015 23:18:22 +0000 (00:18 +0100)
app/Http/Controllers/ImageController.php
app/Http/routes.php
app/Image.php
resources/assets/js/image-manager.js
resources/assets/sass/_animations.scss [new file with mode: 0644]
resources/assets/sass/_forms.scss
resources/assets/sass/_text.scss
resources/assets/sass/image-manager.scss
resources/assets/sass/styles.scss
resources/views/pages/image-manager.blade.php

index 2ef5b1228e9fc8498fe3f2a18f40112eee370c0c..9fcd5c304acbdfba3ec2bd2e1cb1daeada9fed1b 100644 (file)
@@ -10,6 +10,9 @@ use Intervention\Image\Facades\Image as ImageTool;
 use Illuminate\Support\Facades\DB;
 use Oxbow\Http\Requests;
 use Oxbow\Image;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+use RegexIterator;
 
 class ImageController extends Controller
 {
@@ -71,7 +74,7 @@ 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) {
@@ -146,5 +149,44 @@ class ImageController extends Controller
         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');
+    }
+
 
 }
index 0b723b98a89d701883f7b7701dcef6742e936081..ecda757932800bad4d8c76420406bb7711dfb4ad 100644 (file)
@@ -68,6 +68,8 @@ Route::group(['middleware' => 'auth'], function() {
 
     // 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', '.*');
 
index 9d2835dc3e3763375aec36d5d7d32017e84909d4..07db687854943815d9fc61714c5e35e088e0a477 100644 (file)
@@ -6,6 +6,9 @@ use Illuminate\Database\Eloquent\Model;
 
 class Image extends Model
 {
+
+    protected $fillable = ['name'];
+
     public function getFilePath()
     {
         return storage_path() . $this->url;
index 27deed51ee11a1ee2ad266875900b2b12f7782cc..4f8695ff80ed4711b18ba6f5902997437bfa9965 100644 (file)
@@ -1,4 +1,30 @@
 
+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({
@@ -56,7 +82,7 @@
                 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');
+                })
             }
 
         }
diff --git a/resources/assets/sass/_animations.scss b/resources/assets/sass/_animations.scss
new file mode 100644 (file)
index 0000000..147197e
--- /dev/null
@@ -0,0 +1,17 @@
+
+.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
index 5472e3053f48ee64e1db167b195ba06b5a665827..f6b4a3d8af670dc45351af43e68647a6d080b6bd 100644 (file)
@@ -45,6 +45,12 @@ input[type="text"], input[type="number"], input[type="email"], input[type="searc
   margin-bottom: $-s;
 }
 
+.form-group {
+  .text-pos, .text-neg {
+    padding: $-xs 0;
+  }
+}
+
 .inline-input-style {
   border: 2px dotted #BBB;
   display: block;
index f09c571219a46627d14d4c183ec4d2fa5d273105..a542da8a1b740a48f3f0a60364ffae978ade849f 100644 (file)
@@ -82,7 +82,7 @@ hr {
   &.faded {
     background-image: linear-gradient(to right, #FFF, #e3e0e0 20%, #e3e0e0 80%, #FFF);
   }
-  &.margin-top {
+  &.margin-top, &.even {
     margin-top: $-l;
   }
 }
@@ -227,4 +227,11 @@ ul {
 
 .list > * {
   display: block;
+}
+
+/**
+  * Icons
+  */
+i {
+  padding-right: $-xs;
 }
\ No newline at end of file
index 15976e733278bb637a841675dabdecb4d0eca2c9..fc2d2f36806e314d7a8e83e11e59b3242db54d9f 100644 (file)
@@ -1,6 +1,5 @@
 .image-manager-body {
-  background-color: rgb(37, 37, 37);
-  max-width: 90%;
+  background-color: #FFF;
   max-height: 90%;
   width: 90%;
   height: 90%;
@@ -9,18 +8,7 @@
   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 {
@@ -75,6 +77,7 @@
 .image-manager-list {
   overflow-y: scroll;
   flex: 1;
+  border-top: 1px solid #ddd;
 }
 
 .image-manager-content {
  * Copyright (c) 2012 Matias Meno <[email protected]>
  */
 .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;
index 7d23045bb6719411c72a22812fdb6935cd3278db..c7301336fbe27b376574972988663f0b40bdcf42 100644 (file)
@@ -8,6 +8,7 @@
 @import "buttons";
 @import "forms";
 @import "tables";
+@import "animations";
 @import "tinymce";
 @import "image-manager";
 
index c68434d7b13558dc5aba44269c29010a86cbd748..1a38978117b29c0fc7c0d451fcaca5e6b1bfb885 100644 (file)
@@ -3,19 +3,42 @@
     <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>