231 líneas
6.9 KiB
JavaScript
231 líneas
6.9 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import { FaBook, FaShare } from 'react-icons/fa';
|
|
import BookList from './components/BookList';
|
|
import EpubViewer from './components/EpubViewer';
|
|
import GatewaySelector from './components/GatewaySelector';
|
|
import { parseIPFSCatalog, IPFS_GATEWAYS, buildEpubUrl } from './utils/ipfsParser';
|
|
import './App.css';
|
|
|
|
/**
|
|
* Componente principal de La Biblioteca
|
|
* Aplicación para visualizar documentos EPUB desde IPFS
|
|
*/
|
|
function App() {
|
|
const [books, setBooks] = useState([]);
|
|
const [selectedBook, setSelectedBook] = useState(null);
|
|
const [selectedGateway, setSelectedGateway] = useState(IPFS_GATEWAYS[0].url);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [showShareDialog, setShowShareDialog] = useState(false);
|
|
|
|
useEffect(() => {
|
|
loadCatalog();
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [selectedGateway]);
|
|
|
|
useEffect(() => {
|
|
// Verificar si hay un hash IPFS en la URL al cargar la página
|
|
const params = new URLSearchParams(window.location.search);
|
|
const ipfsHash = params.get('epub');
|
|
const gateway = params.get('gateway');
|
|
|
|
if (ipfsHash && books.length > 0) {
|
|
// Si hay un gateway en la URL, usarlo
|
|
if (gateway && gateway !== selectedGateway) {
|
|
setSelectedGateway(gateway);
|
|
}
|
|
// Cargar el EPUB directamente
|
|
const book = books.find(b => b.ipfsHash === ipfsHash);
|
|
if (book) {
|
|
setSelectedBook(book);
|
|
} else {
|
|
// Si no está en el catálogo, crear un objeto básico
|
|
setSelectedBook({
|
|
id: ipfsHash,
|
|
ipfsHash: ipfsHash,
|
|
title: 'Libro compartido',
|
|
filename: `${ipfsHash}.epub`,
|
|
href: ''
|
|
});
|
|
}
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [books]);
|
|
|
|
/**
|
|
* Carga el catálogo de libros desde IPFS
|
|
*/
|
|
const loadCatalog = async () => {
|
|
try {
|
|
setIsLoading(true);
|
|
|
|
// Cargar el catálogo desde IPFS usando el gateway seleccionado
|
|
const catalogUrl = `${selectedGateway}/ipfs/QmYuyPJZs6J6LfKgVFsMPX5kApWYfSLcr1BXox8NMS7UpL/`;
|
|
const response = await fetch(catalogUrl);
|
|
const htmlContent = await response.text();
|
|
|
|
// Parsear el contenido y extraer los libros
|
|
const parsedBooks = parseIPFSCatalog(htmlContent);
|
|
setBooks(parsedBooks);
|
|
setIsLoading(false);
|
|
} catch (error) {
|
|
console.error('Error cargando el catálogo:', error);
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Carga un libro directamente desde su hash IPFS
|
|
*/
|
|
const loadBookFromHash = (ipfsHash) => {
|
|
// Buscar el libro en el catálogo si ya está cargado
|
|
const book = books.find(b => b.ipfsHash === ipfsHash);
|
|
if (book) {
|
|
setSelectedBook(book);
|
|
} else {
|
|
// Si no está en el catálogo, crear un objeto básico
|
|
setSelectedBook({
|
|
id: ipfsHash,
|
|
ipfsHash: ipfsHash,
|
|
title: 'Libro compartido',
|
|
filename: `${ipfsHash}.epub`,
|
|
href: ''
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Maneja la selección de un libro
|
|
*/
|
|
const handleBookSelect = (book) => {
|
|
// Actualizar la URL del navegador
|
|
const url = new URL(window.location);
|
|
url.searchParams.set('epub', book.ipfsHash);
|
|
url.searchParams.set('gateway', selectedGateway);
|
|
window.history.pushState({}, '', url);
|
|
|
|
setSelectedBook(book);
|
|
};
|
|
|
|
/**
|
|
* Cierra el visor de EPUB
|
|
*/
|
|
const handleCloseViewer = () => {
|
|
// Limpiar la URL
|
|
const url = new URL(window.location);
|
|
url.searchParams.delete('epub');
|
|
url.searchParams.delete('gateway');
|
|
window.history.pushState({}, '', url);
|
|
|
|
setSelectedBook(null);
|
|
};
|
|
|
|
/**
|
|
* Genera el enlace para compartir
|
|
*/
|
|
const getShareUrl = () => {
|
|
if (!selectedBook) return '';
|
|
const url = new URL(window.location.origin + window.location.pathname);
|
|
url.searchParams.set('epub', selectedBook.ipfsHash);
|
|
url.searchParams.set('gateway', selectedGateway);
|
|
return url.toString();
|
|
};
|
|
|
|
/**
|
|
* Copia el enlace al portapapeles
|
|
*/
|
|
const handleShare = async () => {
|
|
const shareUrl = getShareUrl();
|
|
try {
|
|
await navigator.clipboard.writeText(shareUrl);
|
|
setShowShareDialog(true);
|
|
setTimeout(() => setShowShareDialog(false), 3000);
|
|
} catch (err) {
|
|
console.error('Error al copiar:', err);
|
|
alert('No se pudo copiar el enlace. URL: ' + shareUrl);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="App">
|
|
<a href="#main-content" className="skip-link">Saltar al contenido principal</a>
|
|
{selectedBook ? (
|
|
<>
|
|
<EpubViewer
|
|
epubUrl={buildEpubUrl(selectedBook.ipfsHash, selectedGateway)}
|
|
bookTitle={selectedBook.title}
|
|
bookFilename={selectedBook.filename}
|
|
onClose={handleCloseViewer}
|
|
onShare={handleShare}
|
|
/>
|
|
{showShareDialog && (
|
|
<div
|
|
className="share-toast"
|
|
role="status"
|
|
aria-live="polite"
|
|
aria-atomic="true"
|
|
>
|
|
✓ Enlace copiado al portapapeles
|
|
</div>
|
|
)}
|
|
</>
|
|
) : (
|
|
<>
|
|
<header className="App-header" role="banner">
|
|
<div className="header-content">
|
|
<div className="logo-section">
|
|
<FaBook className="app-logo" aria-hidden="true" />
|
|
<h1>La Biblioteca</h1>
|
|
</div>
|
|
<p className="subtitle">Visor de documentos EPUB accesibles desde IPFS</p>
|
|
</div>
|
|
</header>
|
|
|
|
<main className="App-main" id="main-content" role="main">
|
|
<div className="controls-section">
|
|
<GatewaySelector
|
|
gateways={IPFS_GATEWAYS}
|
|
selectedGateway={selectedGateway}
|
|
onGatewayChange={setSelectedGateway}
|
|
/>
|
|
</div>
|
|
|
|
<div className="catalog-section">
|
|
<BookList
|
|
books={books}
|
|
onBookSelect={handleBookSelect}
|
|
isLoading={isLoading}
|
|
/>
|
|
</div>
|
|
</main>
|
|
|
|
<footer className="App-footer" role="contentinfo">
|
|
<p>
|
|
<span aria-label="Libros disponibles">{books.length} libros disponibles en IPFS</span>
|
|
{' | '}
|
|
<a
|
|
href="https://ipfs.io"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
aria-label="Información sobre IPFS (abre en nueva ventana)"
|
|
>
|
|
¿Qué es IPFS?
|
|
</a>
|
|
{' | '}
|
|
<a
|
|
href="https://git.manalejandro.com/ale/labiblioteca"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
aria-label="Repositorio git del proyecto (abre en nueva ventana)"
|
|
>
|
|
📦 Repositorio
|
|
</a>
|
|
</p>
|
|
</footer>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default App;
|