Fix image input on models + Image Input evolution get original image size
This commit is contained in:
parent
7a1fac7a36
commit
9938686435
9 changed files with 145 additions and 144 deletions
|
|
@ -14,7 +14,7 @@ trim_trailing_whitespace = false
|
|||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.{vue}]
|
||||
[*.vue]
|
||||
indent_size = 2
|
||||
|
||||
[docker-compose.yml]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use App\Http\Requests\Bo\ChangeOrderRequest;
|
|||
use App\Http\Requests\Bo\Sliders\StoreSliderRequest;
|
||||
use App\Http\Requests\Bo\Sliders\UpdateSliderRequest;
|
||||
use App\Models\Slider;
|
||||
use App\Traits\UpdateModelStatus;
|
||||
use App\Traits\Controllers\UpdateModelStatus;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Http\Requests\Bo\News;
|
||||
|
||||
use App\Enums\PublishedStatus;
|
||||
use App\Traits\Requests\HasPicture;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Str;
|
||||
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
|
||||
|
|
@ -12,6 +13,8 @@ use Kwaadpepper\Enum\Rules\EnumIsValidRule;
|
|||
*/
|
||||
class StoreNewsRequest extends FormRequest
|
||||
{
|
||||
use HasPicture;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
|
|
@ -32,6 +35,7 @@ class StoreNewsRequest extends FormRequest
|
|||
$this->merge([
|
||||
'slug' => Str::slug($this->title),
|
||||
]);
|
||||
$this->mergePicture();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -41,13 +45,12 @@ class StoreNewsRequest extends FormRequest
|
|||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
return \array_merge([
|
||||
'title' => 'required|min:6|max:255',
|
||||
'slug' => 'required|max:255',
|
||||
'seo_title' => 'required|min:45|max:70',
|
||||
'seo_description' => 'required|min:50|max:160',
|
||||
'content' => 'required|string|min:15',
|
||||
'picture' => 'sometimes|image|dimensions:width=300,height=300|mimes:png,jpg,jpeg,gif',
|
||||
'picture_alt' => 'required_with:picture|nullable|string|min:6|max:255',
|
||||
'picture_title' => 'required_with:picture|nullable|string|min:6|max:255',
|
||||
'published' => ['required', new EnumIsValidRule(PublishedStatus::class)],
|
||||
|
|
@ -55,7 +58,7 @@ class StoreNewsRequest extends FormRequest
|
|||
'tags.*' => 'required|array',
|
||||
'tags.*.id' => 'required|numeric|exists:tags,id|distinct',
|
||||
'tags.*.name' => 'required|string',
|
||||
];
|
||||
], $this->pictureRules(300, 300, 300, 300));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@
|
|||
namespace App\Http\Requests\Bo\Sliders;
|
||||
|
||||
use App\Enums\Sliders\SliderPosition;
|
||||
use App\Traits\Requests\HasPicture;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
|
||||
|
||||
class StoreSliderRequest extends FormRequest
|
||||
{
|
||||
use HasPicture;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
|
|
@ -31,6 +34,7 @@ class StoreSliderRequest extends FormRequest
|
|||
'publish_at' => $this->publish_at ? \str_replace('T', ' ', $this->publish_at) : null,
|
||||
'unpublish_at' => $this->unpublish_at ? \str_replace('T', ' ', $this->unpublish_at) : null,
|
||||
]);
|
||||
$this->mergePicture();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -40,12 +44,11 @@ class StoreSliderRequest extends FormRequest
|
|||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
return \array_merge([
|
||||
'title' => 'required|min:3|max:255',
|
||||
'title_color' => 'required|color_hex',
|
||||
'title_color_bg' => 'required|color_hex',
|
||||
'position' => ['required', new EnumIsValidRule(SliderPosition::class)],
|
||||
'picture' => 'required|image|dimensions:width=1400,height=630|mimes:png,jpg,jpeg,gif',
|
||||
'link' => 'required|url',
|
||||
'link_title' => 'required|min:3|max:255',
|
||||
'link_color' => 'required|color_hex',
|
||||
|
|
@ -54,7 +57,7 @@ class StoreSliderRequest extends FormRequest
|
|||
'status' => 'required|boolean',
|
||||
'publish_at' => 'nullable|date_format:Y-m-d H:i',
|
||||
'unpublish_at' => 'nullable|date_format:Y-m-d H:i',
|
||||
];
|
||||
], $this->pictureRules(1400, 630, 1400, 630));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
namespace App\Traits\Controllers;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Enums\InputType;
|
||||
use App\Lib\ToolboxHelper;
|
||||
|
||||
trait MiniCrudModel
|
||||
{
|
||||
/**
|
||||
* Fill the model with an array of attributes.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return void
|
||||
* @throws \Illuminate\Database\Eloquent\MassAssignmentException If fill prop is not allowed.
|
||||
* @phpcs:disable Squiz.Commenting.FunctionComment.EmptyThrows
|
||||
*/
|
||||
public function fill(array $attributes)
|
||||
{
|
||||
parent::fill($attributes);
|
||||
if (isset($attributes['data'])) {
|
||||
$this->setData($attributes['data']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a specific prop
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
private function setData(array $data): void
|
||||
{
|
||||
$attrs = $this->attributes;
|
||||
if (isset($attrs['data'])) {
|
||||
unset($attrs['data']);
|
||||
}
|
||||
$data = $this->serializeData($data);
|
||||
$this->setRawAttributes(\array_merge_recursive(
|
||||
$attrs,
|
||||
['data' => $data]
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data attribute
|
||||
* Cast json_decode to object.
|
||||
*
|
||||
* @return object|null
|
||||
*/
|
||||
protected function getDataAttribute(): ?object
|
||||
{
|
||||
return \json_decode($this->getAttributes()['data'] ?? '[]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data attribute
|
||||
* Cast json_decode to object.
|
||||
*
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function setDataAttribute(array $data): void
|
||||
{
|
||||
$this->setData($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize properties
|
||||
*
|
||||
* @param array $data
|
||||
* @return string
|
||||
* @throws \ErrorException If input type is no handled.
|
||||
* @throws \Illuminate\Validation\ValidationException If url is a bo url.
|
||||
* @phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
|
||||
* @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
*/
|
||||
private function serializeData(array $data): string
|
||||
{
|
||||
foreach ($data as $o => $input) {
|
||||
switch ($input['inputType']->value ?? $input['inputType']['value']) {
|
||||
case InputType::image()->value:
|
||||
$data[$o]['value'] = ToolboxHelper::storeImage($this->getTable(), $input['value']);
|
||||
break;
|
||||
case InputType::boolean()->value:
|
||||
case InputType::text()->value:
|
||||
case InputType::number()->value:
|
||||
case InputType::string()->value:
|
||||
case InputType::videoVimeo()->value:
|
||||
break;
|
||||
case InputType::url()->value:
|
||||
ToolboxHelper::assertNoInternalBoUrls('content', $input['value']);
|
||||
break;
|
||||
default:
|
||||
throw new \ErrorException(sprintf(
|
||||
'Unknown input Type %s',
|
||||
$input['inputType']->label ?? $input['inputType']['label']
|
||||
));
|
||||
}
|
||||
}//end foreach
|
||||
return json_encode($data);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
namespace App\Traits\Requests;
|
||||
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
trait HasPicture
|
||||
{
|
||||
/**
|
||||
* Images rules
|
||||
* Minimum width
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private $minWith = 400;
|
||||
|
||||
/**
|
||||
* Maximum width
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private $maxWith = 1800;
|
||||
|
||||
/**
|
||||
* Minimum height
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private $minHeight = 300;
|
||||
|
||||
/**
|
||||
* Maximum height
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private $maxHeight = 1800;
|
||||
|
||||
/**
|
||||
* Image rules
|
||||
*
|
||||
* @param integer|null $minWith
|
||||
* @param integer|null $minHeight
|
||||
* @param integer|null $maxWith
|
||||
* @param integer|null $maxHeight
|
||||
* @return array
|
||||
*/
|
||||
public function pictureRule(): array
|
||||
{
|
||||
public function pictureRules(
|
||||
?int $minWith = null,
|
||||
?int $minHeight = null,
|
||||
?int $maxWith = null,
|
||||
?int $maxHeight = null
|
||||
): array {
|
||||
$this->minWith = $minWith ?? $this->minWith;
|
||||
$this->minHeight = $minHeight ?? $this->minHeight;
|
||||
$this->maxWith = $maxWith ?? $this->maxWith;
|
||||
$this->maxHeight = $maxHeight ?? $this->maxHeight;
|
||||
return [
|
||||
'picture' => [
|
||||
'required',
|
||||
|
|
@ -26,10 +66,22 @@ trait HasPicture
|
|||
/**
|
||||
* Images rules
|
||||
*
|
||||
* @param integer|null $minWith
|
||||
* @param integer|null $minHeight
|
||||
* @param integer|null $maxWith
|
||||
* @param integer|null $maxHeight
|
||||
* @return array
|
||||
*/
|
||||
public function pictureRules(): array
|
||||
{
|
||||
public function picturesRules(
|
||||
?int $minWith = null,
|
||||
?int $minHeight = null,
|
||||
?int $maxWith = null,
|
||||
?int $maxHeight = null
|
||||
): array {
|
||||
$this->minWith = $minWith ?? $this->minWith;
|
||||
$this->minHeight = $minHeight ?? $this->minHeight;
|
||||
$this->maxWith = $maxWith ?? $this->maxWith;
|
||||
$this->maxHeight = $maxHeight ?? $this->maxHeight;
|
||||
return [
|
||||
'pictures' => 'sometimes|array',
|
||||
'pictureValidate' => 'required_with:pictures|array',
|
||||
|
|
@ -57,25 +109,35 @@ trait HasPicture
|
|||
$fail(trans(":attribute n'existe pas dans le système de fichier"));
|
||||
return;
|
||||
}
|
||||
if (!\in_array(File::mimeType($value), ['image/jpeg', 'image/jpg', 'image/png'])) {
|
||||
$fail(trans(":attribute doit être une image de type 'image/jpeg,image/jpg,image/png'"));
|
||||
if (!\in_array(File::mimeType($value), ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'])) {
|
||||
$fail(trans(":attribute doit être une image de type 'image/jpeg,image/jpg,image/png,image/gif'"));
|
||||
}
|
||||
if (!($dimensions = \getimagesize($value)) or !isset($dimensions[0]) or !isset($dimensions[1])) {
|
||||
$fail(trans(":attribute impossible de valider les dimensions"));
|
||||
} else {
|
||||
$minWith = 400;
|
||||
$maxWith = 1800;
|
||||
$minHeight = 300;
|
||||
$maxHeight = 1800;
|
||||
$width = intval($dimensions[0]);
|
||||
$height = intval($dimensions[1]);
|
||||
if (($width < $minWith) or ($width > $maxWith)) {
|
||||
$fail(trans(":attribute la largeur doit être comprise entre $minWith et $maxWith"));
|
||||
$width = intval($dimensions[0]);
|
||||
$height = intval($dimensions[1]);
|
||||
|
||||
$xMessage = trans(
|
||||
":attribute la largeur doit être comprise entre {$this->minWith}px et {$this->maxWith}px"
|
||||
);
|
||||
if ($this->minWith === $this->maxWith) {
|
||||
$xMessage = trans(":attribute la largeur doit être de {$this->minWith}px");
|
||||
}
|
||||
if (($height < $minHeight) or ($height > $maxHeight)) {
|
||||
$fail(trans(":attribute la hauteur doit être comprise entre $minHeight et $maxHeight"));
|
||||
$yMessage = trans(
|
||||
":attribute la hauteur doit être comprise entre {$this->minWith}px et {$this->maxWith}px"
|
||||
);
|
||||
if ($this->minHeight === $this->maxHeight) {
|
||||
$yMessage = trans(":attribute la hauteur doit être de {$this->minWith}px");
|
||||
}
|
||||
}
|
||||
|
||||
if (($width < $this->minWith) or ($width > $this->maxWith)) {
|
||||
$fail($xMessage);
|
||||
}
|
||||
if (($height < $this->minHeight) or ($height > $this->maxHeight)) {
|
||||
$fail($yMessage);
|
||||
}
|
||||
} //end if
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
<div class="container ImageInput p-0">
|
||||
<div class="row">
|
||||
<div :class="`col-12 ${_preview ? 'col-md-6' : ''} form-group`">
|
||||
<label v-if="_preview" :for="_id" class="col-form-label"
|
||||
><b>{{ __("Choisir un fichier") }}</b></label
|
||||
>
|
||||
<label v-if="_preview" :for="_id" class="col-form-label">
|
||||
<b>{{ __("Choisir un fichier") }}</b>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<button
|
||||
v-on:click.prevent="chooseAFile"
|
||||
|
|
@ -89,11 +89,13 @@
|
|||
v-if="_value"
|
||||
:id="`PreviewHelp${_id}`"
|
||||
class="form-text text-muted d-block"
|
||||
>{{ __("Prévisualisation de l'image")
|
||||
}}<span v-if="_originalFile" class="fw-bold"
|
||||
> {{ humanFileSize(_originalFile.size) }} </span
|
||||
></small
|
||||
>
|
||||
>{{ __("Prévisualisation de l'image") }}
|
||||
<span v-if="_originalFile" class="fw-bold">
|
||||
{{ humanFileSize(_originalFile.size) }}
|
||||
{{ `hauteur ${_originalFileDimensions.height}px` }}
|
||||
{{ `largeur ${_originalFileDimensions.width}px` }}
|
||||
</span>
|
||||
</small>
|
||||
<img
|
||||
v-if="_value"
|
||||
class="img-fluid"
|
||||
|
|
@ -114,7 +116,9 @@
|
|||
<div class="modal-dialog modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{{ __("Modification d'image avant import") }}</h5>
|
||||
<h5 class="modal-title">
|
||||
{{ __("Modification d'image avant import") }}
|
||||
</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
|
|
@ -236,7 +240,11 @@
|
|||
</div>
|
||||
<div class="mt-1 ms-4 rotateDrag">
|
||||
<label for="range" class="form-label me-4"
|
||||
>{{ __(":deg degrès", { deg: String(_rotationText) }) }}
|
||||
>{{
|
||||
__(":deg degrès", {
|
||||
deg: String(_rotationText),
|
||||
})
|
||||
}}
|
||||
</label>
|
||||
<input
|
||||
ref="rotationSlider"
|
||||
|
|
@ -300,7 +308,9 @@
|
|||
<div class="modal-dialog modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{{ __("Prévisualisation") }}</h5>
|
||||
<h5 class="modal-title">
|
||||
{{ __("Prévisualisation") }}
|
||||
</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
|
|
@ -352,7 +362,9 @@ export default defineComponent({
|
|||
document.querySelectorAll('.ImageInput [data-bs-toggle="tooltip"]')
|
||||
);
|
||||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new Tooltip(tooltipTriggerEl, { delay: { show: 1000, hide: 300 } });
|
||||
return new Tooltip(tooltipTriggerEl, {
|
||||
delay: { show: 1000, hide: 300 },
|
||||
});
|
||||
});
|
||||
if (json.length) {
|
||||
// * Init uppon Laravel Blade
|
||||
|
|
@ -453,6 +465,10 @@ export default defineComponent({
|
|||
_cropper: Cropper | null;
|
||||
|
||||
_originalFile: File | null;
|
||||
_originalFileDimensions: {
|
||||
height: number;
|
||||
width: number;
|
||||
};
|
||||
_originalFileName: string;
|
||||
_previewStyle: string;
|
||||
} {
|
||||
|
|
@ -478,6 +494,10 @@ export default defineComponent({
|
|||
_cropper: null,
|
||||
|
||||
_originalFile: null,
|
||||
_originalFileDimensions: {
|
||||
height: 0,
|
||||
width: 0,
|
||||
},
|
||||
_originalFileName: "",
|
||||
_previewStyle: "",
|
||||
};
|
||||
|
|
@ -625,6 +645,7 @@ export default defineComponent({
|
|||
if (el.files[0]) {
|
||||
self._originalFile = el.files[0];
|
||||
reader.readAsDataURL(el.files[0]);
|
||||
reader.onloadend = () => this.fetchDataUrlImageSizes(String(reader.result));
|
||||
this._valueFileName = el.files[0].name;
|
||||
}
|
||||
},
|
||||
|
|
@ -686,6 +707,20 @@ export default defineComponent({
|
|||
}
|
||||
return new Blob([u8arr], { type: mime[1] });
|
||||
},
|
||||
fetchDataUrlImageSizes(dataUrl: string) {
|
||||
const img = new Image(),
|
||||
self = this;
|
||||
try {
|
||||
img.onload = () => {
|
||||
self._originalFileDimensions.width = img.width;
|
||||
self._originalFileDimensions.height = img.height;
|
||||
};
|
||||
img.src = dataUrl;
|
||||
} catch (e) {
|
||||
self._originalFileDimensions.width = img.width;
|
||||
self._originalFileDimensions.height = img.height;
|
||||
}
|
||||
},
|
||||
exportToBlob() {
|
||||
const self = this,
|
||||
actualImage = this.$refs.actualImage as HTMLInputElement,
|
||||
|
|
@ -706,6 +741,7 @@ export default defineComponent({
|
|||
imageSmoothingQuality: "high",
|
||||
})
|
||||
.toDataURL("image/png");
|
||||
this.fetchDataUrlImageSizes(self._value);
|
||||
const file = new File([self.dataURLtoBlob(self._value)], self._originalFileName, {
|
||||
type: "image/png",
|
||||
lastModified: new Date().getTime(),
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@
|
|||
$data = [
|
||||
'id' => 'sliderImageInput',
|
||||
'name' => 'picture',
|
||||
'helper' => __('L\'image qui sera affiché sur le slide'),
|
||||
'helper' => __('L\'image qui sera affiché sur le slide, doit être de 630px (hauteur) par 1400px (largeur)'),
|
||||
'width' => 1400,
|
||||
'height' => 630,
|
||||
'value' => $slider->picture ?? '',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue