import { useEffect, useRef, useState, type PointerEvent as ReactPointerEvent } from "react"; import { useAtlasStore, type BodyRegion } from "../store/useAtlasStore.js"; const REGIONS: { region: BodyRegion; label: string }[] = [ { region: "cabeca", label: "Cabeça" }, { region: "pescoco", label: "Pescoço" }, { region: "torax", label: "Tórax" }, { region: "abdome", label: "Abdome" }, { region: "pelve", label: "Pelve" }, { region: "membros_superiores", label: "Membros Sup." }, { region: "membros_inferiores", label: "Membros Inf." }, { region: "corpo_inteiro", label: "Corpo Inteiro" }, ]; const STORAGE_KEY = "atlas.quickRegionNav.pos"; function loadStoredPos(): { x: number; y: number } | null { try { const raw = localStorage.getItem(STORAGE_KEY); return raw ? JSON.parse(raw) : null; } catch { return null; } } // Navegação rápida por região — em vez de precisar acertar o clique numa // estrutura minúscula pra girar/aproximar a câmera manualmente, o usuário // pede direto pra "ir" numa região do corpo. Arrastável pela alcinha (⠿), // pois a barra pode cair em cima de uma parte do modelo que o usuário // queira ver — a posição escolhida fica salva entre visitas. export function QuickRegionNav() { const focusOnRegion = useAtlasStore((s) => s.focusOnRegion); const [pos, setPos] = useState<{ x: number; y: number } | null>(() => loadStoredPos()); const elRef = useRef(null); const dragRef = useRef<{ startX: number; startY: number; origX: number; origY: number } | null>(null); function clamp(x: number, y: number) { const el = elRef.current; const parent = el?.offsetParent as HTMLElement | null; if (!el || !parent) return { x, y }; const maxX = Math.max(0, parent.clientWidth - el.offsetWidth); const maxY = Math.max(0, parent.clientHeight - el.offsetHeight); return { x: Math.min(Math.max(0, x), maxX), y: Math.min(Math.max(0, y), maxY) }; } function onGripPointerDown(e: ReactPointerEvent) { const el = elRef.current; const parent = el?.offsetParent as HTMLElement | null; if (!el || !parent) return; // Antes do primeiro arrasto a barra fica centralizada via CSS transform // (translate-x-1/2) — offsetLeft/offsetTop ignoram transform, então // calculamos a posição visual real a partir do bounding rect pra não // dar aquele "pulo" no primeiro clique. const elRect = el.getBoundingClientRect(); const parentRect = parent.getBoundingClientRect(); dragRef.current = { startX: e.clientX, startY: e.clientY, origX: elRect.left - parentRect.left, origY: elRect.top - parentRect.top, }; (e.target as HTMLElement).setPointerCapture(e.pointerId); } function onGripPointerMove(e: ReactPointerEvent) { if (!dragRef.current) return; const dx = e.clientX - dragRef.current.startX; const dy = e.clientY - dragRef.current.startY; setPos(clamp(dragRef.current.origX + dx, dragRef.current.origY + dy)); } function onGripPointerUp() { dragRef.current = null; setPos((current) => { if (current) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(current)); } catch { // ignore } } return current; }); } // Se a janela mudar de tamanho (ex: girar o celular), reaplica o clamp // pra barra não ficar presa fora da área visível. useEffect(() => { if (!pos) return; function onResize() { setPos((current) => (current ? clamp(current.x, current.y) : current)); } window.addEventListener("resize", onResize); return () => window.removeEventListener("resize", onResize); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return (
⠿
{REGIONS.map((r) => ( ))}
); }