diff --git a/src/CrudTrait.php b/src/CrudTrait.php index 9a1f21a55e..9b18c2ffbb 100644 --- a/src/CrudTrait.php +++ b/src/CrudTrait.php @@ -191,4 +191,215 @@ public function uploadMultipleFilesToDisk($value, $attribute_name, $disk, $desti $this->attributes[$attribute_name] = json_encode($attribute_value); } + + /** + * Handle image upload and DB storage for a image: + * - on CREATE + * - stores the image at the destination path + * - generates a name + * - creates image variations + * - stores json object into database with variations and paths + * - on UPDATE + * - if the value is null, deletes the file and sets null in the DB + * - if the value is different, stores the different file and updates DB value. + * + * @param [type] $value Value for that column sent from the input. + * @param [type] $attribute_name Model attribute name (and column in the db). + * @param [type] $disk Filesystem disk used to store files. + * @param [type] $destination_path Path in disk where to store the files. + * @param [type] $variations Array of variations and their dimensions + */ + public function uploadImageToDisk($value, $attribute_name, $disk, $destination_path, $variations = null) + { + if (! $variations || ! is_array($variations)) { + $variations = ['original' => null, 'thumb' => [150, 150]]; + } + + //Needed for the original image + if (! array_key_exists('original', $variations)) { + $variations['original'] = null; + } + + //Needed for admin thumbnails + if (! array_key_exists('thumb', $variations)) { + $variations['thumb'] = [150, 150]; + } + + $request = \Request::instance(); + + //We need to setup the disk paths as they're handled differently + //depending if you need a public path or internal storage + $disk_config = config('filesystems.disks.'.$disk); + $disk_root = $disk_config['root']; + + //if the disk is public, we need to know the public path + if ($disk_config['visibility'] == 'public') { + $public_path = str_replace(public_path(), '', $disk_root); + } else { + $public_path = $disk_root; + } + + // if a new file is uploaded, delete the file from the disk + if (($request->hasFile($attribute_name) || starts_with($value, 'data:image')) && $this->{$attribute_name}) { + foreach ($variations as $variant => $dimensions) { + $variant_name = str_replace('-original', '-'.$variant, $this->{$attribute_name}); + \Storage::disk($disk)->delete($variant_name); + } + $this->attributes[$attribute_name] = null; + } + + // if the file input is empty, delete the file from the disk + if (empty($value)) { + foreach ($variations as $variant => $dimensions) { + $variant_name = str_replace('-original', '-'.$variant, $this->{$attribute_name}); + \Storage::disk($disk)->delete($variant_name); + } + + return $this->attributes[$attribute_name] = null; + } + + // if a new file is uploaded, store it on disk and its filename in the database + if ($request->hasFile($attribute_name) && $request->file($attribute_name)->isValid()) { + + // 1. Generate a new file name + $file = $request->file($attribute_name); + $new_file_name = md5($file->getClientOriginalName().time()); + $new_file = $new_file_name.'.'.$file->getClientOriginalExtension(); + + // 2. Move the new file to the correct path + $file_path = $file->storeAs($destination_path, $new_file, $disk); + $image_variations = []; + + // 3. but only if they have the ability to crop/handle images + if (class_exists('\Intervention\Image\ImageManagerStatic')) { + foreach ($variations as $variant => $dimensions) { + $img = \Intervention\Image\ImageManagerStatic::make($file); + + $variant_name = $new_file_name.'-'.$variant.'.'.$file->getClientOriginalExtension(); + $variant_file = $destination_path.'/'.$variant_name; + + if ($dimensions) { + $width = $dimensions[0]; + $height = $dimensions[1]; + + if ($img->width() > $width || $img->height() > $height) { + $img->resize($width, $height, function ($constraint) { + $constraint->aspectRatio(); + }) + ->save($disk_root.'/'.$variant_file); + } else { + $img->save($disk_root.'/'.$variant_file); + } + + $image_variations[$variant] = $public_path.'/'.$variant_file; + } else { + $image_variations['original'] = $public_path.'/'.$file_path; + } + } + } else { + $image_variations['original'] = $public_path.'/'.$file_path; + $image_variations['thumb'] = $public_path.'/'.$file_path; + } + + // 3. Save the complete path to the database + $this->attributes[$attribute_name] = $image_variations['original']; + } elseif (starts_with($value, 'data:image')) { + $new_file_name = md5($value.time()); + + if (! \Illuminate\Support\Facades\File::exists($disk_root.'/'.trim($destination_path, '/'))) { + \Illuminate\Support\Facades\File::makeDirectory($disk_root.'/'.trim($destination_path, '/'), 0775, true); + } + + foreach ($variations as $variant => $dimensions) { + $img = \Intervention\Image\ImageManagerStatic::make($value); + + switch ($img->mime()) { + case 'image/bmp': + case 'image/ief': + case 'image/jpeg': + case 'image/pipeg': + case 'image/tiff': + case 'image/x-jps': + $extension = '.jpg'; + break; + case 'image/gif': + $extension = '.gif'; + break; + case 'image/x-icon': + case 'image/png': + $extension = '.png'; + break; + default: + $extension = '.jpg'; + break; + } + + $variant_name = $new_file_name.'-'.$variant.$extension; + $variant_file = $destination_path.'/'.$variant_name; + + if ($dimensions) { + $width = $dimensions[0]; + $height = $dimensions[1]; + + if ($img->width() > $width || $img->height() > $height) { + $img->resize($width, $height, function ($constraint) { + $constraint->aspectRatio(); + }) + ->save($disk_root.'/'.$variant_file); + } else { + $img->save($disk_root.'/'.$variant_file); + } + + $image_variations[$variant] = $public_path.'/'.$variant_file; + } else { + $img->save($disk_root.'/'.$variant_file); + $image_variations['original'] = $variant_file; + } + } + + return $this->attributes[$attribute_name] = $image_variations['original']; + } + } + + /** + * Handles the retrieval of an image by variant:. + * + * @param [type] $attribute Name of the attribute within the model that contains the json + * @param [type] $variant Name of the variant you want to extract + * @param [type] $disk Filesystem disk used to store files. + */ + public function getUploadedImageFromDisk($attribute, $variant = 'original', $disk = null) + { + $image = $this->attributes[$attribute]; + $url = null; + if (! empty($image)) { + $image_variant = str_replace('-original', '-'.$variant, $image); + + if ($disk) { + $disk_config = config('filesystems.disks.'.$disk); + $disk_root = $disk_config['root']; + + //if the disk is public, we need to know the public path + if ($disk_config['visibility'] == 'public') { + $public_path = str_replace(public_path(), '', $disk_root); + } else { + $public_path = $disk_root; + } + + if (\Storage::disk($disk)->exists($image_variant)) { + $url = asset($public_path.'/'.$image_variant); + } else { + $url = asset($public_path.'/'.trim($image, '/')); + } + } else { + if (\Storage::exists($image_variant)) { + $url = \Storage::url(trim($image_variant, '/')); + } else { + $url = url($image); + } + } + } + + return $url; + } } diff --git a/src/public/owenmelbz/blockui.css b/src/public/owenmelbz/blockui.css new file mode 100644 index 0000000000..6f35c2826c --- /dev/null +++ b/src/public/owenmelbz/blockui.css @@ -0,0 +1,82 @@ +/*csslint box-sizing: false*/ + +@-webkit-keyframes block-ui-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@-moz-keyframes block-ui-spin { + 0% { + -moz-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -moz-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@-o-keyframes block-ui-spin { + 0% { + -o-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -o-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes block-ui-spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.block-ui-target { + position: relative; + pointer-events: none; +} + +.block-ui-block::after, +.block-ui-block::before { + display: block; + content: ""; + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + pointer-events: none; +} + +.block-ui-block::before { + z-index: 2000; + top: 50%; + left: 50%; + margin-left: -7px; + margin-top: -7px; + width: 14px; + height: 14px; + box-sizing: border-box; + border: solid 2px #000000; + border-top-color: #605ca8; + border-left-color: #605ca8; + border-radius: 10px; + animation: block-ui-spin 400ms linear infinite; +} + +.block-ui-block::after { + background: #FFF; + opacity: .7; + z-index: 1999; +} diff --git a/src/public/owenmelbz/blockui.js b/src/public/owenmelbz/blockui.js new file mode 100644 index 0000000000..8a7332848a --- /dev/null +++ b/src/public/owenmelbz/blockui.js @@ -0,0 +1,18 @@ +/*jslint browser: true*/ + +//Block UI JS +(function (jQuery) { + + 'use strict'; + + jQuery.fn.blockui = jQuery.fn.blockUI = function () { + this.addClass('block-ui-target block-ui-block'); + return this; + }; + + jQuery.fn.unblockui = jQuery.fn.unblockUI = function () { + this.removeClass('block-ui-target block-ui-block'); + return this; + }; + +}(window.jQuery)); diff --git a/src/public/owenmelbz/blockui.min.css b/src/public/owenmelbz/blockui.min.css new file mode 100644 index 0000000000..09b28f7981 --- /dev/null +++ b/src/public/owenmelbz/blockui.min.css @@ -0,0 +1 @@ +@-webkit-keyframes block-ui-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes block-ui-spin{0%{-moz-transform:rotate(0);transform:rotate(0)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-o-keyframes block-ui-spin{0%{-o-transform:rotate(0);transform:rotate(0)}100%{-o-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes block-ui-spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.block-ui-target{position:relative;pointer-events:none}.block-ui-block::after,.block-ui-block::before{display:block;content:"";width:100%;height:100%;position:absolute;top:0;left:0;pointer-events:none}.block-ui-block::before{z-index:2000;top:50%;left:50%;margin-left:-7px;margin-top:-7px;width:14px;height:14px;box-sizing:border-box;border:2px solid #000;border-top-color:#605ca8;border-left-color:#605ca8;border-radius:10px;animation:block-ui-spin .4s linear infinite}.block-ui-block::after{background:#FFF;opacity:.7;z-index:1999} diff --git a/src/public/owenmelbz/blockui.min.js b/src/public/owenmelbz/blockui.min.js new file mode 100644 index 0000000000..536f5b8537 --- /dev/null +++ b/src/public/owenmelbz/blockui.min.js @@ -0,0 +1 @@ +!function(a){"use strict";a.fn.blockui=a.fn.blockUI=function(){return this.addClass("block-ui-target block-ui-block"),this},a.fn.unblockui=a.fn.unblockUI=function(){return this.removeClass("block-ui-target block-ui-block"),this}}(window.jQuery); diff --git a/src/resources/views/columns/image.blade.php b/src/resources/views/columns/image.blade.php new file mode 100644 index 0000000000..a07a1653ae --- /dev/null +++ b/src/resources/views/columns/image.blade.php @@ -0,0 +1,8 @@ +{{-- regular object attribute --}} + + @if ( isset($entry->{$column['name']}) && !empty($entry->{$column['name']}) ) + entry image preview + @else + n/a + @endif + diff --git a/src/resources/views/fields/image.blade.php b/src/resources/views/fields/image.blade.php index 0b320f4e9d..1b8c1d9e2c 100644 --- a/src/resources/views/fields/image.blade.php +++ b/src/resources/views/fields/image.blade.php @@ -1,42 +1,46 @@ -
-
- -
- -
-
- -
- @if(isset($field['crop']) && $field['crop']) -
-
-
- -
-
-
- @endif -
-
- - @if(isset($field['crop']) && $field['crop']) - - - - - - @endif - -
- - {{-- HINT --}} - @if (isset($field['hint'])) -

{!! $field['hint'] !!}

- @endif +
+ +
+ +
+ + +
+ +
+ +
+ + @if(isset($field['crop']) && $field['crop']) +
+
+
+ +
+
+
+ @endif + +
+ +
+ + + + @if(isset($field['crop']) && $field['crop']) + + + + + + @endif + +
+
{{-- ########################################## --}} @@ -44,178 +48,232 @@ {{-- If a field type is shown multiple times on a form, the CSS and JS will only be loaded once --}} @if ($crud->checkIfFieldIsFirstOfItsType($field, $fields)) - {{-- FIELD CSS - will be loaded in the after_styles section --}} - @push('crud_fields_styles') - {{-- YOUR CSS HERE --}} - - - @endpush - - {{-- FIELD JS - will be loaded in the after_scripts section --}} - @push('crud_fields_scripts') - {{-- YOUR JS HERE --}} - - - - - @endpush + {{-- FIELD CSS - will be loaded in the after_styles section --}} + @push('crud_fields_styles') + {{-- YOUR CSS HERE --}} + + + @endpush + + {{-- FIELD JS - will be loaded in the after_scripts section --}} + @push('crud_fields_scripts') + {{-- YOUR JS HERE --}} + + + + + @endpush @endif {{-- End of Extra CSS and JS --}} {{-- ########################################## --}}