From 8fa2808b5d1a3d5b52388bfa2b6f6966359aa35c Mon Sep 17 00:00:00 2001 From: IliesAllali Date: Fri, 16 Oct 2026 18:13:52 +0200 Subject: [PATCH] =?UTF-8?q?fix(galaxy):=20un=20deuxi=C3=A8me=20doigt=20ann?= =?UTF-8?q?ule=20le=20glisser=20d'une=20personne?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Au passage à deux doigts (ou en cas d'interruption tactile), le glisser en cours est annulé et la personne revient à sa place : plus d'enregistrement pendant un pincement. Un doigt qui reste après un pincement ne saisit plus de personne, il ne fait que déplacer l'arbre. Co-Authored-By: Claude Opus 5.5 --- frontend/src/components/Galaxy.jsx | 10 ++++--- .../galaxy/useGalaxyInteractions.js | 27 ++++++++++++++++--- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/Galaxy.jsx b/frontend/src/components/Galaxy.jsx index 6edf462..6ef12a2 100644 --- a/frontend/src/components/Galaxy.jsx +++ b/frontend/src/components/Galaxy.jsx @@ -119,7 +119,7 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, onPersonPositionChange, editMod const { isDragging, isZooming, zoomTimeoutRef, hasDragged, - handleMouseDown, handleMouseMove, handleMouseUp, + handleMouseDown, handleMouseMove, handleMouseUp, cancelPersonDrag, handleMouseLeave, handleWheel, handleClick, } = useGalaxyInteractions({ canvasRef, transformRef, targetTransformRef, @@ -706,7 +706,8 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, onPersonPositionChange, editMod handleMouseDown({ clientX: t.clientX, clientY: t.clientY, preventDefault: () => e.preventDefault() }) } else if (e.touches.length === 2) { e.preventDefault() - // Stop any ongoing single-finger pan before starting pinch + // Un second doigt annule le glisser d'une personne (rien n'est enregistré) puis arrête le pan + cancelPersonDrag() handleMouseUp() const [a, b] = e.touches const dx = a.clientX - b.clientX @@ -744,15 +745,16 @@ const Galaxy = ({ onPersonSelect, onMediaSelect, onPersonPositionChange, editMod // and mark hasDragged=true so the lift doesn't fire an accidental click const t = e.touches[0] handleMouseUp() - handleMouseDown({ clientX: t.clientX, clientY: t.clientY, preventDefault: () => e.preventDefault() }) + handleMouseDown({ clientX: t.clientX, clientY: t.clientY, preventDefault: () => e.preventDefault() }, { panOnly: true }) hasDragged.current = true } else { handleMouseUp() } }} onTouchCancel={() => { - // System interruption (notification, keyboard, etc.) — reset all touch state + // System interruption (notification, keyboard, etc.) — reset all touch state, sans enregistrer de position pinchRef.current = null + cancelPersonDrag() handleMouseUp() }} onClick={handleClick} diff --git a/frontend/src/components/galaxy/useGalaxyInteractions.js b/frontend/src/components/galaxy/useGalaxyInteractions.js index fc0f600..984a22c 100644 --- a/frontend/src/components/galaxy/useGalaxyInteractions.js +++ b/frontend/src/components/galaxy/useGalaxyInteractions.js @@ -50,7 +50,8 @@ export function useGalaxyInteractions({ } }, [canvasRef, transformRef]) - const handleMouseDown = useCallback((e) => { + // panOnly : un doigt qui reste après un pincement ne saisit pas de personne, il ne fait que déplacer l'arbre + const handleMouseDown = useCallback((e, { panOnly = false } = {}) => { const ah = annotationHandlers const worldPos = getWorldPos(e) @@ -72,7 +73,7 @@ export function useGalaxyInteractions({ } } - if (editModeActive && (!ah || ah.activeTool === 'pointer') && worldPos && layoutDataRef.current) { + if (editModeActive && !panOnly && (!ah || ah.activeTool === 'pointer') && worldPos && layoutDataRef.current) { const now = performance.now() for (let index = layoutDataRef.current.children.length - 1; index >= 0; index -= 1) { const node = layoutDataRef.current.children[index] @@ -86,6 +87,10 @@ export function useGalaxyInteractions({ offsetY: worldPos.y - pos.cy, startClientX: e.clientX, startClientY: e.clientY, + // Place d'origine, pour un glisser annulé (second doigt, interruption) + originX: node.x, + originY: node.y, + originManual: node._manualPosition, } hoveredNodeIdRef.current = node.id hasDragged.current = false @@ -257,6 +262,22 @@ export function useGalaxyInteractions({ } }, [handleGlobalMouseUp]) + // Annule le glisser d'une personne sans rien enregistrer : la personne revient à sa place d'avant + const cancelPersonDrag = useCallback(() => { + const drag = draggedPersonRef.current + if (!drag) return + draggedPersonRef.current = null + const { node, originX, originY, originManual } = drag + node.x = originX + node.y = originY + node._manualPosition = originManual + if (typeof window !== 'undefined') { + window.removeEventListener('mouseup', handleGlobalMouseUp) + } + hoveredNodeIdRef.current = node.id + setCanvasCursor(canvasRef, editModeActive ? CURSOR_NODE_GRAB : CURSOR_POINTER) + }, [canvasRef, hoveredNodeIdRef, editModeActive, handleGlobalMouseUp]) + const handleMouseLeave = useCallback(() => { isDragging.current = false annotationDragging.current = false @@ -366,6 +387,6 @@ export function useGalaxyInteractions({ return { isDragging, isZooming, zoomTimeoutRef, hasDragged, handleMouseDown, handleMouseMove, handleMouseUp, - handleMouseLeave, handleWheel, handleClick, + handleMouseLeave, handleWheel, handleClick, cancelPersonDrag, } }