import { Button, Classes } from '@blueprintjs/core'; import type { NMRPeak1D, Peak1D, Signal1D } from '@zakodium/nmr-types'; import type { Spectrum1D } from '@zakodium/nmrium-core'; import { xFindClosestIndex } from 'ml-spectra-processing'; import { getShiftX } from 'nmr-processing'; import type { CSSProperties } from 'react'; import { useCallback, useMemo, useRef } from 'react'; import { useFormContext, useWatch } from 'react-hook-form'; import { FaPlus, FaRegTrashAlt } from 'react-icons/fa'; import { Toolbar } from 'react-science/ui'; import type { CellProps } from 'react-table'; import { NumberInput2Controller } from '../../../../../elements/NumberInput2Controller.js'; import type { Column } from '../../../../../elements/ReactTable/ReactTable.js'; import ReactTable from '../../../../../elements/ReactTable/ReactTable.js'; import { useTabsController } from '../../../../../elements/TabsProvider.js'; import useSpectrum from '../../../../../hooks/useSpectrum.js'; import { useEvent } from '../../../../../utility/Events.js'; import { useEventFocusInput } from '../SignalsContent.js'; const styles: Record<'input' | 'column', CSSProperties> = { input: { width: '100%', padding: '0.25rem 0.5rem', }, column: { padding: '2px', }, }; interface SignalPeaksTableProps { index: number; } function getPeakKey(signalIndex: number, peakIndex: any, key?: keyof Peak1D) { const path = `signals[${signalIndex}].peaks.${peakIndex}`; if (!key) { return path; } return `${path}.${key}`; } export function SignalPeaksTable(props: SignalPeaksTableProps) { const { focusSource, setFocusSource } = useEventFocusInput(); const { setValue, control, setFocus } = useFormContext(); const { signals } = useWatch(); const { selectedTabId: signalIndex = 0 } = useTabsController(); const signal: Signal1D | undefined = signals?.[signalIndex] || {}; const delta = signal?.delta || 0; const spectrum = useSpectrum() as Spectrum1D; const { data: { x: xArray, re }, } = spectrum; const shiftX = getShiftX(spectrum); const lastSelectedPeakIndexRef = useRef(null); useEvent({ onClick: ({ xPPM, shiftKey }) => { if ( props.index === signalIndex && typeof lastSelectedPeakIndexRef.current === 'number' && shiftKey && focusSource === 'peak' ) { const delta = xPPM; const xIndex = xFindClosestIndex(xArray, delta, { sorted: false }); const intensity = re[xIndex]; setValue(getPeakKey(signalIndex, lastSelectedPeakIndexRef.current), { ...signal?.peaks?.[lastSelectedPeakIndexRef.current], x: delta, y: intensity, }); } }, onBrushEnd: (options) => { const { range: [from, to], shiftKey, } = options; if ( props.index === signalIndex && typeof lastSelectedPeakIndexRef.current === 'number' && shiftKey && focusSource === 'peak' ) { const delta = (to - from) / 2 + from; const xIndex = xFindClosestIndex(xArray, delta, { sorted: false }); setValue(getPeakKey(signalIndex, lastSelectedPeakIndexRef.current), { ...signal?.peaks?.[lastSelectedPeakIndexRef.current], x: delta, y: re[xIndex], }); } }, }); const addHandler = useCallback( (data: NMRPeak1D[]) => { const xIndex = xFindClosestIndex(xArray, delta, { sorted: false }); const peak: NMRPeak1D = { id: crypto.randomUUID(), x: delta, y: xIndex !== -1 ? re[xIndex] : 0, originalX: delta - shiftX, width: 1, }; setValue(`signals[${signalIndex}].peaks`, [...data, peak]); const rowIndex = data?.length || 0; lastSelectedPeakIndexRef.current = rowIndex; }, [delta, re, setValue, shiftX, signalIndex, xArray], ); const deleteHandler = useCallback( (data: any, index: number) => { const peaks = data.filter( (_: any, columnIndex: any) => columnIndex !== index, ); setValue(`signals[${signalIndex}].peaks`, peaks); if (lastSelectedPeakIndexRef.current === index) { lastSelectedPeakIndexRef.current = null; } }, [setValue, signalIndex], ); function deleteAllHandler() { setValue(`signals[${signalIndex}].peaks`, []); lastSelectedPeakIndexRef.current = null; } const changeDeltaHandler = useCallback( (delta: any, index: number) => { const xIndex = xFindClosestIndex(xArray, delta, { sorted: false }); setValue(`signals.${signalIndex}.peaks.${index}.y`, re[xIndex]); }, [re, setValue, signalIndex, xArray], ); const COLUMNS = useMemo>>( () => [ { Header: '#', style: { width: '25px', ...styles.column }, accessor: (_: any, index) => index + 1, }, { Header: 'delta', style: { padding: 0, ...styles.column }, Cell: ({ row }: CellProps) => { return ( changeDeltaHandler(valueAsNumber, row.index) } noShadowBox style={{ backgroundColor: 'transparent' }} fill buttonPosition="none" debounceTime={250} /> ); }, }, { Header: 'Intensity', style: { padding: 0, ...styles.column }, Cell: ({ row }: CellProps) => { return ( ); }, }, { Header: '', style: { width: '70px', ...styles.column }, id: 'action-button', Cell: ({ data, row }: CellProps) => { const record: any = row.original; return (
{!record?.name && ( )}
); }, }, ], [changeDeltaHandler, control, deleteHandler, signalIndex], ); function selectRowHandler(index: any) { setFocusSource('peak'); lastSelectedPeakIndexRef.current = index; setFocus(`signals.${signalIndex}.peaks.${index}.x`, { shouldSelect: true }); } return (
} tooltip="Add a new peak" intent="success" onClick={() => addHandler(signal?.peaks || [])} /> } tooltip="Delete all peaks" intent="danger" onClick={deleteAllHandler} />
selectRowHandler(rowData.index)} emptyDataRowText="No peaks" />
); }