import { useState, useMemo } from "react"; import { ZoomIn, RotateCcw, Minus } from "lucide-react"; import { useCurveStore } from "../../store/curveStore.js"; import type { VFPoint } from "../../types.js"; interface Props { /** All curve points — used by global offset slider */ activePts: VFPoint[]; /** Called to reset the x-axis zoom to default */ onResetZoom: () => void; /** True if the chart is currently zoomed in */ isZoomed: boolean; /** True when viewing a read-only domain (memory) */ readOnly?: boolean; /** Current zoom factor (1 = no zoom) */ zoomFactor: number; /** Called when user changes zoom via slider */ onZoomChange: (factor: number) => void; } export function CurveToolbar({ activePts, onResetZoom, isZoomed, readOnly, zoomFactor, onZoomChange, }: Props) { const { pendingDeltas, selectedPoints, anchorPoint, curve, stageRangeEdit, flattenToAnchor, } = useCurveStore(); const [offsetMhz, setOffsetMhz] = useState(0); const uniformDeltaMhz = useMemo(() => { if (activePts.length === 0) return 0; const firstD = pendingDeltas.get(activePts[0].index) ?? activePts[0].delta_khz; const uniform = activePts.every( (p) => (pendingDeltas.get(p.index) ?? p.delta_khz) === firstD, ); return uniform ? firstD / 1000 : null; }, [activePts, pendingDeltas]); // Sync the slider to the uniform delta when it changes (adjust state during // render instead of an effect; undefined sentinel so the first render syncs). const [lastUniformDelta, setLastUniformDelta] = useState< number | null | undefined >(); if (uniformDeltaMhz !== null && lastUniformDelta !== uniformDeltaMhz) { setLastUniformDelta(uniformDeltaMhz); setOffsetMhz(uniformDeltaMhz); } function handleOffsetChange(mhz: number) { setOffsetMhz(mhz); stageRangeEdit(activePts, Math.round(mhz * 1000)); } return (
{/* Zoom control */}
onZoomChange(Number(e.target.value))} className="w-20 h-1 cursor-pointer accent-cyan-400" /> {zoomFactor.toFixed(1)}× {isZoomed && ( )}
{/* Divider */} {/* Global offset slider — GPU only */} {!readOnly && uniformDeltaMhz !== null && (
Global Offset handleOffsetChange(Number(e.target.value))} className="w-32 accent-cyan-400" title={`${offsetMhz > 0 ? "+" : ""}${offsetMhz} MHz`} /> 0 ? "text-cyan-400" : offsetMhz < 0 ? "text-orange-400" : "text-zinc-500", ].join(" ")} > {offsetMhz > 0 ? "+" : ""} {offsetMhz} MHz
)} {/* Flatten — visible when 2+ points are selected */} {!readOnly && selectedPoints.size >= 2 && (() => { const anchor = anchorPoint !== null && selectedPoints.has(anchorPoint) ? anchorPoint : Math.min(...selectedPoints); const anchorDelta = pendingDeltas.get(anchor) ?? curve?.points.find((p) => p.index === anchor)?.delta_khz ?? 0; const label = `·${anchor} ${anchorDelta >= 0 ? "+" : ""}${anchorDelta / 1000} MHz`; return ( ); })()} {/* Legend — right-aligned */}
{" "} effective {" "} pending {" "} current
); }