diff --git a/assets/react/app/App.jsx b/assets/react/app/App.jsx deleted file mode 100644 index b2dc2e5..0000000 --- a/assets/react/app/App.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; -import { HomePage } from './presentation/pages/HomePage.jsx'; -import { MangaDetailPage } from './presentation/pages/MangaDetailPage.jsx'; -import { AddMangaPage } from './presentation/pages/AddMangaPage.jsx'; -import { ReaderPage } from './presentation/pages/ReaderPage.jsx'; -import { MangaProvider } from './presentation/context/MangaContext.jsx'; -import { ReaderProvider } from './presentation/context/ReaderContext.jsx'; - -// Placeholder components for new routes -const PlaceholderPage = ({ title }) => ( -
-

{title}

-

Cette fonctionnalité sera bientôt disponible.

-
-); - -function App() { - return ( - - - - - } /> - } /> - } /> - } /> - - } /> - } /> - } /> - } /> - } /> - - } /> - } /> - } /> - } /> - - } /> - } /> - } /> - } /> - - } /> - - - - - ); -} - -export default App; \ No newline at end of file diff --git a/assets/react/app/application/useCases/getChapterContext.js b/assets/react/app/application/useCases/getChapterContext.js deleted file mode 100644 index b692c79..0000000 --- a/assets/react/app/application/useCases/getChapterContext.js +++ /dev/null @@ -1,9 +0,0 @@ -export class GetChapterContext { - constructor(readerRepository) { - this.readerRepository = readerRepository; - } - - async execute(chapterId) { - return await this.readerRepository.getChapterContext(chapterId); - } -} \ No newline at end of file diff --git a/assets/react/app/application/useCases/getMangaCollection.js b/assets/react/app/application/useCases/getMangaCollection.js deleted file mode 100644 index 4418605..0000000 --- a/assets/react/app/application/useCases/getMangaCollection.js +++ /dev/null @@ -1,9 +0,0 @@ -export class GetMangaCollection { - constructor(mangaRepository) { - this.mangaRepository = mangaRepository; - } - - async execute(page = 1) { - return await this.mangaRepository.getMangaCollection(page); - } -} \ No newline at end of file diff --git a/assets/react/app/application/useCases/getMangaDetail.js b/assets/react/app/application/useCases/getMangaDetail.js deleted file mode 100644 index d9aed45..0000000 --- a/assets/react/app/application/useCases/getMangaDetail.js +++ /dev/null @@ -1,9 +0,0 @@ -export class GetMangaDetail { - constructor(mangaRepository) { - this.mangaRepository = mangaRepository; - } - - async execute(slug) { - return await this.mangaRepository.getMangaBySlug(slug); - } -} \ No newline at end of file diff --git a/assets/react/app/application/useCases/getPage.js b/assets/react/app/application/useCases/getPage.js deleted file mode 100644 index 46cd33e..0000000 --- a/assets/react/app/application/useCases/getPage.js +++ /dev/null @@ -1,9 +0,0 @@ -export class GetPage { - constructor(readerRepository) { - this.readerRepository = readerRepository; - } - - async execute(chapterId, pageNumber) { - return await this.readerRepository.getPage(chapterId, pageNumber); - } -} \ No newline at end of file diff --git a/assets/react/app/application/useCases/getPages.js b/assets/react/app/application/useCases/getPages.js deleted file mode 100644 index e05def8..0000000 --- a/assets/react/app/application/useCases/getPages.js +++ /dev/null @@ -1,9 +0,0 @@ -export class GetPages { - constructor(readerRepository) { - this.readerRepository = readerRepository; - } - - async execute(chapterId) { - return await this.readerRepository.getPages(chapterId); - } -} \ No newline at end of file diff --git a/assets/react/app/application/useCases/searchMangas.js b/assets/react/app/application/useCases/searchMangas.js deleted file mode 100644 index dc63692..0000000 --- a/assets/react/app/application/useCases/searchMangas.js +++ /dev/null @@ -1,9 +0,0 @@ -export class SearchMangas { - constructor(mangaRepository) { - this.mangaRepository = mangaRepository; - } - - async execute(query) { - return await this.mangaRepository.searchMangas(query); - } -} \ No newline at end of file diff --git a/assets/react/app/assets/react.svg b/assets/react/app/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/assets/react/app/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/react/app/domain/chapter.js b/assets/react/app/domain/chapter.js deleted file mode 100644 index fcd4aa2..0000000 --- a/assets/react/app/domain/chapter.js +++ /dev/null @@ -1,10 +0,0 @@ -export class Chapter { - constructor(id, number, title, volume, isVisible, createdAt) { - this.id = id; - this.number = number; - this.title = title; - this.volume = volume; - this.isVisible = isVisible; - this.createdAt = createdAt; - } -} \ No newline at end of file diff --git a/assets/react/app/domain/manga.js b/assets/react/app/domain/manga.js deleted file mode 100644 index f3564d1..0000000 --- a/assets/react/app/domain/manga.js +++ /dev/null @@ -1,78 +0,0 @@ -import { Chapter } from './chapter.js'; - -export class Manga { - constructor( - id, - title, - slug, - imageUrl, - author, - publicationYear, - genres, - status, - rating, - description = '', - createdAt = null - ) { - this.id = id; - this.title = title; - this.slug = slug; - this.imageUrl = imageUrl; - this.author = author; - this.publicationYear = publicationYear; - this.genres = genres; - this.status = status; - this.rating = rating; - this.description = description; - this.createdAt = createdAt; - } -} - -export class MangaCollection { - constructor(items, total, page, limit, hasNextPage, hasPreviousPage) { - this.items = items; - this.total = total; - this.page = page; - this.limit = limit; - this.hasNextPage = hasNextPage; - this.hasPreviousPage = hasPreviousPage; - } -} - -export class MangaDetail extends Manga { - constructor(manga, chapters = []) { - super( - manga.id, - manga.title, - manga.slug, - manga.imageUrl, - manga.author, - manga.publicationYear, - manga.genres, - manga.status, - manga.rating, - manga.description, - manga.createdAt - ); - this.chapters = this.organizeChaptersByVolume(chapters); - } - - organizeChaptersByVolume(chapters) { - const volumeMap = new Map(); - - chapters.forEach(chapter => { - const volume = chapter.volume || 0; - if (!volumeMap.has(volume)) { - volumeMap.set(volume, []); - } - volumeMap.get(volume).push(chapter); - }); - - // Sort chapters within each volume - volumeMap.forEach(chapters => { - chapters.sort((a, b) => b.number - a.number); - }); - - return new Map([...volumeMap.entries()].sort((a, b) => b[0] - a[0])); - } -} \ No newline at end of file diff --git a/assets/react/app/domain/ports/mangaRepository.js b/assets/react/app/domain/ports/mangaRepository.js deleted file mode 100644 index 29ffa21..0000000 --- a/assets/react/app/domain/ports/mangaRepository.js +++ /dev/null @@ -1,6 +0,0 @@ -// Port (interface) for manga data access -export class MangaRepository { - async getMangaCollection(page = 1) { - throw new Error('Not implemented'); - } -} \ No newline at end of file diff --git a/assets/react/app/domain/ports/readerRepository.js b/assets/react/app/domain/ports/readerRepository.js deleted file mode 100644 index f7e0d47..0000000 --- a/assets/react/app/domain/ports/readerRepository.js +++ /dev/null @@ -1,30 +0,0 @@ -// Port (interface) for reader data access -export class ReaderRepository { - /** - * Récupère le contexte d'un chapitre - * @param {string} chapterId - L'identifiant du chapitre - * @returns {Promise} - */ - async getChapterContext(chapterId) { - throw new Error('Not implemented'); - } - - /** - * Récupère une page spécifique d'un chapitre - * @param {string} chapterId - L'identifiant du chapitre - * @param {number} pageNumber - Le numéro de la page - * @returns {Promise} - */ - async getPage(chapterId, pageNumber) { - throw new Error('Not implemented'); - } - - /** - * Récupère toutes les pages d'un chapitre - * @param {string} chapterId - L'identifiant du chapitre - * @returns {Promise<{pages: Page[], totalItems: number, currentPage: number, itemsPerPage: number, totalPages: number}>} - */ - async getPages(chapterId) { - throw new Error('Not implemented'); - } -} \ No newline at end of file diff --git a/assets/react/app/domain/reader.js b/assets/react/app/domain/reader.js deleted file mode 100644 index f857c60..0000000 --- a/assets/react/app/domain/reader.js +++ /dev/null @@ -1,19 +0,0 @@ -export class ReaderContext { - constructor(id, title, number, manga, navigation) { - this.id = id; - this.title = title; - this.number = number; - this.manga = manga; - this.navigation = navigation; - } -} - -export class Page { - constructor(id, pageNumber, base64Content, mimeType, dimensions) { - this.id = id; - this.pageNumber = pageNumber; - this.base64Content = base64Content; - this.mimeType = mimeType; - this.dimensions = dimensions; - } -} \ No newline at end of file diff --git a/assets/react/app/index.jsx b/assets/react/app/index.jsx deleted file mode 100644 index cec8494..0000000 --- a/assets/react/app/index.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import App from './App.jsx' -import '../../styles/app.scss' - -createRoot(document.getElementById('react-app')).render( - - - , -) diff --git a/assets/react/app/infrastructure/api/apiMangaRepository.js b/assets/react/app/infrastructure/api/apiMangaRepository.js deleted file mode 100644 index 1c27aca..0000000 --- a/assets/react/app/infrastructure/api/apiMangaRepository.js +++ /dev/null @@ -1,104 +0,0 @@ -import axios from 'axios'; -import { Manga, MangaCollection, MangaDetail } from '../../domain/manga'; -import { Chapter } from '../../domain/chapter'; - -export class ApiMangaRepository { - constructor() { - this.api = axios.create({ - baseURL: '/api' - }); - } - - async getMangaCollection(page = 1) { - try { - const response = await this.api.get(`/mangas?page=${page}`); - const data = response.data; - - const mangas = data.items.map(item => new Manga( - item.id, - item.title, - item.slug, - item.imageUrl, - item.author, - item.publicationYear, - item.genres, - item.status, - item.rating, - item.description, - item.createdAt - )); - - return new MangaCollection( - mangas, - data.total, - data.page, - data.limit, - data.hasNextPage, - data.hasPreviousPage - ); - } catch (error) { - console.error('Error fetching manga collection:', error); - throw error; - } - } - - async searchMangas(query) { - try { - const response = await this.api.get(`/mangas-search?title=${encodeURIComponent(query)}`); - const data = response.data; - - return data.items.map(item => new Manga( - item.externalId, - item.title, - item.slug, - item.imageUrl, - item.author, - item.publicationYear, - item.genres, - item.status, - item.rating, - item.description, - item.createdAt - )); - } catch (error) { - console.error('Error searching mangas:', error); - throw error; - } - } - - async getMangaBySlug(slug) { - try { - const mangaResponse = await this.api.get(`/mangas/by-slug/${slug}`); - const mangaData = mangaResponse.data; - - const chaptersResponse = await this.api.get(`/mangas/${mangaData.id}/chapters?page=1&limit=1000&sortOrder=desc`); - const chaptersData = chaptersResponse.data; - - const chapters = chaptersData.items.map(item => new Chapter( - item.id, - parseFloat(item.number), - item.title, - item.volume, - item.isVisible, - item.createdAt - )); - - return new MangaDetail({ - id: mangaData.id, - title: mangaData.title, - slug: mangaData.slug, - imageUrl: mangaData.imageUrl, - author: mangaData.author, - publicationYear: mangaData.publicationYear, - genres: mangaData.genres, - status: mangaData.status, - rating: mangaData.rating, - description: mangaData.description, - createdAt: mangaData.createdAt - }, chapters); - } catch (error) { - console.error('Error fetching manga details:', error); - throw error; - } - } -} \ No newline at end of file diff --git a/assets/react/app/infrastructure/api/apiReaderRepository.js b/assets/react/app/infrastructure/api/apiReaderRepository.js deleted file mode 100644 index 2fb9177..0000000 --- a/assets/react/app/infrastructure/api/apiReaderRepository.js +++ /dev/null @@ -1,74 +0,0 @@ -import axios from 'axios'; -import { ReaderContext, Page } from '../../domain/reader'; -import { ReaderRepository } from '../../domain/ports/readerRepository'; - -export class ApiReaderRepository extends ReaderRepository { - constructor() { - super(); - this.api = axios.create({ - baseURL: '/api' - }); - } - - async getChapterContext(chapterId) { - try { - const response = await this.api.get(`/reader/chapter/${chapterId}`); - const data = response.data; - - return new ReaderContext( - data.id, - data.title, - data.number, - data.manga, - data.navigation - ); - } catch (error) { - console.error('Error fetching chapter context:', error); - throw error; - } - } - - async getPage(chapterId, pageNumber) { - try { - const response = await this.api.get(`/reader/chapter/${chapterId}/page/${pageNumber}`); - const data = response.data; - - return new Page( - data.id, - data.pageNumber, - data.base64Content, - data.mimeType, - data.dimensions - ); - } catch (error) { - console.error('Error fetching page:', error); - throw error; - } - } - - async getPages(chapterId) { - try { - const response = await this.api.get(`/reader/chapter/${chapterId}/pages`); - const data = response.data; - - // Charger chaque page individuellement pour obtenir le contenu base64 - const pagesPromises = data.pages.map(async (page) => { - const pageResponse = await this.getPage(chapterId, page.pageNumber); - return pageResponse; - }); - - const loadedPages = await Promise.all(pagesPromises); - - return { - pages: loadedPages, - totalItems: data.totalItems, - currentPage: data.currentPage, - itemsPerPage: data.itemsPerPage, - totalPages: data.totalPages - }; - } catch (error) { - console.error('Error fetching pages:', error); - throw error; - } - } -} \ No newline at end of file diff --git a/assets/react/app/infrastructure/api/mockMangaRepository.js b/assets/react/app/infrastructure/api/mockMangaRepository.js deleted file mode 100644 index 6794d33..0000000 --- a/assets/react/app/infrastructure/api/mockMangaRepository.js +++ /dev/null @@ -1,109 +0,0 @@ -import { Manga, MangaCollection, MangaDetail } from '../../domain/manga.js'; -import { Chapter } from '../../domain/chapter.js'; - -export class MockMangaRepository { - constructor() { - this.mangas = { - 'one-piece': { - id: '1', - title: 'One Piece', - slug: 'one-piece', - imageUrl: 'https://images.unsplash.com/photo-1607604276583-eef5d076aa5f?auto=format&fit=crop&w=800&q=80', - author: 'Eiichiro Oda', - publicationYear: 1997, - genres: ['Action', 'Adventure', 'Comedy'], - status: 'ongoing', - rating: 4.9, - description: 'Monkey D. Luffy refuses to let anyone or anything stand in the way of his quest to become king of all pirates.' - }, - 'naruto': { - id: '2', - title: 'Naruto', - slug: 'naruto', - imageUrl: 'https://images.unsplash.com/photo-1618519764620-7403abdbdfe9?auto=format&fit=crop&w=800&q=80', - author: 'Masashi Kishimoto', - publicationYear: 1999, - genres: ['Action', 'Adventure', 'Fantasy'], - status: 'completed', - rating: 4.8, - description: 'Twelve years ago the Village Hidden in the Leaves was attacked by a fearsome threat.' - }, - 'berserk': { - id: '3', - title: 'Berserk', - slug: 'berserk', - imageUrl: 'https://images.unsplash.com/photo-1607604276583-eef5d076aa5f?auto=format&fit=crop&w=800&q=80', - author: 'Kentaro Miura', - publicationYear: 1989, - genres: ['Action', 'Dark Fantasy', 'Horror', 'Psychological'], - status: 'ongoing', - rating: 4.9, - description: 'Guts, known as the Black Swordsman, seeks sanctuary from the demonic forces.' - } - }; - } - - async searchMangas(query) { - if (!query) return []; - - const normalizedQuery = query.toLowerCase(); - return Object.values(this.mangas) - .filter(manga => - manga.title.toLowerCase().includes(normalizedQuery) || - manga.author.toLowerCase().includes(normalizedQuery) - ) - .map(manga => new Manga( - manga.id, - manga.title, - manga.slug, - manga.imageUrl, - manga.author, - manga.publicationYear, - manga.genres, - manga.status, - manga.rating - )); - } - - async getMangaCollection(page = 1) { - const mangas = Object.values(this.mangas).map(manga => new Manga( - manga.id, - manga.title, - manga.slug, - manga.imageUrl, - manga.author, - manga.publicationYear, - manga.genres, - manga.status, - manga.rating - )); - - return new MangaCollection( - mangas, - mangas.length, - page, - 10, - false, - false - ); - } - - async getMangaBySlug(slug) { - const manga = this.mangas[slug]; - - if (!manga) { - throw new Error(`Manga with slug "${slug}" not found`); - } - - const chapters = [ - new Chapter('1', 378, 'Un assassin invité', 42, true, '2024-02-15'), - new Chapter('2', 377, 'Snake In One\'s Bosom', 42, true, '2024-02-01'), - new Chapter('3', 376, 'La mer tremble, la guerre se profile', 42, true, '2024-01-15'), - new Chapter('4', 375, 'L\'aube suivant la nuit brumeuse', 41, true, '2024-01-01'), - new Chapter('5', 374, 'Le monstre noir va-t-il se laisser faire ?', 41, true, '2023-12-15'), - new Chapter('6', 373, 'Confrontation', 41, true, '2023-12-01'), - ]; - - return new MangaDetail(manga, chapters); - } -} \ No newline at end of file diff --git a/assets/react/app/infrastructure/api/mockReaderRepository.js b/assets/react/app/infrastructure/api/mockReaderRepository.js deleted file mode 100644 index d6e0ec6..0000000 --- a/assets/react/app/infrastructure/api/mockReaderRepository.js +++ /dev/null @@ -1,61 +0,0 @@ -export class MockReaderRepository { - async getChapterContext(chapterId) { - // Simuler un délai réseau - await new Promise(resolve => setTimeout(resolve, 500)); - - return { - id: chapterId, - title: "Un assassin invité", - number: "378", - manga: { - id: "1", - title: "One Piece" - }, - navigation: { - previous: { - id: "prev-chapter", - number: "377" - }, - next: { - id: "next-chapter", - number: "379" - } - } - }; - } - - async getPage(chapterId, pageNumber) { - // Simuler un délai réseau - await new Promise(resolve => setTimeout(resolve, 500)); - - return { - id: `page-${pageNumber}`, - pageNumber: pageNumber, - base64Content: "data:image/jpeg;base64,/9j/4AAQSkZJRg...", // Simulé - mimeType: "image/jpeg", - dimensions: { - width: 800, - height: 1200 - } - }; - } - - async getPages(chapterId) { - // Simuler un délai réseau - await new Promise(resolve => setTimeout(resolve, 500)); - - return { - pages: Array.from({ length: 20 }, (_, i) => ({ - number: i + 1, - dimensions: { - width: 800, - height: 1200 - } - })), - totalItems: 20, - currentPage: 1, - itemsPerPage: 20, - totalPages: 1 - }; - } -} \ No newline at end of file diff --git a/assets/react/app/presentation/components/Header.jsx b/assets/react/app/presentation/components/Header.jsx deleted file mode 100644 index 1e0b4c7..0000000 --- a/assets/react/app/presentation/components/Header.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { Link } from 'react-router-dom'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faBars } from '@fortawesome/free-solid-svg-icons'; -import { SearchBar } from './SearchBar/SearchBar.jsx'; - -export function Header({ onMenuClick }) { - return ( -
- -
- - Mangarr - - -
-
- ); -} \ No newline at end of file diff --git a/assets/react/app/presentation/components/Layout/Layout.jsx b/assets/react/app/presentation/components/Layout/Layout.jsx deleted file mode 100644 index c599ea5..0000000 --- a/assets/react/app/presentation/components/Layout/Layout.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { useState } from 'react'; -import { Header } from '../Header'; -import { Sidebar } from '../Sidebar'; - -export function Layout({ children, onMangaClick, onAddMangaClick }) { - const [isSidebarOpen, setIsSidebarOpen] = useState(false); - - return ( -
-
setIsSidebarOpen(!isSidebarOpen)} - onMangaClick={onMangaClick} - onAddMangaClick={onAddMangaClick} - /> - setIsSidebarOpen(false)} - onAddMangaClick={onAddMangaClick} - /> - -
- {children} -
-
- ); -} \ No newline at end of file diff --git a/assets/react/app/presentation/components/MangaCard.jsx b/assets/react/app/presentation/components/MangaCard.jsx deleted file mode 100644 index f993e5c..0000000 --- a/assets/react/app/presentation/components/MangaCard.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; - -export function MangaCard({ manga }) { - const navigate = useNavigate(); - - const handleClick = () => { - navigate(`/manga/${manga.slug}`); - }; - - const formatDate = (dateString) => { - const date = new Date(dateString); - return date.toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - year: 'numeric' - }); - }; - - return ( -
-
- {manga.title} -
-
-

{manga.title}

-
- {manga.publicationYear} -
-
- Added: {formatDate(manga.createdAt)} -
-
-
- ); -} \ No newline at end of file diff --git a/assets/react/app/presentation/components/MangaGrid.jsx b/assets/react/app/presentation/components/MangaGrid.jsx deleted file mode 100644 index b025504..0000000 --- a/assets/react/app/presentation/components/MangaGrid.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import { MangaCard } from './MangaCard.jsx'; - -export function MangaGrid({ mangas }) { - return ( -
- {mangas.map((manga) => ( - - ))} -
- ); -} \ No newline at end of file diff --git a/assets/react/app/presentation/components/SearchBar/SearchBar.jsx b/assets/react/app/presentation/components/SearchBar/SearchBar.jsx deleted file mode 100644 index 36ff23a..0000000 --- a/assets/react/app/presentation/components/SearchBar/SearchBar.jsx +++ /dev/null @@ -1,131 +0,0 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faSearch, faPlus } from '@fortawesome/free-solid-svg-icons'; -import { ApiMangaRepository } from '../../../infrastructure/api/apiMangaRepository.js'; -import { SearchMangas } from '../../../application/useCases/searchMangas.js'; - -const mangaRepository = new ApiMangaRepository(); -const searchMangas = new SearchMangas(mangaRepository); - -export function SearchBar() { - const navigate = useNavigate(); - const [query, setQuery] = useState(''); - const [results, setResults] = useState([]); - const [isOpen, setIsOpen] = useState(false); - const [loading, setLoading] = useState(false); - const [hasSearched, setHasSearched] = useState(false); - const searchRef = useRef(null); - - useEffect(() => { - const handleClickOutside = (event) => { - if (searchRef.current && !searchRef.current.contains(event.target)) { - setIsOpen(false); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - }, []); - - useEffect(() => { - const searchManga = async () => { - if (!query.trim()) { - setResults([]); - setHasSearched(false); - return; - } - - setLoading(true); - try { - const searchResults = await searchMangas.execute(query); - setResults(searchResults); - setHasSearched(true); - } catch (error) { - console.error('Search error:', error); - } finally { - setLoading(false); - } - }; - - const timeoutId = setTimeout(searchManga, 300); - return () => clearTimeout(timeoutId); - }, [query]); - - const handleMangaClick = (slug) => { - navigate(`/manga/${slug}`); - setIsOpen(false); - setQuery(''); - setHasSearched(false); - }; - - const handleAddMangaClick = () => { - navigate(`/add${query ? `?q=${encodeURIComponent(query)}` : ''}`); - setIsOpen(false); - setQuery(''); - setHasSearched(false); - }; - - return ( -
-
- - { - setQuery(e.target.value); - setIsOpen(true); - }} - onFocus={() => setIsOpen(true)} - placeholder="Rechercher" - className="appearance-none outline-none ml-2 pl-0 bg-transparent border-b border-white w-full placeholder:text-white text-white py-1 px-2 leading-tight transition-all duration-500 ease-in-out focus:placeholder:text-opacity-0 focus:border-opacity-0" - /> -
- - {isOpen && query.trim() && ( -
- {loading ? ( -
Chargement...
- ) : results.length > 0 ? ( -
-

- Mangas existants -

- {results.map((manga) => ( - - ))} -
- ) : hasSearched && ( -
- -
- )} -
- )} -
- ); -} \ No newline at end of file diff --git a/assets/react/app/presentation/components/Sidebar.jsx b/assets/react/app/presentation/components/Sidebar.jsx deleted file mode 100644 index 8e2311f..0000000 --- a/assets/react/app/presentation/components/Sidebar.jsx +++ /dev/null @@ -1,171 +0,0 @@ -import React, { useState } from 'react'; -import { Link } from 'react-router-dom'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { - faBook, - faPlus, - faFileImport, - faCompass, - faExchangeAlt, - faCalendar, - faClockRotateLeft, - faCog, - faDesktop, - faChevronDown, - faChevronUp -} from '@fortawesome/free-solid-svg-icons'; - -export function Sidebar({ isOpen, onClose, onAddMangaClick }) { - const [expandedMenus, setExpandedMenus] = useState({ - mangas: true, // Par défaut, le menu Mangas est ouvert - settings: false, - system: false - }); - - const menuItems = [ - { - icon: faBook, - text: 'Mangas', - id: 'mangas', - subItems: [ - { icon: faPlus, text: 'Ajouter un nouveau', onClick: () => onAddMangaClick() }, - { icon: faFileImport, text: 'Import bibliothèque', to: '/import' }, - { icon: faCompass, text: 'Découvrir', to: '/discover' }, - ] - }, - { - icon: faExchangeAlt, - text: 'Convertir CBR en CBZ', - to: '/convert' - }, - { - icon: faCalendar, - text: 'Calendrier', - to: '/calendar' - }, - { - icon: faClockRotateLeft, - text: 'Activité', - to: '/activity', - badge: '3' - }, - { - icon: faCog, - text: 'Paramètres', - id: 'settings', - subItems: [ - { text: 'Général', to: '/settings/general' }, - { text: 'Dossiers', to: '/settings/folders' }, - { text: 'Scrappers', to: '/settings/scrappers' }, - { text: 'UI', to: '/settings/ui' } - ] - }, - { - icon: faDesktop, - text: 'Système', - id: 'system', - subItems: [ - { text: 'Status', to: '/system/status' }, - { text: 'Backup', to: '/system/backup' }, - { text: 'Logs', to: '/system/logs' }, - { text: 'Updates', to: '/system/updates' } - ] - }, - ]; - - const toggleMenu = (menuId) => { - setExpandedMenus(prev => ({ - ...prev, - [menuId]: !prev[menuId] - })); - }; - - const MenuItem = ({ item }) => { - const hasSubItems = item.subItems && item.subItems.length > 0; - const isExpanded = item.id ? expandedMenus[item.id] : false; - - const handleClick = (e) => { - if (hasSubItems) { - e.preventDefault(); - toggleMenu(item.id); - } - }; - - const renderLink = (linkItem, className) => { - if (linkItem.onClick) { - return ( - - ); - } - - return ( - - {linkItem.icon && } - {linkItem.text} - - ); - }; - - return ( -
- {item.to || item.onClick ? ( - renderLink(item, "flex items-center px-4 py-2 text-gray-300 hover:text-green-600 transition-colors duration-150") - ) : ( - - )} - - {hasSubItems && isExpanded && ( -
- {item.subItems.map((subItem, index) => { - const link = renderLink( - subItem, - "block py-2 text-gray-300 hover:text-green-600 transition-colors duration-150" - ); - - return React.cloneElement(link, { key: `${subItem.text}-${index}` }); - })} -
- )} -
- ); - }; - - return ( - - ); -} \ No newline at end of file diff --git a/assets/react/app/presentation/components/Toolbar/Toolbar.jsx b/assets/react/app/presentation/components/Toolbar/Toolbar.jsx deleted file mode 100644 index 7bc33ce..0000000 --- a/assets/react/app/presentation/components/Toolbar/Toolbar.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { ToolbarButton } from './ToolbarButton'; - -export function Toolbar({ - leftSection = [], - centerSection = [], - rightSection = [], - className = '' -}) { - const renderSection = (items) => ( -
- {items.map((item, index) => ( - - ))} -
- ); - - return ( -
-
-
- {renderSection(leftSection)} - {renderSection(centerSection)} - {renderSection(rightSection)} -
-
-
- ); -} \ No newline at end of file diff --git a/assets/react/app/presentation/components/Toolbar/ToolbarButton.jsx b/assets/react/app/presentation/components/Toolbar/ToolbarButton.jsx deleted file mode 100644 index 6f1c91a..0000000 --- a/assets/react/app/presentation/components/Toolbar/ToolbarButton.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; - -export function ToolbarButton({ icon, label, onClick, navigateTo, navigateBack = false, active = false }) { - const navigate = useNavigate(); - - const handleClick = () => { - if (navigateBack) { - navigate(-1, { replace: true }); - } else if (navigateTo) { - navigate(navigateTo, { replace: true }); - } else if (onClick) { - onClick(); - } - }; - - return ( - - ); -} \ No newline at end of file diff --git a/assets/react/app/presentation/context/MangaContext.jsx b/assets/react/app/presentation/context/MangaContext.jsx deleted file mode 100644 index c3252d0..0000000 --- a/assets/react/app/presentation/context/MangaContext.jsx +++ /dev/null @@ -1,194 +0,0 @@ -import React, { createContext, useContext, useReducer, useCallback, useEffect } from 'react'; -import { ApiMangaRepository } from '../../infrastructure/api/apiMangaRepository'; -import { GetMangaCollection } from '../../application/useCases/getMangaCollection'; -import { GetMangaDetail } from '../../application/useCases/getMangaDetail'; - -const mangaRepository = new ApiMangaRepository(); -const getMangaCollection = new GetMangaCollection(mangaRepository); -const getMangaDetail = new GetMangaDetail(mangaRepository); - -const MangaContext = createContext(null); - -const initialState = { - collection: null, - detailedMangas: {}, - loading: false, - error: null, - lastCollectionUpdate: null, - isBackgroundLoading: false -}; - -function mangaReducer(state, action) { - switch (action.type) { - case 'SET_LOADING': - return { ...state, loading: action.payload }; - case 'SET_BACKGROUND_LOADING': - return { ...state, isBackgroundLoading: action.payload }; - case 'SET_ERROR': - return { ...state, error: action.payload, loading: false }; - case 'SET_COLLECTION': - return { - ...state, - collection: action.payload, - loading: false, - error: null, - lastCollectionUpdate: Date.now() - }; - case 'UPDATE_COLLECTION': - return { - ...state, - collection: action.payload, - isBackgroundLoading: false, - lastCollectionUpdate: Date.now() - }; - case 'SET_MANGA_DETAIL': - // Mettre à jour également le manga dans la collection si présent - const updatedCollection = state.collection ? { - ...state.collection, - items: state.collection.items.map(manga => - manga.slug === action.payload.slug - ? { - ...manga, - ...action.payload, - createdAt: manga.createdAt || action.payload.createdAt - } - : manga - ) - } : state.collection; - - return { - ...state, - collection: updatedCollection, - detailedMangas: { - ...state.detailedMangas, - [action.payload.slug]: { - ...action.payload, - createdAt: state.collection?.items.find(m => m.slug === action.payload.slug)?.createdAt || action.payload.createdAt - } - }, - loading: false, - error: null - }; - default: - return state; - } -} - -export function MangaProvider({ children }) { - const [state, dispatch] = useReducer(mangaReducer, initialState); - - // Fonction pour charger la collection en arrière-plan - const refreshCollectionInBackground = useCallback(async () => { - if (state.isBackgroundLoading) return; - - dispatch({ type: 'SET_BACKGROUND_LOADING', payload: true }); - try { - const collection = await getMangaCollection.execute(1); - dispatch({ type: 'UPDATE_COLLECTION', payload: collection }); - } catch (error) { - console.error('Background collection refresh failed:', error); - dispatch({ type: 'SET_BACKGROUND_LOADING', payload: false }); - } - }, [state.isBackgroundLoading]); - - // Rafraîchir la collection toutes les 5 minutes si elle est chargée - useEffect(() => { - if (!state.collection) return; - - const interval = setInterval(() => { - refreshCollectionInBackground(); - }, 5 * 60 * 1000); - - return () => clearInterval(interval); - }, [state.collection, refreshCollectionInBackground]); - - const loadCollection = useCallback(async () => { - // Si nous avons déjà des données, les afficher immédiatement - if (state.collection) { - // Rafraîchir en arrière-plan si les données sont vieilles de plus de 30 secondes - const isStale = state.lastCollectionUpdate && - (Date.now() - state.lastCollectionUpdate) > 30 * 1000; - - if (isStale && !state.isBackgroundLoading) { - refreshCollectionInBackground(); - } - return; - } - - dispatch({ type: 'SET_LOADING', payload: true }); - try { - const collection = await getMangaCollection.execute(1); - dispatch({ type: 'SET_COLLECTION', payload: collection }); - } catch (error) { - dispatch({ type: 'SET_ERROR', payload: 'Failed to load manga collection' }); - console.error(error); - } - }, [state.collection, state.lastCollectionUpdate, state.isBackgroundLoading, refreshCollectionInBackground]); - - const loadMangaDetail = useCallback(async (slug) => { - // Retourner les données en cache si disponibles - if (state.detailedMangas[slug]) { - // Rafraîchir en arrière-plan si les données sont vieilles de plus de 5 minutes - const cachedManga = state.detailedMangas[slug]; - const isStale = cachedManga.lastUpdate && - (Date.now() - cachedManga.lastUpdate) > 5 * 60 * 1000; - - if (isStale) { - // Charger les nouvelles données en arrière-plan - getMangaDetail.execute(slug).then(manga => { - dispatch({ type: 'SET_MANGA_DETAIL', payload: { ...manga, lastUpdate: Date.now() } }); - }).catch(console.error); - } - - return state.detailedMangas[slug]; - } - - // Si le manga est dans la collection, l'utiliser comme données temporaires - const collectionManga = getMangaFromCollection(slug); - if (collectionManga) { - dispatch({ - type: 'SET_MANGA_DETAIL', - payload: { ...collectionManga, isPartial: true, lastUpdate: Date.now() } - }); - } - - // Charger les détails complets - dispatch({ type: 'SET_LOADING', payload: true }); - try { - const manga = await getMangaDetail.execute(slug); - dispatch({ type: 'SET_MANGA_DETAIL', payload: { ...manga, lastUpdate: Date.now() } }); - return manga; - } catch (error) { - dispatch({ type: 'SET_ERROR', payload: 'Failed to load manga details' }); - console.error(error); - return null; - } - }, [state.detailedMangas]); - - const getMangaFromCollection = useCallback((slug) => { - if (!state.collection) return null; - return state.collection.items.find(manga => manga.slug === slug); - }, [state.collection]); - - const value = { - ...state, - loadCollection, - loadMangaDetail, - getMangaFromCollection, - refreshCollectionInBackground - }; - - return ( - - {children} - - ); -} - -export function useManga() { - const context = useContext(MangaContext); - if (!context) { - throw new Error('useManga must be used within a MangaProvider'); - } - return context; -} \ No newline at end of file diff --git a/assets/react/app/presentation/context/ReaderContext.jsx b/assets/react/app/presentation/context/ReaderContext.jsx deleted file mode 100644 index 155e043..0000000 --- a/assets/react/app/presentation/context/ReaderContext.jsx +++ /dev/null @@ -1,129 +0,0 @@ -import React, { createContext, useContext, useReducer, useCallback } from 'react'; -import { ApiReaderRepository } from '../../infrastructure/api/apiReaderRepository'; -import { GetChapterContext } from '../../application/useCases/getChapterContext'; -import { GetPage } from '../../application/useCases/getPage'; -import { GetPages } from '../../application/useCases/getPages'; - -const readerRepository = new ApiReaderRepository(); -const getChapterContext = new GetChapterContext(readerRepository); -const getPage = new GetPage(readerRepository); -const getPages = new GetPages(readerRepository); - -const ReaderContext = createContext(null); - -const initialState = { - context: null, - currentPage: 1, - pages: [], - loading: false, - error: null, - mode: 'classic', // 'classic' ou 'scrolling' -}; - -function readerReducer(state, action) { - switch (action.type) { - case 'SET_LOADING': - return { ...state, loading: action.payload }; - case 'SET_ERROR': - return { ...state, error: action.payload, loading: false }; - case 'SET_CONTEXT': - return { ...state, context: action.payload, loading: false }; - case 'SET_PAGES': - return { ...state, pages: action.payload, loading: false }; - case 'SET_CURRENT_PAGE': - return { ...state, currentPage: action.payload }; - case 'SET_MODE': - return { ...state, mode: action.payload }; - case 'RESET_STATE': - return initialState; - default: - return state; - } -} - -export function ReaderProvider({ children }) { - const [state, dispatch] = useReducer(readerReducer, initialState); - - const loadChapterContext = useCallback(async (chapterId) => { - dispatch({ type: 'RESET_STATE' }); - dispatch({ type: 'SET_LOADING', payload: true }); - try { - const context = await getChapterContext.execute(chapterId); - dispatch({ type: 'SET_CONTEXT', payload: context }); - return true; - } catch (error) { - dispatch({ type: 'SET_ERROR', payload: error.response?.status === 404 ? 'Chapitre introuvable' : 'Erreur lors du chargement du chapitre' }); - console.error(error); - return false; - } - }, []); - - const loadPage = useCallback(async (chapterId, pageNumber) => { - dispatch({ type: 'SET_LOADING', payload: true }); - try { - const page = await getPage.execute(chapterId, pageNumber); - dispatch({ type: 'SET_LOADING', payload: false }); - return page; - } catch (error) { - const errorMessage = error.response?.status === 404 - ? 'Page introuvable' - : 'Erreur lors du chargement de la page'; - dispatch({ type: 'SET_ERROR', payload: errorMessage }); - dispatch({ type: 'SET_LOADING', payload: false }); - return null; - } - }, []); - - const loadPages = useCallback(async (chapterId) => { - dispatch({ type: 'SET_LOADING', payload: true }); - try { - const { pages } = await getPages.execute(chapterId); - if (!pages || pages.length === 0) { - dispatch({ type: 'SET_ERROR', payload: 'Aucune page trouvée dans ce chapitre' }); - return false; - } - dispatch({ type: 'SET_PAGES', payload: pages }); - dispatch({ type: 'SET_CURRENT_PAGE', payload: 1 }); - dispatch({ type: 'SET_LOADING', payload: false }); - return true; - } catch (error) { - const errorMessage = error.response?.status === 404 - ? 'Chapitre introuvable' - : 'Erreur lors du chargement des pages'; - dispatch({ type: 'SET_ERROR', payload: errorMessage }); - dispatch({ type: 'SET_LOADING', payload: false }); - return false; - } - }, []); - - const setCurrentPage = useCallback((pageNumber) => { - dispatch({ type: 'SET_CURRENT_PAGE', payload: pageNumber }); - }, []); - - const setMode = useCallback((mode) => { - dispatch({ type: 'SET_MODE', payload: mode }); - }, []); - - const value = { - ...state, - loadChapterContext, - loadPage, - loadPages, - setCurrentPage, - setMode, - }; - - return ( - - {children} - - ); -} - -export function useReader() { - const context = useContext(ReaderContext); - if (!context) { - throw new Error('useReader must be used within a ReaderProvider'); - } - return context; -} \ No newline at end of file diff --git a/assets/react/app/presentation/pages/AddMangaPage.jsx b/assets/react/app/presentation/pages/AddMangaPage.jsx deleted file mode 100644 index 8cd827c..0000000 --- a/assets/react/app/presentation/pages/AddMangaPage.jsx +++ /dev/null @@ -1,185 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { useNavigate, useSearchParams } from 'react-router-dom'; -import { Layout } from '../components/Layout/Layout'; -import { Toolbar } from '../components/Toolbar/Toolbar'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faArrowLeft, faSearch, faStar } from '@fortawesome/free-solid-svg-icons'; -import { ApiMangaRepository } from '../../infrastructure/api/apiMangaRepository'; -import { SearchMangas } from '../../application/useCases/searchMangas'; - -const mangaRepository = new ApiMangaRepository(); -const searchMangas = new SearchMangas(mangaRepository); - -export function AddMangaPage() { - const navigate = useNavigate(); - const [searchParams] = useSearchParams(); - const initialQuery = searchParams.get('q') || ''; - - const [query, setQuery] = useState(initialQuery); - const [results, setResults] = useState([]); - const [loading, setLoading] = useState(false); - const [selectedManga, setSelectedManga] = useState(null); - - useEffect(() => { - if (initialQuery) { - handleSearch(initialQuery); - } - }, [initialQuery]); - - const handleSearch = async (searchQuery) => { - if (!searchQuery.trim()) { - setResults([]); - return; - } - - setLoading(true); - try { - const searchResults = await searchMangas.execute(searchQuery); - setResults(searchResults); - } catch (error) { - console.error('Search error:', error); - } finally { - setLoading(false); - } - }; - - const handleMangaClick = (slug) => { - navigate(`/manga/${slug}`); - }; - - const handleAddMangaClick = (query = '') => { - navigate(`/add${query ? `?q=${encodeURIComponent(query)}` : ''}`); - }; - - const toolbarConfig = { - leftSection: [ - { icon: faArrowLeft, onClick: () => navigate(-1) } - ] - }; - - return ( - - - -
-

Ajouter un manga

- -
-
-
- { - setQuery(e.target.value); - handleSearch(e.target.value); - }} - placeholder="Rechercher un manga..." - className="w-full px-4 py-2 pl-10 border-b border-gray-300 focus:border-green-600 outline-none transition-colors" - /> - -
- - {loading ? ( -
-
-
- ) : results.length > 0 ? ( -
- {results.map((manga) => ( -
setSelectedManga(manga)} - className="flex gap-4 p-4 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors cursor-pointer" - > - {manga.title} -
-
-
-

{manga.title}

-

{manga.author}

-
-
- - {manga.rating} -
-
-
- - {manga.publicationYear} • {manga.status} - -
-
- {manga.genres.map((genre, index) => ( - - {genre} - - ))} -
-
-
- ))} -
- ) : query && !loading && ( -
- Aucun résultat trouvé pour "{query}" -
- )} -
-
-
- - {/* Modal de confirmation */} - {selectedManga && ( -
-
-
-

Ajouter à la bibliothèque

-
- {selectedManga.title} -
-

{selectedManga.title}

-

{selectedManga.author}

-

- {selectedManga.genres.join(', ')} -

-
-
-
- - -
-
-
-
- )} -
- ); -} \ No newline at end of file diff --git a/assets/react/app/presentation/pages/HomePage.jsx b/assets/react/app/presentation/pages/HomePage.jsx deleted file mode 100644 index 8ea6841..0000000 --- a/assets/react/app/presentation/pages/HomePage.jsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { MangaGrid } from '../components/MangaGrid.jsx'; -import { Layout } from '../components/Layout/Layout.jsx'; -import { Toolbar } from '../components/Toolbar/Toolbar.jsx'; -import { useManga } from '../context/MangaContext.jsx'; -import { - faRefresh, - faSearch, - faGear, - faEye, - faSort, - faFilter -} from '@fortawesome/free-solid-svg-icons'; - -export function HomePage() { - const navigate = useNavigate(); - const { - collection, - loading, - error, - isBackgroundLoading, - loadCollection, - refreshCollectionInBackground - } = useManga(); - - useEffect(() => { - loadCollection(); - }, [loadCollection]); - - const handleAddMangaClick = (query = '') => { - navigate(`/add${query ? `?q=${encodeURIComponent(query)}` : ''}`); - }; - - const toolbarConfig = { - leftSection: [ - { - icon: faRefresh, - label: 'Refresh', - onClick: refreshCollectionInBackground, - active: isBackgroundLoading - }, - { icon: faSearch, label: 'Search', onClick: () => {} } - ], - rightSection: [ - { icon: faGear, onClick: () => {} }, - { icon: faEye, onClick: () => {} }, - { icon: faSort, onClick: () => {} }, - { icon: faFilter, onClick: () => {} } - ] - }; - - if (loading && !collection) { - return
Loading...
; - } - - if (error) { - return
{error}
; - } - - return ( - - -
- - {isBackgroundLoading && ( -
- Mise à jour en cours... -
- )} -
-
- ); -} \ No newline at end of file diff --git a/assets/react/app/presentation/pages/MangaDetailPage.jsx b/assets/react/app/presentation/pages/MangaDetailPage.jsx deleted file mode 100644 index 1ca3b98..0000000 --- a/assets/react/app/presentation/pages/MangaDetailPage.jsx +++ /dev/null @@ -1,205 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { useParams, useNavigate } from 'react-router-dom'; -import { Layout } from '../components/Layout/Layout.jsx'; -import { Toolbar } from '../components/Toolbar/Toolbar.jsx'; -import { useManga } from '../context/MangaContext.jsx'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { - faStar, - faDownload, - faEye, - faArrowLeft, - faRefresh, - faBookmark, - faShare -} from '@fortawesome/free-solid-svg-icons'; - -export function MangaDetailPage() { - const { slug } = useParams(); - const navigate = useNavigate(); - const { - detailedMangas, - loading, - error, - loadMangaDetail, - getMangaFromCollection - } = useManga(); - - const [isLoadingDetails, setIsLoadingDetails] = useState(false); - - // Obtenir les données du manga depuis le cache ou la collection - const manga = detailedMangas[slug]; - const collectionManga = getMangaFromCollection(slug); - const displayManga = manga || collectionManga; - - useEffect(() => { - const loadDetails = async () => { - if (!manga && displayManga) { - setIsLoadingDetails(true); - await loadMangaDetail(slug); - setIsLoadingDetails(false); - } else if (!manga && !displayManga) { - await loadMangaDetail(slug); - } - }; - - loadDetails(); - }, [slug, manga, displayManga, loadMangaDetail]); - - const handleAddMangaClick = (query = '') => { - navigate(`/add${query ? `?q=${encodeURIComponent(query)}` : ''}`); - }; - - const toolbarConfig = { - leftSection: [ - { icon: faArrowLeft, navigateBack: true }, - { icon: faRefresh, onClick: () => {} } - ], - rightSection: [ - { icon: faBookmark, onClick: () => {} }, - { icon: faShare, onClick: () => {} }, - { icon: faDownload, onClick: () => {} } - ] - }; - - if (loading && !displayManga) { - return
Loading...
; - } - - if (error) { - return
{error}
; - } - - if (!displayManga) { - return
Manga not found
; - } - - return ( - - - - {/* Hero section with manga info */} -
-
- {displayManga.title} -
-
- -
-
-
-

{displayManga.title}

- - {displayManga.status} - - {isLoadingDetails && ( - - Chargement des détails... - - )} -
- -
-
- - {displayManga.rating} -
- {displayManga.publicationYear} - {displayManga.author} -
- -
- {displayManga.genres.map((genre, index) => ( - - {genre} - - ))} -
- -

- {displayManga.description} -

-
-
-
- - {/* Chapters section - only shown when full details are loaded */} - {manga && manga.chapters && ( -
- {Array.from(manga.chapters.entries()).map(([volume, chapters]) => ( -
-

- Volume {volume || 'Unknown'} - - ({chapters.length} chapters) - -

- -
- - - - - - - - - - - {chapters.map((chapter) => ( - - - - - - - ))} - -
- # - - Title - - Added - - Actions -
- {chapter.number} - - {chapter.title} - - {new Date(chapter.createdAt).toLocaleDateString()} - - - -
-
-
- ))} -
- )} - - {!manga && ( -
-
- Chargement des chapitres... -
-
- )} - - ); -} \ No newline at end of file diff --git a/assets/react/app/presentation/pages/ReaderPage.jsx b/assets/react/app/presentation/pages/ReaderPage.jsx deleted file mode 100644 index 84114d3..0000000 --- a/assets/react/app/presentation/pages/ReaderPage.jsx +++ /dev/null @@ -1,239 +0,0 @@ -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 ( -
-
-
-
Chargement du chapitre...
-
-
- ); - } - - if (error) { - return ( -
-
{error}
- -
- ); - } - - return ( -
- {/* Toolbar */} -
-
-
- - {context && ( -
- Chapitre {context.number} -
- )} -
-
-
- - {/* Reader content */} -
- {mode === 'classic' ? ( - // Mode classique -
- {currentPageData && ( - {`Page - )} -
- Page {currentPage} of {pages.length} -
-
- ) : ( - // Mode scrolling -
- {pages.map((page, index) => ( -
- {page.base64Content ? ( - {`Page - ) : ( -
- Chargement... -
- )} -
- ))} -
- )} -
- - {/* Navigation buttons for classic mode */} - {mode === 'classic' && ( - <> - - - - )} -
- ); -} \ No newline at end of file