Skip to content

Commit

Permalink
Merge pull request #50 from ctessier/1.x
Browse files Browse the repository at this point in the history
Release 1.2.0
  • Loading branch information
ctessier authored Aug 29, 2020
2 parents f71abde + f55fbd8 commit 8eb4244
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 177 deletions.
38 changes: 24 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# Nova Advanced Image Field

[![StyleCI](https://github.styleci.io/repos/156091175/shield?branch=1.0)](https://github.styleci.io/repos/156091175)
[![StyleCI](https://github.styleci.io/repos/156091175/shield?branch=1.x)](https://github.styleci.io/repos/156091175)
[![Latest Version on Packagist](https://img.shields.io/packagist/v/ctessier/nova-advanced-image-field.svg?style=flat-square)](https://packagist.org/packages/ctessier/nova-advanced-image-field)
[![Total Downloads](https://img.shields.io/packagist/dt/ctessier/nova-advanced-image-field.svg?style=flat-square)](https://packagist.org/packages/ctessier/nova-advanced-image-field)
[![Total Downloads](https://img.shields.io/packagist/dm/ctessier/nova-advanced-image-field.svg?style=flat-square)](https://packagist.org/packages/ctessier/nova-advanced-image-field)
[![License](https://img.shields.io/github/license/ctessier/nova-advanced-image-field?color=%23B2878B&style=flat-square)](https://packagist.org/packages/ctessier/nova-advanced-image-field)

This package provides an advanced image field for Nova resources allowing you to upload, crop and resize any image.

It uses [Cropper.js](https://fengyuanchen.github.io/cropperjs) with [vue-cropperjs](https://github.com/Agontuk/vue-cropperjs) in the frontend and [Intervention Image](http://image.intervention.io) in the backend.

![screenshot of the advanced image field](https://github.com/ctessier/nova-advanced-image-field/blob/1.0/screenshot.png?raw=true)
![screenshot of the advanced image field](https://github.com/ctessier/nova-advanced-image-field/blob/1.x/screenshot.png?raw=true)

## Requirements

To work correctly, this package requires the following components:
- Laravel & Nova
- Laravel & Nova (2 or 3)
- Fileinfo Extension

And **one of** the following libraries:
Expand All @@ -36,14 +36,15 @@ This will provide you with a new configuration file where you can specify the dr

## Usage

`AdvancedImage` extends from `File` so you can use any methods that `File` implements. See the documentation [here](https://nova.laravel.com/docs/2.0/resources/fields.html#file-field).
`AdvancedImage` extends from `Image` so you can use any methods that `Image` implements. See the documentation [here](https://nova.laravel.com/docs/3.0/resources/file-fields.html).

```php
<?php

namespace App\Nova;

// ...
use Illuminate\Http\Request;
use Ctessier\NovaAdvancedImageField\AdvancedImage;

class Post extends Resource
Expand All @@ -56,31 +57,40 @@ class Post extends Resource
// ...

// Simple image upload
AdvancedImage::make('photo'),
AdvancedImage::make('Photo'),

// Show a cropbox with a free ratio
AdvancedImage::make('photo')->croppable(),
AdvancedImage::make('Photo')->croppable(),

// Show a cropbox with a fixed ratio
AdvancedImage::make('photo')->croppable(16/9),
AdvancedImage::make('Photo')->croppable(16/9),

// Resize the image to a max width
AdvancedImage::make('photo')->resize(1920),
AdvancedImage::make('Photo')->resize(1920),

// Resize the image to a max height
AdvancedImage::make('photo')->resize(null, 1080),
AdvancedImage::make('Photo')->resize(null, 1080),

// Show a cropbox and resize the image
AdvancedImage::make('photo')->croppable()->resize(400, 300),
AdvancedImage::make('Photo')->croppable()->resize(400, 300),

// Override the image processing driver for this field only
AdvancedImage::make('photo')->driver('imagick')->croppable(),
AdvancedImage::make('Photo')->driver('imagick')->croppable(),

// Store to AWS S3
AdvancedImage::make('photo')->disk('s3'),
AdvancedImage::make('Photo')->disk('s3'),

// Specify a custom subdirectory
AdvancedImage::make('photo')->disk('s3')->path('image'),
AdvancedImage::make('Photo')->croppable()->disk('s3')->path('image'),

// Store custom attributes
AdvancedImage::make('Photo')->croppable()->store(function (Request $request, $model) {
return [
'photo' => $request->photo->store('/', 's3'),
'photo_mime' => $request->photo->getMimeType(),
'photo_name' => $request->photo->getClientOriginalName(),
];
}),
];
}
}
Expand Down
2 changes: 1 addition & 1 deletion dist/js/field.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/field.js.LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
/**
* @license
* Lodash <https://lodash.com/>
* Copyright JS Foundation and other contributors <https://js.foundation/>
* Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
* Released under MIT license <https://lodash.com/license>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
Expand Down
1 change: 1 addition & 0 deletions resources/js/components/FormField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
type="file"
:id="idAttr"
name="name"
:accept="field.acceptedTypes"
@change="fileChange"
/>
<label :for="labelFor" class="form-file-btn btn btn-default btn-primary">
Expand Down
8 changes: 7 additions & 1 deletion resources/js/components/IndexField.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<template>
<p>
<img v-if="field.thumbnailUrl" :src="field.thumbnailUrl" style="object-fit: cover;" class="rounded-full w-8 h-8" />
<img
v-if="field.thumbnailUrl"
:src="field.thumbnailUrl"
style="object-fit: cover;"
class="w-8 h-8"
:class="{ 'rounded-full': field.rounded, rounded: !field.rounded }"
/>
<span v-else>&mdash;</span>
</p>
</template>
Expand Down
172 changes: 12 additions & 160 deletions src/AdvancedImage.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
namespace Ctessier\NovaAdvancedImageField;

use Illuminate\Support\Facades\Storage;
use Intervention\Image\Facades\Image;
use Laravel\Nova\Fields\File;
use Laravel\Nova\Fields\Image;
use Laravel\Nova\Http\Requests\NovaRequest;

class AdvancedImage extends File
class AdvancedImage extends Image
{
use TransformableImage;

/**
* The field's component.
*
Expand All @@ -17,49 +18,11 @@ class AdvancedImage extends File
public $component = 'advanced-image-field';

/**
* The driver library to use for image manipulation.
*
* This value will override the driver configured for Intervention
* in the `config/image.php` file of the Laravel project.
*
* @var string|null
*/
public $driver = null;

/**
* Indicates if the image is croppable.
*
* @var bool
*/
public $croppable = false;

/**
* The fixed aspect ratio of the crop box.
*
* @var float
*/
public $cropAspectRatio;

/**
* The width for the resizing of the image.
*
* @var int
*/
public $width;

/**
* The height for the resizing of the image.
*
* @var int
*/
public $height;

/**
* Indicates if the element should be shown on the index view.
* Indicates whether the image should be fully rounded or not.
*
* @var bool
*/
public $showOnIndex = true;
public $rounded = true;

/**
* Create a new field.
Expand All @@ -82,63 +45,6 @@ public function __construct($name, $attribute = null, $disk = 'public', $storage
});
}

/**
* Override the default driver to be used by Intervention for the image manipulation.
*
* @param string $driver
*
* @throws \Exception
*
* @return $this
*/
public function driver(string $driver)
{
if (!in_array($driver, ['gd', 'imagick'])) {
throw new \Exception("The driver \"$driver\" is not a valid Intervention driver.");
}

$this->driver = $driver;

return $this;
}

/**
* Specify if the underlying image should be croppable.
* If a numeric value is given as a first parameter, it will be used to define a fixed aspect
* ratio for the crop box.
*
* @param mixed $param
*
* @return $this
*/
public function croppable($param = true)
{
if (is_numeric($param)) {
$this->cropAspectRatio = $param;
$param = true;
}

$this->croppable = $param;

return $this;
}

/**
* Specify the size (width and height) the image should be resized to.
*
* @param int|null $width
* @param int|null $height
*
* @return $this
*/
public function resize($width = null, $height = null)
{
$this->width = $width;
$this->height = $height;

return $this;
}

/**
* Hydrate the given attribute on the model based on the incoming request.
*
Expand All @@ -157,77 +63,23 @@ protected function fillAttribute(NovaRequest $request, $requestAttribute, $model

$previousFileName = $model->{$attribute};

if (!$this->croppable && !$this->width && !$this->height) {
parent::fillAttribute($request, $requestAttribute, $model, $attribute);
} else {
if ($this->driver) {
Image::configure([
'driver' => $this->driver,
]);
}
$image = Image::make($request->{$this->attribute});
if ($this->croppable) {
$this->handleCrop($image, json_decode($request->{$this->attribute.'_data'}));
}
if ($this->width || $this->height) {
$this->handleResize($image, $this->width, $this->height);
}
$this->transformImage($request->{$this->attribute}, json_decode($request->{$this->attribute.'_data'}));

$image->stream();
if ($this->storagePath === '/') {
$fileName = $request->{$this->attribute}->hashName();
} else {
$fileName = trim($this->storagePath, '/').'/'.$request->{$this->attribute}->hashName();
}
Storage::disk($this->disk)->put($fileName, $image->__toString());
$image->destroy();

$model->{$attribute} = $fileName;
}
parent::fillAttribute($request, $requestAttribute, $model, $attribute);

Storage::disk($this->disk)->delete($previousFileName);
}

/**
* Crop the uploaded image.
*
* @param \Intervention\Image\Image $image
* @param object $cropperData
*
* @return void
*/
private function handleCrop($image, $cropperData)
{
$image->crop($cropperData->width, $cropperData->height, $cropperData->x, $cropperData->y);
}

/**
* Resize the uploaded image.
*
* @param \Intervention\Image\Image $image
* @param int|null $width
* @param int|null $height
*
* @return void
*/
private function handleResize($image, $width, $height)
{
$image->resize($width, $height, function ($constraint) {
$constraint->upsize();
$constraint->aspectRatio();
});
}

/**
* Get additional meta information to merge with the element payload.
* Prepare the field element for JSON serialization.
*
* @return array
*/
public function meta()
public function jsonSerialize()
{
return array_merge([
return array_merge(parent::jsonSerialize(), [
'croppable' => $this->croppable,
'aspectRatio' => $this->cropAspectRatio,
], parent::meta());
]);
}
}
Loading

0 comments on commit 8eb4244

Please sign in to comment.