Last active
October 19, 2022 00:01
-
-
Save tanthammar/a4c0ff03a1bb09d63b942da4e05133c8 to your computer and use it in GitHub Desktop.
LiveWire Spatie Media Image upload with VueCroppa
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// npm install --save vue-croppa | |
import Vue from 'vue' | |
import Croppa from 'vue-croppa' | |
Vue.use(Croppa) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.croppa-container>canvas { | |
border: 5px; | |
border-color: #e1e1e1; | |
border-style: dashed; | |
} | |
.croppa-container { | |
@apply flex; | |
} | |
.croppa-container>svg.icon.icon-remove { | |
width: 30px; | |
height: 30px; | |
margin-left: 10px; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div id="media-comp" class="display-contents"> | |
<media inline-template> | |
<form> | |
<div v-for="(image, index) in form" :key="index" @touchstart.stop @mousedown.stop class="col-span-6"> | |
<h3 class="text-3xl font-medium">@{{ image.label }}</h3> | |
<p class="py-3">Allowed Width @{{image.width}}px, Height @{{image.height}}px, Max file size @{{image.maxFileSize}}</p> | |
<input v-model="image.value" wire:model="@{{ image.name }}" type="hidden"> | |
<croppa v-model="image.model" :width="image.width" :height="image.height" | |
:placeholder="locale == 'sv' ? 'Välj en bild' : 'Choose an image'" | |
:accept="'image/*'" :file-size-limit="image.maxByte" :zoom-speed="3" :disable-drag-and-drop="false" | |
:show-loading="true" :prevent-white-space="image.preventWhite" :show-remove-button="true" | |
:remove-button-color="'red'" :initial-image="image.value" :initial-size="image.fit" | |
@file-size-exceed="imageToLarge" /> | |
</croppa> | |
</div> | |
<div class="w-full mt-8 border-t border-gray-200 pt-5"> | |
<div class="space-x-3 flex justify-end items-center"> | |
<span class="inline-flex rounded-md shadow-sm"> | |
<input | |
v-on:click.prevent="formReset" | |
type="reset" name="form-reset" class="py-2 px-4 | |
border border-gray-300 rounded-md | |
bg-red-600 | |
text-sm leading-5 font-medium text-white | |
hover:outline-none hover:border-red-800 hover:bg-red-700 hover:shadow-outline-red | |
transition duration-150 ease-in-out" /> | |
</span> | |
<span class="inline-flex rounded-md shadow-sm"> | |
<button name="submit" | |
v-on:click.prevent="save" | |
class="inline-flex justify-center py-2 px-4 | |
border border-transparent rounded-md | |
text-sm leading-5 font-medium text-white | |
bg-night-lighter hover:bg-night-dark | |
hover:border-indigo-800 hover:outline-none hover:border-indigo-700 hover:shadow-indigo-blue | |
transition duration-150 ease-in-out">@lang('global.save')</button> | |
</span> | |
</div> | |
</form> | |
</media> | |
</div> | |
@push('pre-scripts') | |
{{-- assumes you loaded vue.js elsewhere --}} | |
<script src="{{ mix('js/croppa.js') }}"></script> | |
@endpush | |
@push('scripts') | |
<script> | |
Vue.component('media', { | |
data() { | |
return { | |
locale: '{{ app()->getLocale() }}', | |
form: { | |
banner: { | |
name: "banner", | |
model: {}, | |
width: 600, | |
height: 335, | |
maxFileSize: "1.5MB(1536KB)", | |
maxByte: 1572864, | |
preventWhite: true, | |
fit: "cover", | |
value: '{{ $banner }}', | |
label: "Banner", | |
hint: "Width 600px, Height 335px, Max file size 1.5MB(1536KB)" | |
}, | |
logo: { | |
name: "logo", | |
model: {}, | |
width: 300, | |
height: 300, | |
maxFileSize: "0.5MB(512KB)", | |
maxByte: 524288, | |
preventWhite: false, | |
fit: "natural", | |
value: '{{ $logo }}', | |
label: "Logo", | |
hint: "Width 600px, Height 335px, Max file size 1.5MB(1536KB)" | |
} | |
} | |
}; | |
}, | |
methods: { | |
generateImages() { | |
this.form.logo.value = this.form.logo.model.generateDataUrl(); | |
this.form.banner.value = this.form.banner.model.generateDataUrl(); | |
}, | |
imageToLarge() { | |
//alert('your error msg'); | |
//check out calebs sponsor videos to make this notification work | |
@this.set('alert', { | |
type: 'negative', | |
message: this.locale == 'sv' | |
? "Bilden du valt överskrider max tillåten dokument storlek. Se 'Max file size' ovanför bilden." | |
: "The image you uploaded exceeds max allowed file size, stated above the picture" | |
}); | |
}, | |
formReset() { | |
this.form.banner.model.refresh(); | |
this.form.logo.model.refresh(); | |
}, | |
save() { | |
this.generateImages(); | |
window.livewire.emit('vueMediaSave', { | |
logo: this.form.logo.value, | |
banner: this.form.banner.value, | |
}); | |
}, | |
} | |
}); | |
var mediaComp = new Vue({ | |
el: '#media-comp' | |
}); | |
</script> | |
@endpush |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Http\Livewire\App\Organizers\Forms; | |
use App\Models\Organizer; | |
use Livewire\Component; | |
class Media extends Component | |
{ | |
public $logo; | |
public $banner; | |
protected $listeners = ['vueMediaSave']; | |
public function mount(Organizer $organizer) | |
{ | |
$this->fill([ | |
'model' => $organizer, | |
'formTitle' => "Media", | |
'logo' => $organizer->getFirstMediaUrl('logo'), | |
'banner' => $organizer->getFirstMediaUrl('banner'), | |
]); | |
} | |
public function vueMediaSave($array) | |
{ | |
// spatie media library handles validation | |
// * @throws InvalidBase64Data | |
// * @throws \Spatie\MediaLibrary\MediaCollections\Exceptions\FileCannotBeAdded | |
// trait Spatie\MediaLibrary\InteractsWithMedia@addMediaFromBase64 | |
// TODO Nasty N+1 queries: Model::media loaded 4 times. | |
// Out of this scope, have to dig deep into Spatie pkg | |
$logo = data_get($array, 'logo'); | |
if (filled($logo)) { | |
try { | |
$this->logo = $this->model->addMediaFromBase64($logo)->toMediaCollection('logo')->getUrl(); | |
} catch (\Throwable $th) { | |
//se caleb sponsor videos for notify() | |
$this->notify('negative', trans('messages.logo_error' . ' Server says: ' . $th)); | |
} | |
} else { //delete logo | |
$this->model->getFirstMedia('logo')->delete(); | |
$this->logo = ""; | |
} | |
$banner = data_get($array, 'banner'); | |
if (filled($banner)) { | |
try { | |
$this->banner = $this->model->addMediaFromBase64($banner)->toMediaCollection('banner')->getUrl(); | |
} catch (\Throwable $th) { | |
//se caleb sponsor videos for notify() | |
$this->notify('negative', trans('messages.banner_error') . ' Server says: ' . $th); | |
} | |
} else { //delete banner | |
$this->model->getFirstMedia('banner')->delete(); | |
$this->banner = ""; | |
} | |
$this->notify(); | |
} | |
public function render() | |
{ | |
return view('livewire.app.organizers.forms.media'); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Livewire
Spatie Laravel Medialibrary
TailwindUi
VueCroppa
Features: