Laravel 11

Albums – La galerie

Maintenant qu’on a créé l’essentiel de la gestion des images, qu’on a des utilisateurs et des catégories, on va enfin passer à la réalisation de la galerie elle-même pour visualiser les images sur la page d’accueil. On va aussi donner la possibilité de zoomer les images. On va prévoir une pagination, un affichage par catégorie…

Pour les besoins de visualisation des images, on va utiliser une light box. Il en existe de multiples et le choix n’est pas facile. Après analyse d’un certain nombre, je me suis finalement décidé pour SimpleLightbox. Elle est simple, efficace, élégante et légère, que demander de plus ?

Une nouvelle directive Blade

On va ajouter une directive Blade pour filtrer les administrateurs et les propriétaires des photos. Ça va bien nous simplifier la syntaxe dans notre vue. Ça se passe dans AppServiceProvider :

use Illuminate\Support\Facades\Blade;

class AppServiceProvider extends ServiceProvider
{
    ...

    public function boot(): void
    {
        Blade::if ('adminOrOwner', function ($id) {
            return auth()->check () && (auth()->id() === $id || auth()->user()->admin);
        });
    }

On pourra ainsi écrire @adminOrOwner dans les vues. Ça nous servira pour autoriser la modification ou la suppression des images.

Un repository et la route

Comme on aura pas mal de traitement au niveau de la table images dans la base de données, on va créer un repository pour tout ça :

On allègera ainsi le code du composant Volt. Il est important de répartir le code de façon logique et organisée pour améliorer la lisibilité, la maintenance et la réutilisation du code. pour l’affichage de la galerie, on va prévoir plusieurs cas :

  • toutes les images avec une pagination
  • seulement les images d’une catégorie avec une pagination
  • uniquement les images d’un utilisateur avec une pagination

Au niveau de l’URL, on aura :

  • …/all pour toutes les catégories
  • …/{category} pour une catégorie
  • …/{category(all ou le slug de la catégorie)}/id du user

On va donc démarrer avec ce code :

<?php

namespace App\Repositories;

use App\Models\Image;
use Illuminate\Support\Facades\Auth;
use Illuminate\Pagination\LengthAwarePaginator;

class ImageRepository
{
    public function getImagesPaginate(string $category, string $param): LengthAwarePaginator
    {
        $user = Auth::user();
        
        $query = Image::with('user')->latest();

        if (!$user || !$user->adult) {
            $query->whereAdult(false);
        }

        if ($param != '') {
            $query->whereUserId($param);
        }

        if ($category != 'all') {
            $query->whereHas('category', function ($query) use($category) {
                $query->whereSlug($category);
            });
        }

        return $query->paginate($user->pagination ?? config('app.pagination'));
    }
}

On voit que pour la pagination, on prend la valeur de l’utilisateur ou alors, s’il n’y en a pas de connecté, une valeur par défaut présente dans la configuration. On va donc ajouter cette valeur par défaut dans config/app.php :

'pagination' => 6,

On va aussi modifier la route pour tenir compte des nouveaux paramètres avec aussi une redirection vers toutes les catégories pour l’URL de base :

Route::get('/', function () {
    return redirect()->route('home', ['category' => 'all']);
});
Volt::route('/{category}/{param?}', 'index')->name('home');

Le composant Volt

On a déjà un composant pour la page d’accueil (index), on va le compléter :

<?php

use Livewire\Volt\Component;
use Illuminate\Pagination\LengthAwarePaginator;
use App\Models\Category;
use App\Repositories\ImageRepository;

new class extends Component {

    public string $category;
    public string $param;

    public function mount($category,  $param = ''): void
    {
        $this->category = $category;
        $this->param = $param;
    }

    public function images(): LengthAwarePaginator
    {     
        $imageRepository = new ImageRepository;

        $images = $imageRepository->getImagesPaginate($this->category, $this->param);

        return $images;
    }

    public function userImages(int $id): void
    {
        redirect()->route('home', ['category' => $this->category, 'param' => $id]); 
    }

    public function with(): array
    {
        return [
            'images' => $this->images(),
            'categories' => Category::all(),
        ];
    }

}; ?>

<div class="relative items-center grid w-full px-5 py-5 mx-auto md:px-12 max-w-7xl">

