From 55b130edadfea020361cef3fb86f1ea877d08982 Mon Sep 17 00:00:00 2001 From: fede Date: Thu, 27 Mar 2025 00:10:40 -0300 Subject: [PATCH] =?UTF-8?q?a=C3=B1adido=20crd=20para=20Song=20y=20translad?= =?UTF-8?q?ada=20la=20logica=20de=20/gallery=20a=20un=20controller?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/GalleryController.php | 14 ++ app/Http/Controllers/RootController.php | 7 +- app/Http/Controllers/SongController.php | 76 ++++++--- app/Models/Song.php | 2 +- app/Policies/SongPolicy.php | 10 +- resources/js/Components/ApplicationLogo.tsx | 19 +-- resources/js/Components/BotonAdd.tsx | 27 ++++ resources/js/Components/Icons/AddIcon.tsx | 11 ++ .../js/Components/Icons/AddStackIcon.tsx | 12 ++ resources/js/Components/Icons/PauseIcon.tsx | 15 ++ resources/js/Components/Icons/PlayIcon.tsx | 15 ++ .../js/Components/ModalAñadirCancion.tsx | 52 ++++++ .../js/Components/ModalRemoveCancion.tsx | 46 ++++++ resources/js/Components/Reproductor.tsx | 27 +--- resources/js/Layouts/AuthenticatedLayout.tsx | 18 +++ resources/js/Pages/Gallery.tsx | 17 +- resources/js/Pages/Songs/Index.tsx | 150 ++++++++++++++++-- resources/js/types/types.d.ts | 8 + routes/web.php | 18 +-- 19 files changed, 464 insertions(+), 80 deletions(-) create mode 100644 app/Http/Controllers/GalleryController.php create mode 100644 resources/js/Components/BotonAdd.tsx create mode 100644 resources/js/Components/Icons/AddIcon.tsx create mode 100644 resources/js/Components/Icons/AddStackIcon.tsx create mode 100644 resources/js/Components/Icons/PauseIcon.tsx create mode 100644 resources/js/Components/Icons/PlayIcon.tsx create mode 100644 resources/js/Components/ModalAñadirCancion.tsx create mode 100644 resources/js/Components/ModalRemoveCancion.tsx create mode 100644 resources/js/types/types.d.ts diff --git a/app/Http/Controllers/GalleryController.php b/app/Http/Controllers/GalleryController.php new file mode 100644 index 0000000..003c17a --- /dev/null +++ b/app/Http/Controllers/GalleryController.php @@ -0,0 +1,14 @@ + false, 'status' => session('status'), - ]); } + ]); + } } diff --git a/app/Http/Controllers/SongController.php b/app/Http/Controllers/SongController.php index 2a1f890..11e786c 100644 --- a/app/Http/Controllers/SongController.php +++ b/app/Http/Controllers/SongController.php @@ -2,44 +2,80 @@ namespace App\Http\Controllers; +use App\Models\User; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use App\Models\Song; -use App\Policies\SongPolicy; +use Illuminate\Support\Facades\Storage; use Inertia\Inertia; +use Inertia\Response; +use Illuminate\Support\Facades\Gate; +use Illuminate\Foundation\Auth\Access\AuthorizesRequests; class SongController extends Controller -{ - public function index() +{ use AuthorizesRequests; + public function index(Request $request): Response { - // render page - Inertia::render('Songs/Index', [ - 'songs' => Song::all() + return Inertia::render('Songs/Index', [ + 'songs' => Song::where('user_id', auth()->id())->paginate(10), ]); } - public function store(Request $request) - { - // Store a new song + public function store(Request $request): JsonResponse + { + Gate::authorize("create", Song::class); - } + $validated = $request->validate([ + 'song' => 'max:108826630', + ]); + $song = new Song(); + $song->user_id = auth()->id(); - public function update(Request $request, $id) + $file = $request->song; + $path = $file->store('songs'); + $song->title = $file->getClientOriginalName(); + $song->path = $path; + $song->artist = $file->getClientOriginalName(); + + $ret = $song->save(); + + return response()->json([ + 'data' => $ret ? "Guardado " . $song->title : 'sin archivo' + ]); + } + /** + * @return void + * @param mixed $id + */ + public function update(Request $request, $id):void { // Update specified song } - + /** + * @param int $id + */ public function destroy($id) { - // Delete specified song - $this->authorize('delete', Song::class); - $song = Song::find($id); - if ($song) { - $song->delete(); - } - } + $user = User::find(auth()->id()); - public function stream($id) + //Gate::authorize('delete', $user , $song); + $this->authorize('delete', $song); + + if ($song) { + // Delete the related file + if (Storage::exists($song->path)) { + Storage::delete($song->path); + } + Song::destroy($id); + } + return response()->noContent(200); + } + /** + * @return void + * @param mixed $id + */ + public function stream($id):void { // Stream specified song } diff --git a/app/Models/Song.php b/app/Models/Song.php index c01b9fd..ff65ff3 100644 --- a/app/Models/Song.php +++ b/app/Models/Song.php @@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; class Song extends Model { // - protected $fillable = ["title", 'artist', 'cover', 'user_id']; + protected $fillable = ["title", 'artist', 'cover', 'user_id', 'path']; /** * @return BelongsTo */ diff --git a/app/Policies/SongPolicy.php b/app/Policies/SongPolicy.php index f8f7224..af43088 100644 --- a/app/Policies/SongPolicy.php +++ b/app/Policies/SongPolicy.php @@ -26,10 +26,10 @@ class SongPolicy /** * Determine whether the user can create models. */ - public function create(User $user): bool - { - return true; - } + public function create(): bool + { + return true; + } /** * Determine whether the user can update the model. @@ -44,7 +44,7 @@ class SongPolicy */ public function delete(User $user, Song $song): bool { - return $song->user_id === $user->id; + return $song->user_id == $user->id; } /** diff --git a/resources/js/Components/ApplicationLogo.tsx b/resources/js/Components/ApplicationLogo.tsx index ccd9285..908271b 100644 --- a/resources/js/Components/ApplicationLogo.tsx +++ b/resources/js/Components/ApplicationLogo.tsx @@ -1,13 +1,14 @@ import { SVGAttributes } from 'react'; export default function ApplicationLogo(props: SVGAttributes) { - return ( - - - - ); + return ( + + + + ); } diff --git a/resources/js/Components/BotonAdd.tsx b/resources/js/Components/BotonAdd.tsx new file mode 100644 index 0000000..6dd44b1 --- /dev/null +++ b/resources/js/Components/BotonAdd.tsx @@ -0,0 +1,27 @@ +export default function BotonAdd({ + show, + setshow, +}: { + show: boolean; + setshow: (arg0: boolean) => void; +}) { + if (!show) return null; + + return ( + + ); +} diff --git a/resources/js/Components/Icons/AddIcon.tsx b/resources/js/Components/Icons/AddIcon.tsx new file mode 100644 index 0000000..301f4b3 --- /dev/null +++ b/resources/js/Components/Icons/AddIcon.tsx @@ -0,0 +1,11 @@ +export default function AddIcon() { + return ( + + + + ); +} diff --git a/resources/js/Components/Icons/AddStackIcon.tsx b/resources/js/Components/Icons/AddStackIcon.tsx new file mode 100644 index 0000000..73be81c --- /dev/null +++ b/resources/js/Components/Icons/AddStackIcon.tsx @@ -0,0 +1,12 @@ +export default function AddStackIcon() { + return ( + + + + ); +} diff --git a/resources/js/Components/Icons/PauseIcon.tsx b/resources/js/Components/Icons/PauseIcon.tsx new file mode 100644 index 0000000..e55c934 --- /dev/null +++ b/resources/js/Components/Icons/PauseIcon.tsx @@ -0,0 +1,15 @@ +const PauseIcon = () => { + return ( + + + + ); +}; + +export default PauseIcon; diff --git a/resources/js/Components/Icons/PlayIcon.tsx b/resources/js/Components/Icons/PlayIcon.tsx new file mode 100644 index 0000000..d861c3f --- /dev/null +++ b/resources/js/Components/Icons/PlayIcon.tsx @@ -0,0 +1,15 @@ +const PlayIcon = () => { + return ( + + + + ); +}; + +export default PlayIcon; diff --git a/resources/js/Components/ModalAñadirCancion.tsx b/resources/js/Components/ModalAñadirCancion.tsx new file mode 100644 index 0000000..724725f --- /dev/null +++ b/resources/js/Components/ModalAñadirCancion.tsx @@ -0,0 +1,52 @@ +import { Input } from '@headlessui/react'; +import { FormEvent, useState } from 'react'; +import InputLabel from './InputLabel'; +import Modal from './Modal'; +import PrimaryButton from './PrimaryButton'; + +export default function ModalAñadirCancion({ + showaddmodal, + addcancion, + setShowaddmodal, +}: { + showaddmodal: boolean; + addcancion: (event: FormEvent) => void; + setShowaddmodal: (arg0: boolean) => void; +}) { + const [selectedFile, setSelectedFile] = useState(null); + + return ( + setShowaddmodal(false)} + closeable={true} + maxWidth="md" + > +
+

+ Añadiendo Cancion +

+
+
+ Cancion a añadir. máximo 50MB + setSelectedFile(e.target.files[0])} + name="song" + className="mt-1 block w-full rounded-md border-gray-300 shadow-sm file:mr-4 file:rounded-md file:border-0 file:bg-indigo-100 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-indigo-700 hover:file:bg-indigo-100 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300" + /> +
+
+ + Subir + +
+
+
+
+ ); +} diff --git a/resources/js/Components/ModalRemoveCancion.tsx b/resources/js/Components/ModalRemoveCancion.tsx new file mode 100644 index 0000000..269eb6a --- /dev/null +++ b/resources/js/Components/ModalRemoveCancion.tsx @@ -0,0 +1,46 @@ +import { Cancion } from '@/types/types'; +import { FormEvent, useState } from 'react'; +import Checkbox from './Checkbox'; +import InputLabel from './InputLabel'; +import Modal from './Modal'; +import PrimaryButton from './PrimaryButton'; + +export default function ModalRemoveCancion({ + show, + cancion, + remove, + close, +}: { + show: boolean; + cancion: Cancion; + remove: (e: FormEvent) => void; + close: (arg0: boolean) => void; +}) { + const [checked, setcheked] = useState(false); + + return ( + close(false)} + closeable={true} + maxWidth="md" + > +
+

+ Eliminando Canción +

+
+
+ setcheked(!checked)} /> + Cancion a eliminar: {cancion.title} +
+
+ + Aceptar + +
+
+
+
+ ); +} diff --git a/resources/js/Components/Reproductor.tsx b/resources/js/Components/Reproductor.tsx index 2fef514..e6652f7 100644 --- a/resources/js/Components/Reproductor.tsx +++ b/resources/js/Components/Reproductor.tsx @@ -1,6 +1,8 @@ import { useEffect, useState } from 'react'; +import PauseIcon from './Icons/PauseIcon'; +import PlayIcon from './Icons/PlayIcon'; -export default function Reproductor() { +export default function Reproductor({ titulo }) { const [isPlaying, setIsPlaying] = useState(false); const [progress, setProgress] = useState(0); const [timeLeft, setTimeLeft] = useState('0:00'); @@ -34,28 +36,9 @@ export default function Reproductor() { className="mx-4 text-white" onClick={() => setIsPlaying(!isPlaying)} > - {isPlaying ? ( - - - - - ) : ( - - - - )} + {!isPlaying ? : } - + {titulo} {timeLeft} diff --git a/resources/js/Layouts/AuthenticatedLayout.tsx b/resources/js/Layouts/AuthenticatedLayout.tsx index d09c716..1cca2c0 100644 --- a/resources/js/Layouts/AuthenticatedLayout.tsx +++ b/resources/js/Layouts/AuthenticatedLayout.tsx @@ -40,6 +40,12 @@ export default function Authenticated({ > Gallery + + Canciones + @@ -137,6 +143,18 @@ export default function Authenticated({ > Dashboard + + Gallery + + + Canciones +
diff --git a/resources/js/Pages/Gallery.tsx b/resources/js/Pages/Gallery.tsx index 184d60b..561a5f2 100644 --- a/resources/js/Pages/Gallery.tsx +++ b/resources/js/Pages/Gallery.tsx @@ -1,4 +1,8 @@ +import AddIcon from '@/Components/Icons/AddIcon'; +import AddStackIcon from '@/Components/Icons/AddStackIcon'; +import PrimaryButton from '@/Components/PrimaryButton'; import Reproductor from '@/Components/Reproductor'; +import SecondaryButton from '@/Components/SecondaryButton'; import Authenticated from '@/Layouts/AuthenticatedLayout'; import { useState } from 'react'; @@ -8,7 +12,8 @@ export default function Gallery() { id: 1, title: 'Song 1', artist: 'Artist 1', - coverArt: '/images/song1.jpg', + coverArt: + 'https://duckduckgo.com/?q=Metallica%20American%20heavy%20metal%20band&ia=images&iax=images', }, { id: 2, @@ -26,7 +31,7 @@ export default function Gallery() { const [progress, setProgress] = useState(0); const [isPlaying, setIsPlaying] = useState(false); - + const [cancionSeleccionada, setCancion] = useState({ title: '' }); return ( {song.artist}

+
+ + + + + + +
))} diff --git a/resources/js/Pages/Songs/Index.tsx b/resources/js/Pages/Songs/Index.tsx index 6a89825..71648ab 100644 --- a/resources/js/Pages/Songs/Index.tsx +++ b/resources/js/Pages/Songs/Index.tsx @@ -1,13 +1,143 @@ -export default function Index({ songs }) { - console.log('Songs/Index.jsx se está ejecutando'); +import BotonAdd from '@/Components/BotonAdd'; +import ModalAñadirCancion from '@/Components/ModalAñadirCancion'; +import ModalRemoveCancion from '@/Components/ModalRemoveCancion'; +import Authenticated from '@/Layouts/AuthenticatedLayout'; +import { Cancion } from '@/types/types'; +import { Head } from '@inertiajs/react'; +import axios from 'axios'; +import { FormEvent, useEffect, useState } from 'react'; + +export default function Index({ songs }: { songs: Cancion[] }) { + const [showaddmodal, setshowmodal] = useState(false); + const [showRemove, setRemove] = useState(false); + const [selcan, setcan] = useState({} as Cancion); + + useEffect(() => { + const handleKeyPress = (event: KeyboardEvent) => { + if (event.ctrlKey && event.key === 'k') { + event.preventDefault(); + setshowmodal(true); + } + }; + + window.addEventListener('keydown', handleKeyPress); + + return () => { + window.removeEventListener('keydown', handleKeyPress); + }; + }, []); + + function addcancion(event: FormEvent): void { + event.preventDefault(); + + const formData = new FormData(event.currentTarget); + + axios + .post('/canciones', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + .then((response: { data: string }) => { + console.log('Song added successfully', response.data); + window.location.reload(); + }) + .catch(() => { + setshowmodal(false); + }); + } + + function handleRemove(e: FormEvent) { + e.preventDefault(); + axios + .delete(`/canciones/${selcan.id}`) + .then(() => { + setcan({} as Cancion); + setRemove(false); + window.location.reload(); + }) + .catch((error) => { + console.error('Error removing song:', error); + setRemove(false); + }); + } + return ( -
-

Mis Canciones

-
    - {songs.map((song) => ( -
  • {song.title}
  • - ))} -
-
+ + Crud Canciones + + } + > + +
+ {songs.data.length === 0 ? ( +

+ No hay canciones cargadas para tu usuario. Quiere Añadir una? Aprete{' '} +
+ Ctrl +{' '} + k +

+ ) : ( +
+ + + + + + + + + + + {songs.data.map((song: Cancion) => ( + + + + + + + ))} + +
NombreArtistapathAcciones
+ {song.title} + + {song.artist} + + {song.path} + + + +
+
+ )} + + + +
+
); } diff --git a/resources/js/types/types.d.ts b/resources/js/types/types.d.ts new file mode 100644 index 0000000..3eb1f04 --- /dev/null +++ b/resources/js/types/types.d.ts @@ -0,0 +1,8 @@ +export type Cancion = { + name: ReactNode; + id: number; + title: string; + artist: string | null; + path: string; + cover: string | null; +}; diff --git a/routes/web.php b/routes/web.php index c7a02e1..bdde713 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,8 +1,8 @@ group(function () { }); -Route::get('/gallery', function () { - return Inertia::render('Gallery'); -})->middleware(['auth', 'verified'])->name('gallery'); +Route::get('/gallery', [GalleryController::class, "index"]) + ->middleware(['auth', 'verified']) + ->name('gallery'); // Api canciones // -Route::get('/songs', [SongController::class, 'index']); -Route::get('/songs/stream/{id}', [SongController::class, 'stream']); -Route::post('/songs', [SongController::class, 'store']); -Route::put('/songs/{id}', [SongController::class, 'update']); -Route::delete('/songs/{id}', [SongController::class, 'destroy']); +Route::get('/canciones', [SongController::class, 'index'])->name('canciones'); +Route::get('/canciones/stream/{id}', [SongController::class, 'stream']); +Route::post('/canciones', [SongController::class, 'store']); +Route::put('/canciones/{id}', [SongController::class, 'update']); +Route::delete('/canciones/{id}', [SongController::class, 'destroy'])->middleware(['auth', 'verified']); require __DIR__.'/auth.php';