import { yupResolver } from '@hookform/resolvers/yup'; import type { KeyboardEvent, MouseEvent, PropsWithChildren, ReactNode, } from 'react'; import { createContext, useCallback, useContext, useMemo, useState, } from 'react'; import { useForm } from 'react-hook-form'; import * as Yup from 'yup'; import { useChartData } from '../../context/ChartContext.js'; import { useDispatch } from '../../context/DispatchContext.js'; import { useScaleChecked } from '../../context/ScaleContext.js'; import { NumberInput2Controller } from '../../elements/NumberInput2Controller.js'; import { useActiveSpectrum } from '../../hooks/useActiveSpectrum.js'; import { useIsInset } from '../inset/InsetProvider.js'; const validationSchema = Yup.object({ value: Yup.number().required(), }); const InputDimension = { height: 28, width: 100 }; interface PeakEditionListenerProps { x: number; y: number; useScaleX?: boolean; useScaleY?: boolean; dx?: number; dy?: number; value: number; id: string; } interface PeaksEditionContextProps { onEdit: ( e: React.MouseEvent, peak?: Required, ) => void; id?: string; } const peaksEditionContext: PeaksEditionContextProps = { onEdit: () => { // Empty default. }, }; const PeaksEditionContext = createContext(peaksEditionContext); function usePeaksEditionManager() { const context = useContext(PeaksEditionContext); if (!context) { throw new Error('Peak edition manager context was not found'); } return context; } function stopPropagation(e: MouseEvent) { e.stopPropagation(); } export function PeakEditionProvider({ children }: Required) { const { scaleX, scaleY } = useScaleChecked(); const { width } = useChartData(); const spectrum = useActiveSpectrum(); const [peak, setPeak] = useState | null>( null, ); function getPosition() { let x = 0; let y = 0; if (!peak) { return { x, y }; } const { x: px, y: py, dx, dy, useScaleX, useScaleY } = peak; x = px + dx; if (useScaleX) { x = scaleX()(px) + dx; } y = py + dy; if (useScaleY) { y = scaleY(spectrum?.id)(py) + dy; } if (x + InputDimension.width > width) { x = x - InputDimension.width; } return { x, y }; } const handleOnEdit = useCallback( (e, peak) => { e.stopPropagation(); if (peak) { setPeak({ ...peak }); } else { setPeak(null); } }, [], ); const editionManagerState = useMemo( () => ({ onEdit: handleOnEdit, id: peak?.id }), [peak?.id, handleOnEdit], ); const { x, y } = getPosition(); return (
setPeak(null)} onContextMenu={() => setPeak(null)} style={{ position: 'relative' }} > {children} {peak && (
setPeak(null)} />
)}
); } interface PeakFieldProps { value: number; onClose: () => void; } interface FieldValues { value: number; } function PeakEditionField({ value, onClose }: PeakFieldProps) { const dispatch = useDispatch(); const { control, handleSubmit } = useForm({ defaultValues: { value }, resolver: yupResolver(validationSchema), }); function keyDownCheck(event: KeyboardEvent) { if (event.key === 'Enter') { return true; } else if (event.key === 'Escape') { onClose(); return false; } else { return false; } } function handleOnSubmit({ value: newValue }: FieldValues) { if (value) { const shift = newValue - value; dispatch({ type: 'SHIFT_SPECTRUM', payload: { shift } }); onClose(); } } return ( { if (keyDownCheck(e)) { void handleSubmit(handleOnSubmit)(); } }} onClick={stopPropagation} onMouseDown={stopPropagation} buttonPosition="none" /> ); } export function PeakEditionListener( props: PeakEditionListenerProps & { children: ReactNode }, ) { const isInset = useIsInset(); const { children, x, y, value, useScaleX = false, useScaleY = false, dx = 0, dy = -InputDimension.height, id, } = props; const { onEdit, id: editingPeakId } = usePeaksEditionManager(); if (isInset) return children; return ( onEdit(e, { x, y, value, useScaleX, useScaleY, dx, dy, id }) } > {editingPeakId !== id && children} ); }