import styled from '@emotion/styled'; import { yupResolver } from '@hookform/resolvers/yup'; import type { Spectrum1D } from '@zakodium/nmrium-core'; import { xFindClosestIndex } from 'ml-spectra-processing'; import { useMemo } from 'react'; import { useForm } from 'react-hook-form'; import * as Yup from 'yup'; import { REFERENCES } from '../../../data/constants/References.js'; import type { CalibrateOptions } from '../../../data/data1d/Spectrum1D/getReferenceShift.js'; import { useDispatch } from '../../context/DispatchContext.js'; import { useToaster } from '../../context/ToasterContext.js'; import ActionButtons from '../../elements/ActionButtons.js'; import type { LabelStyle } from '../../elements/Label.js'; import Label from '../../elements/Label.js'; import { NumberInput2Controller } from '../../elements/NumberInput2Controller.js'; import { Select2 } from '../../elements/Select2.js'; import useSpectraByActiveNucleus from '../../hooks/useSpectraPerNucleus.js'; import { useEvent } from '../../utility/Events.js'; const labelStyle: LabelStyle = { label: { flex: 4, fontWeight: '500' }, wrapper: { flex: 8, display: 'flex', alignItems: 'center' }, container: { padding: '5px 0' }, }; const baseList = [{ key: 1, value: 'manual', label: 'Manual' }]; interface AlignSpectraProps { nucleus: any; onClose: () => void; } const DEFAULT_OPTIONS: CalibrateOptions = { from: -1, to: 1, nbPeaks: 1, targetX: 0, }; const schemaValidation = Yup.object({ from: Yup.number().required(), to: Yup.number().required(), nbPeaks: Yup.number().required(), targetX: Yup.number().required(), }); function checkSpectra(options: CalibrateOptions, spectra: Spectrum1D[]) { const { from, to } = options; for (const spectrum of spectra) { const { data: { x }, } = spectrum; const min = x[0]; const max = x.at(-1) as number; if (from < min || to > max) { throw new Error('Some spectra do not have data in the selected range'); } if (Math.abs(xFindClosestIndex(x, from) - xFindClosestIndex(x, to)) < 10) { throw new Error( 'The selected range is too small to provide accurate results', ); } } } function checkOptions(options: CalibrateOptions) { const returnedOptions = { ...options }; if (options.from > options.to) { returnedOptions.to = options.from; returnedOptions.from = options.to; } return returnedOptions; } function getList(nucleus: any) { if (!(REFERENCES as any)?.[nucleus]) { return []; } const list = Object.entries((REFERENCES as any)[nucleus]).map((item) => ({ value: item[0], label: item[0], })); return baseList.concat(list as any); } const Container = styled.div` display: flex; flex: 1; flex-direction: column; max-height: 100%; padding: 10px 0 5px 20px; .body { overflow: auto; padding: 10px 10px 25px 0; } .header { font-size: 15px; font-weight: bold; padding: 5px 0; } .footer { display: flex; padding-top: 5px; } `; function AlignSpectra({ onClose = () => null, nucleus }: AlignSpectraProps) { const spectra = useSpectraByActiveNucleus(); const dispatch = useDispatch(); const toaster = useToaster(); const { handleSubmit, reset, control, getValues } = useForm( { defaultValues: DEFAULT_OPTIONS, resolver: yupResolver(schemaValidation), }, ); function submitHandler(inputOptions: any) { const options = checkOptions(inputOptions); reset(options); try { checkSpectra(options, spectra as Spectrum1D[]); dispatch({ type: 'ALIGN_SPECTRA', payload: options }); onClose(); } catch (error: unknown) { const message = (error as Error).message; toaster.show({ intent: 'danger', message }); } } useEvent({ onBrushEnd: (options) => { const { range: [from, to], shiftKey, } = options; if (shiftKey) { reset({ ...getValues(), from, to }); } }, }); function optionChangeHandler({ value: key }: { value: string }) { const { delta: targetX = 0, ...otherOptions } = (REFERENCES as any)?.[nucleus]?.[key] || {}; const value = { ...DEFAULT_OPTIONS, targetX, ...otherOptions, }; reset(value); } const List = useMemo(() => getList(nucleus), [nucleus]); return (
Spectra calibration
handleSubmit(submitHandler)()} onCancel={onClose} />
); } export default AlignSpectra;