Files
stripstream/src/components/layout/ClientLayout.tsx

188 lines
6.2 KiB
TypeScript

"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<string | null>(null);
const pathname = usePathname();
const { preferences } = usePreferences();
const prevLibraryIdsRef = useRef<string>("");
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 (
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<ServiceWorkerProvider>
{/* Background fixe pour les images et gradients */}
{hasCustomBackground && <div className="fixed inset-0 -z-10" style={backgroundStyle} />}
<div
className={`relative min-h-screen ${hasCustomBackground ? "" : "bg-background"}`}
style={
hasCustomBackground
? { backgroundColor: `rgba(var(--background-rgb, 255, 255, 255), ${contentOpacity})` }
: undefined
}
>
{!isPublicRoute && (
<Header
onToggleSidebar={handleToggleSidebar}
onRefreshBackground={fetchRandomBook}
showRefreshBackground={preferences.background.type === "komga-random"}
/>
)}
{!isPublicRoute && (
<Sidebar
isOpen={isSidebarOpen}
onClose={handleCloseSidebar}
initialLibraries={initialLibraries}
initialFavorites={initialFavorites}
userIsAdmin={userIsAdmin}
/>
)}
<main className={!isPublicRoute ? "pt-safe" : ""}>{children}</main>
<InstallPWA />
<Toaster />
<NetworkStatus />
</div>
</ServiceWorkerProvider>
</ThemeProvider>
);
}