import { useEffect, useRef, useState, type KeyboardEvent, type MouseEvent, type PointerEvent } from "react"; import { AGENT_LAUNCHER_POSITION_KEY, clampAgentLauncherPosition, moveAgentLauncher, restoreAgentLauncherPosition, type AgentLauncherGesture, type AgentLauncherPosition } from "@/lib/canvas/agent-launcher-position"; const viewport = () => ({ width: window.innerWidth, height: window.innerHeight }); export function useAgentLauncherPosition(onOpen: () => void, width = 76, height = 76) { const size = { width, height }; const [position, setPosition] = useState(() => { try { return restoreAgentLauncherPosition(localStorage.getItem(AGENT_LAUNCHER_POSITION_KEY), viewport(), size); } catch (error) { console.warn("Agent 入口位置无法读取", error); return restoreAgentLauncherPosition(null, viewport(), size); } }); const [dragging, setDragging] = useState(false); const gestureRef = useRef<(AgentLauncherGesture & { pointerId: number }) | null>(null); const suppressClick = useRef(false); const save = (next: AgentLauncherPosition) => { try { localStorage.setItem(AGENT_LAUNCHER_POSITION_KEY, JSON.stringify(next)); } catch (error) { console.warn("Agent 入口位置无法保存", error); } }; useEffect(() => { const resize = () => setPosition((current) => clampAgentLauncherPosition(current, viewport(), size)); resize(); window.addEventListener("resize", resize); return () => window.removeEventListener("resize", resize); }, [width, height]); const onPointerDown = (event: PointerEvent) => { event.stopPropagation(); if (event.button !== 0 || !event.isPrimary || gestureRef.current) return; suppressClick.current = false; gestureRef.current = { pointerId: event.pointerId, x: event.clientX, y: event.clientY, start: position, dragged: false }; event.currentTarget.setPointerCapture(event.pointerId); }; const onPointerMove = (event: PointerEvent) => { event.stopPropagation(); const gesture = gestureRef.current; if (!gesture || gesture.pointerId !== event.pointerId) return; const next = moveAgentLauncher(gesture, event.clientX, event.clientY, viewport(), size); gesture.dragged = next.dragged; if (!next.dragged) return; event.preventDefault(); suppressClick.current = true; setDragging(true); setPosition(next.position); }; const finish = (event: PointerEvent, cancelled: boolean) => { event.stopPropagation(); const gesture = gestureRef.current; if (!gesture || gesture.pointerId !== event.pointerId) return; gestureRef.current = null; const next = cancelled ? { dragged: gesture.dragged, position: clampAgentLauncherPosition(position, viewport(), size) } : moveAgentLauncher(gesture, event.clientX, event.clientY, viewport(), size); suppressClick.current = cancelled || next.dragged; setDragging(false); setPosition(next.position); if (next.dragged) save(next.position); if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId); }; const onClick = (event: MouseEvent) => { event.stopPropagation(); // Keyboard/assistive activation remains available after a drag or cancellation. if (event.detail !== 0 && suppressClick.current) { event.preventDefault(); return; } onOpen(); }; const onKeyDown = (event: KeyboardEvent) => { event.stopPropagation(); const delta = event.shiftKey ? 40 : 10; const offsets: Record = { ArrowLeft: [delta, 0], ArrowRight: [-delta, 0], ArrowUp: [0, delta], ArrowDown: [0, -delta] }; const offset = offsets[event.key]; if (!offset) return; event.preventDefault(); const next = clampAgentLauncherPosition({ right: position.right + offset[0], bottom: position.bottom + offset[1] }, viewport(), size); setPosition(next); save(next); }; return { position, dragging, handlers: { onPointerDown, onPointerMove, onPointerUp: (event: PointerEvent) => finish(event, false), onPointerCancel: (event: PointerEvent) => finish(event, true), onLostPointerCapture: (event: PointerEvent) => finish(event, true), onClick, onKeyDown, onDoubleClick: (event: MouseEvent) => event.stopPropagation(), }, }; }