Files
Mangarr/assets/react/app/presentation/pages/ReaderPage.jsx
ext.jeremy.guillot@maxicoffee.domains 140cc14316 feat: SPA pour les pages existantes
2025-02-17 14:50:36 +01:00

239 lines
7.6 KiB
JavaScript

import React, { useEffect, useState, useCallback } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { useReader } from '../context/ReaderContext';
import { Toolbar } from '../components/Toolbar/Toolbar.jsx';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
faArrowLeft,
faList,
faExpand,
faCompress,
faBookOpen,
faScroll
} from '@fortawesome/free-solid-svg-icons';
export function ReaderPage() {
const { chapterId } = useParams();
const navigate = useNavigate();
const {
context,
currentPage,
pages,
loading,
error,
mode,
loadChapterContext,
loadPage,
loadPages,
setCurrentPage,
setMode
} = useReader();
const [currentPageData, setCurrentPageData] = useState(null);
const [isFullscreen, setIsFullscreen] = useState(false);
useEffect(() => {
const initializeChapter = async () => {
const contextLoaded = await loadChapterContext(chapterId);
if (contextLoaded) {
await loadPages(chapterId);
}
};
initializeChapter();
}, [chapterId, loadChapterContext, loadPages]);
useEffect(() => {
if (mode === 'classic' && currentPage > 0 && pages.length > 0) {
loadPage(chapterId, currentPage).then(setCurrentPageData);
}
}, [chapterId, currentPage, loadPage, mode, pages.length]);
const handleKeyDown = useCallback((e) => {
if (mode === 'classic') {
if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
} else if (context?.navigation.previous) {
navigate(`/reader/${context.navigation.previous.id}`);
}
} else if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
if (currentPage < pages.length) {
setCurrentPage(currentPage + 1);
} else if (context?.navigation.next) {
navigate(`/reader/${context.navigation.next.id}`);
}
}
}
}, [currentPage, context, mode, navigate, pages.length, setCurrentPage]);
useEffect(() => {
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [handleKeyDown]);
const toggleFullscreen = () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
setIsFullscreen(true);
} else {
document.exitFullscreen();
setIsFullscreen(false);
}
};
const handleImageClick = (e) => {
if (mode !== 'classic') return;
const rect = e.target.getBoundingClientRect();
const x = e.clientX - rect.left;
const width = rect.width;
if (x < width / 2) {
// Clic sur la partie gauche
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
} else if (context?.navigation.previous) {
navigate(`/reader/${context.navigation.previous.id}`);
}
} else {
// Clic sur la partie droite
if (currentPage < pages.length) {
setCurrentPage(currentPage + 1);
} else if (context?.navigation.next) {
navigate(`/reader/${context.navigation.next.id}`);
}
}
};
const toolbarConfig = {
leftSection: [
{ icon: faArrowLeft, navigateBack: true }
],
rightSection: [
{
icon: mode === 'classic' ? faScroll : faBookOpen,
onClick: () => setMode(mode === 'classic' ? 'scrolling' : 'classic'),
label: `Mode ${mode === 'classic' ? 'défilement' : 'page par page'}`
},
{
icon: isFullscreen ? faCompress : faExpand,
onClick: toggleFullscreen,
label: isFullscreen ? 'Quitter le plein écran' : 'Plein écran'
},
{ icon: faList, onClick: () => {}, label: 'Chapitres' }
]
};
if (loading || (!currentPageData && mode === 'classic' && pages.length === 0)) {
return (
<div className="flex justify-center items-center h-screen bg-gray-900 text-white">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-white mx-auto mb-4"></div>
<div>Chargement du chapitre...</div>
</div>
</div>
);
}
if (error) {
return (
<div className="flex flex-col justify-center items-center h-screen bg-gray-900 text-white">
<div className="text-red-500 text-xl mb-4">{error}</div>
<button
onClick={() => navigate(-1)}
className="px-4 py-2 bg-gray-800 hover:bg-gray-700 rounded-lg transition-colors"
>
Retour
</button>
</div>
);
}
return (
<div className="min-h-screen bg-gray-900 text-white">
{/* Toolbar */}
<div className="fixed top-0 left-0 right-0 bg-gray-800 z-50">
<div className="container mx-auto px-4">
<div className="h-16 flex items-center justify-between">
<Toolbar {...toolbarConfig} />
{context && (
<div className="text-center flex-1">
<span className="font-medium">Chapitre {context.number}</span>
</div>
)}
</div>
</div>
</div>
{/* Reader content */}
<div className="pt-16">
{mode === 'classic' ? (
// Mode classique
<div className="relative max-w-5xl mx-auto">
{currentPageData && (
<img
src={`data:${currentPageData.mimeType};base64,${currentPageData.base64Content}`}
alt={`Page ${currentPageData.pageNumber}`}
className="w-full h-auto cursor-pointer"
onClick={handleImageClick}
/>
)}
<div className="fixed bottom-4 left-1/2 transform -translate-x-1/2 bg-gray-800 px-4 py-2 rounded-lg">
Page {currentPage} of {pages.length}
</div>
</div>
) : (
// Mode scrolling
<div className="max-w-5xl mx-auto space-y-4 p-4">
{pages.map((page, index) => (
<div key={index} className="relative">
{page.base64Content ? (
<img
src={`data:${page.mimeType};base64,${page.base64Content}`}
alt={`Page ${page.pageNumber}`}
className="w-full h-auto"
loading="lazy"
/>
) : (
<div className="w-full aspect-[2/3] bg-gray-800 animate-pulse flex items-center justify-center">
<span className="text-gray-400">Chargement...</span>
</div>
)}
</div>
))}
</div>
)}
</div>
{/* Navigation buttons for classic mode */}
{mode === 'classic' && (
<>
<button
onClick={() => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
} else if (context?.navigation.previous) {
navigate(`/reader/${context.navigation.previous.id}`);
}
}}
className="fixed left-4 top-1/2 transform -translate-y-1/2 bg-gray-800 p-4 rounded-full opacity-50 hover:opacity-100 transition-opacity"
>
</button>
<button
onClick={() => {
if (currentPage < pages.length) {
setCurrentPage(currentPage + 1);
} else if (context?.navigation.next) {
navigate(`/reader/${context.navigation.next.id}`);
}
}}
className="fixed right-4 top-1/2 transform -translate-y-1/2 bg-gray-800 p-4 rounded-full opacity-50 hover:opacity-100 transition-opacity"
>
</button>
</>
)}
</div>
);
}