From 9b77cca7716a1451a346bb5cab410879f41a3b0e Mon Sep 17 00:00:00 2001 From: IliesAllali Date: Sat, 7 Nov 2026 00:04:25 +0100 Subject: [PATCH 1/5] =?UTF-8?q?onglets=20:=20un=20onglet=20par=20nom=20de?= =?UTF-8?q?=20famille,=20seuil=20r=C3=A9glable,=20ancien=20d=C3=A9coupage?= =?UTF-8?q?=20par=20largeur=20retir=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- frontend/src/App.jsx | 12 +- frontend/src/components/AdminPanel.jsx | 48 ++++-- frontend/src/components/Galaxy.jsx | 139 +++--------------- frontend/src/components/SettingsPanels.css | 4 +- frontend/src/components/galaxy/blockLayout.js | 125 +--------------- .../src/components/galaxy/blockLayout.test.js | 33 +---- frontend/src/components/galaxy/elkLayout.js | 2 - .../src/components/galaxy/familyTabsWorker.js | 12 -- frontend/src/components/galaxy/nameTabs.js | 74 ++++++++++ .../src/components/galaxy/nameTabs.test.js | 42 ++++++ .../components/galaxy/nomEtProches.test.js | 47 ------ frontend/src/i18n/en/arbre.js | 15 +- 12 files changed, 190 insertions(+), 363 deletions(-) delete mode 100644 frontend/src/components/galaxy/familyTabsWorker.js create mode 100644 frontend/src/components/galaxy/nameTabs.js create mode 100644 frontend/src/components/galaxy/nameTabs.test.js delete mode 100644 frontend/src/components/galaxy/nomEtProches.test.js diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 571ec68..aae7433 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -585,9 +585,9 @@ function App() { 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. - // Proches des autres familles dessinés, estompés, autour d'un onglet : 0 (ma ligne seulement) à 3 liens - const [tabKinDepth, setTabKinDepthState] = useState(() => { try { const v = localStorage.getItem('parenthese.tabKinDepth'); return ['0', '1', '2', '3'].includes(v) ? Number(v) : 1 } catch { return 1 } }) - const setTabKinDepth = useCallback((d) => { setTabKinDepthState(d); try { localStorage.setItem('parenthese.tabKinDepth', String(d)) } catch { /* navigateur sans stockage */ } }, []) + // Onglets par nom : nombre de porteurs d'un nom à partir duquel il a son onglet (8 par défaut, Patch, 07/11) + const [tabMinCount, setTabMinCountState] = useState(() => { try { const v = Number(localStorage.getItem('parenthese.tabMinCount')); return v >= 2 && v <= 99 ? v : 8 } catch { return 8 } }) + const setTabMinCount = useCallback((n) => { setTabMinCountState(n); try { localStorage.setItem('parenthese.tabMinCount', String(n)) } catch { /* navigateur sans stockage */ } }, []) 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 @@ -3574,7 +3574,7 @@ function App() { reorderEnabled={canReorderSiblings} onReorderSiblings={handleReorderSiblings} familyTabs={familyTabs} - tabKinDepth={tabKinDepth} + tabMinCount={tabMinCount} fullNames={fullNames} annotationViewRef={annotationViewRef} onAnnotationsAnchored={persistAnnotationAnchors} @@ -3756,8 +3756,8 @@ function App() { onClose={() => setAdminPanelVisible(false)} familyTabs={familyTabs} onFamilyTabsChange={setFamilyTabs} - tabKinDepth={tabKinDepth} - onTabKinDepthChange={setTabKinDepth} + tabMinCount={tabMinCount} + onTabMinCountChange={setTabMinCount} fullNames={fullNames} onFullNamesChange={setFullNames} /> diff --git a/frontend/src/components/AdminPanel.jsx b/frontend/src/components/AdminPanel.jsx index d224b5d..2dcd1db 100644 --- a/frontend/src/components/AdminPanel.jsx +++ b/frontend/src/components/AdminPanel.jsx @@ -41,8 +41,8 @@ const TREE_ICONS = ['🌳', '🌿', '🌻', '🏡', '❤️', '🧡', '⭐', ' const AdminPanel = ({ familyTabs = true, onFamilyTabsChange = null, - tabKinDepth = 1, - onTabKinDepthChange = null, + tabMinCount = 8, + onTabMinCountChange = null, fullNames = false, onFullNamesChange = null, visible, @@ -540,27 +540,43 @@ const AdminPanel = ({

{t('Réglages de ce navigateur.')}

-
- {t("Dans un onglet, montrer aussi les proches de l'autre côté")} -
- {[[0, t('Ma ligne seulement')], [1, t('1 lien')], [2, t('2 liens')], [3, t('3 liens')]].map(([d, text]) => ( - - ))} + {familyTabs && ( +
+ + { const n = Math.round(Number(event.target.value)); if (n >= 2 && n <= 99) onTabMinCountChange?.(n) }} + /> + {t('personnes au même nom')}
-

{t('Un lien : un parent, un enfant ou un conjoint. Ils apparaissent en plus clair ; votre propre ligne reste toujours visible.')}

-
+ )} + {familyTabs && ( -
+
Date: Sat, 7 Nov 2026 00:11:32 +0100 Subject: [PATCH 4/5] =?UTF-8?q?r=C3=A9glages=20:=20=C2=AB=20=C3=80=20parti?= =?UTF-8?q?r=20de=20N=20personnes=20au=20m=C3=AAme=20nom=20=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- frontend/src/components/AdminPanel.jsx | 2 +- frontend/src/components/SettingsPanels.css | 2 +- frontend/src/i18n/en/arbre.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/AdminPanel.jsx b/frontend/src/components/AdminPanel.jsx index 372cad2..935855e 100644 --- a/frontend/src/components/AdminPanel.jsx +++ b/frontend/src/components/AdminPanel.jsx @@ -552,7 +552,7 @@ const AdminPanel = ({ {familyTabs && (
- + Date: Sat, 7 Nov 2026 00:45:15 +0100 Subject: [PATCH 5/5] =?UTF-8?q?r=C3=A9glages=20:=20curseur=20du=20seuil,?= =?UTF-8?q?=20onglets=20cr=C3=A9=C3=A9s=20affich=C3=A9s=20en=20direct?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- frontend/src/App.jsx | 2 +- frontend/src/components/AdminPanel.jsx | 32 ++++++++++++++++------ frontend/src/components/SettingsPanels.css | 6 ++-- frontend/src/i18n/en/arbre.js | 4 +-- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index aae7433..2f315c6 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -586,7 +586,7 @@ function App() { 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. // Onglets par nom : nombre de porteurs d'un nom à partir duquel il a son onglet (8 par défaut, Patch, 07/11) - const [tabMinCount, setTabMinCountState] = useState(() => { try { const v = Number(localStorage.getItem('parenthese.tabMinCount')); return v >= 2 && v <= 99 ? v : 8 } catch { return 8 } }) + const [tabMinCount, setTabMinCountState] = useState(() => { try { const v = Number(localStorage.getItem('parenthese.tabMinCount')); return v >= 2 && v <= 30 ? v : 8 } catch { return 8 } }) const setTabMinCount = useCallback((n) => { setTabMinCountState(n); try { localStorage.setItem('parenthese.tabMinCount', String(n)) } catch { /* navigateur sans stockage */ } }, []) 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 */ } }, []) diff --git a/frontend/src/components/AdminPanel.jsx b/frontend/src/components/AdminPanel.jsx index 935855e..fa0ece3 100644 --- a/frontend/src/components/AdminPanel.jsx +++ b/frontend/src/components/AdminPanel.jsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { Download, ImagePlus, Trash2, Unplug, X } from 'lucide-react' import './SettingsPanels.css' import { @@ -25,6 +25,7 @@ import { t } from '../i18n/index.js' import { readGedcomFile } from '../utils/gedcomText.js' import { readGedcomBundle, zipErrorMessage } from '../utils/gedcomBundle.js' import Skeleton from './Skeleton.jsx' +import { nameTabsOf } from './galaxy/nameTabs.js' function normalizeSlug(value) { return String(value || '') @@ -62,6 +63,16 @@ const AdminPanel = ({ onOpenPhotoStart, }) => { const [section, setSection] = useState('tree') + // Seuil des onglets : le curseur montre tout de suite les onglets qu'il crée ; l'arbre ne se recalcule qu'une + // fois le curseur posé + const [tabDraft, setTabDraft] = useState(tabMinCount) + useEffect(() => { setTabDraft(tabMinCount) }, [tabMinCount]) + useEffect(() => { + if (tabDraft === tabMinCount) return undefined + const timer = setTimeout(() => onTabMinCountChange?.(tabDraft), 350) + return () => clearTimeout(timer) + }, [tabDraft, tabMinCount, onTabMinCountChange]) + const tabPreview = useMemo(() => (visible && familyTabs ? nameTabsOf(null, tabDraft).slice(1) : []), [visible, familyTabs, tabDraft]) const [loading, setLoading] = useState(false) const [saving, setSaving] = useState(false) const [errorMessage, setErrorMessage] = useState('') @@ -552,18 +563,21 @@ const AdminPanel = ({ {familyTabs && (
- + { const n = Math.round(Number(event.target.value)); if (n >= 2 && n <= 99) onTabMinCountChange?.(n) }} + max={30} + step={1} + value={tabDraft} + onChange={(event) => setTabDraft(Number(event.target.value))} /> - {t('personnes au même nom')} +

+ {tabPreview.length + ? tabPreview.map((tb) => {tb.label} {tb.count}) + : t("Aucun onglet à ce seuil : tout l'arbre sur une seule page.")} +

)}