Skip to content

Instantly share code, notes, and snippets.

@tanthammar
Last active October 19, 2022 00:01
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save tanthammar/a4c0ff03a1bb09d63b942da4e05133c8 to your computer and use it in GitHub Desktop.
Save tanthammar/a4c0ff03a1bb09d63b942da4e05133c8 to your computer and use it in GitHub Desktop.
LiveWire Spatie Media Image upload with VueCroppa
// npm install --save vue-croppa
import Vue from 'vue'
import Croppa from 'vue-croppa'
Vue.use(Croppa)
.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;
}
<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
<?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');
}
}
@tanthammar
Copy link
Author

tanthammar commented May 15, 2020

Livewire
Spatie Laravel Medialibrary
TailwindUi
VueCroppa

Features:

  • Drag'n'drop image upload
  • Crop images
  • Form reset to initial images
  • Save to models Spatie Media library collection

May-15-2020 20-35-59

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment