Para dar la opción al usuario de poder subir fotos a la aplicación Web he utilizado el componente vue-media-upload de Saimow.
Instalo mediante npm
npm install vue-media-upload
El componente funciona subiendo las imágenes con Axios a una carpeta temporal cada vez que añadimos una. Luego si queremos guardar las imágenes hay que pasarlas a la carpeta final.
Definimos la ruta para subir las carpetas a la ruta temporal:
Route::post('/media/upload', [MediaController::class, 'uploadMedia'])-> name('media.upload')->;
En la página Vue:
import Uploader from 'vue-media-upload';
// los datos de las imágenes obtenidas las guardaré a través del componente useForm de Inertia
const form = useForm({
_method: 'POST',
...
media: {},
});
// Definimos los eventos necesarios del Uploader para actualizar los datos de las imágenes añadidas o eliminadas
const addMedia = (addedImage, addedMedia) => {
form.media.added = addedMedia
}
const removeMedia = (removedImage, removedMedia) => {
form.media.removed = removedMedia
}
// Al montar el componente Vue vamos a obtener las imagenes
onMounted(() => {
getMedia();
});
const getMedia = () => {
axios.get(route('ruta.get.media', form.id))
.then(response => {
form.media = {saved: [], added:[], removed:[]};
form.media.saved = response.data.media;
form.defaults('media', form.media); // Para quitar el aviso de que hay cambios pendientes de grabar.
hasMediaResponse.value = true; // Aquí es donde vamos a dar la señal para generar el componente Uploader
})
}
...
<Uploader
v-if="hasMediaResponse"
:server="route('media.upload')"
:media="form.media.saved"
location="/media"
@add="addMedia"
@remove="removeMedia"
/>
Es importante el v-if para que solo se genere el componente cuando ya ha obtenido las imágenes del servidor, sino, no se actualizará.
A continuación vamos a completar el controlador encargado de subir y grabar las fotos:
public function getMedia(Request $request, int $work_id)
{
$media = Media::
select('type', 'file_name as name')->
whereModelId($work_id)->get();
return response()->json(['media' => $media]);
}
// Se llama cuando al componente se le asigna una imagen, la sube a una carpeta temporal
public function uploadMedia(Request $request)
{
if ($request->hasFile('image'))
{
// Guardo en carpeta temporal
$file = $request->file('image');
$name = uniqid() . '_' . $file->getClientOriginalName();
$file->storeAs('tmp', $name);
return ['name' => $name];
}
}
// Grabar el modelo con las imágenes
public function update(Request $request)
{
...
$model->save();
$this->saveTemporaryFilesToMedia($model->id, $request->media['added']);
$this->removeFilesFromMedia($model->id, $request->media['removed']);
return redirect(route('...'));
}
// Guarda las imágenes temporales que finalmente se hayan grabado
private function saveTemporaryFilesToMedia(int $model_id, array $media)
{
foreach($media as $image){
$from = storage_path('app/tmp/' . $image['name']);
$to = storage_path('app/media/' . $image['name']);
File::move($from, $to);
$media = Media::create([
'model_name' => Work::class,
'model_id' => $model_id,
'type' => '',
'file_name' => $image['name'],
]);
}
}
// Elimina las imagenes guardadas que se han borrado (no las temporales)
private function removeFilesFromMedia(int $model_id, array $media)
{
foreach($media as $image) {
File::delete('app/media/' . $image['name']);
Media::
whereModelName(Work::class)->
whereModelId($model_id)->
whereFileName($image['name'])->
delete();
}
}
Las imágenes las guardo en la carpeta storage la cual no es pública, por lo que debo crear una función en el controlador para obtenerlas. Primero defino la ruta:
Route::get('/media/{file_name}', [MediaController::class, 'getFile'])->name('media.get');
Y su definición en el controlador:
public function getFile(Request $request, string $fileName)
{
return response()->file(storage_path('app/media/') . $fileName);
}
Por último nos faltaría generar un cron para que todas las noches se borraran todas las imágenes de la carpeta temporal, eso para más adelante.