mirror of
https://github.com/IliesAllali/parenthese.git
synced 2026-11-10 15:52:14 +00:00
Front : mineures et correctifs groupés (Dependabot #31), refs hors du rendu
React et react-dom 19.3.0, @types/react et @types/react-dom 19.3.0, posthog-js 1.434.15, autoprefixer 10.6.1, eslint-plugin-react-hooks 7.1.1, eslint-plugin-react-refresh 0.5.7. La règle react-hooks/refs de la 7.1 voit maintenant les refs lus ou écrits pendant le rendu, corrigés plutôt que désactivés : le nom de l'arbre partagé affiché par l'écran compte passe par un état (un ref ne redessine rien), la personne de départ de l'arbre est recopiée dans un effet déclaré avant celui du cadrage, l'API de zoom est exposée par useImperativeHandle, l'horodatage des portraits est posé au premier chargement au lieu de Date.now() au rendu (react-hooks/purity). react-refresh 0.5 garde l'export par défaut et la config vite utilisés ici. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ea49f77651
commit
5fcaa85d8f
5 files changed
+125
-113
No files matched your search
Generated
+65
-57
@@ -9,19 +9,19 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"lucide-react": "^0.564.0",
|
||||
"posthog-js": "^1.353.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0"
|
||||
"posthog-js": "^1.435.6",
|
||||
"react": "^19.3.0",
|
||||
"react-dom": "^19.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/react": "^19.3.0",
|
||||
"@types/react-dom": "^19.3.0",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"autoprefixer": "^10.4.24",
|
||||
"autoprefixer": "^10.6.1",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.7",
|
||||
"globals": "^16.5.0",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^3.4.19",
|
||||
@@ -1070,28 +1070,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@posthog/browser-common": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@posthog/browser-common/-/browser-common-0.9.0.tgz",
|
||||
"integrity": "sha512-EWoVzs1vVY7ihpn8yOjj3jyjriHUEEMVSBNL/TW3hk09fTriWRe76vJXrRg0G0yjTYxHmtlSkNn4mOV24ZEzzA==",
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@posthog/browser-common/-/browser-common-0.9.1.tgz",
|
||||
"integrity": "sha512-Dl8uV2OZAxfHfWyC7K0Y3HtT7ld+WGLbXIwgCgAGziOMGDIa/bepCRrCxy7IxsEzqY1HOrq/VDdcoNN7hBTSQQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@posthog/core": "^1.54.2",
|
||||
"@posthog/types": "^1.412.1"
|
||||
"@posthog/core": "^1.55.3",
|
||||
"@posthog/types": "^1.413.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@posthog/core": {
|
||||
"version": "1.55.2",
|
||||
"resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.55.2.tgz",
|
||||
"integrity": "sha512-2mYxGbDmTLUs096VbcZqSWo0lrI48/BrX65r54NynxxFq409BOx9w73HxOAtOKZ1vblkdV6/GxQTe7KDcIQidg==",
|
||||
"version": "1.55.3",
|
||||
"resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.55.3.tgz",
|
||||
"integrity": "sha512-XnmG9jo7WlxTeN4oCNWH4ESLQHR6ZPZ+mcq+8JxU62k5i1BAe3BSrybnmXHjqKp7e3rUvLK2bOzGZGSlfHvlNA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@posthog/types": "^1.412.4"
|
||||
"@posthog/types": "^1.413.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@posthog/types": {
|
||||
"version": "1.412.4",
|
||||
"resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.412.4.tgz",
|
||||
"integrity": "sha512-Q7lV9O9TbLngjOYw1ucm3bS3tn48xb1B4ZQdDy8gv7yfKe99hCTeWYG4xZ36nJM80VxpTF9TriiJt5hngDOkBg==",
|
||||
"version": "1.413.0",
|
||||
"resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.413.0.tgz",
|
||||
"integrity": "sha512-tYAVzNojWckuG/wxBB0fwE70sFruQQq4xbhkci7eH2ARU3mMfUTkvpN1HwBMpm0AtbxidLiH0IFvagmdJ8opPw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
@@ -1530,21 +1530,23 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz",
|
||||
"integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==",
|
||||
"version": "19.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz",
|
||||
"integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"version": "19.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz",
|
||||
"integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
"@types/react": "^19.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
@@ -1795,9 +1797,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.24",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz",
|
||||
"integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==",
|
||||
"version": "10.6.1",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.6.1.tgz",
|
||||
"integrity": "sha512-cL1Qz6ADZhcEbny/8HPfe99J6HhNoYtpX2LFLIbhgGE7Q1hlQVkYFdetDN7Id3KiQxhDrHwzlHr/YQCnZ8+xSA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1813,9 +1815,10 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.28.1",
|
||||
"caniuse-lite": "^1.0.30001766",
|
||||
"browserslist": "^4.28.9",
|
||||
"caniuse-lite": "^1.0.30001810",
|
||||
"fraction.js": "^5.3.4",
|
||||
"picocolors": "^1.1.1",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
@@ -2286,10 +2289,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-hooks": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
|
||||
"integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz",
|
||||
"integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.24.4",
|
||||
"@babel/parser": "^7.24.4",
|
||||
@@ -2301,16 +2305,17 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
|
||||
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-refresh": {
|
||||
"version": "0.4.26",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz",
|
||||
"integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==",
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.7.tgz",
|
||||
"integrity": "sha512-XhJSzLljuYD4UjNuFGJu2v7aD3sqNVK12w5/DCUfrNfHWegZo2+TsavNx8MuxMwEquuFAzNbNfXKU0WoZ+YUIg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"eslint": ">=8.40"
|
||||
"eslint": "^9 || ^10"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
@@ -3356,14 +3361,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/posthog-js": {
|
||||
"version": "1.434.12",
|
||||
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.434.12.tgz",
|
||||
"integrity": "sha512-gD7Xv8/iuSGiYYQHB9fxnfTsiPvi+WNJiEW9OXnZ/UFS9ZVkOzMVmEq29/NFLeYebVGQpVCBh0H4xbK1sJ7j5g==",
|
||||
"version": "1.435.6",
|
||||
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.435.6.tgz",
|
||||
"integrity": "sha512-PdBZw1Np4Ch2ZzvQ/Ij0CTBrJSkSeEmDJsyJfbaipTYH8XJKJk45BYLtNgL4z9DlevRcYJxhwb15VrDzbw7shA==",
|
||||
"license": "(Apache-2.0 AND MIT)",
|
||||
"dependencies": {
|
||||
"@posthog/browser-common": "^0.9.0",
|
||||
"@posthog/core": "^1.55.2",
|
||||
"@posthog/types": "^1.412.4",
|
||||
"@posthog/browser-common": "^0.9.1",
|
||||
"@posthog/core": "^1.55.3",
|
||||
"@posthog/types": "^1.413.0",
|
||||
"core-js": "^3.49.0",
|
||||
"dompurify": "^3.4.13",
|
||||
"fflate": "^0.4.8",
|
||||
@@ -3448,22 +3453,24 @@
|
||||
]
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||
"version": "19.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz",
|
||||
"integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
|
||||
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
|
||||
"version": "19.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz",
|
||||
"integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
"scheduler": "^0.28.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.4"
|
||||
"react": "^19.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
@@ -3618,9 +3625,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="
|
||||
"version": "0.28.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz",
|
||||
"integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
|
||||
@@ -13,19 +13,19 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"lucide-react": "^0.564.0",
|
||||
"posthog-js": "^1.353.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0"
|
||||
"posthog-js": "^1.435.6",
|
||||
"react": "^19.3.0",
|
||||
"react-dom": "^19.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/react": "^19.3.0",
|
||||
"@types/react-dom": "^19.3.0",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"autoprefixer": "^10.4.24",
|
||||
"autoprefixer": "^10.6.1",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.7",
|
||||
"globals": "^16.5.0",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^3.4.19",
|
||||
|
||||
@@ -436,6 +436,8 @@ function App() {
|
||||
useEffect(() => { setBooting(tree.bootState === 'loading') }, [tree.bootState])
|
||||
// Arbre partagé d'où l'on vient quand on ouvre l'écran compte : on y revient après connexion/création
|
||||
const pendingSharedTreeRef = useRef(null)
|
||||
// Son nom, affiché par l'écran compte : un ref lu au rendu ne redessine rien quand il change
|
||||
const [pendingSharedTreeName, setPendingSharedTreeName] = useState('')
|
||||
// Écran de compte ouvert depuis un arbre affiché : la croix y ramène
|
||||
const [accountFromTree, setAccountFromTree] = useState(false)
|
||||
const refreshTreeAndKeepSelection = useCallback(async (personId, options = {}) => {
|
||||
@@ -2064,6 +2066,7 @@ function App() {
|
||||
treeOwnerEmail: ctx.treeOwnerEmail,
|
||||
}
|
||||
: null
|
||||
setPendingSharedTreeName(pendingSharedTreeRef.current?.treeName || '')
|
||||
setTreeNotFound(false)
|
||||
tree.setGateError('')
|
||||
auth.setAccountError('')
|
||||
@@ -2130,6 +2133,7 @@ function App() {
|
||||
return true
|
||||
} finally {
|
||||
pendingSharedTreeRef.current = null
|
||||
setPendingSharedTreeName('')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2456,7 +2460,7 @@ function App() {
|
||||
trees={auth.accountTrees}
|
||||
activeTreeId={tree.treeContext.treeId}
|
||||
defaultMode={accountEntryMode}
|
||||
pendingTreeName={pendingSharedTreeRef.current?.treeName || ''}
|
||||
pendingTreeName={pendingSharedTreeName}
|
||||
onLogin={handleLogin}
|
||||
onRegister={handleRegister}
|
||||
onOpenTree={handleOpenTreeFromAccount}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useEffect, useRef, useState, useCallback } from 'react'
|
||||
import { memo, useEffect, useImperativeHandle, useRef, useState, useCallback } from 'react'
|
||||
import { getPersonMedias, getParents, getChildren, getPersonUnions } from '../data/mockData'
|
||||
import { PERSON_R, FLOAT_SPEED, FLOAT_AMPLITUDE, REPULSION_RADIUS, REPULSION_STRENGTH, MAX_ORBIT_MEDIAS, ENTRANCE_GEN_DELAY, ENTRANCE_NODE_DURATION, ENTRANCE_NODE_STAGGER, ENTRANCE_MAX_GEN_SPAN, ENTRANCE_MAX_NODE_SPAN, ENTRANCE_LINE_DELAY, ENTRANCE_LINE_DURATION, ENTRANCE_ORBIT_DELAY, ENTRANCE_ORBIT_DURATION, ENTRANCE_ORBIT_STAGGER } from './galaxy/constants'
|
||||
import { easeOutCubic } from './galaxy/utils'
|
||||
@@ -33,7 +33,8 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
|
||||
const canvasRef = useRef(null)
|
||||
// Lu au calcul du cadrage seulement : changer de personne de départ ne recadre pas une vue en cours
|
||||
const focusPersonIdRef = useRef(focusPersonId)
|
||||
focusPersonIdRef.current = focusPersonId
|
||||
// Mis à jour hors du rendu, avant l'effet de cadrage déclaré plus bas (même phase, ordre de déclaration)
|
||||
useEffect(() => { focusPersonIdRef.current = focusPersonId }, [focusPersonId])
|
||||
const layoutDataRef = useRef(null)
|
||||
const [layoutReady, setLayoutReady] = useState(false)
|
||||
const transformRef = useRef({ x: 0, y: 0, scale: 1 })
|
||||
@@ -118,48 +119,46 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, selectedPersonId, searchHighlig
|
||||
maxScale: MAX_SCALE,
|
||||
})
|
||||
|
||||
// Expose zoom API to parent (ZoomControl component)
|
||||
if (zoomApiRef) {
|
||||
zoomApiRef.current = {
|
||||
zoomTo: (newScale) => {
|
||||
const canvas = canvasRef.current
|
||||
if (!canvas) return
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
const cx = rect.width / 2
|
||||
const cy = rect.height / 2
|
||||
const base = targetTransformRef.current
|
||||
const clamped = Math.min(MAX_SCALE, Math.max(MIN_SCALE, newScale))
|
||||
const ratio = clamped / base.scale
|
||||
targetTransformRef.current = {
|
||||
scale: clamped,
|
||||
x: cx - ratio * (cx - base.x),
|
||||
y: cy - ratio * (cy - base.y),
|
||||
}
|
||||
},
|
||||
resetZoom: () => {
|
||||
if (initialTransformRef.current) {
|
||||
targetTransformRef.current = { ...initialTransformRef.current }
|
||||
}
|
||||
},
|
||||
// Recherche : glisse jusqu'à la personne, dans la partie de l'écran que la fiche ne couvre pas
|
||||
// (fiche à gauche sur ordinateur, 408 px ; par le bas sur téléphone)
|
||||
focusPerson: (personId) => {
|
||||
const canvas = canvasRef.current
|
||||
const node = layoutDataRef.current?.children.find(n => n.id === `p-${personId}`)
|
||||
if (!canvas || !node) return
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
const pos = getAnimatedPos(node, performance.now())
|
||||
const scale = Math.min(MAX_SCALE, Math.max(targetTransformRef.current.scale, 1))
|
||||
const phone = rect.width <= 768
|
||||
const screenX = phone ? rect.width / 2 : (408 + rect.width) / 2
|
||||
const screenY = phone ? rect.height * 0.22 : rect.height / 2
|
||||
targetTransformRef.current = { scale, x: screenX - pos.cx * scale, y: screenY - pos.cy * scale }
|
||||
},
|
||||
getScale: () => transformRef.current.scale,
|
||||
MIN_SCALE,
|
||||
MAX_SCALE,
|
||||
}
|
||||
}
|
||||
// Expose zoom API to parent (ZoomControl component), posée au commit et non pendant le rendu
|
||||
useImperativeHandle(zoomApiRef, () => ({
|
||||
zoomTo: (newScale) => {
|
||||
const canvas = canvasRef.current
|
||||
if (!canvas) return
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
const cx = rect.width / 2
|
||||
const cy = rect.height / 2
|
||||
const base = targetTransformRef.current
|
||||
const clamped = Math.min(MAX_SCALE, Math.max(MIN_SCALE, newScale))
|
||||
const ratio = clamped / base.scale
|
||||
targetTransformRef.current = {
|
||||
scale: clamped,
|
||||
x: cx - ratio * (cx - base.x),
|
||||
y: cy - ratio * (cy - base.y),
|
||||
}
|
||||
},
|
||||
resetZoom: () => {
|
||||
if (initialTransformRef.current) {
|
||||
targetTransformRef.current = { ...initialTransformRef.current }
|
||||
}
|
||||
},
|
||||
// Recherche : glisse jusqu'à la personne, dans la partie de l'écran que la fiche ne couvre pas
|
||||
// (fiche à gauche sur ordinateur, 408 px ; par le bas sur téléphone)
|
||||
focusPerson: (personId) => {
|
||||
const canvas = canvasRef.current
|
||||
const node = layoutDataRef.current?.children.find(n => n.id === `p-${personId}`)
|
||||
if (!canvas || !node) return
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
const pos = getAnimatedPos(node, performance.now())
|
||||
const scale = Math.min(MAX_SCALE, Math.max(targetTransformRef.current.scale, 1))
|
||||
const phone = rect.width <= 768
|
||||
const screenX = phone ? rect.width / 2 : (408 + rect.width) / 2
|
||||
const screenY = phone ? rect.height * 0.22 : rect.height / 2
|
||||
targetTransformRef.current = { scale, x: screenX - pos.cx * scale, y: screenY - pos.cy * scale }
|
||||
},
|
||||
getScale: () => transformRef.current.scale,
|
||||
MIN_SCALE,
|
||||
MAX_SCALE,
|
||||
}), [getAnimatedPos])
|
||||
|
||||
// Zoom util (used by pinch)
|
||||
const zoomAt = useCallback((factor, center) => {
|
||||
|
||||
@@ -52,8 +52,9 @@ export function useTreeAccess() {
|
||||
// Change à chaque chargement des données, même sans nouveau placement : l'arbre y charge les images manquantes
|
||||
const [assetRevision, setAssetRevision] = useState(0)
|
||||
// Horodatage des portraits stable pendant la session : un rechargement ne force pas à tout retélécharger.
|
||||
// Il ne change que lorsqu'un portrait change (envoi d'une nouvelle photo).
|
||||
const avatarCacheBustRef = useRef(Date.now())
|
||||
// Il ne change que lorsqu'un portrait change (envoi d'une nouvelle photo). Posé au premier chargement
|
||||
// d'un arbre, pas au rendu (Date.now() rendrait le rendu impur).
|
||||
const avatarCacheBustRef = useRef(null)
|
||||
|
||||
const canEditCurrentTree = useMemo(() => {
|
||||
const role = normalizeRole(treeContext.role)
|
||||
@@ -70,7 +71,7 @@ export function useTreeAccess() {
|
||||
loadGraphData(graphPayload.graph, {
|
||||
mediaToken: graphPayload.mediaToken,
|
||||
apiBaseUrl: API_BASE_URL,
|
||||
avatarCacheBust: options.avatarCacheBust ?? avatarCacheBustRef.current,
|
||||
avatarCacheBust: options.avatarCacheBust ?? (avatarCacheBustRef.current ??= Date.now()),
|
||||
})
|
||||
if (options.avatarCacheBust) avatarCacheBustRef.current = options.avatarCacheBust
|
||||
setAssetRevision((current) => current + 1)
|
||||
|
||||
Reference in new issue
Block a user