From 3632085fa322d6ec82d3bb8af517946d100ef81e Mon Sep 17 00:00:00 2001 From: hamed musallam Date: Fri, 8 Mar 2024 12:32:27 +0100 Subject: [PATCH 01/10] feat: edit range peaks --- .../forms/components/CouplingsTable.tsx | 31 ++-- .../editRange/forms/components/PeaksTable.tsx | 135 ++++++++++++++++++ .../forms/components/SignalFormTab.tsx | 44 ++++-- 3 files changed, 183 insertions(+), 27 deletions(-) create mode 100644 src/component/modal/editRange/forms/components/PeaksTable.tsx diff --git a/src/component/modal/editRange/forms/components/CouplingsTable.tsx b/src/component/modal/editRange/forms/components/CouplingsTable.tsx index 3929eb819..541c4bbe2 100644 --- a/src/component/modal/editRange/forms/components/CouplingsTable.tsx +++ b/src/component/modal/editRange/forms/components/CouplingsTable.tsx @@ -5,6 +5,7 @@ import lodashGet from 'lodash/get'; import { translateMultiplet } from 'nmr-processing'; import { memo, useCallback } from 'react'; import { FaPlus, FaRegTrashAlt } from 'react-icons/fa'; +import { Toolbar } from 'react-science/ui'; import { Multiplets } from '../../../../../data/constants/Multiplets'; import Button from '../../../../elements/Button'; @@ -69,21 +70,21 @@ function CouplingsTable({ return (
- - push({ - multiplicity: translateMultiplet('m'), - coupling: '', - }) - } - > - - - Add New Coupling - - +
+ + } + title="Add a new coupling" + intent="success" + onClick={() => + push({ + multiplicity: translateMultiplet('m'), + coupling: '', + }) + } + /> + +
diff --git a/src/component/modal/editRange/forms/components/PeaksTable.tsx b/src/component/modal/editRange/forms/components/PeaksTable.tsx new file mode 100644 index 000000000..2138b4d80 --- /dev/null +++ b/src/component/modal/editRange/forms/components/PeaksTable.tsx @@ -0,0 +1,135 @@ +import { v4 } from '@lukeed/uuid'; +import { useFormikContext } from 'formik'; +import lodashGet from 'lodash/get'; +import { Peak1D } from 'nmr-processing'; +import { CSSProperties, useCallback, useMemo } from 'react'; +import { FaPlus, FaRegTrashAlt } from 'react-icons/fa'; +import { Toolbar } from 'react-science/ui'; + +import Button from '../../../../elements/Button'; +import ReactTable, { Column } from '../../../../elements/ReactTable/ReactTable'; +import FormikInput from '../../../../elements/formik/FormikInput'; + +const styles: Record<'input' | 'column', CSSProperties> = { + input: { + width: '100%', + padding: '0.25rem 0.5rem', + }, + column: { + padding: '2px', + }, +}; + +export default function PeaksTable() { + const { values, setFieldValue } = useFormikContext(); + + const peaks = lodashGet(values, `signals[${values.activeTab}].peaks`, []); + + const addHandler = useCallback( + (data: readonly any[], index: number) => { + let columns: any[] = []; + const peak: Peak1D = { + id: v4(), + x: 0, + y: 0, + originalX: 0, + width: 0, + }; + if (data && Array.isArray(data)) { + columns = [...data.slice(0, index), peak, ...data.slice(index)]; + } else { + columns.push(peak); + } + void setFieldValue(`signals[${values.activeTab}].peaks`, columns); + }, + [setFieldValue, values.activeTab], + ); + + const deleteHandler = useCallback( + (data, index: number) => { + const peaks = data.filter((_, columnIndex) => columnIndex !== index); + void setFieldValue(`signals[${values.activeTab}].peaks`, peaks); + }, + [setFieldValue, values.activeTab], + ); + + function deleteAllHandler() { + void setFieldValue(`signals[${values.activeTab}].peaks`, []); + } + + const COLUMNS: Array> = useMemo( + () => [ + { + Header: '#', + style: { width: '25px', ...styles.column }, + accessor: (_, index) => index + 1, + }, + { + Header: 'delta', + style: { padding: 0, ...styles.column }, + Cell: ({ row }) => { + return ( + + ); + }, + }, + { + Header: '', + style: { width: '70px', ...styles.column }, + id: 'action-button', + Cell: ({ data, row }) => { + const record: any = row.original; + return ( +
+ {!record?.name && ( + deleteHandler(data, row.index)} + > + + + )} +
+ ); + }, + }, + ], + [deleteHandler, values.activeTab], + ); + + return ( +
+
+ + } + title="Add a new peak" + intent="success" + onClick={() => addHandler(peaks, 0)} + /> + } + title="Delete all peaks" + intent="danger" + onClick={deleteAllHandler} + /> + +
+
+ +
+
+ ); +} diff --git a/src/component/modal/editRange/forms/components/SignalFormTab.tsx b/src/component/modal/editRange/forms/components/SignalFormTab.tsx index 0dfcf9a18..30f7b92db 100644 --- a/src/component/modal/editRange/forms/components/SignalFormTab.tsx +++ b/src/component/modal/editRange/forms/components/SignalFormTab.tsx @@ -1,7 +1,11 @@ +/** @jsxImportSource @emotion/react */ +import { Tab, Tabs } from '@blueprintjs/core'; +import { css } from '@emotion/react'; import { useFormikContext, FieldArray } from 'formik'; import { CSSProperties, memo } from 'react'; import CouplingsTable from './CouplingsTable'; +import PeaksTable from './PeaksTable'; const style: CSSProperties = { borderSpacing: '0', @@ -22,18 +26,34 @@ function SignalFormTab({ onFocus, onBlur }: SignalFormTabProps) { const { values } = useFormikContext<{ activeTab: string }>(); return ( -
- ( - - )} - /> +
+ + ( + + )} + /> + } + /> + } /> +
); } From dd58270828e13b18a3faa15dc36fe8e7f3334f03 Mon Sep 17 00:00:00 2001 From: hamed musallam Date: Wed, 13 Mar 2024 14:54:17 +0100 Subject: [PATCH 02/10] refactor: add a new signal tab --- .../forms/components/AddSignalFormTab.tsx | 207 ++++++++---------- 1 file changed, 91 insertions(+), 116 deletions(-) diff --git a/src/component/modal/editRange/forms/components/AddSignalFormTab.tsx b/src/component/modal/editRange/forms/components/AddSignalFormTab.tsx index eb7f91d36..bf30f0e85 100644 --- a/src/component/modal/editRange/forms/components/AddSignalFormTab.tsx +++ b/src/component/modal/editRange/forms/components/AddSignalFormTab.tsx @@ -1,40 +1,30 @@ -/** @jsxImportSource @emotion/react */ -import { css } from '@emotion/react'; import { Formik, useFormikContext } from 'formik'; import { WorkSpacePanelPreferences } from 'nmr-load-save'; import { translateMultiplet } from 'nmr-processing'; -import { forwardRef, useCallback, useMemo } from 'react'; -import { FaPlus } from 'react-icons/fa'; +import { CSSProperties, forwardRef } from 'react'; import * as Yup from 'yup'; import Button from '../../../../elements/Button'; import FormikInput from '../../../../elements/formik/FormikInput'; import { formatNumber } from '../../../../utility/formatNumber'; -const styles = { - container: css` - text-align: center; - width: 100%; - height: 100%; - padding: 0.4rem; - `, - inputInfo: css` - font-size: 10px; - color: black; - font-weight: bold; - `, - infoText: css` - padding: 10px; - font-size: 13px; - `, - - signalContainer: css` - border: 0.55px solid #dedede; - `, - inputContainer: css` - display: flex; - justify-content: center; - `, +const styles: Record< + 'container' | 'innerContainer' | 'infoText', + CSSProperties +> = { + container: { + padding: '0.4rem', + }, + innerContainer: { + width: '70%', + display: 'block', + margin: 'auto', + }, + infoText: { + padding: '10px 0', + fontSize: '13px', + textAlign: 'left', + }, }; interface AddSignalFormTabProps { @@ -51,99 +41,84 @@ function AddSignalFormTab( ) { const { values, setFieldValue } = useFormikContext(); - const saveHandler = useCallback( - (val) => { - const newSignal = { - multiplicity: 'm', - kind: 'signal', - delta: Number(val.newSignalDelta), - js: [{ multiplicity: translateMultiplet('m'), coupling: '' }], - }; - const _signals = values.signals.slice().concat(newSignal); - - void setFieldValue('signals', _signals); - void setFieldValue('activeTab', String(_signals.length - 1)); - }, - [setFieldValue, values.signals], - ); - - const validation = useMemo(() => { - return Yup.object().shape({ - newSignalDelta: Yup.number() - .test(`test-range`, '', function testNewSignalDelta(value) { - // eslint-disable-next-line no-invalid-this - const { path, createError } = this; - if (value && value > range.from && value < range.to) { - return true; - } + function saveHandler(val) { + const newSignal = { + multiplicity: 'm', + kind: 'signal', + delta: Number(val.newSignalDelta), + js: [{ multiplicity: translateMultiplet('m'), coupling: '' }], + }; + const _signals = values.signals.slice().concat(newSignal); - const errorMessage = ` ${ - value ? value.toFixed(5) : 0 - } ppm out of the range`; - return createError({ path, message: errorMessage }); - }) - .required(), - }); - }, [range]); - - const triggerSubmitHandler = useCallback(() => { - ref.current.submitForm(); - }, [ref]); + void setFieldValue('signals', _signals); + void setFieldValue('activeTab', String(_signals.length - 1)); + } return ( -
-
-

- Edit or select a delta value of new signal (ppm): -

- - <> -
- - - - -
-

- [ - {`${formatNumber( - range.from, - preferences.from.format, - )} ppm - ${formatNumber(range.to, preferences.to.format)} ppm`} - ] -

- -
-
+
+ +
+

+ Edit or select a delta value of new signal in range [ + {`${formatNumber( + range.from, + preferences.from.format, + )} ppm - ${formatNumber(range.to, preferences.to.format)} ppm`} + ]: +

+ + ref.current.submitForm()} + > + Add a signal + +
+
); } +function getSignalValidationSchema(range) { + return Yup.object().shape({ + newSignalDelta: Yup.number() + .test(`test-range`, '', function testNewSignalDelta(value) { + // eslint-disable-next-line no-invalid-this + const { path, createError } = this; + if (value && value >= range.from && value <= range.to) { + return true; + } + + const errorMessage = ` ${ + value ? value.toFixed(5) : 0 + } ppm out of the range`; + return createError({ path, message: errorMessage }); + }) + .required(), + }); +} + export default forwardRef(AddSignalFormTab); From 96c5c38b0282c9364de27c7970486ece2971522f Mon Sep 17 00:00:00 2001 From: hamed musallam Date: Thu, 14 Mar 2024 09:35:29 +0100 Subject: [PATCH 03/10] feat: add peaks --- .../forms/components/AddSignalFormTab.tsx | 2 +- .../forms/components/CouplingsTable.tsx | 10 +-- .../editRange/forms/components/PeaksTable.tsx | 82 +++++++++++++------ .../forms/components/SignalFormInfo.tsx | 2 +- .../forms/components/SignalFormTab.tsx | 4 +- .../forms/components/SignalsForm.tsx | 20 ++--- 6 files changed, 77 insertions(+), 43 deletions(-) diff --git a/src/component/modal/editRange/forms/components/AddSignalFormTab.tsx b/src/component/modal/editRange/forms/components/AddSignalFormTab.tsx index bf30f0e85..53a06e316 100644 --- a/src/component/modal/editRange/forms/components/AddSignalFormTab.tsx +++ b/src/component/modal/editRange/forms/components/AddSignalFormTab.tsx @@ -51,7 +51,7 @@ function AddSignalFormTab( const _signals = values.signals.slice().concat(newSignal); void setFieldValue('signals', _signals); - void setFieldValue('activeTab', String(_signals.length - 1)); + void setFieldValue('signalIndex', String(_signals.length - 1)); } return ( diff --git a/src/component/modal/editRange/forms/components/CouplingsTable.tsx b/src/component/modal/editRange/forms/components/CouplingsTable.tsx index 541c4bbe2..75aaf28ef 100644 --- a/src/component/modal/editRange/forms/components/CouplingsTable.tsx +++ b/src/component/modal/editRange/forms/components/CouplingsTable.tsx @@ -64,7 +64,7 @@ function CouplingsTable({ const couplingsList = lodashGet( values, - `signals[${values.activeTab}].js`, + `signals[${values.signalIndex}].js`, [], ); @@ -100,19 +100,19 @@ function CouplingsTable({ {couplingsList.map((_coupling, i) => (
{i + 1} { multiplicityChangeHandler( value, - `signals.${values.activeTab}.js.${i}.coupling`, + `signals.${values.signalIndex}.js.${i}.coupling`, ); }} style={{ @@ -122,7 +122,7 @@ function CouplingsTable({ = { input: { @@ -22,41 +24,60 @@ const styles: Record<'input' | 'column', CSSProperties> = { export default function PeaksTable() { const { values, setFieldValue } = useFormikContext(); - - const peaks = lodashGet(values, `signals[${values.activeTab}].peaks`, []); + const signal = values?.signals?.[values?.signalIndex] || {}; + const peaks = signal?.peaks || []; + const delta = signal?.delta || 0; + const spectrum = useSpectrum() as Spectrum1D; + const { + data: { x, re }, + } = spectrum; + const shiftX = getShiftX(spectrum); const addHandler = useCallback( - (data: readonly any[], index: number) => { - let columns: any[] = []; + (data: Peak1D[]) => { + const xIndex = xFindClosestIndex(x, delta, { sorted: false }); + const peak: Peak1D = { id: v4(), - x: 0, - y: 0, - originalX: 0, - width: 0, + x: delta, + y: xIndex !== -1 ? re[xIndex] : 0, + originalX: delta - shiftX, + width: 1, }; - if (data && Array.isArray(data)) { - columns = [...data.slice(0, index), peak, ...data.slice(index)]; - } else { - columns.push(peak); - } - void setFieldValue(`signals[${values.activeTab}].peaks`, columns); + void setFieldValue(`signals[${values?.signalIndex}].peaks`, [ + ...data, + peak, + ]); }, - [setFieldValue, values.activeTab], + [delta, re, setFieldValue, shiftX, values?.signalIndex, x], ); const deleteHandler = useCallback( (data, index: number) => { const peaks = data.filter((_, columnIndex) => columnIndex !== index); - void setFieldValue(`signals[${values.activeTab}].peaks`, peaks); + void setFieldValue(`signals[${values?.signalIndex}].peaks`, peaks); }, - [setFieldValue, values.activeTab], + [setFieldValue, values?.signalIndex], ); function deleteAllHandler() { - void setFieldValue(`signals[${values.activeTab}].peaks`, []); + void setFieldValue(`signals[${values?.signalIndex}].peaks`, []); } + const changeDeltaHandler = useCallback( + (event, index: number) => { + const delta = Number(event.target.value); + + const xIndex = xFindClosestIndex(x, delta, { sorted: false }); + + void setFieldValue( + `signals[${values?.signalIndex}].peaks.${index}.y`, + re[xIndex], + ); + }, + [re, setFieldValue, values?.signalIndex, x], + ); + const COLUMNS: Array> = useMemo( () => [ { @@ -70,8 +91,23 @@ export default function PeaksTable() { Cell: ({ row }) => { return ( changeDeltaHandler(event, row.index)} + type="number" + /> + ); + }, + }, + { + Header: 'Intensity', + style: { padding: 0, ...styles.column }, + Cell: ({ row }) => { + return ( + ); }, @@ -97,7 +133,7 @@ export default function PeaksTable() { }, }, ], - [deleteHandler, values.activeTab], + [changeDeltaHandler, deleteHandler, values?.signalIndex], ); return ( @@ -108,7 +144,7 @@ export default function PeaksTable() { icon={} title="Add a new peak" intent="success" - onClick={() => addHandler(peaks, 0)} + onClick={() => addHandler(peaks)} /> } diff --git a/src/component/modal/editRange/forms/components/SignalFormInfo.tsx b/src/component/modal/editRange/forms/components/SignalFormInfo.tsx index 547ceed92..a7e380825 100644 --- a/src/component/modal/editRange/forms/components/SignalFormInfo.tsx +++ b/src/component/modal/editRange/forms/components/SignalFormInfo.tsx @@ -61,7 +61,7 @@ export function SignalFormInfo() { )} - ) : values.activeTab === 'addSignalTab' ? ( + ) : values.signalIndex === '-1' ? ( ) : ( diff --git a/src/component/modal/editRange/forms/components/SignalFormTab.tsx b/src/component/modal/editRange/forms/components/SignalFormTab.tsx index 30f7b92db..252d83a84 100644 --- a/src/component/modal/editRange/forms/components/SignalFormTab.tsx +++ b/src/component/modal/editRange/forms/components/SignalFormTab.tsx @@ -23,7 +23,7 @@ interface SignalFormTabProps { } function SignalFormTab({ onFocus, onBlur }: SignalFormTabProps) { - const { values } = useFormikContext<{ activeTab: string }>(); + const { values } = useFormikContext<{ signalIndex: string }>(); return (
( { function handle(event) { if (info?.originFrequency && activeField) { - if (values.activeTab === 'addSignalTab') { + if (values.signalIndex === '-1') { newSignalFormRef.current.setValues({ [activeField]: (event.range[1] - event.range[0]) / 2 + event.range[0], @@ -89,15 +89,15 @@ function SignalsForm({ range, preferences }: SignalsFormProps) { }, [ activeField, setFieldValue, - values.activeTab, + values.signalIndex, info, - preferences?.deltaHz?.format, + preferences.deltaHz.format, ]); useEffect(() => { function handle(event) { if (activeField) { - if (values.activeTab === 'addSignalTab') { + if (values.signalIndex === '-1') { newSignalFormRef.current.setValues({ [activeField]: event.xPPM }); } else if (activeField.includes('delta')) { setFieldValue(activeField, event.xPPM); @@ -111,7 +111,7 @@ function SignalsForm({ range, preferences }: SignalsFormProps) { return () => { Events.off('mouseClick', handle); }; - }, [values.activeTab, activeField, setFieldValue]); + }, [values.signalIndex, activeField, setFieldValue]); const handleOnFocus = useCallback( (event) => { @@ -122,7 +122,7 @@ function SignalsForm({ range, preferences }: SignalsFormProps) { const tapClickHandler = useCallback( ({ tabid }) => { - setFieldValue('activeTab', tabid); + setFieldValue('signalIndex', tabid); }, [setFieldValue], ); @@ -139,10 +139,8 @@ function SignalsForm({ range, preferences }: SignalsFormProps) { useEffect(() => { setFieldValue( - 'activeTab', - values.signals.length > 0 - ? (values.signals.length - 1).toString() - : 'addSignalTab', + 'signalIndex', + values.signals.length > 0 ? (values.signals.length - 1).toString() : '-1', ); }, [setFieldValue, values.signals.length]); @@ -210,7 +208,7 @@ function SignalsForm({ range, preferences }: SignalsFormProps) {
From 9bdbba1f06fe4fc89ee23e2a924a9631b8fea535 Mon Sep 17 00:00:00 2001 From: hamed musallam Date: Thu, 14 Mar 2024 18:22:43 +0100 Subject: [PATCH 04/10] chore: create useEvent hook --- src/component/1d/Viewer1D.tsx | 15 ++--- src/component/EventsTrackers/BrushTracker.tsx | 6 +- src/component/utility/Events.ts | 14 ----- src/component/utility/Events.tsx | 55 +++++++++++++++++++ 4 files changed, 65 insertions(+), 25 deletions(-) delete mode 100644 src/component/utility/Events.ts create mode 100644 src/component/utility/Events.tsx diff --git a/src/component/1d/Viewer1D.tsx b/src/component/1d/Viewer1D.tsx index 848fcc520..fb047ab7a 100644 --- a/src/component/1d/Viewer1D.tsx +++ b/src/component/1d/Viewer1D.tsx @@ -305,12 +305,11 @@ function Viewer1D({ emptyText = undefined }: Viewer1DProps) { const xPPM = scaleState.scaleX().invert(event.x); - const propagateEvent = () => { - Events.emit('mouseClick', { - ...event, - xPPM, - }); - }; + Events.emit('mouseClick', { + ...event, + xPPM, + }); + const keyModifiers = getModifiersKey(event as unknown as MouseEvent); switch (keyModifiers) { @@ -322,9 +321,7 @@ function Viewer1D({ emptyText = undefined }: Viewer1DProps) { payload: event, }); break; - case options.editRange.id: - propagateEvent(); - break; + case options.integral.id: dispatch({ type: 'CUT_INTEGRAL', diff --git a/src/component/EventsTrackers/BrushTracker.tsx b/src/component/EventsTrackers/BrushTracker.tsx index 298ee2088..1479d637b 100644 --- a/src/component/EventsTrackers/BrushTracker.tsx +++ b/src/component/EventsTrackers/BrushTracker.tsx @@ -75,11 +75,13 @@ export function useBrushTracker() { return useContext(BrushContext); } -interface Position { +export interface Position { x: number; y: number; } -export type OnClick = (element: React.MouseEvent & Position) => void; + +export type ClickOptions = React.MouseEvent & Position; +export type OnClick = (element: ClickOptions) => void; export type { OnClick as OnDoubleClick }; export type OnZoom = ( event: Pick & Position, diff --git a/src/component/utility/Events.ts b/src/component/utility/Events.ts deleted file mode 100644 index eb90fba41..000000000 --- a/src/component/utility/Events.ts +++ /dev/null @@ -1,14 +0,0 @@ -import EE from 'eventemitter3'; - -const eventEmitter = new EE(); - -const Emitter = { - on: (event, fn) => eventEmitter.on(event, fn), - once: (event, fn) => eventEmitter.once(event, fn), - off: (event, fn) => eventEmitter.off(event, fn), - emit: (event, payload) => eventEmitter.emit(event, payload), -}; - -Object.freeze(Emitter); - -export default Emitter; diff --git a/src/component/utility/Events.tsx b/src/component/utility/Events.tsx new file mode 100644 index 000000000..3c1bcf235 --- /dev/null +++ b/src/component/utility/Events.tsx @@ -0,0 +1,55 @@ +import EE from 'eventemitter3'; +import { useEffect } from 'react'; + +import { + BrushTrackerContext, + ClickOptions, +} from '../EventsTrackers/BrushTracker'; + +const eventEmitter = new EE(); + +const Emitter = { + on: (event, fn) => eventEmitter.on(event, fn), + once: (event, fn) => eventEmitter.once(event, fn), + off: (event, fn) => eventEmitter.off(event, fn), + emit: (event, payload) => eventEmitter.emit(event, payload), +}; + +Object.freeze(Emitter); + +export type EventEmitterClickOptions = ClickOptions & { xPPM: number }; + +interface UseEventOptions { + onBrushEnd?: (options: BrushTrackerContext & { range: number[] }) => void; + onClick?: (options: EventEmitterClickOptions) => void; +} + +export function useEvent(options: UseEventOptions) { + const { onBrushEnd, onClick } = options; + + useEffect(() => { + function handle(event) { + onBrushEnd?.(event); + } + + Emitter.on('brushEnd', handle); + + return () => { + Emitter.off('brushEnd', handle); + }; + }, [onBrushEnd]); + + useEffect(() => { + function handle(event) { + onClick?.(event); + } + + Emitter.on('mouseClick', handle); + + return () => { + Emitter.off('mouseClick', handle); + }; + }, [onClick]); +} + +export default Emitter; From 2bdb898b5f67a3883e16281d2ba92c281da5c892 Mon Sep 17 00:00:00 2001 From: hamed musallam Date: Thu, 14 Mar 2024 18:25:44 +0100 Subject: [PATCH 05/10] refactor: add new signal tab --- .../forms/components/AddSignalFormTab.tsx | 39 ++++++++++++------- .../forms/components/SignalFormTab.tsx | 14 ++----- .../forms/components/SignalsForm.tsx | 13 ++----- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/component/modal/editRange/forms/components/AddSignalFormTab.tsx b/src/component/modal/editRange/forms/components/AddSignalFormTab.tsx index 53a06e316..88f6054a4 100644 --- a/src/component/modal/editRange/forms/components/AddSignalFormTab.tsx +++ b/src/component/modal/editRange/forms/components/AddSignalFormTab.tsx @@ -1,11 +1,12 @@ import { Formik, useFormikContext } from 'formik'; import { WorkSpacePanelPreferences } from 'nmr-load-save'; import { translateMultiplet } from 'nmr-processing'; -import { CSSProperties, forwardRef } from 'react'; +import { CSSProperties, useRef } from 'react'; import * as Yup from 'yup'; import Button from '../../../../elements/Button'; import FormikInput from '../../../../elements/formik/FormikInput'; +import { useEvent } from '../../../../utility/Events'; import { formatNumber } from '../../../../utility/formatNumber'; const styles: Record< @@ -28,18 +29,14 @@ const styles: Record< }; interface AddSignalFormTabProps { - onFocus: (element: any) => void; - onBlur?: () => void; range: any; preferences: WorkSpacePanelPreferences['ranges']; } -// TODO: this seems to be a hacky use of ref. -function AddSignalFormTab( - { onFocus, onBlur, range, preferences }: AddSignalFormTabProps, - ref: any, -) { +export function AddSignalFormTab(props: AddSignalFormTabProps) { + const { range, preferences } = props; const { values, setFieldValue } = useFormikContext(); + const newSignalFormRef = useRef(); function saveHandler(val) { const newSignal = { @@ -54,10 +51,28 @@ function AddSignalFormTab( void setFieldValue('signalIndex', String(_signals.length - 1)); } + useEvent({ + onClick: (options) => { + if (values.signalIndex === '-1') { + newSignalFormRef.current.setValues({ newSignalDelta: options.xPPM }); + } + }, + onBrushEnd: (options) => { + const { + range: [from, to], + } = options; + if (values.signalIndex === '-1') { + newSignalFormRef.current.setValues({ + newSignalDelta: (to - from) / 2 + from, + }); + } + }, + }); + return (
ref.current.submitForm()} + onClick={() => newSignalFormRef.current.submitForm()} > Add a signal @@ -120,5 +133,3 @@ function getSignalValidationSchema(range) { .required(), }); } - -export default forwardRef(AddSignalFormTab); diff --git a/src/component/modal/editRange/forms/components/SignalFormTab.tsx b/src/component/modal/editRange/forms/components/SignalFormTab.tsx index 252d83a84..575c16089 100644 --- a/src/component/modal/editRange/forms/components/SignalFormTab.tsx +++ b/src/component/modal/editRange/forms/components/SignalFormTab.tsx @@ -18,11 +18,10 @@ const style: CSSProperties = { }; interface SignalFormTabProps { - onFocus: (element: any) => void; - onBlur?: () => void; + index: number; } -function SignalFormTab({ onFocus, onBlur }: SignalFormTabProps) { +function SignalFormTab({ index }: SignalFormTabProps) { const { values } = useFormikContext<{ signalIndex: string }>(); return ( @@ -42,17 +41,12 @@ function SignalFormTab({ onFocus, onBlur }: SignalFormTabProps) { ( - + )} /> } /> - } /> + } />
); diff --git a/src/component/modal/editRange/forms/components/SignalsForm.tsx b/src/component/modal/editRange/forms/components/SignalsForm.tsx index 96a8adbda..d41994839 100644 --- a/src/component/modal/editRange/forms/components/SignalsForm.tsx +++ b/src/component/modal/editRange/forms/components/SignalsForm.tsx @@ -12,7 +12,7 @@ import useSpectrum from '../../../../hooks/useSpectrum'; import Events from '../../../../utility/Events'; import { formatNumber } from '../../../../utility/formatNumber'; -import AddSignalFormTab from './AddSignalFormTab'; +import { AddSignalFormTab } from './AddSignalFormTab'; import DeltaInput from './DeltaInput'; import { SignalFormInfo } from './SignalFormInfo'; import SignalFormTab from './SignalFormTab'; @@ -165,14 +165,14 @@ function SignalsForm({ range, preferences }: SignalsFormProps) { )} > - + )) : []; const addSignalTab = ( )} > - + ); From 93310cd94d4b284d8b6225e90b78beb0ab37c50f Mon Sep 17 00:00:00 2001 From: hamed musallam Date: Thu, 14 Mar 2024 18:26:22 +0100 Subject: [PATCH 06/10] feat: improve edit peaks --- .../editRange/forms/components/PeaksTable.tsx | 66 ++++++++++++++++--- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/src/component/modal/editRange/forms/components/PeaksTable.tsx b/src/component/modal/editRange/forms/components/PeaksTable.tsx index c960157a0..22fd351b5 100644 --- a/src/component/modal/editRange/forms/components/PeaksTable.tsx +++ b/src/component/modal/editRange/forms/components/PeaksTable.tsx @@ -3,7 +3,7 @@ import { useFormikContext } from 'formik'; import { xFindClosestIndex } from 'ml-spectra-processing'; import { Spectrum1D } from 'nmr-load-save'; import { Peak1D, getShiftX } from 'nmr-processing'; -import { CSSProperties, useCallback, useMemo } from 'react'; +import { CSSProperties, useCallback, useMemo, useState } from 'react'; import { FaPlus, FaRegTrashAlt } from 'react-icons/fa'; import { Toolbar } from 'react-science/ui'; @@ -11,6 +11,7 @@ import Button from '../../../../elements/Button'; import ReactTable, { Column } from '../../../../elements/ReactTable/ReactTable'; import FormikInput from '../../../../elements/formik/FormikInput'; import useSpectrum from '../../../../hooks/useSpectrum'; +import { useEvent } from '../../../../utility/Events'; const styles: Record<'input' | 'column', CSSProperties> = { input: { @@ -22,20 +23,44 @@ const styles: Record<'input' | 'column', CSSProperties> = { }, }; -export default function PeaksTable() { +interface PeaksTableProps { + index: number; +} + +export default function PeaksTable(props: PeaksTableProps) { const { values, setFieldValue } = useFormikContext(); const signal = values?.signals?.[values?.signalIndex] || {}; const peaks = signal?.peaks || []; const delta = signal?.delta || 0; const spectrum = useSpectrum() as Spectrum1D; const { - data: { x, re }, + data: { x: xArray, re }, } = spectrum; const shiftX = getShiftX(spectrum); + const [lastSelectedPeak, setLastSelectedPeak] = useState(null); + + useEvent({ + onClick: (options) => { + if (`${props.index}` === values.signalIndex) { + const index = peaks.findIndex( + (peak) => peak.id === lastSelectedPeak?.id, + ); + if (index !== -1) { + const delta = options.xPPM; + const xIndex = xFindClosestIndex(xArray, delta, { sorted: false }); + void setFieldValue(`signals[${values?.signalIndex}].peaks.${index}`, { + ...peaks[index], + x: delta, + y: re[xIndex], + }); + } + } + }, + }); const addHandler = useCallback( (data: Peak1D[]) => { - const xIndex = xFindClosestIndex(x, delta, { sorted: false }); + const xIndex = xFindClosestIndex(xArray, delta, { sorted: false }); const peak: Peak1D = { id: v4(), @@ -48,34 +73,44 @@ export default function PeaksTable() { ...data, peak, ]); + setLastSelectedPeak(peak); }, - [delta, re, setFieldValue, shiftX, values?.signalIndex, x], + [delta, re, setFieldValue, shiftX, values?.signalIndex, xArray], ); const deleteHandler = useCallback( (data, index: number) => { + const lastPeakIndex = data.findIndex( + (peak) => peak.id === lastSelectedPeak?.id, + ); + const peaks = data.filter((_, columnIndex) => columnIndex !== index); + void setFieldValue(`signals[${values?.signalIndex}].peaks`, peaks); + if (lastPeakIndex === index) { + setLastSelectedPeak(null); + } }, - [setFieldValue, values?.signalIndex], + [lastSelectedPeak?.id, setFieldValue, values?.signalIndex], ); function deleteAllHandler() { void setFieldValue(`signals[${values?.signalIndex}].peaks`, []); + setLastSelectedPeak(null); } const changeDeltaHandler = useCallback( (event, index: number) => { const delta = Number(event.target.value); - const xIndex = xFindClosestIndex(x, delta, { sorted: false }); + const xIndex = xFindClosestIndex(xArray, delta, { sorted: false }); void setFieldValue( `signals[${values?.signalIndex}].peaks.${index}.y`, re[xIndex], ); }, - [re, setFieldValue, values?.signalIndex, x], + [re, setFieldValue, values?.signalIndex, xArray], ); const COLUMNS: Array> = useMemo( @@ -123,7 +158,10 @@ export default function PeaksTable() { {!record?.name && ( deleteHandler(data, row.index)} + onClick={(e) => { + e.stopPropagation(); + deleteHandler(data, row.index); + }} > @@ -136,6 +174,14 @@ export default function PeaksTable() { [changeDeltaHandler, deleteHandler, values?.signalIndex], ); + function selectRowHandler(data) { + setLastSelectedPeak((prevPeak) => (prevPeak?.id === data.id ? null : data)); + } + + function handleActiveRow(row) { + return row?.original.id === lastSelectedPeak?.id; + } + return (
@@ -163,6 +209,8 @@ export default function PeaksTable() { selectRowHandler(rowData.original)} + activeRow={handleActiveRow} emptyDataRowText="No peaks" />
From 0e3852ec2051eb2b0f8db661ff0b13275d6e91dc Mon Sep 17 00:00:00 2001 From: hamed musallam Date: Fri, 15 Mar 2024 10:20:37 +0100 Subject: [PATCH 07/10] refactor: j couplings table --- src/component/elements/Select.tsx | 2 + .../forms/components/CouplingsTable.tsx | 162 ------------ .../forms/components/JCouplingsTable.tsx | 248 ++++++++++++++++++ .../forms/components/SignalFormTab.tsx | 16 +- 4 files changed, 253 insertions(+), 175 deletions(-) delete mode 100644 src/component/modal/editRange/forms/components/CouplingsTable.tsx create mode 100644 src/component/modal/editRange/forms/components/JCouplingsTable.tsx diff --git a/src/component/elements/Select.tsx b/src/component/elements/Select.tsx index 688d58ac2..5faa8fe6c 100644 --- a/src/component/elements/Select.tsx +++ b/src/component/elements/Select.tsx @@ -66,6 +66,7 @@ const Select = forwardRef(function Select( itemTextField = 'label', returnValue = true, textRender, + ...otherProps } = props; const handleOnChanged = useCallback( @@ -87,6 +88,7 @@ const Select = forwardRef(function Select( className={className} style={style} required + {...otherProps} > {placeholder && (