feat: Reader working, some work still need to be done
This commit is contained in:
parent
33f5a5568a
commit
668702b1fb
246
assets/react/app/presentation/pages/ReaderPage.jsx
Normal file
246
assets/react/app/presentation/pages/ReaderPage.jsx
Normal file
@@ -0,0 +1,246 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useReader } from '../context/ReaderContext';
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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">
|
||||
<div className="flex items-center space-x-4">
|
||||
<button
|
||||
onClick={() => navigate(-1)}
|
||||
className="text-gray-300 hover:text-white"
|
||||
>
|
||||
<FontAwesomeIcon icon={faArrowLeft} />
|
||||
</button>
|
||||
{context && (
|
||||
<div>
|
||||
<span className="font-medium">Manga title</span>
|
||||
<span className="mx-2">-</span>
|
||||
<span>Chapter {context.number}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<button
|
||||
onClick={() => setMode(mode === 'classic' ? 'scrolling' : 'classic')}
|
||||
className="text-gray-300 hover:text-white"
|
||||
title={`Switch to ${mode === 'classic' ? 'scrolling' : 'classic'} mode`}
|
||||
>
|
||||
<FontAwesomeIcon icon={mode === 'classic' ? faScroll : faBookOpen} />
|
||||
</button>
|
||||
<button
|
||||
onClick={toggleFullscreen}
|
||||
className="text-gray-300 hover:text-white"
|
||||
>
|
||||
<FontAwesomeIcon icon={isFullscreen ? faCompress : faExpand} />
|
||||
</button>
|
||||
<button className="text-gray-300 hover:text-white">
|
||||
<FontAwesomeIcon icon={faList} />
|
||||
</button>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user