Merge pull request #186 from IliesAllali/feat/noms-complets

Tree: optional last name under the first name, maiden name first (#168)
This commit is contained in:
IliesAllali authored and GitHub committed 2026-10-28 00:00:54 +01:00
commit 51ac0de755
5 files changed
+42 -7

No files matched your search

+6
View File
@@ -582,6 +582,9 @@ function App() {
// Grandes belles-familles dans des onglets (réglage de ce navigateur, voir Paramètres de l'arbre)
const [familyTabs, setFamilyTabsState] = useState(() => { try { return localStorage.getItem('parenthese.familyTabs') !== 'off' } catch { return true } })
const setFamilyTabs = useCallback((on) => { setFamilyTabsState(on); try { localStorage.setItem('parenthese.familyTabs', on ? 'on' : 'off') } catch { /* navigateur sans stockage */ } }, [])
// Nom de famille sous le prénom dans l'arbre, celui de naissance s'il est connu (#168). Réglage de ce navigateur.
const [fullNames, setFullNamesState] = useState(() => { try { return localStorage.getItem('parenthese.fullNames') === 'on' } catch { return false } })
const setFullNames = useCallback((on) => { setFullNamesState(on); try { localStorage.setItem('parenthese.fullNames', on ? 'on' : 'off') } catch { /* navigateur sans stockage */ } }, [])
// Premiers visages : fenêtre ouverte après un import GEDCOM, ou depuis les paramètres de l'arbre
const [photoStart, setPhotoStart] = useState({ visible: false, personsImported: null, refs: [], mediaRefs: [], faces: [], bundledFiles: [] })
const photoStartDirtyRef = useRef(false)
@@ -3485,6 +3488,7 @@ function App() {
reorderEnabled={canReorderSiblings}
onReorderSiblings={handleReorderSiblings}
familyTabs={familyTabs}
fullNames={fullNames}
annotationViewRef={annotationViewRef}
onAnnotationsAnchored={persistAnnotationAnchors}
selfPersonId={linkedSelfPersonId || null}
@@ -3659,6 +3663,8 @@ function App() {
onClose={() => setAdminPanelVisible(false)}
familyTabs={familyTabs}
onFamilyTabsChange={setFamilyTabs}
fullNames={fullNames}
onFullNamesChange={setFullNames}
/>
<PhotoStartPanel
visible={photoStart.visible}
+14
View File
@@ -41,6 +41,8 @@ const TREE_ICONS = ['🌳', '🌿', '🌻', '🏡', '❤️', '🧡', '⭐', '
const AdminPanel = ({
familyTabs = true,
onFamilyTabsChange = null,
fullNames = false,
onFullNamesChange = null,
visible,
treeId,
authToken,
@@ -545,6 +547,18 @@ const AdminPanel = ({
<small>{t("Une belle-famille plus large qu'un écran s'ouvre dans son propre onglet au lieu d'être dessinée dans l'arbre. Réglage de ce navigateur.")}</small>
</span>
</label>
<label className={`ts-choice ${fullNames ? 'is-active' : ''}`}>
<input
type="checkbox"
name="fullNames"
checked={fullNames}
onChange={(event) => onFullNamesChange?.(event.target.checked)}
/>
<span className="ts-choice-text">
<strong>{t("Le nom de famille sous le prénom")}</strong>
<small>{t("Le nom de naissance quand il est connu, sinon le nom d'usage. Réglage de ce navigateur.")}</small>
</span>
</label>
</fieldset>
</form>
)}
+4 -1
View File
@@ -47,7 +47,7 @@ const centerX = (node) => node.x + node.width / 2
const easeOutQuint = (t) => 1 - (1 - t) ** 5
const prefersReducedMotion = () => typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches
const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlightIds = [], graphRevision = 0, annotationHandlers = null, transformReadRef = null, zoomApiRef = null, focusPersonId = null, assetRevision = 0, reorderEnabled = false, onReorderSiblings = null, familyTabs = true, annotationViewRef = null, onAnnotationsAnchored = null, selfPersonId = null, ghosts = null, initialFamilyKey = null, onFamilyChange = null }) => {
const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlightIds = [], graphRevision = 0, annotationHandlers = null, transformReadRef = null, zoomApiRef = null, focusPersonId = null, assetRevision = 0, reorderEnabled = false, onReorderSiblings = null, familyTabs = true, fullNames = false, annotationViewRef = null, onAnnotationsAnchored = null, selfPersonId = null, ghosts = null, initialFamilyKey = null, onFamilyChange = null }) => {
const canvasRef = useRef(null)
const layoutDataRef = useRef(null)
// Onglets de familles (réglage `familyTabs`) : la liste des familles de l'arbre, calculée une fois par
@@ -79,6 +79,8 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
useEffect(() => { familyTabsRef.current = familyTabs }, [familyTabs])
// Propositions pas encore relues (utils/ghosts.js) : placées et dessinées en transparence, jamais dans les données
const ghostsRef = useRef(ghosts)
const fullNamesRef = useRef(fullNames)
useEffect(() => { fullNamesRef.current = fullNames; needsRedrawRef.current = true }, [fullNames])
useEffect(() => { ghostsRef.current = ghosts; needsRedrawRef.current = true }, [ghosts])
const onAnnotationsAnchoredRef = useRef(onAnnotationsAnchored)
useEffect(() => { onAnnotationsAnchoredRef.current = onAnnotationsAnchored }, [onAnnotationsAnchored])
@@ -1084,6 +1086,7 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
view,
liftScales: liftScalesRef.current,
ghostPatches: ghostsRef.current?.patches || null,
fullNames: fullNamesRef.current,
})
drawRenvoiPills(ctx)
// Annotations dessinées APRES les noeuds pour que stickers/texte soient au-dessus.
+16 -6
View File
@@ -472,7 +472,7 @@ function measureTextCached(ctx, text, fontKey) {
// ============================================================
function drawPersonNode(ctx, node, pos, nodeProgress, {
nodeRandomData, imageCache, hoverScales, selectedGlowScales, searchGlowScales,
mediaHoverState, entrance, entranceActive, elapsed, view = null, liftScales = null,
mediaHoverState, entrance, entranceActive, elapsed, view = null, liftScales = null, fullNames = false,
}) {
const { cx, cy } = pos
const person = getPersonById(node._data.id) || node._data
@@ -633,15 +633,20 @@ function drawPersonNode(ctx, node, pos, nodeProgress, {
ctx.font = `${nameUnknown ? 'italic ' : ''}400 13px "Newsreader", Georgia, serif`
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
// Réglage « nom de famille sous le prénom » (#168) : celui de naissance s'il est connu, coupé comme le surnom
const familyName = fullNames && withDetail ? String(person.birthName || person.lastName || '').trim() : ''
const familyLabel = familyName ? fitNickname(ctx, familyName, NICKNAME_MAX_W) : ''
const lineShift = familyLabel ? 15 : 0
if (nameLabel) {
if (withDetail) {
const nameMetrics = measureTextCached(ctx, nameLabel, nameUnknown ? 'name-unknown' : 'name')
const nameBlockH = Math.max(
18,
(nameMetrics.actualBoundingBoxAscent || 7) + (nameMetrics.actualBoundingBoxDescent || 4) + 8,
)
const labelGlowY = 6
const labelGlowRx = Math.max(26, Math.round(nameMetrics.width * 0.62))
) + lineShift
const labelGlowY = 6 + lineShift / 2
const labelW = familyLabel ? Math.max(nameMetrics.width, ctx.measureText(familyLabel).width) : nameMetrics.width
const labelGlowRx = Math.max(26, Math.round(labelW * 0.62))
const labelGlowRy = Math.max(16, Math.round(nameBlockH * 1.15))
const labelGlow = ctx.createRadialGradient(0, labelGlowY, 0, 0, labelGlowY, labelGlowRx)
labelGlow.addColorStop(0, 'rgba(251, 250, 247, 0.98)')
@@ -661,13 +666,18 @@ function drawPersonNode(ctx, node, pos, nodeProgress, {
ctx.fillStyle = COLORS.text
ctx.fillText(nameLabel, 0, 0)
}
if (familyLabel) {
ctx.font = '400 13px "Newsreader", Georgia, serif'
ctx.fillStyle = COLORS.text
ctx.fillText(familyLabel, 0, 15)
}
// Surnom sous le nom, en italique : les dates descendent pour lui faire place
const hasNickname = withDetail && Boolean(person.nickname)
if (hasNickname) {
ctx.font = 'italic 300 13px "Newsreader", Georgia, serif'
ctx.fillStyle = COLORS.textLight
ctx.fillText(fitNickname(ctx, person.nickname, NICKNAME_MAX_W), 0, 15)
ctx.fillText(fitNickname(ctx, person.nickname, NICKNAME_MAX_W), 0, 15 + lineShift)
}
if (withDetail) {
@@ -677,7 +687,7 @@ function drawPersonNode(ctx, node, pos, nodeProgress, {
const years = person.deathYear
? `${birthYearLabel}–${person.deathYear}`
: person.isAlive === false ? `${birthYearLabel}–?` : `${birthYearLabel}`
ctx.fillText(years, 0, hasNickname ? 31 : 15)
ctx.fillText(years, 0, (hasNickname ? 31 : 15) + lineShift)
}
ctx.restore()
+2
View File
@@ -6,6 +6,8 @@ export default {
'Affichage': 'Display',
'Les grandes familles par alliance dans des onglets': 'Large in-law families in tabs',
"Une belle-famille plus large qu'un écran s'ouvre dans son propre onglet au lieu d'être dessinée dans l'arbre. Réglage de ce navigateur.": 'An in-law family wider than a screen opens in its own tab instead of being drawn in the tree. Saved in this browser.',
'Le nom de famille sous le prénom': 'Last name under the first name',
"Le nom de naissance quand il est connu, sinon le nom d'usage. Réglage de ce navigateur.": 'The maiden name when it is known, otherwise the current last name. Saved in this browser.',
// Arbre sur canvas (components/galaxy/renderers.js)
'Voir les parents': 'See parents',
'Voir {firstName}': 'See {firstName}',