File size: 6,908 Bytes
eebc40f 6bda4a6 eebc40f 6bda4a6 eebc40f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
import { useCallback, useRef } from 'react';
import * as d3 from 'd3';
/**
* Hook spécialisé pour la gestion des états visuels (hover, sélection, transitions)
* Centralise toute la logique d'affichage des états visuels
*/
export const useVisualState = () => {
const visualStateRef = useRef({
isTransitioning: false,
selectedFont: null,
hoveredFont: null
});
// Fonction pour mettre à jour les états visuels
const updateVisualStates = useCallback((svg, viewportGroup, selectedFont, hoveredFont, darkMode) => {
if (!svg || !viewportGroup) return;
const glyphGroups = viewportGroup.selectAll('.font-glyph-group');
glyphGroups.each(function(d) {
const group = d3.select(this);
const isActive = selectedFont && selectedFont.name === d.name;
const isHovered = hoveredFont && hoveredFont.name === d.name;
// Supprimer tous les cercles existants
group.select('.active-background-circle').remove();
group.select('.hover-background-circle').remove();
if (isActive) {
// Cercle actif
// Creating active circle
group.insert('circle', ':first-child')
.attr('class', 'active-background-circle')
.attr('r', 12)
.attr('fill', darkMode ? '#000000' : '#ffffff')
.attr('stroke', 'none')
.attr('stroke-width', 0)
.style('pointer-events', 'none')
.style('opacity', 1);
// Forcer l'opacité du groupe parent à 1 pour les lettres actives
group.style('opacity', 1);
// Active circle created
} else if (isHovered) {
// Cercle hover
group.insert('circle', ':first-child')
.attr('class', 'hover-background-circle')
.attr('r', 12)
.attr('fill', darkMode ? '#000000' : '#ffffff')
.style('pointer-events', 'none')
.style('opacity', 1);
// Forcer l'opacité du groupe parent à 1 pour les lettres en hover
group.style('opacity', 1);
}
});
}, []);
// Fonction pour mettre à jour la taille des glyphes
const updateGlyphSizes = useCallback((viewportGroup, selectedFont, characterSize) => {
if (!viewportGroup) return;
console.log('🎯 updateGlyphSizes called with characterSize:', characterSize);
const baseSize = 16;
const currentSize = baseSize * characterSize;
// NOTE: calculateLogarithmicSize supprimé car non utilisé
const fontGlyphs = viewportGroup.selectAll('.font-glyph');
fontGlyphs
.attr('width', d => {
const isActive = selectedFont && selectedFont.name === d.name;
const isMerged = d.fusionInfo && d.fusionInfo.merged;
// Pour les polices fusionnées, ne pas redimensionner (useGlyphRenderer s'en charge)
if (isMerged) {
return isActive ? d.__logSize * 2 : d.__logSize;
}
// Pour les polices normales, utiliser currentSize
return isActive ? currentSize * 2 : currentSize;
})
.attr('height', d => {
const isActive = selectedFont && selectedFont.name === d.name;
const isMerged = d.fusionInfo && d.fusionInfo.merged;
// Pour les polices fusionnées, ne pas redimensionner (useGlyphRenderer s'en charge)
if (isMerged) {
return isActive ? d.__logSize * 2 : d.__logSize;
}
// Pour les polices normales, utiliser currentSize
return isActive ? currentSize * 2 : currentSize;
})
.attr('x', d => {
const isActive = selectedFont && selectedFont.name === d.name;
const isMerged = d.fusionInfo && d.fusionInfo.merged;
// Pour les polices fusionnées, ne pas redimensionner (useGlyphRenderer s'en charge)
if (isMerged) {
const logSize = d.__logSize;
const activeOffset = isActive ? -(logSize * 2) / 2 : -logSize / 2;
return activeOffset;
}
// Pour les polices normales, utiliser currentSize
const activeOffset = isActive ? -(currentSize * 2) / 2 : -currentSize / 2;
return activeOffset;
})
.attr('y', d => {
const isActive = selectedFont && selectedFont.name === d.name;
const isMerged = d.fusionInfo && d.fusionInfo.merged;
// Pour les polices fusionnées, ne pas redimensionner (useGlyphRenderer s'en charge)
if (isMerged) {
const logSize = d.__logSize;
const activeOffset = isActive ? -(logSize * 2) / 2 : -logSize / 2;
return activeOffset;
}
// Pour les polices normales, utiliser currentSize
const activeOffset = isActive ? -(currentSize * 2) / 2 : -currentSize / 2;
return activeOffset;
});
}, []);
// Fonction pour mettre à jour l'opacité des glyphes
const updateGlyphOpacity = useCallback((viewportGroup, positions, filter, searchTerm, selectedFont) => {
if (!viewportGroup) return;
const glyphGroups = viewportGroup.selectAll('.font-glyph-group');
glyphGroups
.data(positions)
.style('opacity', d => {
const familyMatch = filter === 'all' || d.family === filter;
const searchMatch = !searchTerm ||
d.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
d.family.toLowerCase().includes(searchTerm.toLowerCase());
const isActive = selectedFont && selectedFont.name === d.name;
// Si la lettre est active, toujours opacité 1, sinon appliquer les filtres
return isActive ? 1 : (familyMatch && searchMatch ? 1 : 0.2);
});
}, []);
// Fonction pour mettre à jour les couleurs
const updateGlyphColors = useCallback((viewportGroup, fonts, darkMode) => {
if (!viewportGroup) return;
const colorScale = d3.scaleOrdinal(
darkMode
? ['#ffffff', '#cccccc', '#999999', '#666666', '#333333']
: ['#000000', '#333333', '#666666', '#999999', '#cccccc']
);
const families = [...new Set(fonts.map(d => d.family))];
families.forEach(family => colorScale(family));
const fontGlyphs = viewportGroup.selectAll('.font-glyph');
if (!fontGlyphs.empty()) {
fontGlyphs
.attr('fill', darkMode ? '#ffffff' : d => colorScale(d.family))
.style('fill', darkMode ? '#ffffff' : null)
.style('color', darkMode ? '#ffffff' : null);
}
}, []);
// Fonction pour démarrer une transition
const startTransition = useCallback(() => {
visualStateRef.current.isTransitioning = true;
}, []);
// Fonction pour terminer une transition
const endTransition = useCallback(() => {
visualStateRef.current.isTransitioning = false;
}, []);
return {
visualStateRef,
updateVisualStates,
updateGlyphSizes,
updateGlyphOpacity,
updateGlyphColors,
startTransition,
endTransition
};
};
|