import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { LucideAngularModule, X, Image as ImageIcon, ChevronLeft, ChevronRight } from 'lucide-angular';
import { TranslatePipe } from '@ngx-translate/core';
import { ImageService } from '../../services/image.service';
import { Image } from '../../services/image.model';
import { ImageLayout } from '../../services/template.model';
import { ImageUploaderComponent } from '../image-uploader/image-uploader.component';
/**
* Composant reutilisable de galerie d'images.
*
* Deux modes :
* - editable=false (defaut) : affichage en grille, clic sur une image ouvre
* un lightbox plein ecran pour zoomer.
* - editable=true : bouton "+ ajouter" en fin de grille (via app-image-uploader),
* chaque vignette a un bouton "X" pour la retirer.
*
* La galerie raisonne sur une liste d'IDs d'images (string[]). Elle ne stocke
* pas les objets Image eux-memes : les thumbs utilisent `imageService.contentUrl(id)`
* directement comme src. Le navigateur cache les binaires via Cache-Control immutable
* pose par le backend, donc aucune requete redondante.
*
* Usage :
*
*
*/
@Component({
selector: 'app-image-gallery',
imports: [LucideAngularModule, ImageUploaderComponent, TranslatePipe],
templateUrl: './image-gallery.component.html',
styleUrls: ['./image-gallery.component.scss']
})
export class ImageGalleryComponent {
readonly X = X;
readonly ImageIcon = ImageIcon;
readonly ChevronLeft = ChevronLeft;
readonly ChevronRight = ChevronRight;
/** IDs d'images a afficher. */
@Input() imageIds: string[] = [];
/** Mode edition : afficher le bouton d'ajout + les boutons de suppression. */
@Input() editable = false;
/**
* Variante de mise en page. Null/undefined = GALLERY (rendu historique).
* HERO : premiere image en banniere pleine largeur, suivantes en petit dessous.
* MASONRY : mosaique a hauteurs variables.
* CAROUSEL : defilement horizontal avec fleches.
*/
@Input() layout: ImageLayout | null | undefined = 'GALLERY';
/** Emet la nouvelle liste quand l'utilisateur ajoute/retire une image. */
@Output() imageIdsChange = new EventEmitter();
/** ID de l'image actuellement ouverte en lightbox (null = ferme). */
lightboxId: string | null = null;
@ViewChild('carouselTrack') carouselTrack?: ElementRef;
constructor(private imageService: ImageService) {}
/** Layout effectif (null → GALLERY). */
get effectiveLayout(): ImageLayout {
return this.layout ?? 'GALLERY';
}
/** Premiere image (pour le layout HERO). */
get heroId(): string | null {
return this.imageIds[0] ?? null;
}
/** Images restantes apres la hero (pour le layout HERO). */
get restIds(): string[] {
return this.imageIds.slice(1);
}
scrollCarousel(direction: -1 | 1): void {
const el = this.carouselTrack?.nativeElement;
if (!el) return;
el.scrollBy({ left: direction * Math.max(240, el.clientWidth * 0.8), behavior: 'smooth' });
}
/** URL absolue du binaire d'une image. */
urlFor(id: string): string {
return this.imageService.contentUrl(id);
}
onUploaded(image: Image): void {
this.imageIdsChange.emit([...this.imageIds, image.id]);
}
remove(id: string, event: MouseEvent): void {
event.stopPropagation(); // Evite d'ouvrir le lightbox en cliquant sur X.
// On supprime aussi cote serveur pour ne pas laisser d'image orpheline.
// Best-effort : on n'attend pas le retour pour emettre la nouvelle liste.
this.imageService.delete(id).subscribe({ error: () => { /* best-effort : erreur ignorée volontairement */ } });
this.imageIdsChange.emit(this.imageIds.filter(i => i !== id));
}
openLightbox(id: string): void {
this.lightboxId = id;
}
closeLightbox(): void {
this.lightboxId = null;
}
}