initial commit

Signed-off-by: ale <ale@manalejandro.com>
Este commit está contenido en:
ale
2025-11-18 21:45:12 +01:00
padre 585716d86c
commit b2bd63a47e
Se han modificado 21 ficheros con 2633 adiciones y 17660 borrados

Ver fichero

@@ -1,23 +1,228 @@
import logo from './logo.svg';
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">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<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>
);
}