"use client"; import { ThemeProvider } from "next-themes"; import { useState, useEffect, useMemo, useCallback, useRef } from "react"; import { Header } from "@/components/layout/Header"; import { Sidebar } from "@/components/layout/Sidebar"; import { InstallPWA } from "../ui/InstallPWA"; import { Toaster } from "@/components/ui/toaster"; import { usePathname } from "next/navigation"; import { NetworkStatus } from "../ui/NetworkStatus"; import { usePreferences } from "@/contexts/PreferencesContext"; import { ServiceWorkerProvider } from "@/contexts/ServiceWorkerContext"; import type { KomgaLibrary, KomgaSeries } from "@/types/komga"; import logger from "@/lib/logger"; import { getRandomBookFromLibraries } from "@/app/actions/library"; // Routes qui ne nécessitent pas d'authentification const publicRoutes = ["/login", "/register"]; interface ClientLayoutProps { children: React.ReactNode; initialLibraries: KomgaLibrary[]; initialFavorites: KomgaSeries[]; userIsAdmin?: boolean; } export default function ClientLayout({ children, initialLibraries = [], initialFavorites = [], userIsAdmin = false, }: ClientLayoutProps) { const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [randomBookId, setRandomBookId] = useState(null); const pathname = usePathname(); const { preferences } = usePreferences(); const prevLibraryIdsRef = useRef(""); const backgroundType = preferences.background.type; const komgaLibraries = preferences.background.komgaLibraries; // Stabiliser libraryIds - ne change que si le contenu change vraiment const libraryIdsString = useMemo(() => { const newIds = komgaLibraries?.join(",") || ""; if (newIds !== prevLibraryIdsRef.current) { prevLibraryIdsRef.current = newIds; } return prevLibraryIdsRef.current; }, [komgaLibraries]); // Récupérer un book aléatoire pour le background const fetchRandomBook = useCallback(async () => { if (backgroundType === "komga-random" && libraryIdsString) { try { const libraryIds = libraryIdsString.split(",").filter(Boolean); const result = await getRandomBookFromLibraries(libraryIds); if (result.success && result.bookId) { setRandomBookId(result.bookId); } } catch (error) { logger.error({ err: error }, "Erreur lors de la récupération d'un book aléatoire:"); } } }, [backgroundType, libraryIdsString]); useEffect(() => { if (backgroundType === "komga-random" && libraryIdsString) { fetchRandomBook(); } }, [backgroundType, libraryIdsString, fetchRandomBook]); const backgroundStyle = useMemo(() => { const bg = preferences.background; const blur = bg.blur || 0; if (bg.type === "gradient" && bg.gradient) { return { backgroundImage: bg.gradient, filter: blur > 0 ? `blur(${blur}px)` : undefined, }; } if (bg.type === "image" && bg.imageUrl) { return { backgroundImage: `url(${bg.imageUrl})`, backgroundSize: "cover" as const, backgroundPosition: "center" as const, backgroundRepeat: "no-repeat" as const, filter: blur > 0 ? `blur(${blur}px)` : undefined, }; } if (bg.type === "komga-random" && randomBookId) { return { backgroundImage: `url(/api/komga/images/books/${randomBookId}/thumbnail)`, backgroundSize: "cover" as const, backgroundPosition: "top center" as const, backgroundRepeat: "no-repeat" as const, filter: blur > 0 ? `blur(${blur}px)` : undefined, }; } return {}; }, [preferences.background, randomBookId]); const handleCloseSidebar = () => { setIsSidebarOpen(false); }; const handleToggleSidebar = () => { setIsSidebarOpen(!isSidebarOpen); }; // Gestionnaire pour fermer la barre latérale lors d'un clic en dehors useEffect(() => { const handleClickOutside = (event: MouseEvent) => { const sidebar = document.getElementById("sidebar"); const toggleButton = document.getElementById("sidebar-toggle"); if ( sidebar && !sidebar.contains(event.target as Node) && toggleButton && !toggleButton.contains(event.target as Node) ) { handleCloseSidebar(); } }; if (isSidebarOpen) { document.addEventListener("mousedown", handleClickOutside); } return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [isSidebarOpen]); // Ne pas afficher le header et la sidebar sur les routes publiques et le reader const isPublicRoute = publicRoutes.includes(pathname) || pathname.startsWith("/books/"); const hasCustomBackground = preferences.background.type === "gradient" || preferences.background.type === "image" || (preferences.background.type === "komga-random" && randomBookId); const contentOpacity = (preferences.background.opacity || 100) / 100; return ( {/* Background fixe pour les images et gradients */} {hasCustomBackground &&
}
{!isPublicRoute && (
)} {!isPublicRoute && ( )}
{children}
); }