refacto: tentative de refacto
This commit is contained in:
@@ -23,10 +23,6 @@ export function HomeContent({ data }: HomeContentProps) {
|
||||
await router.push(path);
|
||||
};
|
||||
|
||||
const handleSeriesClick = (seriesId: string) => {
|
||||
router.push(`/series/${seriesId}`);
|
||||
};
|
||||
|
||||
// Vérification des données pour le debug
|
||||
// console.log("HomeContent - Données reçues:", {
|
||||
// ongoingCount: data.ongoing?.length || 0,
|
||||
|
||||
@@ -86,18 +86,9 @@ function MediaCard({ item, onClick }: MediaCardProps) {
|
||||
? item.metadata.title
|
||||
: item.metadata.title || `Tome ${item.metadata.number}`;
|
||||
|
||||
const handleClick = () => {
|
||||
console.log("MediaCard - handleClick:", {
|
||||
itemType: isSeries ? "series" : "book",
|
||||
itemId: item.id,
|
||||
itemTitle: title,
|
||||
});
|
||||
onClick?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={handleClick}
|
||||
onClick={onClick}
|
||||
className="flex-shrink-0 w-[200px] relative flex flex-col rounded-lg border bg-card text-card-foreground shadow-sm hover:bg-accent hover:text-accent-foreground transition-colors overflow-hidden cursor-pointer"
|
||||
>
|
||||
{/* Image de couverture */}
|
||||
|
||||
@@ -6,8 +6,7 @@ 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, useRouter } from "next/navigation";
|
||||
import { authService } from "@/lib/services/auth.service";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { PreferencesProvider } from "@/contexts/PreferencesContext";
|
||||
|
||||
// Routes qui ne nécessitent pas d'authentification
|
||||
@@ -15,7 +14,6 @@ const publicRoutes = ["/login", "/register"];
|
||||
|
||||
export default function ClientLayout({ children }: { children: React.ReactNode }) {
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const handleCloseSidebar = () => {
|
||||
@@ -56,8 +54,8 @@ export default function ClientLayout({ children }: { children: React.ReactNode }
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker
|
||||
.register("/sw.js")
|
||||
.then((registration) => {
|
||||
console.log("Service Worker enregistré avec succès:", registration);
|
||||
.then(() => {
|
||||
// Succès silencieux
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Erreur lors de l'enregistrement du Service Worker:", error);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { BookOpen, Home, Library, Settings, LogOut, RefreshCw, Star } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { Home, Library, Settings, LogOut, RefreshCw, Star } from "lucide-react";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { authService } from "@/lib/services/auth.service";
|
||||
@@ -32,7 +31,9 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
||||
const data = await response.json();
|
||||
setLibraries(data);
|
||||
} catch (error) {
|
||||
console.error("Erreur:", error);
|
||||
if (error instanceof Error) {
|
||||
console.error("Erreur de chargement des bibliothèques:", error.message);
|
||||
}
|
||||
setLibraries([]);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -43,7 +44,6 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
||||
const fetchFavorites = useCallback(async () => {
|
||||
setIsLoadingFavorites(true);
|
||||
try {
|
||||
// Récupérer les IDs des favoris depuis l'API
|
||||
const favoritesResponse = await fetch("/api/komga/favorites");
|
||||
if (!favoritesResponse.ok) {
|
||||
throw new Error("Erreur lors de la récupération des favoris");
|
||||
@@ -55,7 +55,6 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Récupérer les détails des séries pour chaque ID
|
||||
const promises = favoriteIds.map(async (id: string) => {
|
||||
const response = await fetch(`/api/komga/series/${id}`);
|
||||
if (!response.ok) return null;
|
||||
@@ -65,7 +64,9 @@ export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
||||
const results = await Promise.all(promises);
|
||||
setFavorites(results.filter((series): series is KomgaSeries => series !== null));
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la récupération des favoris:", error);
|
||||
if (error instanceof Error) {
|
||||
console.error("Erreur de chargement des favoris:", error.message);
|
||||
}
|
||||
setFavorites([]);
|
||||
} finally {
|
||||
setIsLoadingFavorites(false);
|
||||
|
||||
@@ -6,9 +6,10 @@ import { useRouter, usePathname, useSearchParams } from "next/navigation";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Loader2, Filter } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { KomgaSeries } from "@/types/komga";
|
||||
|
||||
interface PaginatedSeriesGridProps {
|
||||
series: any[];
|
||||
series: KomgaSeries[];
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
totalElements: number;
|
||||
@@ -61,10 +62,6 @@ export function PaginatedSeriesGrid({
|
||||
await router.push(`${pathname}?${params.toString()}`);
|
||||
};
|
||||
|
||||
const handleSeriesClick = (seriesId: string) => {
|
||||
router.push(`/series/${seriesId}`);
|
||||
};
|
||||
|
||||
// Calcul des indices de début et de fin pour l'affichage
|
||||
const startIndex = (currentPage - 1) * pageSize + 1;
|
||||
const endIndex = Math.min(currentPage * pageSize, totalElements);
|
||||
|
||||
@@ -24,12 +24,9 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) {
|
||||
setIsLoading,
|
||||
secondPageLoading,
|
||||
setSecondPageLoading,
|
||||
imageError,
|
||||
setImageError,
|
||||
handlePreviousPage,
|
||||
handleNextPage,
|
||||
shouldShowDoublePage,
|
||||
syncReadProgress,
|
||||
} = usePageNavigation({
|
||||
book,
|
||||
pages,
|
||||
@@ -66,9 +63,12 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erreur lors du chargement des URLs:", error);
|
||||
setImageError(true);
|
||||
} finally {
|
||||
if (error instanceof Error) {
|
||||
console.error(
|
||||
`Erreur de chargement des URLs pour la page ${currentPage}:`,
|
||||
error.message
|
||||
);
|
||||
}
|
||||
// On s'assure que le chargement est terminé même en cas d'erreur
|
||||
if (isMounted) {
|
||||
setIsLoading(false);
|
||||
@@ -91,7 +91,6 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) {
|
||||
getPageUrl,
|
||||
setIsLoading,
|
||||
setSecondPageLoading,
|
||||
setImageError,
|
||||
]);
|
||||
|
||||
// Effet pour précharger la page courante et les pages adjacentes
|
||||
@@ -205,6 +204,13 @@ export function BookReader({ book, pages, onClose }: BookReaderProps) {
|
||||
{/* Pages */}
|
||||
<div className="relative flex-1 flex items-center justify-center overflow-hidden p-1">
|
||||
<div className="relative w-full h-[calc(100vh-2rem)] flex items-center justify-center gap-0">
|
||||
{/*
|
||||
Note: Nous utilisons intentionnellement des balises <img> natives au lieu de next/image pour :
|
||||
1. Avoir un contrôle précis sur le chargement et le préchargement des pages
|
||||
2. Gérer efficacement le mode double page et les transitions
|
||||
3. Les images sont déjà optimisées côté serveur
|
||||
4. La performance est critique pour une lecture fluide
|
||||
*/}
|
||||
{/* Page courante */}
|
||||
<div
|
||||
className={cn(
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
import { KomgaBook } from "@/types/komga";
|
||||
import { BookReader } from "./BookReader";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
|
||||
interface ClientBookWrapperProps {
|
||||
book: KomgaBook;
|
||||
|
||||
@@ -31,6 +31,10 @@ export const ControlButtons = ({
|
||||
"absolute top-4 left-1/2 -translate-x-1/2 z-30 flex items-center gap-2 transition-all duration-300",
|
||||
showControls ? "opacity-100" : "opacity-0 pointer-events-none"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onToggleControls();
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NavigationBarProps } from "../types";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Thumbnail } from "./Thumbnail";
|
||||
import { useThumbnails } from "../hooks/useThumbnails";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export const NavigationBar = ({
|
||||
currentPage,
|
||||
@@ -11,16 +11,13 @@ export const NavigationBar = ({
|
||||
showControls,
|
||||
book,
|
||||
}: NavigationBarProps) => {
|
||||
const {
|
||||
loadedThumbnails,
|
||||
handleThumbnailLoad,
|
||||
getThumbnailUrl,
|
||||
visibleThumbnails,
|
||||
scrollToActiveThumbnail,
|
||||
} = useThumbnails({
|
||||
book,
|
||||
currentPage,
|
||||
});
|
||||
const { loadedThumbnails, handleThumbnailLoad, getThumbnailUrl, visibleThumbnails } =
|
||||
useThumbnails({
|
||||
book,
|
||||
currentPage,
|
||||
});
|
||||
|
||||
const thumbnailsContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Scroll à l'ouverture des contrôles et au changement de page
|
||||
useEffect(() => {
|
||||
@@ -53,6 +50,7 @@ export const NavigationBar = ({
|
||||
onTouchStart={(e) => e.stopPropagation()}
|
||||
onTouchMove={(e) => e.stopPropagation()}
|
||||
onTouchEnd={(e) => e.stopPropagation()}
|
||||
ref={thumbnailsContainerRef}
|
||||
>
|
||||
<div className="w-[calc(50vw-18rem)] flex-shrink-0" />
|
||||
{pages.map((_, index) => {
|
||||
|
||||
@@ -6,15 +6,7 @@ import { forwardRef, useEffect, useState, useCallback, useRef } from "react";
|
||||
|
||||
export const Thumbnail = forwardRef<HTMLButtonElement, ThumbnailProps>(
|
||||
(
|
||||
{
|
||||
pageNumber,
|
||||
currentPage,
|
||||
onPageChange,
|
||||
getThumbnailUrl,
|
||||
loadedThumbnails,
|
||||
onThumbnailLoad,
|
||||
isVisible,
|
||||
},
|
||||
{ pageNumber, currentPage, onPageChange, getThumbnailUrl, loadedThumbnails, onThumbnailLoad },
|
||||
ref
|
||||
) => {
|
||||
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
||||
@@ -59,9 +51,6 @@ export const Thumbnail = forwardRef<HTMLButtonElement, ThumbnailProps>(
|
||||
if (loadAttempts.current < maxAttempts) {
|
||||
// Réessayer avec un délai croissant
|
||||
const delay = Math.min(1000 * Math.pow(2, loadAttempts.current - 1), 5000);
|
||||
console.log(
|
||||
`Réessai ${loadAttempts.current}/${maxAttempts} dans ${delay}ms pour la page ${pageNumber}`
|
||||
);
|
||||
setTimeout(() => {
|
||||
setImageUrl((prev) => (prev ? `${prev}?retry=${loadAttempts.current}` : null));
|
||||
}, delay);
|
||||
@@ -94,7 +83,7 @@ export const Thumbnail = forwardRef<HTMLButtonElement, ThumbnailProps>(
|
||||
src={imageUrl}
|
||||
alt={`Miniature page ${pageNumber}`}
|
||||
className={cn(
|
||||
"object-cover transition-opacity duration-300",
|
||||
"object-contain transition-opacity duration-300",
|
||||
isLoading ? "opacity-0" : "opacity-100"
|
||||
)}
|
||||
fill
|
||||
|
||||
@@ -12,12 +12,11 @@ export const usePageNavigation = ({
|
||||
book,
|
||||
pages,
|
||||
isDoublePage,
|
||||
onClose = () => {},
|
||||
onClose,
|
||||
}: UsePageNavigationProps) => {
|
||||
const [currentPage, setCurrentPage] = useState(book.readProgress?.page || 1);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [secondPageLoading, setSecondPageLoading] = useState(true);
|
||||
const [imageError, setImageError] = useState(false);
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const touchStartXRef = useRef<number | null>(null);
|
||||
const touchStartYRef = useRef<number | null>(null);
|
||||
@@ -39,7 +38,12 @@ export const usePageNavigation = ({
|
||||
body: JSON.stringify({ page, completed }),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la synchronisation de la progression:", error);
|
||||
if (error instanceof Error) {
|
||||
console.error(
|
||||
`Erreur de synchronisation de la progression pour le livre ${book.id} à la page ${page}:`,
|
||||
error.message
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
[book.id, pages.length]
|
||||
@@ -73,7 +77,6 @@ export const usePageNavigation = ({
|
||||
setCurrentPage(page);
|
||||
setIsLoading(true);
|
||||
setSecondPageLoading(true);
|
||||
setImageError(false);
|
||||
debouncedSyncReadProgress(page);
|
||||
},
|
||||
[debouncedSyncReadProgress]
|
||||
@@ -167,11 +170,8 @@ export const usePageNavigation = ({
|
||||
setIsLoading,
|
||||
secondPageLoading,
|
||||
setSecondPageLoading,
|
||||
imageError,
|
||||
setImageError,
|
||||
handlePreviousPage,
|
||||
handleNextPage,
|
||||
shouldShowDoublePage,
|
||||
syncReadProgress: debouncedSyncReadProgress,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -16,7 +16,8 @@ export const useThumbnails = ({ book, currentPage }: UseThumbnailsProps) => {
|
||||
|
||||
const getThumbnailUrl = useCallback(
|
||||
(pageNumber: number) => {
|
||||
return `/api/komga/images/books/${book.id}/pages/${pageNumber}/thumbnail`;
|
||||
const zeroBasedPage = pageNumber - 1;
|
||||
return `/api/komga/images/books/${book.id}/pages/${zeroBasedPage}/thumbnail?zero_based=true`;
|
||||
},
|
||||
[book.id]
|
||||
);
|
||||
|
||||
@@ -70,10 +70,6 @@ export function PaginatedBookGrid({
|
||||
const startIndex = (currentPage - 1) * pageSize + 1;
|
||||
const endIndex = Math.min(currentPage * pageSize, totalElements);
|
||||
|
||||
const getBookThumbnailUrl = (bookId: string) => {
|
||||
return `/api/komga/images/books/${bookId}/thumbnail`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="flex items-center justify-between flex-wrap gap-4">
|
||||
@@ -115,11 +111,7 @@ export function PaginatedBookGrid({
|
||||
isChangingPage ? "opacity-25" : "opacity-100"
|
||||
)}
|
||||
>
|
||||
<BookGrid
|
||||
books={books}
|
||||
onBookClick={handleBookClick}
|
||||
getBookThumbnailUrl={getBookThumbnailUrl}
|
||||
/>
|
||||
<BookGrid books={books} onBookClick={handleBookClick} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,15 +5,13 @@ import { KomgaSeries } from "@/types/komga";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Button } from "../ui/button";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Cover } from "@/components/ui/cover";
|
||||
|
||||
interface SeriesHeaderProps {
|
||||
series: KomgaSeries;
|
||||
onSeriesUpdate?: (series: KomgaSeries) => void;
|
||||
}
|
||||
|
||||
export const SeriesHeader = ({ series, onSeriesUpdate }: SeriesHeaderProps) => {
|
||||
export const SeriesHeader = ({ series }: SeriesHeaderProps) => {
|
||||
const { toast } = useToast();
|
||||
const [isFavorite, setIsFavorite] = useState(false);
|
||||
|
||||
|
||||
@@ -9,10 +9,6 @@ import { usePreferences } from "@/contexts/PreferencesContext";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
interface ErrorMessage {
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface KomgaConfig {
|
||||
url: string;
|
||||
username: string;
|
||||
@@ -256,7 +252,10 @@ export function ClientSettings({ initialConfig, initialTTLConfig }: ClientSettin
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Erreur",
|
||||
description: "Une erreur est survenue lors de la mise à jour des préférences",
|
||||
description:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Une erreur est survenue lors de la mise à jour des préférences",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface ImageLoaderProps {
|
||||
|
||||
@@ -12,7 +12,7 @@ type ToasterToast = ToastProps & {
|
||||
action?: ToastActionElement;
|
||||
};
|
||||
|
||||
const actionTypes = {
|
||||
const _actionTypes = {
|
||||
ADD_TOAST: "ADD_TOAST",
|
||||
UPDATE_TOAST: "UPDATE_TOAST",
|
||||
DISMISS_TOAST: "DISMISS_TOAST",
|
||||
@@ -26,7 +26,7 @@ function genId() {
|
||||
return count.toString();
|
||||
}
|
||||
|
||||
type ActionType = typeof actionTypes;
|
||||
type ActionType = typeof _actionTypes;
|
||||
|
||||
type Action =
|
||||
| {
|
||||
|
||||
Reference in New Issue
Block a user