From c535803c624ae577dc38450f95052d5f090f97dc Mon Sep 17 00:00:00 2001 From: hamed-musallam <35760236+hamed-musallam@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:50:02 +0100 Subject: [PATCH] refactor: summary panel modals (#2996) * refactor: shift tolerances modal * refactor: set molecular formula modal --- src/component/1d/ranges/Range.tsx | 2 +- .../elements/MolecularFormulaInput.tsx | 82 ------ src/component/elements/formik/FormikError.tsx | 7 +- src/component/elements/formik/FormikInput.tsx | 5 +- .../panels/RangesPanel/RangesHeader.tsx | 2 +- .../SummaryPanel/SetMolecularFormulaModal.tsx | 271 ++++++++++-------- .../SummaryPanel/SetShiftTolerancesModal.tsx | 266 ++++++++--------- .../panels/SummaryPanel/SummaryPanel.tsx | 71 ++--- 8 files changed, 302 insertions(+), 404 deletions(-) delete mode 100644 src/component/elements/MolecularFormulaInput.tsx diff --git a/src/component/1d/ranges/Range.tsx b/src/component/1d/ranges/Range.tsx index 122589fb1..670bc1938 100644 --- a/src/component/1d/ranges/Range.tsx +++ b/src/component/1d/ranges/Range.tsx @@ -19,8 +19,8 @@ import { IntegralIndicator } from '../integral/IntegralIndicator'; import { useScaleX } from '../utilities/scale'; import { AssignmentActionsButtons } from './AssignmentActionsButtons'; -import { Atoms } from './Atoms'; import { AssignmentLabel } from './AssignmentLabel'; +import { Atoms } from './Atoms'; const style = css` .target { diff --git a/src/component/elements/MolecularFormulaInput.tsx b/src/component/elements/MolecularFormulaInput.tsx deleted file mode 100644 index 402e0891b..000000000 --- a/src/component/elements/MolecularFormulaInput.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/** @jsxImportSource @emotion/react */ -import { css } from '@emotion/react'; -import { useCallback, useState } from 'react'; - -import getAtomsFromMF from '../../data/utilities/getAtomsFromMF'; - -const inputStyle = css` - text-align: center; - - input { - height: 100%; - width: 200px; - border-radius: 5px; - border: 0.55px solid #c7c7c7; - margin: 0 5px; - text-align: center; - } - - button { - flex: 2; - padding: 5px; - border: 1px solid gray; - border-radius: 5px; - height: 36px; - margin: 0 auto; - margin-top: 15px; - display: block; - width: 20%; - color: white; - background-color: gray; - } -`; - -interface MolecularFormulaInputProps { - onSave?: (element: string) => void; - previousMF: string; -} - -function isValidMf(mf: string) { - if (mf.trim().length === 0) { - return false; - } - try { - getAtomsFromMF(mf); - return true; - } catch { - return false; - } -} - -export default function MolecularFormulaInput({ - onSave = () => null, - previousMF, -}: MolecularFormulaInputProps) { - const [mf, setMF] = useState(''); - const [isValidMF, setIsValidMF] = useState(true); - const [hasChanged, setHasChanged] = useState(false); - - const onChangeHandler = useCallback((e) => { - setHasChanged(true); - if (isValidMf(e.target.value)) { - setIsValidMF(true); - setMF(e.target.value); - } else { - setIsValidMF(false); - setMF(''); - } - }, []); - - const onSaveHandler = useCallback(() => { - onSave(hasChanged ? mf.trim() : previousMF); - }, [hasChanged, mf, onSave, previousMF]); - - return ( -
- - -
- ); -} diff --git a/src/component/elements/formik/FormikError.tsx b/src/component/elements/formik/FormikError.tsx index 0c0a21a9c..c9dcddd8a 100644 --- a/src/component/elements/formik/FormikError.tsx +++ b/src/component/elements/formik/FormikError.tsx @@ -4,7 +4,6 @@ import { CSSProperties, ReactNode } from 'react'; const styles: Record<'container' | 'text', CSSProperties> = { container: { - padding: '0.4rem', display: 'flex', flexDirection: 'column', }, @@ -22,15 +21,13 @@ export interface FormikErrorProps { } function FormikError({ style, name, children }: FormikErrorProps) { - const { errors, isValid, dirty, touched } = useFormikContext(); + const { errors, isValid, dirty } = useFormikContext(); const errorText = lodashGet(errors, name); - const isTouched = lodashGet(touched, name); - return (
{children} - {!isValid && dirty && isTouched && ( + {!isValid && dirty && ( {errorText} )}
diff --git a/src/component/elements/formik/FormikInput.tsx b/src/component/elements/formik/FormikInput.tsx index 8c7d82065..07d2f613b 100644 --- a/src/component/elements/formik/FormikInput.tsx +++ b/src/component/elements/formik/FormikInput.tsx @@ -26,7 +26,7 @@ function FormikInput(props: FormikInputProps & InputProps) { ...resProps } = props; - const { values, handleChange, errors, touched, setFieldValue } = + const { values, handleChange, errors, touched, setFieldValue, handleBlur } = useFormikContext(); function changeHandler(e) { @@ -37,7 +37,6 @@ function FormikInput(props: FormikInputProps & InputProps) { handleChange(e); } } - let isInvalid = lodashGet(errors, name); if (checkErrorAfterInputTouched) { @@ -46,11 +45,13 @@ function FormikInput(props: FormikInputProps & InputProps) { let val = value || lodashGet(values, name); val = mapValue ? mapValue(val) : val; + return ( + isValidMf(value), + ), +}); + +const styles: Record<'container' | 'input' | 'error' | 'title', CSSProperties> = + { + input: { + padding: '0.5rem', + }, + container: { + padding: '0.5rem', + }, + error: { + textAlign: 'center', + color: 'red', + fontSize: '0.9em', + }, + title: { + textAlign: 'center', + }, + }; - .header { - height: 24px; - border-bottom: 1px solid #f0f0f0; - display: flex; - align-items: center; - - button { - height: 36px; - margin: 2px; - background-color: transparent; - border: none; - - svg { - height: 16px; - } - } - } +interface InnerMolecularFormulaModalProps { + onClose: () => void; +} +interface MolecularFormulaModalProps extends InnerMolecularFormulaModalProps { + isOpen: boolean; +} - .info { - margin-top: 0; - margin-bottom: 10px; - padding: 0 10px; - width: 100%; - text-align: center; - } +export function SetMolecularFormulaModal(props: MolecularFormulaModalProps) { + const { isOpen, ...otherProps } = props; - .optional { - margin-top: 20px; - margin-bottom: 5px; - padding: 0 10px; - text-align: center; - font-size: 18px; - font-weight: bold; + if (!isOpen) { + return; } - .optional2 { - margin-top: 5px; - margin-bottom: 25px; - padding: 0 10px; - width: 100%; - text-align: center; + return ; +} + +function InnerSetMolecularFormulaModal({ + onClose, +}: InnerMolecularFormulaModalProps) { + const dispatch = useDispatch(); + + function saveHandler(mf) { + dispatch({ + type: 'SET_CORRELATIONS_MF', + payload: { + mf, + }, + }); + onClose?.(); } -`; -interface SetMolecularFormulaModalProps { - onClose: () => void; - onSave?: (element: any) => void; - molecules: any; - previousMF: any; + return ( + + + + } + /> + } + /> + + + + ); } -export default function SetMolecularFormulaModal({ - onClose, - onSave, - molecules, - previousMF, -}: SetMolecularFormulaModalProps) { - const [currentIndex, setCurrentIndex] = useState(0); +function ManualFormula(props) { + const { correlations } = useChartData(); - useEffect(() => { - if (molecules && molecules.length > 0) { - setCurrentIndex(0); - } - }, [molecules]); + function saveHandler(data) { + props?.onSave(data.mf); + } - const onSaveHandlerMolecularFormulaInput = useCallback( - (mf) => { - onSave?.(mf); - onClose?.(); - }, - [onClose, onSave], + return ( +
+

Please type in a molecular formula!

+ + + {({ isValid, handleSubmit }) => ( + <> + + + + handleSubmit()} + disabled={!isValid} + style={{ marginTop: '10px' }} + > + Set molecular formula + + + )} + +
); +} - const onSaveHandlerMoleculeSelection = useCallback(() => { - onSave?.(molecules[currentIndex].mf); - onClose?.(); - }, [currentIndex, molecules, onClose, onSave]); +function AutoFormula(props) { + const { molecules } = useChartData(); + + const [currentIndex, setCurrentIndex] = useState(0); - const onChangeHandlerMoleculeSelection = useCallback((index) => { + function onChangeHandlerMoleculeSelection(index) { setCurrentIndex(index); - }, []); + } + + function saveHandler() { + const mf = molecules?.[currentIndex]?.mf; + props?.onSave(mf); + } + + if (!molecules || molecules?.length === 0) { + return ( +

+ You have to Select a spectrum and Add a molecule from the Structure + panel to select as a reference! +

+ ); + } return ( -
-
- -
-
-

Please type in a molecular formula!

-
- -
-

OR

-

Select a molecule as reference!

-
+
+

Select a molecule as reference!

- + + Set molecular formula +
); } diff --git a/src/component/panels/SummaryPanel/SetShiftTolerancesModal.tsx b/src/component/panels/SummaryPanel/SetShiftTolerancesModal.tsx index 587dc6c61..c77b8800d 100644 --- a/src/component/panels/SummaryPanel/SetShiftTolerancesModal.tsx +++ b/src/component/panels/SummaryPanel/SetShiftTolerancesModal.tsx @@ -1,166 +1,138 @@ /** @jsxImportSource @emotion/react */ -import { css } from '@emotion/react'; -import { useEffect, useState } from 'react'; - -import { CloseButton } from '../../elements/CloseButton'; - -const modalContainer = css` - width: 220px; - height: 270px; - padding: 5px; - text-align: center; - - button { - flex: 2; - padding: 5px; - border: 1px solid gray; - border-radius: 5px; - height: 30px; - margin: 0 auto; - margin-top: 15px; - display: block; - width: 20%; - color: white; - background-color: gray; - } - - button:focus { - outline: none; - } - - .header { - height: 24px; - border-bottom: 1px solid #f0f0f0; - display: flex; - align-items: center; - - span { - color: #464646; - font-size: 15px; - flex: 1; - user-select: none; - } - - button { - height: 36px; - margin: 2px; - background-color: transparent; - border: none; +import { Dialog, DialogBody, DialogFooter } from '@blueprintjs/core'; +import { Formik } from 'formik'; +import { Tolerance } from 'nmr-correlation'; +import { CSSProperties, useMemo } from 'react'; +import * as Yup from 'yup'; + +import { useDispatch } from '../../context/DispatchContext'; +import Button from '../../elements/Button'; +import ReactTable, { Column } from '../../elements/ReactTable/ReactTable'; +import FormikInput from '../../elements/formik/FormikInput'; +import { useChartData } from '../../context/ChartContext'; + +const styles: Record<'input' | 'column', CSSProperties> = { + input: { + padding: '0.25rem 0.5rem', + }, + column: { + padding: '2px', + }, +}; + +interface ToleranceItem { + atom: string; + value: number; +} - svg { - height: 16px; - } - } - } +interface SetShiftToleranceModalProps extends InnerSetShiftToleranceModalProps { + isOpen: boolean; +} +interface InnerSetShiftToleranceModalProps { + onClose: () => void; +} - table { - margin-top: 10px; - width: 100%; +const toleranceValidationSchema = Yup.array() + .of(Yup.object({ value: Yup.number().required() })) + .min(1); - input::-webkit-outer-spin-button, - input::-webkit-inner-spin-button { - appearance: none; - } +export function SetShiftToleranceModal(props: SetShiftToleranceModalProps) { + const { isOpen, ...otherPros } = props; - input { - width: 100px; - text-align: center; - border-radius: 5px; - border: 0.55px solid #c7c7c7; - } + if (!isOpen) { + return; } -`; -interface SetShiftToleranceModalProps { - onClose: () => void; - onSave?: (element: any) => void; - previousTolerance: any; + return ; } -export default function SetShiftToleranceModal({ - onClose, - onSave, - previousTolerance, -}: SetShiftToleranceModalProps) { - const [tolerance, setTolerance] = useState(); - const [isValid, setIsValid] = useState>({}); +function InnerSetShiftToleranceModal(props: InnerSetShiftToleranceModalProps) { + const { onClose } = props; + const { correlations } = useChartData(); + const dispatch = useDispatch(); - useEffect(() => { - if (previousTolerance) { - setTolerance(previousTolerance); - - setIsValid( - Object.fromEntries( - Object.keys(previousTolerance).map((atomType) => [atomType, true]), - ), - ); - } else { - setTolerance(undefined); + function onSaveHandler(data) { + const tolerance: Tolerance = {}; + for (const { atom, value } of data) { + tolerance[atom] = value; } - }, [previousTolerance]); - - function onSaveHandler() { - onSave?.(tolerance); + dispatch({ + type: 'SET_CORRELATIONS_TOLERANCE', + payload: { + tolerance, + }, + }); onClose?.(); } - function onChangeHandler(e, atomType: string) { - const value: string = e.target.value; - if (value.trim().length > 0) { - setTolerance({ ...tolerance, [atomType]: Number(value) }); - setIsValid({ ...isValid, [atomType]: true }); - } else { - setIsValid({ ...isValid, [atomType]: false }); - } - } + const COLUMNS: Array> = useMemo( + () => [ + { + Header: '#', + style: { width: '25px', textAlign: 'center', ...styles.column }, + accessor: (_, index) => index + 1, + }, + { + Header: 'Atom', + style: { width: '25px', textAlign: 'center', ...styles.column }, + accessor: 'atom', + }, + { + Header: 'Value', + style: { padding: 0, ...styles.column }, + Cell: ({ row }) => { + return ( + + ); + }, + }, + ], + [], + ); - return ( -
-
- Set Shift Tolerances - -
+ const tolerances = correlations?.options?.tolerance || {}; - - - - - - - - - - -
AtomValue
- -
+ const tolerancesData: ToleranceItem[] = Object.keys(tolerances).map( + (atom) => ({ + atom, + value: tolerances[atom], + }), ); -} -function Rows({ tolerance, onChange, isValid }) { - return Object.keys(tolerance).map((atomType) => { - return ( - - {atomType} - - onChange(e, atomType)} - defaultValue={tolerance[atomType]} - style={!isValid[atomType] ? { backgroundColor: 'orange' } : {}} - /> - - - ); - }); + return ( + + + {({ isValid, handleSubmit }) => ( + <> + + + + + handleSubmit()} disabled={!isValid}> + Set shift tolerances + + + + )} + + + ); } diff --git a/src/component/panels/SummaryPanel/SummaryPanel.tsx b/src/component/panels/SummaryPanel/SummaryPanel.tsx index 27dda87a3..e57da869a 100644 --- a/src/component/panels/SummaryPanel/SummaryPanel.tsx +++ b/src/component/panels/SummaryPanel/SummaryPanel.tsx @@ -23,13 +23,13 @@ import { useAssignmentData } from '../../assignment/AssignmentsContext'; import { useChartData } from '../../context/ChartContext'; import { useDispatch } from '../../context/DispatchContext'; import Select from '../../elements/Select'; -import { useModal } from '../../elements/popup/Modal'; +import { useDialogToggle } from '../../hooks/useDialogToggle'; import DefaultPanelHeader from '../header/DefaultPanelHeader'; import CorrelationTable from './CorrelationTable/CorrelationTable'; import Overview from './Overview'; -import SetMolecularFormulaModal from './SetMolecularFormulaModal'; -import SetShiftToleranceModal from './SetShiftTolerancesModal'; +import { SetMolecularFormulaModal } from './SetMolecularFormulaModal'; +import { SetShiftToleranceModal } from './SetShiftTolerancesModal'; import { findSignalMatch1D, findSignalMatch2D, @@ -72,7 +72,6 @@ const panelStyle = css` function SummaryPanel() { const { - molecules, correlations: correlationsData, data: spectraData, xDomain, @@ -84,8 +83,11 @@ function SummaryPanel() { } = useChartData(); const dispatch = useDispatch(); - const modal = useModal(); const assignmentData = useAssignmentData(); + const { dialog, openDialog, closeDialog } = useDialogToggle({ + shiftToleranceModal: false, + molecularFormula: false, + }); const [additionalColumnData, setAdditionalColumnData] = useState([]); const [ @@ -226,51 +228,6 @@ function SummaryPanel() { yDomain, ]); - const handleOnSetMolecularFormula = useCallback( - (mf) => { - dispatch({ - type: 'SET_CORRELATIONS_MF', - payload: { - mf, - }, - }); - }, - [dispatch], - ); - - const handleOnSetShiftTolerance = useCallback( - (tolerance) => { - dispatch({ - type: 'SET_CORRELATIONS_TOLERANCE', - payload: { - tolerance, - }, - }); - }, - [dispatch], - ); - - const showSetMolecularFormulaModal = useCallback(() => { - modal.show( - modal.close()} - onSave={handleOnSetMolecularFormula} - molecules={molecules} - previousMF={correlationsData.options.mf} - />, - ); - }, [correlationsData, handleOnSetMolecularFormula, modal, molecules]); - - const showSetShiftToleranceModal = useCallback(() => { - modal.show( - modal.close()} - onSave={handleOnSetShiftTolerance} - previousTolerance={correlationsData.options.tolerance} - />, - ); - }, [correlationsData, handleOnSetShiftTolerance, modal]); - const additionalColumnTypes = useMemo(() => { const columnTypes = ['H', 'H-H'].concat( correlationsData @@ -495,6 +452,14 @@ function SummaryPanel() { const total = correlationsData ? correlationsData.values.length : 0; return (
+ + , - title: `Set molecular formula (${correlationsData.options.mf})`, - onClick: showSetMolecularFormulaModal, + title: `Set molecular formula (${correlationsData?.options?.mf || ''})`, + onClick: () => openDialog('molecularFormula'), }, { icon: , title: 'Set shift tolerance', - onClick: showSetShiftToleranceModal, + onClick: () => openDialog('shiftToleranceModal'), }, ]} >