    <div class="mb-4">{{ $images->links() }}</div>
    <div class="grid w-full grid-cols-1 gap-6 mx-auto sm:grid-cols-2 lg:grid-cols-3 gallery">
        @foreach($images as $image)
            <x-card 
                title="" 
                subtitle="{!! $image->description !!}" shadow separator>
                <div class="flex justify-between">
                    <p wire:click="userImages({{ $image->user->id }})" class="text-left" style="cursor: pointer;">{{ $image->user->name }}</p>
                    <p class="text-right"><em>{{ $image->created_at->isoFormat('LL') }}</em></p>
                </div>
                <x-slot:figure>
                    <a href="{{ asset('storage/images/' . $image->name) }}">
                        <img src="{{ asset('storage/thumbs/' . $image->name) }}" />
                    </a>
                </x-slot:figure>
            </x-card>
        @endforeach
    </div>
</div>

La pagination

Comme on utilise les vues de pagination Tailwind par défaut de Laravel avec le moteur JIT Tailwind, on doit s’assurer que la clé de contenu du fichier tailwind.config.js fait référence aux vues de pagination Laravel afin que les classes Tailwind ne soient pas purgées.

Pour s’assurer que les classes Tailwind utilisées dans les vues de pagination Laravel ne sont pas supprimées lors de l’utilisation du moteur JIT Tailwind, on doit inclure ces vues dans la configuration de contenu de Tailwind. Dans notre fichier tailwind.config.js, on doit ajouter les chemins vers les vues de pagination Laravel dans la propriété content :

export default {
    content: [
        ...

        "./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php",
    ],

De cette façon, lorsque Tailwind JIT effectue le nettoyage des classes CSS non utilisées, les classes Tailwind présentes dans les vues de pagination Laravel seront conservées, ce qui garantit un affichage correct de la pagination lors de l’utilisation de ces vues avec Tailwind.

Cette étape est essentielle pour garantir la compatibilité entre les vues de pagination Laravel et Tailwind JIT, car si les classes Tailwind sont supprimées, cela pourrait entraîner une mauvaise mise en forme ou une absence de style dans la pagination.

Pensez à régénérer ensuite :

nm run build

L’aspect

MaryUI n’a pas de syntaxe spécifique pour rendre une page « responsive », alors j’ai utilisé les classes de Tailwind. Pour chaque image, j’utilise le composant Card de MaryUI, avec affichage de l’image, du nom du créateur et la date de création :

Quand on clique sur le nom du créateur, la page est rechargée avec seulement les images de ce créateur.

Le menu

Maintenant, on va synchroniser le menu avec les nouvelles possibilités dans le composant navigation. Je remets tout le code :

<?php

use Livewire\Volt\Component;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
use App\Models\Category;

new class extends Component 
{
    public function logout(): void
    {
        Auth::guard('web')->logout();

        Session::invalidate();
        Session::regenerateToken();

        $this->redirect('/');
    }

    public function with(): array
    {
        return [
            'categories' => Category::get(),
        ];
    }
}; ?>

<div>
    <x-menu activate-by-route>
        @if($user = auth()->user())
            <x-menu-separator />
                <x-list-item :item="$user" value="name" sub-value="email" no-separator no-hover class="-mx-2 !-my-2 rounded">
                    <x-slot:actions>
                        <x-button icon="o-power" wire:click="logout" class="btn-circle btn-ghost btn-xs" tooltip-left="{{__('Logout')}}" no-wire-navigate />
                    </x-slot:actions>
                </x-list-item>
            <x-menu-separator />
        @else
            <x-menu-item title="{{__('Login')}}" icon="o-user" link="{{ route('login') }}" />                   
        @endif

        <x-menu-sub title="{{__('Categories')}}" icon="o-book-open">
            <x-menu-item title="{{ __('All categories') }}" link="{{ route('home', ['category' => 'all']) }}" />
            @foreach($categories as $category)
                <x-menu-item title="{{ $category->name }}" link="{{ route('home', ['category' => $category->slug]) }}" />
            @endforeach
        </x-menu-sub>
        @auth  
            <x-menu-sub title="{{__('Images')}}" icon="o-photo">
                <x-menu-item title="{{__('Add image')}}" icon="o-plus" link="{{ route('images.create') }}" />
            </x-menu-sub>
        @endauth
    </x-menu>
</div>

 

Au passage, je me rends compte que dans les précédentes versions, je n’avais pas réservé la création d’image à un utilisateur connecté. C’est désormais réparé avec @auth.

On ajoute deux traductions :

"All categories": "Toutes les catégories",
"Categories": "Catégories"

Une lightbox

Voyons à présent l’affichage des images en grand format avec l’utilisation d’une lightbox. Il en existe beaucoup et le choix n’est pas facile. Mon choix s’est porté sur SiimpleLightbox.

J’ai juste rencontré quelques difficultés pour le charger avec Vite!, alors je me suis contenté d’utiliser un CDN, ce qui fonctionne aussi très bien. On ajoute le code dans notre layout :

     ...

    @vite(['resources/css/app.css', 'resources/js/app.js'])
    <script src=" https://cdn.jsdelivr.net/npm/simplelightbox@2.14.2/dist/simple-lightbox.min.js "></script>
    <link href=" https://cdn.jsdelivr.net/npm/simplelightbox@2.14.2/dist/simple-lightbox.min.css " rel="stylesheet">
</head>
<body class="min-h-screen font-sans antialiased bg-base-200/50 dark:bg-base-200">

    ...

    <script>
        // On vérifie si lightbox existe déjà
        if (typeof lightbox === 'undefined') {
            // S'il n'existe pas, on le crée
            const lightbox = new SimpleLightbox('.gallery a', {});
        } else {
            // S'il existe déjà, on met à jour ses propriétés
            lightbox.refresh();
        }
    </script>
</body>
</html>

Maintenant, vous devez pouvoir faire défiler les images présentes sur la page :

Conclusion

Dans cette étape, on a :

  • mis en place la galerie avec une page « responsive »
  • ajouté une lightbox pour bien visualiser les images
  • synchronisé le menu pour afficher les images par catégories
  • prévu un lien sur chaque image pour afficher les images d’un créateur

Pour vous simplifier la vie, vous pouvez charger le projet dans son état à l’issue de ce chapitre.

Print Friendly, PDF & Email

3 commentaires

Laisser un commentaire