import { useState, type CSSProperties, type PointerEvent as ReactPointerEvent, type ReactNode } from "react"; import type { HeatTablePlacement, HeatTag, Weld } from "../api/types"; import { arrowRotation, bpos, clamp01 } from "../lib/geometry"; import { clampCorner, heatGroups, NO_HEAT, type HeatRow } from "../lib/heatTable"; import { weldId } from "../lib/weldDefaults"; import { useViewer } from "./viewerContext"; /** A mark's position while it is dragged, before the server has it. */ interface Pos { fx: number; fy: number; bfx: number; bfy: number; } export interface MarkMove { /** Only the bubble moved. */ bubble: boolean; fx: number; fy: number; bfx: number; bfy: number; } const pct = (v: number) => `${v * 100}%`; /** * One leader mark: the line, the arrowhead at the point and the bubble. Shared by welds (pink) and the older heat * bubbles (amber). The bubble and the arrowhead drag when editable; a tap on the bubble opens it. */ function Leader({ kind, at, editable, carried, label, bubble, bubbleClass, onOpen, onMove, }: { kind: "w" | "h"; at: Pos; editable: boolean; carried: boolean; label: string; bubble: ReactNode; bubbleClass: string; onOpen: () => void; onMove: (m: MarkMove) => Promise; }) { const v = useViewer(); const key = `${at.fx},${at.fy},${at.bfx},${at.bfy}`; // The dragged spot shows until the server's answer moves the mark (a new key) or the write fails. const [drag, setDrag] = useState<{ key: string; pos: Pos } | null>(null); const pos = drag && drag.key === key ? drag.pos : at; const rot = arrowRotation(pos.fx, pos.fy, pos.bfx, pos.bfy, v.aspect); const begin = (e: ReactPointerEvent, part: "anchor" | "bubble") => { if (!editable) return; let last: Pos | null = null; v.startDrag(e, { onMove: (f) => { const x = clamp01(f.fx); const y = clamp01(f.fy); last = part === "anchor" ? { ...at, fx: x, fy: y } : { ...at, bfx: x, bfy: y }; setDrag({ key, pos: last }); }, onEnd: (f) => { const done = f ? last : null; if (!done) { setDrag(null); return; } void onMove({ bubble: part === "bubble", ...done }).then((ok) => { if (!ok) setDrag(null); }); }, }); }; return ( <>