import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { useSearchParams } from 'react-router-dom'; import '../FontMap.css'; import { useStaticFontData } from '../../hooks/useStaticFontData'; import { useMapRenderer } from './hooks/useMapRenderer'; import { useMapZoom } from './hooks/useMapZoom'; import { useArrowNavigation } from './hooks/useArrowNavigation'; import FilterControls from './components/controls/FilterControls'; import SearchBar from './components/controls/SearchBar'; import ZoomControls from './components/controls/ZoomControls'; import CategoryLegend from './components/controls/CategoryLegend'; import ActiveFont from './components/ActiveFont'; import TooltipManager from './components/TooltipManager'; import IntroModal from './components/IntroModal'; import AboutModal from './components/AboutModal'; import FPSMonitor from './components/FPSMonitor'; import { useFontMapStore } from '../../store/fontMapStore'; import './styles/intro-modal.css'; import './styles/about-modal.css'; /** * Composant principal FontMap — Moteur de rendu DebugUMAP + UI prod complète. * Mode debug activable via ?debug=true dans l'URL. */ const FontMap = ({ darkMode = false }) => { const svgRef = useRef(null); const [searchParams] = useSearchParams(); const isDebugMode = searchParams.get('debug') === 'true'; const [filter, setFilter] = useState('all'); const [searchTerm, setSearchTerm] = useState(''); const [appState, setAppState] = useState('loading'); const [showAboutModal, setShowAboutModal] = useState(false); const { selectedFont, hoveredFont, setSelectedFont, setHoveredFont } = useFontMapStore(); // ── Data : polices + chemins de glyphes (pour la sidebar ActiveFont) ── const { fonts, glyphPaths, loading, error } = useStaticFontData(); // ── Moteur de rendu DebugUMAP (viewBox + SVGs individuels + batch) ── const svgReady = !loading && fonts.length > 0; useMapRenderer({ svgRef, fonts, filter, searchTerm, darkMode, loading, enabled: svgReady }); // ── Zoom simplifié (DebugUMAP-style) ── const { centerOnFont, resetZoom } = useMapZoom(svgRef, svgReady); // ── Navigation clavier ── useArrowNavigation(selectedFont, fonts, filter, searchTerm, handleFontSelect); // ── Callbacks ── function handleFontSelect(font) { setSelectedFont(font); } const handleFontHover = useCallback((font) => { setHoveredFont(font); }, [setHoveredFont]); const handleFontUnhover = useCallback(() => { setHoveredFont(null); }, [setHoveredFont]); // ── Centrage sur la police sélectionnée / reset au désélect ── useEffect(() => { if (selectedFont) { centerOnFont(selectedFont); } else { resetZoom(); } }, [selectedFont, centerOnFont, resetZoom]); // ── Callbacks globaux pour le TooltipManager ── useEffect(() => { window.onFontHover = handleFontHover; window.onFontUnhover = handleFontUnhover; return () => { delete window.onFontHover; delete window.onFontUnhover; }; }, [handleFontHover, handleFontUnhover]); // ── Transitions d'état ── useEffect(() => { if (loading) { setAppState('loading'); } else if (fonts.length > 0 && appState === 'loading') { setAppState('intro'); } }, [loading, fonts.length, appState]); // ── Compteurs pour la recherche ── const totalFonts = fonts.length; const filterOnlyCount = filter === 'all' ? totalFonts : fonts.filter(f => f.family === filter).length; const filteredFonts = useMemo(() => fonts.filter(font => { const familyMatch = filter === 'all' || font.family === filter; const searchMatch = !searchTerm || font.name.toLowerCase().includes(searchTerm.toLowerCase()) || font.family.toLowerCase().includes(searchTerm.toLowerCase()); return familyMatch && searchMatch; }), [fonts, filter, searchTerm]); const filteredCount = filteredFonts.length; // ── Symboles SVG cachés pour la sidebar (ActiveFont utilise ) ── const symbolDefs = useMemo(() => { if (!glyphPaths || Object.keys(glyphPaths).length === 0) return null; return ( ); }, [glyphPaths]); if (error) { return (

Erreur de chargement

{error}

); } return (
{/* Symboles SVG cachés pour la sidebar */} {symbolDefs} {/* Sidebar */}
setSelectedFont(null)} onFontSelect={handleFontSelect} />
Source
{/* Zone principale */}

FontMap

{appState === 'loading' && (
Loading font map...
)}
{!loading && fonts.length > 0 && ( )}
{/* Overlays */} {appState === 'loading' && (
Initializing...
)} {appState === 'intro' && ( setAppState('ready')} darkMode={darkMode} /> )} {showAboutModal && ( setShowAboutModal(false)} darkMode={darkMode} /> )} {isDebugMode && }
); }; export default FontMap; export { FontMap };