mirror of
https://github.com/IliesAllali/parenthese.git
synced 2026-11-13 08:22:03 +00:00
nettoyage : l'ancien découpage par largeur retiré du placement, des pastilles, de la barre et du CSS
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5eb1d9e7bd
commit
1256f09c8f
8 files changed
+35
-282
No files matched your search
@@ -570,7 +570,7 @@ function App() {
|
||||
})
|
||||
|
||||
const [adminPanelVisible, setAdminPanelVisible] = useState(false)
|
||||
// Onglet de famille ouvert : l'adresse de la page le porte (?famille=<charnière>), et le lien à envoyer à la
|
||||
// Onglet de famille ouvert : l'adresse de la page le porte (?famille=<ancêtre de la branche>), et le lien à envoyer à la
|
||||
// famille aussi, pour qu'il ouvre l'arbre sur cette famille
|
||||
const [initialFamilyKey] = useState(() => new URLSearchParams(window.location.search).get('famille'))
|
||||
const [openFamily, setOpenFamily] = useState(null)
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
left: 16px;
|
||||
z-index: 500;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
gap: 16px;
|
||||
max-width: calc(100vw - 120px);
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
@@ -120,9 +120,3 @@
|
||||
.family-tab { font-size: 14px; padding: 6px 12px 7px; }
|
||||
}
|
||||
|
||||
/* Trait d'union entre deux onglets voisins quand le second part du premier (planche A, variante 2) */
|
||||
.family-tabs { gap: 0; }
|
||||
.family-tab + .family-tab:not([style]) { margin-left: 6px; }
|
||||
.family-tab--suite { position: relative; }
|
||||
.family-tab--suite::before { content: ""; position: absolute; right: 100%; top: 50%; width: 10px; height: 1.3px; transform: translateY(-50%); background: #E3A590; pointer-events: none; }
|
||||
.family-tab--trait-on::before { background: #D2694A; }
|
||||
@@ -51,49 +51,21 @@ 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
|
||||
|
||||
// Barre des familles rangée comme l'arbre (Patch, 28/10, planche A variante 2 « le trait d'union ») : chaque
|
||||
// famille juste après celle par laquelle on y entre, la famille de départ en tête. Les onglets d'une même branche
|
||||
// (sous-arbre d'un enfant direct de la famille de départ) se serrent à 10 px, 16 px entre les branches ; un trait
|
||||
// relie deux voisins quand le second part du premier (jamais vers la famille de départ, sa place en tête suffit).
|
||||
// Le trait qui mène à l'onglet ouvert est terracotta, les autres couleur fil.
|
||||
function barTabs(tabs, activeKey) {
|
||||
if (!tabs.length) return []
|
||||
const root = tabs[0]
|
||||
const kids = new Map()
|
||||
for (const tb of tabs.slice(1)) { const pk = tabs.some((x) => x.key === tb.parentKey) ? tb.parentKey : root.key; if (!kids.has(pk)) kids.set(pk, []); kids.get(pk).push(tb) }
|
||||
const order = []
|
||||
const visit = (tb) => { order.push(tb); for (const c of kids.get(tb.key) || []) visit(c) }
|
||||
visit(root)
|
||||
for (const tb of tabs) if (!order.includes(tb)) order.push(tb)
|
||||
const byKey = new Map(tabs.map((tb) => [tb.key, tb]))
|
||||
const pathTo = (tb) => { const out = []; for (let t = tb; t; t = byKey.get(t.parentKey)) out.unshift(t.key); return out }
|
||||
const branchOf = (tb) => { const p = pathTo(tb); return p[1] || p[0] }
|
||||
const active = byKey.get(activeKey) || root
|
||||
const lit = new Set(pathTo(active))
|
||||
return order.map((tb, i) => {
|
||||
const prev = order[i - 1]
|
||||
const gap = i === 0 ? 0 : (prev !== root && branchOf(tb) === branchOf(prev) ? 10 : 16)
|
||||
const trait = i > 0 && prev !== root && tb.parentKey === prev.key ? (lit.has(tb.key) ? 'on' : 'off') : null
|
||||
return { tb, gap, trait }
|
||||
})
|
||||
}
|
||||
|
||||
const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlightIds = [], graphRevision = 0, annotationHandlers = null, transformReadRef = null, zoomApiRef = null, focusPersonId = null, assetRevision = 0, reorderEnabled = false, onReorderSiblings = null, familyTabs = true, tabMinCount = 8, 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
|
||||
// version du graphe et par personne de départ, et l'onglet ouvert. Un onglet est le sous-arbre d'une
|
||||
// grande belle-famille, posé avec le conjoint charnière comme référence.
|
||||
// Onglets de familles (réglage `familyTabs`, galaxy/nameTabs.js) : « Tout l'arbre » puis un onglet par nom de
|
||||
// famille assez porté, calculés une fois par version du graphe et par seuil, et l'onglet ouvert
|
||||
const [tabs, setTabs] = useState([])
|
||||
const [activeKey, setActiveKey] = useState(null)
|
||||
const tabsCacheRef = useRef({ rev: -1, min: null, tabs: [] })
|
||||
const pendingFocusRef = useRef(null) // personne cherchée dans un autre onglet, visée après le changement
|
||||
const pendingFrameRef = useRef(null) // personnes à cadrer une fois l'arbre reposé (ajout de plusieurs prénoms)
|
||||
// Lien d'un onglet (?famille=<charnière>) : ouvert dès que l'arbre et ses onglets sont connus, et de nouveau si
|
||||
// Lien d'un onglet (?famille=<ancêtre de la branche>) : ouvert dès que l'arbre et ses onglets sont connus, et de nouveau si
|
||||
// la personne de départ arrive après (visiteur), tant qu'on n'a pas soi-même changé d'onglet
|
||||
const linkFamilyRef = useRef(initialFamilyKey != null ? String(initialFamilyKey) : null)
|
||||
const pendingFamilyRef = useRef(initialFamilyKey != null ? String(initialFamilyKey) : null)
|
||||
// Famille du lien sans onglet dans ce navigateur (onglets coupés) : l'arbre entier s'ouvre sur sa charnière
|
||||
// Famille du lien sans onglet dans ce navigateur (onglets coupés, ancien lien) : l'arbre entier s'ouvre sur cette personne
|
||||
const frameOnRef = useRef(null)
|
||||
const onFamilyChangeRef = useRef(onFamilyChange)
|
||||
useEffect(() => { onFamilyChangeRef.current = onFamilyChange }, [onFamilyChange])
|
||||
@@ -166,7 +138,6 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
|
||||
return () => window.removeEventListener('resize', updateTabsEdges)
|
||||
}, [activeKey, tabs, updateTabsEdges])
|
||||
// Clic sur un badge : l'onglet de cette famille (ou, depuis un onglet, celui d'où l'on vient)
|
||||
const openFamily = useCallback((tabKey) => { if (tabKey != null) chooseTab(String(tabKey)) }, [chooseTab])
|
||||
// Propriétaire : un portrait vide survolé montre « + photo »
|
||||
const [layoutReady, setLayoutReady] = useState(false)
|
||||
const transformRef = useRef({ x: 0, y: 0, scale: 1 })
|
||||
@@ -260,7 +231,7 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
|
||||
canvasRef, transformRef, targetTransformRef,
|
||||
layoutDataRef, nodeRandomDataRef, entranceRef,
|
||||
hoveredNodeIdRef: hoveredNodeId, hoveredMediaKeyRef: hoveredMediaKey, mouseWorldPosRef: mouseWorldPos,
|
||||
getAnimatedPos, onPersonSelect, onMediaSelect, onOpenFamily: openFamily,
|
||||
getAnimatedPos, onPersonSelect, onMediaSelect,
|
||||
annotationHandlers,
|
||||
minScale: MIN_SCALE,
|
||||
maxScale: MAX_SCALE,
|
||||
@@ -313,7 +284,7 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
|
||||
unions: src.unions,
|
||||
filiations: filiationsWithOrder(src.filiations, src.unions, group.source, order),
|
||||
}
|
||||
const result = computeBlockLayout(graph, focusPersonIdRef.current, { familyTabs: false })
|
||||
const result = computeBlockLayout(graph, focusPersonIdRef.current)
|
||||
const baseSource = base.children.find((n) => n.id === group.source)
|
||||
const source = result.children.find((n) => n.id === group.source)
|
||||
const shift = baseSource && source ? baseSource.x - source.x : 0
|
||||
@@ -503,7 +474,7 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
|
||||
if (!node && familyTabsRef.current) {
|
||||
const id = String(personId)
|
||||
const all = tabsCacheRef.current.tabs
|
||||
const home = all.find((tb) => tb.personIds?.has(id) && !(tb.hinges || []).map(String).includes(id)) || all.find((tb) => tb.personIds?.has(id)) || all[0]
|
||||
const home = all.find((tb) => tb.personIds?.has(id)) || all[0]
|
||||
if (home && home.key !== activeTabRef.current?.key) { pendingFocusRef.current = id; chooseTab(home.key) }
|
||||
return
|
||||
}
|
||||
@@ -595,7 +566,7 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
|
||||
setTabs(list)
|
||||
}
|
||||
if (!familyTabs && cache.tabs.length) { tabsCacheRef.current = { rev: -1, min: null, tabs: [] }; setTabs([]) }
|
||||
// La famille du lien : son onglet s'il existe dans ce navigateur, sinon l'arbre entier ouvert sur sa charnière
|
||||
// La famille du lien : son onglet s'il existe dans ce navigateur, sinon l'arbre entier ouvert sur cette personne
|
||||
if (pendingFamilyRef.current != null && graphPersons.length) {
|
||||
const key = pendingFamilyRef.current
|
||||
pendingFamilyRef.current = null
|
||||
@@ -620,8 +591,8 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
|
||||
// Annotations d'avant l'ancrage (x, y seuls) : rattachées une fois à la personne la plus proche dans le
|
||||
// placement de l'onglet de départ, puis enregistrées. Calculé avant l'arbre affiché (coupleBarMeta, voir plus haut)
|
||||
const legacy = annotationData.some((a) => !readAnchor(a))
|
||||
const departure = legacy && USE_BLOCK_LAYOUT && graph ? computeBlockLayout(null, startRefRef.current, { familyTabs: false }) : null
|
||||
const result = USE_BLOCK_LAYOUT ? computeBlockLayout(graph, focusPersonIdRef.current, { familyTabs: false }) : computeLayout(null, focusPersonIdRef.current)
|
||||
const departure = legacy && USE_BLOCK_LAYOUT && graph ? computeBlockLayout(null, startRefRef.current) : null
|
||||
const result = USE_BLOCK_LAYOUT ? computeBlockLayout(graph, focusPersonIdRef.current) : computeLayout(null, focusPersonIdRef.current)
|
||||
if (legacy) {
|
||||
const anchored = anchorLegacy(annotationData, layoutCenters(departure || result))
|
||||
for (const ann of anchored) updateAnnotationInStore(ann.id, { style: ann.style })
|
||||
@@ -1270,14 +1241,13 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
|
||||
)}
|
||||
{familyTabs && tabs.length > 1 && (
|
||||
<nav ref={tabsBarRef} onScroll={updateTabsEdges} className={`family-tabs${annotationHandlers ? ' family-tabs--lowered' : ''}${tabsEdges}`} aria-label={tr('Familles')}>
|
||||
{barTabs(tabs, activeKey).map(({ tb, gap, trait }) => {
|
||||
{tabs.map((tb) => {
|
||||
const active = tb.key === (tabs.some((x) => x.key === activeKey) ? activeKey : tabs[0].key)
|
||||
return (
|
||||
<button
|
||||
key={tb.key}
|
||||
type="button"
|
||||
style={gap ? { marginLeft: gap } : undefined}
|
||||
className={`family-tab${active ? ' is-active' : ''}${trait ? ` family-tab--suite family-tab--trait-${trait}` : ''}`}
|
||||
className={`family-tab${active ? ' is-active' : ''}`}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
onClick={() => chooseTab(tb.key)}
|
||||
onMouseEnter={() => { if (!active) hoverTab(tb.key) }}
|
||||
|
||||
@@ -42,18 +42,6 @@ const RENVOI_FAR_PX = 1440
|
||||
const PARENT_SHIFT_PX = 400
|
||||
// Côté d'une belle-famille : le côté extérieur de la branche, sauf si l'autre côté la rapproche d'au moins ça
|
||||
const SIDE_SWITCH_PX = 1440
|
||||
// Carte de famille et pont (maquette A, retenue le 19/10). Une belle-famille plus large que ça ne se colle pas à
|
||||
// son enfant : elle devient un cluster dessiné à part, et une carte prend sa place au-dessus du conjoint
|
||||
// Une belle-famille plus large que ça n'est pas dessinée dans cet arbre : elle devient un onglet (son propre
|
||||
// arbre, calculé avec le conjoint comme référence) et le conjoint porte un badge vers elle.
|
||||
const FAMILY_TAB_PX = 2880
|
||||
// Une belle-famille poussée plus loin que ça de son enfant ne se colle pas : seuls les parents du conjoint
|
||||
// restent au-dessus de lui, le reste devient un onglet
|
||||
const FAMILY_FIT_PX = 480
|
||||
// Une belle-famille qui grimpe au-dessus des parents du conjoint reste dessinée si elle est toute petite
|
||||
const FAMILY_INLINE_MAX = 6
|
||||
// En dessous, une famille sortie de l'arbre ne fait pas d'onglet : elle reste dessinée
|
||||
const FAMILY_TAB_MIN = 10
|
||||
|
||||
// Le fil d'une filiation union → enfant : une personne d'une autre famille, sur la ligne de l'enfant,
|
||||
// entre l'aplomb de l'union et l'enfant, est croisée. Frères et sœurs et leurs conjoint(e)s ne comptent pas
|
||||
@@ -97,50 +85,34 @@ const overlaps = (l, r, list, gap) => list.some(([a, b]) => l < b + gap && r > a
|
||||
// Règle globale (Patch, 19/10) : l'arbre s'ouvre à partir de la personne de référence. Toute belle-famille,
|
||||
// grosse ou petite, part du côté opposé à la référence (à droite pour un conjoint à sa droite), le couple se
|
||||
// tourne vers elle. Ce côté ne se connaît qu'une fois la lignée posée, d'où une seconde passe.
|
||||
// `familyTabs` : vrai, une belle-famille trop large devient un onglet ; faux, tout l'arbre est dessiné d'un
|
||||
// tenant (mode « tout l'arbre »), les grandes familles sont seulement signalées dans `families`.
|
||||
export function computeBlockLayout(graphData = null, referenceId = null, { familyTabs = true } = {}) {
|
||||
export function computeBlockLayout(graphData = null, referenceId = null) {
|
||||
const forced = new Map()
|
||||
let result = layoutOnce(graphData, referenceId, forced, familyTabs)
|
||||
let result = layoutOnce(graphData, referenceId, forced)
|
||||
// Chaque passe fixe le côté des belles-familles d'après la passe précédente ; on s'arrête dès que plus rien
|
||||
// ne change, ou après quatre passes (les positions bougent d'une passe à l'autre)
|
||||
for (let pass = 0; pass < 4 && result._inlaws?.length; pass++) {
|
||||
const byId = new Map(result.children.map(n => [n.id, n]))
|
||||
const persons = result.children.filter(n => n._type === 'person')
|
||||
// Pivot : le centre de la famille dessinée. La référence elle-même, souvent au bord (dernière génération,
|
||||
// charnière d'un onglet), enverrait toutes les belles-familles du même côté (Patch, 19/10 : « moins clair »)
|
||||
// ou un conjoint), enverrait toutes les belles-familles du même côté (Patch, 19/10 : « moins clair »)
|
||||
const pivot = persons.length ? (Math.min(...persons.map(n => n.x)) + Math.max(...persons.map(n => n.x + n.width))) / 2 : 0
|
||||
let changed = false
|
||||
for (const { anchorId, side } of result._inlaws) {
|
||||
const a = byId.get(anchorId)
|
||||
if (!a) continue // conjoint d'une famille devenue onglet : pas dans cet arbre
|
||||
if (!a) continue
|
||||
const want = a.x + a.width / 2 < pivot ? -1 : 1
|
||||
if (want !== side && forced.get(anchorId) !== want) { forced.set(anchorId, want); changed = true }
|
||||
}
|
||||
if (!changed) break
|
||||
result = layoutOnce(graphData, referenceId, forced, familyTabs)
|
||||
result = layoutOnce(graphData, referenceId, forced)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
|
||||
function layoutOnce(graphData, referenceId, forcedSide) {
|
||||
const result = computeLayout(graphData, referenceId)
|
||||
const nodes = result.children
|
||||
const byId = new Map(nodes.map(n => [n.id, n]))
|
||||
// Nom d'une famille : celui que portent le plus ses membres (nom de naissance, sinon nom), pas celui de la seule
|
||||
// personne de référence. Ex. : 175 personnes dont 5 portent le nom de la référence. Égalité : le nom
|
||||
// de secours (celui de la référence) s'il est parmi les premiers, sinon le premier rencontré.
|
||||
const nomMajoritaire = (ids, secours = '') => {
|
||||
const n = new Map()
|
||||
for (const id of ids) {
|
||||
const d = byId.get(id)?._type === 'person' ? byId.get(id)._data : null
|
||||
const nom = (d?.birthName || d?.lastName || '').trim()
|
||||
if (nom) n.set(nom, (n.get(nom) || 0) + 1)
|
||||
}
|
||||
let best = '', max = 0
|
||||
for (const [nom, c] of n) if (c > max) { best = nom; max = c }
|
||||
return secours && n.get(secours) === max ? secours : (best || secours)
|
||||
}
|
||||
|
||||
// Chaînes de couples (composantes d'une rangée) telles que computeLayout les a ordonnées
|
||||
const chainOf = new Map()
|
||||
@@ -246,10 +218,6 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
|
||||
const placed = new Set()
|
||||
const externalEdges = new Set() // `${union}>${enfant}` : enfant dessiné sous une autre lignée
|
||||
const detached = [] // familles qu'on n'a pas pu coller à leur enfant (trop loin) : posées à part, renvoi
|
||||
const tabs = [] // belles-familles trop larges pour cet arbre, devenues onglets : { anchorId, pu, label, count, members }
|
||||
const hiddenChains = new Set() // chaînes des familles devenues onglets : pas dessinées dans cet arbre
|
||||
const tabOfChain = new Map() // chaîne cachée → son onglet : un second conjoint de la même famille renvoie au même onglet
|
||||
const bigFamilies = [] // mode « tout l'arbre » : les familles qui seraient des onglets, dessinées quand même
|
||||
const attachedEdges = new Set() // `${union}>${enfant}` : fil d'une famille collée à son enfant, toujours dessiné
|
||||
|
||||
// Imbrique des blocs de gauche à droite, rangée par rangée
|
||||
@@ -320,25 +288,8 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
|
||||
|
||||
// `outer` : côté extérieur hérité de la lignée (−1 gauche, +1 droite). Les belles-familles d'une branche vont
|
||||
// toutes du même côté, celui où la branche est au bord de l'arbre, pour ne pas se poser entre deux familles.
|
||||
// Défaire ce qu'une construction a posé : chaînes, onglets nés pendant elle, arêtes externes notées
|
||||
const chainsOf = (members) => new Set([...members].map(m => chainOf.get(m)).filter(Boolean))
|
||||
const unplace = (members) => { for (const ch of chainsOf(members)) { placed.delete(ch); hiddenChains.delete(ch); tabOfChain.delete(ch) } }
|
||||
const forgetTabs = (from, to) => {
|
||||
const nested = new Set()
|
||||
for (const tb of tabs.splice(from, to - from)) {
|
||||
unplace(tb.members)
|
||||
for (const m of tb.members) nested.add(m)
|
||||
for (const h of tb.hinges) { byId.get(h)._familyTab = null; nested.add(h) }
|
||||
byId.get(tb.anchorId)._familyTab = null
|
||||
}
|
||||
return nested
|
||||
}
|
||||
const forgetExternal = (before, after) => { for (const e of after) if (!before.has(e)) externalEdges.delete(e) }
|
||||
const registerTab = (tab) => { tabs.push(tab); for (const ch of chainsOf(tab.members)) { hiddenChains.add(ch); tabOfChain.set(ch, tab) } }
|
||||
|
||||
// `parentsOnly` : la chaîne est au milieu de sa fratrie, les belles-familles de ses conjoints se réduisent
|
||||
// aux parents au-dessus du conjoint, le reste devient un onglet
|
||||
function buildBlock(chain, ownerUnion = null, outer = 0, parentsOnly = false) {
|
||||
function buildBlock(chain, ownerUnion = null, outer = 0) {
|
||||
placed.add(chain)
|
||||
const gen = genOf(chain)
|
||||
const childBlocks = []
|
||||
@@ -361,8 +312,6 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
|
||||
// Belle-famille en construction : une chaîne dont aucun membre du sang n'est enfant de cette union, mais
|
||||
// dont un membre du sang a ses parents ailleurs, ira sous ces parents-là (le sang de la référence gagne)
|
||||
const duSang = anchor && !kc.some(id => blood.has(id) && parentUnionOf.get(id) === unionId) && kc.some(id => blood.has(id) && byId.get(id)._type === 'person' && parentUnionOf.has(id))
|
||||
// Un enfant parti dans un onglet ne laisse pas de place vide : ses parents se centrent sur les enfants dessinés
|
||||
if (tabOfChain.has(kc)) return
|
||||
if (placed.has(kc) || duSang) {
|
||||
external.push({ kid, unionId })
|
||||
externalEdges.add(`${unionId}>${kid}`)
|
||||
@@ -370,10 +319,8 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
|
||||
return
|
||||
}
|
||||
const side = sideOfUnion(unionId) || outer || (i < (kidsOfChain.length - 1) / 2 ? -1 : 1)
|
||||
const snap = { tabsBefore: tabs.length, externalBefore: new Set(externalEdges) }
|
||||
const b = buildBlock(kc, unionId, side)
|
||||
snap.tabsAfter = tabs.length; snap.externalAfter = new Set(externalEdges)
|
||||
childBlocks.push({ ...b, unionId, kid, side, snap })
|
||||
childBlocks.push({ ...b, unionId, kid, side })
|
||||
})
|
||||
// Un enfant dont le conjoint amène sa famille va au bout de la fratrie, de son côté : sa belle-famille,
|
||||
// qui fait partie de son bloc, se retrouve au bord, sans survoler ses frères et sœurs. L'ordre de
|
||||
@@ -392,20 +339,6 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
|
||||
const i = kids.findIndex(b => b.kid != null && anchor.path.has(chainOf.get(b.kid)))
|
||||
if (i >= 0) { const [b] = kids.splice(i, 1); if (anchor.end === 'right') kids.push(b); else kids.unshift(b) }
|
||||
}
|
||||
// Règle du bout (Patch, 19/10 soir) : seule une belle-famille au bout de la fratrie est dessinée entière.
|
||||
// Un enfant marié au milieu de sa fratrie est rebâti avec les seuls parents de son conjoint au-dessus de
|
||||
// lui, le reste de la belle-famille devient un onglet. Ainsi aucun fil ne passe sous une autre famille.
|
||||
const middle = kids.map((b, i) => ({ b, i })).filter(({ b, i }) => i > 0 && i < kids.length - 1 && !b.ghost && b.attachBig)
|
||||
middle.sort((x, y) => y.b.snap.tabsBefore - x.b.snap.tabsBefore)
|
||||
for (const { b, i } of middle) {
|
||||
forgetTabs(b.snap.tabsBefore, b.snap.tabsAfter)
|
||||
unplace(b.members)
|
||||
forgetExternal(b.snap.externalBefore, b.snap.externalAfter)
|
||||
const snap = { tabsBefore: tabs.length, externalBefore: new Set(externalEdges) }
|
||||
const nb = buildBlock(chainOf.get(b.kid), b.unionId, b.side, true)
|
||||
snap.tabsAfter = tabs.length; snap.externalAfter = new Set(externalEdges)
|
||||
kids[i] = { ...nb, unionId: b.unionId, kid: b.kid, side: b.side, snap }
|
||||
}
|
||||
childBlocks.length = 0
|
||||
childBlocks.push(...kids)
|
||||
const packed = packBlocks(childBlocks, FAMILY_GAP)
|
||||
@@ -417,35 +350,6 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
|
||||
// déjà dessinée (reliée deux fois) ne l'est pas une seconde fois : la filiation reste externe, renvoi si
|
||||
// son fil croise une autre famille.
|
||||
const built = []
|
||||
// Réduire une belle-famille à ses seuls parents : frères, sœurs et ancêtres du conjoint deviennent un onglet
|
||||
// dont les parents sont les charnières (badge sur eux), puis les parents seuls sont rebâtis au-dessus du conjoint
|
||||
function reduceToParents(entry) {
|
||||
const { block, anchorId: id, side, pu, pc, snap } = entry
|
||||
// Arbre dessiné en entier (onglets par nom, ou pas d'onglets) : personne n'est caché derrière une pastille
|
||||
if (!familyTabs) return block
|
||||
const pcPersons = pc.filter(x => byId.get(x)._type === 'person')
|
||||
const keep = new Set(pcPersons)
|
||||
const extra = [...block.members].filter(m => byId.get(m)?._type === 'person' && !keep.has(m) && chainOf.get(m) !== pc)
|
||||
// Une famille de FAMILY_TAB_MIN personnes ou moins reste dessinée : pas d'onglet pour si peu (Patch, 20/10)
|
||||
if (extra.length <= FAMILY_TAB_MIN) return block
|
||||
const nested = forgetTabs(snap.tabsBefore, snap.tabsAfter)
|
||||
unplace(block.members)
|
||||
forgetExternal(snap.externalBefore, snap.externalAfter)
|
||||
const A = byId.get(id)
|
||||
const members = new Set([...extra, ...nested].filter(m => !keep.has(m)))
|
||||
const label = nomMajoritaire(members, A._data?.birthName || byId.get(pcPersons[0])?._data?.lastName || A._data?.lastName || '')
|
||||
registerTab({ anchorId: pcPersons[0], pu: parentUnionOf.get(pcPersons[0]) || pu, label, count: members.size, members, hinges: pcPersons.slice(1), hingeEdges: [] })
|
||||
for (const x of pcPersons) byId.get(x)._familyTab = { anchorId: pcPersons[0], label, count: members.size, tabKey: pcPersons[0].replace(/^p-/, ''), place: 'above' }
|
||||
const saved = anchor
|
||||
anchor = { kid: id, end: side < 0 ? 'right' : 'left', path: pathOf(id) }
|
||||
snap.tabsBefore = tabs.length
|
||||
snap.externalBefore = new Set(externalEdges)
|
||||
const nb = buildBlock(pc, null, side)
|
||||
snap.tabsAfter = tabs.length
|
||||
snap.externalAfter = new Set(externalEdges)
|
||||
anchor = saved
|
||||
return nb
|
||||
}
|
||||
// L'enfant de la lignée dans cette chaîne (celui dont les parents sont ownerUnion), s'il est seul
|
||||
const kidsHere = chain.filter(id => byId.get(id)._type === 'person' && ownerUnion && parentUnionOf.get(id) === ownerUnion)
|
||||
const kidOfChain = kidsHere.length === 1 ? kidsHere[0] : null
|
||||
@@ -457,65 +361,14 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
|
||||
if (placed.has(pc)) continue
|
||||
const top = topChainOf(pc)
|
||||
if (placed.has(top)) continue
|
||||
const owner = tabOfChain.get(top) || tabOfChain.get(pc)
|
||||
if (owner) {
|
||||
// Sa famille est déjà un onglet (un autre conjoint l'a amenée) : même badge, même onglet
|
||||
const A = byId.get(id)
|
||||
A._familyTab = { anchorId: owner.anchorId, label: owner.label, count: owner.count, tabKey: owner.anchorId.replace(/^p-/, ''), place: 'above' }
|
||||
owner.hinges.push(id)
|
||||
owner.hingeEdges.push([pu, id])
|
||||
continue
|
||||
}
|
||||
// Le côté extérieur de la branche prime : une branche au bout de sa fratrie pose ses belles-familles au-delà,
|
||||
// jamais dans la fratrie des aînés (Patch, 20/10 : la branche de Paul à droite d'Eugénie)
|
||||
const side = (chain.length === 3 && outer) ? outer : (forcedSide.get(id) ?? (chain.length > 3 ? (chain.indexOf(id) < chain.length / 2 ? -1 : 1) : (chain.indexOf(id) < chain.length / 2 ? -1 : 1)))
|
||||
const saved = anchor
|
||||
const tabsBefore = tabs.length
|
||||
const externalBefore = new Set(externalEdges)
|
||||
anchor = { kid: id, end: side < 0 ? 'right' : 'left', path: pathOf(id) }
|
||||
let block = buildBlock(top, null, side)
|
||||
const block = buildBlock(top, null, side)
|
||||
anchor = saved
|
||||
if (block.width > FAMILY_TAB_PX && !familyTabs) {
|
||||
const A = byId.get(id)
|
||||
bigFamilies.push({ anchorId: id, label: nomMajoritaire(block.members, A._data?.birthName || A._data?.lastName || ''), count: [...block.members].filter(m => byId.get(m)?._type === 'person').length })
|
||||
}
|
||||
if (block.width > FAMILY_TAB_PX && familyTabs) {
|
||||
// Trop large pour cet arbre : la famille devient un onglet et n'est pas dessinée ici. Sa construction ne
|
||||
// laisse aucune trace (onglets imbriqués, chaînes posées, arêtes externes), et le fil de ses parents
|
||||
// vers le conjoint est caché : c'est le badge du conjoint qui dit où est sa famille.
|
||||
// Les onglets nés pendant cette construction (familles emboîtées) rejoignent celle-ci : son onglet les
|
||||
// retrouvera en se dessinant, avec ses propres onglets
|
||||
const nested = forgetTabs(tabsBefore, tabs.length)
|
||||
unplace(block.members)
|
||||
forgetExternal(externalBefore, new Set(externalEdges))
|
||||
const A = byId.get(id)
|
||||
const partners = [byId.get(pu)?._data?.p1Key, byId.get(pu)?._data?.p2Key].map(k => byId.get(k)?._data).filter(Boolean)
|
||||
const members = new Set([...[...block.members].filter(m => byId.get(m)?._type === 'person'), ...nested])
|
||||
const label = nomMajoritaire(members, A._data?.birthName || partners.find(d => d.lastName)?.lastName || A._data?.lastName || '')
|
||||
registerTab({ anchorId: id, pu, label, count: members.size, members, hinges: [], hingeEdges: [[pu, id]] })
|
||||
A._familyTab = { anchorId: id, label, count: members.size, tabKey: id.replace(/^p-/, ''), place: 'above' }
|
||||
continue
|
||||
}
|
||||
const entry = { block, anchorId: id, side, pu, pc, snap: { tabsBefore, tabsAfter: tabs.length, externalBefore, externalAfter: new Set(externalEdges) } }
|
||||
// Une belle-famille collée ne déborde pas, au-dessus de la rangée des parents du conjoint, de la place de
|
||||
// ces parents : des ancêtres plus larges grimperaient dans les rangées des aînés de la lignée et s'y
|
||||
// glisseraient entre deux frères (Patch, 20/10, Marc et Sophie entre Anaïs et Germaine). Les parents
|
||||
// restent, le reste devient un onglet. Des ancêtres en colonne au-dessus des parents restent dessinés.
|
||||
// Une toute petite famille (jusqu'à FAMILY_INLINE_MAX personnes au-delà des parents) reste dessinée,
|
||||
// à condition que ce qui est au-dessus des parents tienne dans leur largeur (une colonne d'ancêtres),
|
||||
// sinon elle déborderait dans les rangées des aînés (Paul et ses frères dans la fratrie de Catherine)
|
||||
const deep = [...block.rows.keys()].some(g => g < gen - 1)
|
||||
const beyondParents = deep ? [...block.members].filter(m => byId.get(m)?._type === 'person' && chainOf.get(m) !== pc).length : 0
|
||||
// Au-dessus des parents, seuls les ancêtres en ligne directe du conjoint sont admis : des oncles ou tantes
|
||||
// élargiraient ces rangées et la famille se glisserait dans la fratrie des aînés de la lignée
|
||||
const ancestors = new Set()
|
||||
for (const stack = [id]; stack.length;) {
|
||||
const u = parentUnionOf.get(stack.pop())
|
||||
if (!u) continue
|
||||
for (const k of [byId.get(u)?._data?.p1Key, byId.get(u)?._data?.p2Key]) if (k && !ancestors.has(k)) { ancestors.add(k); stack.push(k) }
|
||||
}
|
||||
const collaterals = deep && [...block.members].some(m => { const n = byId.get(m); return n?._type === 'person' && n._generation < gen - 1 && !ancestors.has(m) })
|
||||
if (parentsOnly || (deep && (beyondParents > FAMILY_INLINE_MAX || collaterals))) entry.block = reduceToParents(entry)
|
||||
const entry = { block, anchorId: id, side, pu, pc }
|
||||
built.push(entry)
|
||||
orient(chain, kidOfChain ?? (chain.length === 3 ? (chain[0] === id ? chain[2] : chain[0]) : null), side)
|
||||
}
|
||||
@@ -641,14 +494,7 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
|
||||
}
|
||||
return res
|
||||
}
|
||||
let result = placeBest()
|
||||
// 3) Une belle-famille qui ne se colle pas à son enfant (poussée de plus de FAMILY_FIT_PX) se réduit aux
|
||||
// parents du conjoint, le reste devient un onglet, et tout se pose à nouveau
|
||||
const tooFar = result.list.map((a, i) => ({ a, p: built[i] })).filter(({ a, p }) => Math.abs(a.x - a.ideal) > FAMILY_FIT_PX && p.block.rows.size > 1)
|
||||
if (tooFar.length) {
|
||||
for (const { p } of [...tooFar].sort((u, v) => v.p.snap.tabsBefore - u.p.snap.tabsBefore)) p.block = reduceToParents(p)
|
||||
result = placeBest()
|
||||
}
|
||||
const result = placeBest()
|
||||
rows.clear()
|
||||
for (const [g, l] of result.rows) rows.set(g, l)
|
||||
const attachments = result.list.map(a => ({ block: a.block, x: a.x }))
|
||||
@@ -656,8 +502,6 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
|
||||
attachedEdges.add(`${parentUnionOf.get(a.anchorId)}>${a.anchorId}`)
|
||||
}
|
||||
const attachSide = built.length ? (built.every(p => p.side < 0) ? -1 : built.every(p => p.side > 0) ? 1 : 0) : 0
|
||||
// Une belle-famille attachée qui dépasse les seuls parents (frères et sœurs ou ancêtres du conjoint)
|
||||
const attachBig = attachments.some(a => a.block.rows.size > 1)
|
||||
for (const p of built) inlaws.push({ anchorId: p.anchorId, side: p.side })
|
||||
|
||||
const members = new Set(chain)
|
||||
@@ -690,7 +534,7 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
rows, members, external, gen, rel, attachSide, attachBig,
|
||||
rows, members, external, gen, rel, attachSide,
|
||||
chain: [rowX, rowX + rowW],
|
||||
width: extentOf(rows)[1],
|
||||
place(x0) {
|
||||
@@ -729,27 +573,11 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
|
||||
(isBloodChain(b) - isBloodChain(a)) || countDesc(b) - countDesc(a) || genOf(a) - genOf(b) || birthYearOf(a) - birthYearOf(b) || String(a[0]).localeCompare(String(b[0]))
|
||||
))
|
||||
const free = []
|
||||
for (const r of roots) if (!placed.has(r) && !hiddenChains.has(r)) free.push(buildBlock(r))
|
||||
for (const c of chains) if (!placed.has(c) && !hiddenChains.has(c)) free.push(buildBlock(c))
|
||||
for (const r of roots) if (!placed.has(r)) free.push(buildBlock(r))
|
||||
for (const c of chains) if (!placed.has(c)) free.push(buildBlock(c))
|
||||
free.push(...detached)
|
||||
const forest = packBlocks(free, FAMILY_GAP * 2)
|
||||
free.forEach((b, i) => b.place(forest.xs[i]))
|
||||
const referenceNode = result.referenceId != null ? byId.get(`p-${result.referenceId}`) : null
|
||||
// Le fil des parents d'un conjoint dont la famille est un onglet n'est pas dessiné : son badge dit où elle est
|
||||
for (const tb of tabs) for (const [pu, id] of tb.hingeEdges) for (const e of result.edges) if (e.sources[0] === pu && e.targets[0] === id) e._hidden = true
|
||||
|
||||
// Les familles devenues onglets sortent de cet arbre : leurs nœuds et leurs arêtes ne sont pas dessinés
|
||||
if (hiddenChains.size) {
|
||||
const hidden = new Set([...hiddenChains].flatMap(ch => ch))
|
||||
for (const n of nodes) if (n._type === 'unknown' && hidden.has(n.id.replace(/^unknown-/, 'p-'))) hidden.add(n.id)
|
||||
result.children = nodes.filter(n => !hidden.has(n.id))
|
||||
// Un onglet se propose depuis le badge d'un conjoint dessiné : ceux dont le conjoint est caché avec sa famille tombent
|
||||
for (let i = tabs.length - 1; i >= 0; i--) if (hidden.has(tabs[i].anchorId)) tabs.splice(i, 1)
|
||||
for (const tb of tabs) tb.hinges = tb.hinges.filter(h => !hidden.has(h))
|
||||
result.edges = result.edges.filter(e => !hidden.has(e.sources[0]) && !hidden.has(e.targets[0]))
|
||||
nodes.length = 0
|
||||
nodes.push(...result.children)
|
||||
}
|
||||
// Réinjecter le X, normalisé à 0 ; Y inchangé (rangées)
|
||||
let minX = Infinity
|
||||
for (const n of nodes) if (pos.has(n.id)) minX = Math.min(minX, pos.get(n.id))
|
||||
@@ -830,14 +658,6 @@ function layoutOnce(graphData, referenceId, forcedSide, familyTabs = true) {
|
||||
for (const r of list) byId.get(r.id)._railLift = Math.min(4, liftOf(r.id))
|
||||
}
|
||||
|
||||
// Les familles de cet arbre : la lignée dessinée, puis chaque belle-famille devenue onglet. Galaxy en fait
|
||||
// la barre d'onglets ; un onglet est le même arbre recalculé avec le conjoint comme référence.
|
||||
const drawn = nodes.filter(n => n._type === 'person')
|
||||
result.families = [
|
||||
{ id: 'main', label: nomMajoritaire(drawn.map(n => n.id), referenceNode?._data?.lastName || drawn[0]?._data?.lastName || ''), count: drawn.length, referenceId: result.referenceId ?? null },
|
||||
...tabs.map(tb => ({ id: tb.anchorId, anchorId: tb.anchorId, label: tb.label, count: tb.count, members: [...tb.members].map(m => m.replace(/^p-/, '')), hinges: [tb.anchorId, ...tb.hinges].map(m => m.replace(/^p-/, '')) })),
|
||||
...bigFamilies.map(f => ({ id: f.anchorId, anchorId: f.anchorId, label: f.label, count: f.count, members: [], drawn: true })),
|
||||
]
|
||||
result._inlaws = inlaws
|
||||
|
||||
return result
|
||||
|
||||
@@ -20,10 +20,9 @@ describe('dessin complet, sans onglets par largeur', () => {
|
||||
it(`${kind} graine ${seed}`, () => {
|
||||
const tree = generate(kind, seed)
|
||||
const { graph } = tree
|
||||
const result = computeBlockLayout(graph, tree.rootPersonId, { familyTabs: false })
|
||||
const result = computeBlockLayout(graph, tree.rootPersonId)
|
||||
const people = result.children.filter((n) => n._type === 'person')
|
||||
expect(people.length).toBe(graph.persons.length)
|
||||
expect(people.filter((n) => n._familyTab).length).toBe(0)
|
||||
expect(overlaps(people)).toBe(0)
|
||||
})
|
||||
}
|
||||
@@ -53,9 +52,8 @@ function belleFamille() {
|
||||
describe('dessin complet, grande belle-famille', () => {
|
||||
it('tout le monde est dessiné, sans pastille', () => {
|
||||
const graph = belleFamille()
|
||||
const result = computeBlockLayout(graph, 'henri', { familyTabs: false })
|
||||
const result = computeBlockLayout(graph, 'henri')
|
||||
const people = result.children.filter((n) => n._type === 'person')
|
||||
expect(people.filter((n) => n._familyTab).length).toBe(0)
|
||||
expect(people.length).toBe(graph.persons.length)
|
||||
expect(overlaps(people)).toBe(0)
|
||||
})
|
||||
|
||||
@@ -852,7 +852,7 @@ export function computeLayout(graphData = null, referenceId = null) {
|
||||
export function generateNodeRandomData(children, edges = []) {
|
||||
const data = new Map()
|
||||
// Enfant d'un renvoi : la pastille « Voir les parents » occupe le haut du portrait, l'orbite reste sur les côtés
|
||||
const renvoiChildren = new Set([...edges.filter(e => e._renvoi).map(e => e.targets[0]), ...children.filter(n => n._familyTab).map(n => n.id)])
|
||||
const renvoiChildren = new Set(edges.filter(e => e._renvoi).map(e => e.targets[0]))
|
||||
const deg = (d) => d * Math.PI / 180
|
||||
// Partenaire d'un conjoint isolé : ses souvenirs en orbite passent du côté opposé au petit portrait
|
||||
const compactSide = new Map()
|
||||
|
||||
@@ -206,10 +206,7 @@ export function drawCoupleLinks(ctx, coupleBarMeta, posMap, nodeMap, nodeRandomD
|
||||
// useGalaxyInteractions.
|
||||
// ============================================================
|
||||
export const renvoiHitAreas = []
|
||||
// Badges « Famille X » sous les conjoints dont la famille est un onglet : { x0, y0, x1, y1, anchorId }
|
||||
export const familyHitAreas = []
|
||||
const renvoiPills = []
|
||||
const familyPills = [] // badges « Famille X » : { text, cx, cy, anchorId, tabKey, alpha }
|
||||
const RENVOI_FONT = '500 11px "DM Sans", system-ui, sans-serif'
|
||||
// Pastilles collées aux personnes : juste sous le couple, juste au-dessus du portrait de l'enfant
|
||||
const RENVOI_PARENT_DY = 38
|
||||
@@ -242,11 +239,6 @@ function drawRenvoiPill(ctx, { text, cx, cy, target, alpha }) {
|
||||
if (alpha > 0.5) renvoiHitAreas.push({ x0: cx - w / 2, y0: cy - h / 2, x1: cx + w / 2, y1: cy + h / 2, ...target })
|
||||
}
|
||||
|
||||
function drawFamilyPill(ctx, { text, cx, cy, anchorId, tabKey, alpha }) {
|
||||
const { w, h } = drawPillShape(ctx, text, cx, cy, alpha)
|
||||
if (alpha > 0.5) familyHitAreas.push({ x0: cx - w / 2, y0: cy - h / 2, x1: cx + w / 2, y1: cy + h / 2, anchorId, tabKey })
|
||||
}
|
||||
|
||||
function drawRenvoi(ctx, edge, sourcePos, targetPos, targetNode, childR, view) {
|
||||
const scale = view?.scale ?? 1
|
||||
const labelAlpha = Math.min(1, Math.max(0, (scale - LOD_LABEL_FADE_OUT) / (LOD_LABEL_FADE_IN - LOD_LABEL_FADE_OUT)))
|
||||
@@ -268,7 +260,6 @@ function drawRenvoi(ctx, edge, sourcePos, targetPos, targetNode, childR, view) {
|
||||
|
||||
// Après drawNodes : les pastilles passent au-dessus des portraits et des souvenirs
|
||||
export function drawRenvoiPills(ctx) {
|
||||
for (const p of familyPills) drawFamilyPill(ctx, p)
|
||||
renvoiPills.forEach(p => drawRenvoiPill(ctx, p))
|
||||
renvoiPills.length = 0
|
||||
}
|
||||
@@ -279,8 +270,6 @@ export function drawRenvoiPills(ctx) {
|
||||
export function drawFiliations(ctx, edges, posMap, nodeMap, nodeRandomData, entrance, entranceActive, elapsed, view = null) {
|
||||
renvoiHitAreas.length = 0
|
||||
renvoiPills.length = 0
|
||||
familyPills.length = 0
|
||||
familyHitAreas.length = 0
|
||||
if (!edges) return
|
||||
const sibships = new Map()
|
||||
|
||||
@@ -691,19 +680,6 @@ function drawPersonNode(ctx, node, pos, nodeProgress, {
|
||||
}
|
||||
|
||||
ctx.restore()
|
||||
|
||||
// Sa famille est un onglet : une pastille le dit, un clic l'ouvre. Au-dessus du portrait, à la place de ses
|
||||
// parents (l'orbite des souvenirs évite le haut). Même place pour le retour vers la famille d'où l'on vient :
|
||||
// la charnière peut avoir des enfants dessous, jamais rien au-dessus que le fil de ses parents.
|
||||
if (withDetail && node._familyTab) {
|
||||
const tab = node._familyTab
|
||||
// Retour vers la famille d'où l'on vient : même place que la pastille de l'aller (la charnière ne bouge pas),
|
||||
// flèche retournée (Patch, 28/10, planche D)
|
||||
const name = tab.label ? t('Famille {name}', { name: tab.label }) : t('Famille')
|
||||
const text = tab.back ? `‹ ${name} · ${tab.count}` : `${name} · ${tab.count} ›`
|
||||
const py = cy - PERSON_R * personScale - RENVOI_CHILD_GAP
|
||||
familyPills.push({ text, cx, cy: py, anchorId: tab.anchorId, tabKey: tab.tabKey, alpha: nodeProgress * labelAlpha })
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useRef, useCallback } from 'react'
|
||||
import { getPersonMedias } from '../../data/mockData'
|
||||
import { MAX_ORBIT_MEDIAS, ORBIT_MEDIA_SIZE } from './constants'
|
||||
import { nodeRadius, renvoiHitAreas, familyHitAreas } from './renderers'
|
||||
import { nodeRadius, renvoiHitAreas } from './renderers'
|
||||
|
||||
const hitRenvoi = (x, y) => renvoiHitAreas.find(a => x >= a.x0 && x <= a.x1 && y >= a.y0 && y <= a.y1)
|
||||
const hitFamily = (x, y) => familyHitAreas.find(a => x >= a.x0 && x <= a.x1 && y >= a.y0 && y <= a.y1)
|
||||
import { getEntranceProgress } from './utils'
|
||||
|
||||
const CURSOR_GRAB = 'default'
|
||||
@@ -22,7 +21,7 @@ export function useGalaxyInteractions({
|
||||
canvasRef, transformRef, targetTransformRef,
|
||||
layoutDataRef, nodeRandomDataRef, entranceRef,
|
||||
hoveredNodeIdRef, hoveredMediaKeyRef, mouseWorldPosRef,
|
||||
getAnimatedPos, onPersonSelect, onMediaSelect, onOpenFamily = null,
|
||||
getAnimatedPos, onPersonSelect, onMediaSelect,
|
||||
annotationHandlers,
|
||||
minScale = 0.2,
|
||||
maxScale = 4,
|
||||
@@ -138,7 +137,7 @@ export function useGalaxyInteractions({
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!isOverClickable && (hitRenvoi(mouseWorldPosRef.current.x, mouseWorldPosRef.current.y) || hitFamily(mouseWorldPosRef.current.x, mouseWorldPosRef.current.y))) isOverClickable = true
|
||||
if (!isOverClickable && (hitRenvoi(mouseWorldPosRef.current.x, mouseWorldPosRef.current.y))) isOverClickable = true
|
||||
hoveredNodeIdRef.current = newHoveredNode
|
||||
hoveredMediaKeyRef.current = newHoveredMedia
|
||||
|
||||
@@ -277,10 +276,6 @@ export function useGalaxyInteractions({
|
||||
}
|
||||
}
|
||||
|
||||
// Badge de famille : ouvre l'onglet de cette famille
|
||||
const fam = hitFamily(clickX, clickY)
|
||||
if (fam) { onOpenFamily?.(fam.tabKey); return }
|
||||
|
||||
// Renvoi : on glisse vers l'autre extrémité (parents ou enfant), sans dézoomer en dessous de la lecture des prénoms
|
||||
const renvoi = hitRenvoi(clickX, clickY)
|
||||
if (renvoi) {
|
||||
@@ -292,7 +287,7 @@ export function useGalaxyInteractions({
|
||||
y: rect2.height / 2 - renvoi.targetY * scale,
|
||||
}
|
||||
}
|
||||
}, [canvasRef, transformRef, targetTransformRef, layoutDataRef, nodeRandomDataRef, entranceRef, getAnimatedPos, onPersonSelect, onMediaSelect, onOpenFamily, annotationHandlers])
|
||||
}, [canvasRef, transformRef, targetTransformRef, layoutDataRef, nodeRandomDataRef, entranceRef, getAnimatedPos, onPersonSelect, onMediaSelect, annotationHandlers])
|
||||
|
||||
return {
|
||||
isDragging, isZooming, zoomTimeoutRef, hasDragged,
|
||||
|
||||
Reference in new issue
Block a user