tfrere's picture
tfrere HF Staff
first commit
eebc40f
raw
history blame
9.31 kB
import React, { useState, useEffect, useRef } from 'react';
import * as d3 from 'd3';
import '../FontMap.css';
// Import des hooks
import { useFontData } from './hooks/useFontData';
import { useD3Visualization } from './hooks/useD3Visualization';
import { useArrowNavigation } from './hooks/useArrowNavigation';
// Import des composants de contrôles
import FilterControls from './components/controls/FilterControls';
import SearchBar from './components/controls/SearchBar';
import ZoomControls from './components/controls/ZoomControls';
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 du hook Tweakpane
import { useTweakpane } from './hooks/useTweakpane';
import './styles/intro-modal.css';
import './styles/about-modal.css';
/**
* Composant principal FontMap unifié
*/
const FontMap = ({ darkMode = false }) => {
// États locaux
const [filter, setFilter] = useState('all');
const [searchTerm, setSearchTerm] = useState('');
const [dilationFactor, setDilationFactor] = useState(0.055);
const [characterSize, setCharacterSize] = useState(1); // 1.5 / 5 = 0.3
const [variantSizeImpact, setVariantSizeImpact] = useState(false);
const [selectedFont, setSelectedFont] = useState(null);
const [hoveredFont, setHoveredFont] = useState(null);
const [appState, setAppState] = useState('loading'); // 'loading', 'intro', 'ready'
const [showAboutModal, setShowAboutModal] = useState(false);
// Dataset fixe à 'new' (nouveau pipeline)
const dataset = 'new';
console.log('🔧 FontMap using dataset:', dataset);
const handleFilterChange = (newFilter) => {
setFilter(newFilter);
};
const handleSearchChange = (newSearchTerm) => {
setSearchTerm(newSearchTerm);
};
const handleFontSelect = (font) => {
console.log('FontMap: handleFontSelect called with', font);
setSelectedFont(font);
};
const handleCloseDetails = () => {
setSelectedFont(null);
};
const handleShowAbout = () => {
setShowAboutModal(true);
};
const handleCloseAbout = () => {
setShowAboutModal(false);
};
const handleFontHover = (font) => {
setHoveredFont(font);
};
const handleFontUnhover = () => {
setHoveredFont(null);
};
const handleStartExploring = () => {
setAppState('ready');
};
// Hooks personnalisés
const { fonts, loading, error } = useFontData(dataset);
// Hook pour la navigation aux flèches (doit être avant useD3Visualization)
const { canNavigate, filteredFontsCount } = useArrowNavigation(
selectedFont,
fonts,
filter,
searchTerm,
handleFontSelect
);
const svgRef = useD3Visualization(fonts, filter, searchTerm, darkMode, loading, dilationFactor, characterSize, handleFontSelect, selectedFont, hoveredFont, 0.8, variantSizeImpact, canNavigate);
// Initialiser Tweakpane
const { isDebugMode } = useTweakpane(dilationFactor, setDilationFactor, characterSize, setCharacterSize, darkMode, variantSizeImpact, setVariantSizeImpact);
// Calculer les compteurs pour la recherche
const totalFonts = fonts.length;
// Compter les polices correspondant au filtre actif (sans recherche)
const filterOnlyCount = filter === 'all' ? totalFonts : fonts.filter(font => font.family === filter).length;
// Compter les polices correspondant au filtre ET à la recherche
const filteredFonts = 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;
});
const filteredCount = filteredFonts.length;
// Configurer les callbacks globaux pour le TooltipManager
useEffect(() => {
window.onFontHover = handleFontHover;
window.onFontUnhover = handleFontUnhover;
return () => {
delete window.onFontHover;
delete window.onFontUnhover;
};
}, [handleFontHover, handleFontUnhover]);
// Surveiller les changements de selectedFont
useEffect(() => {
console.log('FontMap: selectedFont state changed to', selectedFont);
}, [selectedFont]);
// Gérer les transitions d'état de l'application
useEffect(() => {
if (loading) {
setAppState('loading');
} else if (fonts.length > 0 && appState === 'loading') {
setAppState('intro');
}
}, [loading, fonts.length, appState]);
// Affichage de l'erreur
if (error) {
return (
<div className="fontmap-container">
<div className="error">
<h3>Erreur de chargement</h3>
<p>{error}</p>
<button onClick={() => window.location.reload()}>
Recharger la page
</button>
</div>
</div>
);
}
return (
<div className={`fontmap-container ${darkMode ? 'dark-mode' : ''}`}>
{/* Sidebar */}
<div className="sidebar">
<div className="sidebar-header">
<div className="search-section">
<SearchBar
searchTerm={searchTerm}
onSearchChange={handleSearchChange}
darkMode={darkMode}
big={true}
filteredCount={filteredCount}
totalCount={filterOnlyCount}
filter={filter}
/>
<FilterControls
fonts={fonts}
filter={filter}
onFilterChange={handleFilterChange}
/>
</div>
</div>
<div className="sidebar-content">
<ActiveFont
selectedFont={selectedFont}
fonts={fonts}
darkMode={darkMode}
onClose={handleCloseDetails}
onFontSelect={handleFontSelect}
/>
</div>
{/* Footer avec liens How it works et Source */}
<div className="sidebar-footer">
<button
className="about-link"
onClick={handleShowAbout}
title="How FontMap Works"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10"/>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
<line x1="12" y1="17" x2="12.01" y2="17"/>
</svg>
How it works
</button>
<a
className="source-link"
href="https://huggingface.co/spaces/huggingface/fontmap"
target="_blank"
rel="noopener noreferrer"
title="View Source on Hugging Face Spaces"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/>
</svg>
Source
</a>
</div>
</div>
{/* Zone principale */}
<div className="main-area">
{/* Titre FontMap en position absolue */}
<h1 className="map-title" data-text="FontMap">FontMap</h1>
{/* Contrôles du bas */}
<div className="bottom-controls">
<ZoomControls />
</div>
{/* Rendu de la carte */}
<div className="map-container">
<svg ref={svgRef} className="fontmap-svg"></svg>
{appState === 'loading' && (
<div className="map-loading-overlay">
<div className="map-loading-spinner">
<div className="spinner-large"></div>
<div className="loading-text">Loading map...</div>
</div>
</div>
)}
</div>
{/* Gestionnaire de tooltips */}
{!loading && fonts.length > 0 && (
<TooltipManager
selectedFont={selectedFont}
hoveredFont={hoveredFont}
darkMode={darkMode}
onFontHover={handleFontHover}
onFontUnhover={handleFontUnhover}
/>
)}
</div>
{/* Overlay unifié pour loading */}
{appState === 'loading' && (
<div className="unified-overlay">
<div className="loading">Loading fonts...</div>
</div>
)}
{/* Modale d'introduction */}
{appState === 'intro' && (
<IntroModal
onStartExploring={handleStartExploring}
darkMode={darkMode}
/>
)}
{/* Modale About */}
{showAboutModal && (
<AboutModal
onClose={handleCloseAbout}
darkMode={darkMode}
/>
)}
{/* Moniteur FPS (dev seulement) */}
<FPSMonitor isDebugMode={isDebugMode} />
</div>
);
};
export default FontMap;
export { FontMap };