239 lines
7.6 KiB
JavaScript
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>
|
|
);
|
|
} |