diff --git a/src/component/header/BaseLineCorrectionPanel.tsx b/src/component/header/BaseLineCorrectionPanel.tsx deleted file mode 100644 index e70db8801..000000000 --- a/src/component/header/BaseLineCorrectionPanel.tsx +++ /dev/null @@ -1,281 +0,0 @@ -import { Checkbox } from '@blueprintjs/core'; -import { Select } from '@blueprintjs/select'; -import { yupResolver } from '@hookform/resolvers/yup'; -import { Filter, Filters, BaselineCorrectionOptions } from 'nmr-processing'; -import { memo, useCallback, useEffect, useRef } from 'react'; -import { useForm } from 'react-hook-form'; -import { Button, useSelect } from 'react-science/ui'; -import * as Yup from 'yup'; - -import { useDispatch } from '../context/DispatchContext'; -import ActionButtons from '../elements/ActionButtons'; -import Label from '../elements/Label'; -import { NumberInput2Controller } from '../elements/NumberInput2Controller'; -import { SelectDefaultItem } from '../elements/Select2'; -import { useFilter } from '../hooks/useFilter'; - -import { headerLabelStyle } from './Header'; -import { HeaderContainer } from './HeaderContainer'; - -interface BaseLineCorrectionInnerPanelProps { - filter: Filter | null; -} - -const algorithmsList = ['airPLS', 'Polynomial'].map((val) => ({ - label: val, - value: val.toLowerCase(), -})); - -interface BaseOptions { - algorithm: string; - livePreview: boolean; -} -interface AirplsOptions extends BaseOptions { - maxIterations: number; - tolerance: number; -} -interface PolynomialOptions extends BaseOptions { - degree: number; -} - -const getData = (algorithm, filterValues: BaselineCorrectionOptions) => { - const { zones, algorithm: baseAlgorithm, ...other } = filterValues; - switch (algorithm) { - case 'airpls': { - const validation = Yup.object().shape({ - algorithm: Yup.string().required(), - livePreview: Yup.boolean().required(), - maxIterations: Yup.number().integer().min(1).required(), - tolerance: Yup.number().moreThan(0).required(), - }); - return { - resolver: yupResolver(validation), - values: { - algorithm, - livePreview: true, - maxIterations: 100, - tolerance: 0.001, - ...(baseAlgorithm === 'airpls' ? other : {}), - }, - }; - } - case 'autoPolynomial': - case 'polynomial': { - const validation = Yup.object().shape({ - algorithm: Yup.string().required(), - livePreview: Yup.boolean().required(), - degree: Yup.number().integer().min(1).max(6).required(), - }); - - return { - resolver: yupResolver(validation), - values: { - algorithm, - livePreview: true, - degree: 3, - ...(baseAlgorithm === 'polynomial' ? other : {}), - }, - }; - } - default: - return { - resolver: yupResolver( - Yup.object({ - livePreview: Yup.boolean().required(), - }), - ), - values: { livePreview: true }, - }; - } -}; - -function BaseLineCorrectionInnerPanel( - props: BaseLineCorrectionInnerPanelProps, -) { - const dispatch = useDispatch(); - const previousPreviewRef = useRef(true); - const { algorithm: baseAlgorithm = 'polynomial' } = - props?.filter?.value || {}; - const { - value: algorithm, - onItemSelect, - ...defaultSelectProps - } = useSelect({ - defaultSelectedItem: algorithmsList.find( - (item) => item.value === baseAlgorithm, - ), - itemTextKey: 'label', - }); - - const onChange = useCallback( - (values) => { - const { livePreview, ...options } = values; - - if (livePreview || previousPreviewRef !== livePreview) { - dispatch({ - type: 'CALCULATE_BASE_LINE_CORRECTION_FILTER', - payload: { - options, - livePreview, - }, - }); - } - }, - [dispatch], - ); - - const handleApplyFilter = ( - values, - triggerSource: 'apply' | 'onChange' = 'apply', - ) => { - const { livePreview, ...options } = values; - - switch (triggerSource) { - case 'onChange': { - onChange(values); - break; - } - - case 'apply': { - dispatch({ - type: 'APPLY_BASE_LINE_CORRECTION_FILTER', - payload: { - options, - }, - }); - break; - } - default: - break; - } - previousPreviewRef.current = livePreview; - }; - - const handleCancelFilter = () => { - dispatch({ - type: 'RESET_SELECTED_TOOL', - }); - }; - - const { resolver, values } = getData( - algorithm?.value, - props?.filter?.value || {}, - ); - - const { handleSubmit, control, register, reset } = useForm< - AirplsOptions | PolynomialOptions - >({ - defaultValues: values, - resolver: resolver as any, - }); - - function submitHandler() { - void handleSubmit((values) => handleApplyFilter(values, 'onChange'))(); - } - - const { onChange: onLivePreviewChange, ...otherLivePreviewRegisterOptions } = - register(`livePreview`); - - useEffect(() => { - void handleSubmit((values) => onChange(values))(); - }, [handleSubmit, onChange]); - - return ( - - - - {algorithm && algorithm?.value === 'airpls' && ( -
- - -
- )} - - {algorithm && - ['autoPolynomial', 'polynomial'].includes(algorithm?.value) && ( - - )} - - - handleSubmit((values) => handleApplyFilter(values))()} - onCancel={handleCancelFilter} - /> -
- ); -} - -const MemoizedBaseLineCorrectionPanel = memo(BaseLineCorrectionInnerPanel); - -export default function BaseLineCorrectionPanel() { - const filter = useFilter(Filters.baselineCorrection.id); - return ; -} diff --git a/src/component/header/Header.tsx b/src/component/header/Header.tsx index c1ae1802a..d9bc0b61b 100644 --- a/src/component/header/Header.tsx +++ b/src/component/header/Header.tsx @@ -30,12 +30,12 @@ import WorkspaceItem from '../modal/setting/WorkspaceItem'; import { options } from '../toolbar/ToolTypes'; import { AutoPeakPickingOptionPanel } from './AutoPeakPickingOptionPanel'; -import BaseLineCorrectionPanel from './BaseLineCorrectionPanel'; import { HeaderContainer } from './HeaderContainer'; -import { SimplePhaseCorrectionTwoDimensionsPanel } from './SimplePhaseCorrectionTwoDimensionsPanel'; import RangesPickingOptionPanel from './RangesPickingOptionPanel'; import { SimpleApodizationOptionsPanel } from './SimpleApodizationOptionsPanel'; +import { SimpleBaseLineCorrectionOptionsPanel } from './SimpleBaseLineCorrectionOptionsPanel'; import { SimplePhaseCorrectionOptionsPanel } from './SimplePhaseCorrectionOptionsPanel'; +import { SimplePhaseCorrectionTwoDimensionsPanel } from './SimplePhaseCorrectionTwoDimensionsPanel'; import { SimpleZeroFillingOptionsPanel } from './SimpleZeroFillingOptionsPanel'; import Zones2DOptionPanel from './Zones2DOptionPanel'; @@ -111,12 +111,12 @@ function HeaderInner(props: HeaderInnerProps) { return ; case options.phaseCorrectionTwoDimensions.id: return ; + case options.baselineCorrection.id: + return ; case options.peakPicking.id: return ; case options.rangePicking.id: return ; - case options.baselineCorrection.id: - return ; case options.zonePicking.id: return ; default: diff --git a/src/component/header/SimpleBaseLineCorrectionOptionsPanel.tsx b/src/component/header/SimpleBaseLineCorrectionOptionsPanel.tsx new file mode 100644 index 000000000..fdb32b038 --- /dev/null +++ b/src/component/header/SimpleBaseLineCorrectionOptionsPanel.tsx @@ -0,0 +1,142 @@ +import { Checkbox } from '@blueprintjs/core'; +import { Select } from '@blueprintjs/select'; +import { Filter, Filters } from 'nmr-processing'; +import { memo } from 'react'; +import { Button } from 'react-science/ui'; + +import ActionButtons from '../elements/ActionButtons'; +import Label from '../elements/Label'; +import { NumberInput2Controller } from '../elements/NumberInput2Controller'; +import { useFilter } from '../hooks/useFilter'; +import { + baselineCorrectionsAlgorithms, + getBaselineData, + useBaselineCorrection, +} from '../panels/filtersPanel/Filters/useBaselineCorrection'; + +import { headerLabelStyle } from './Header'; +import { HeaderContainer } from './HeaderContainer'; + +interface BaseLineCorrectionInnerPanelProps { + filter: Filter | null; +} +function BaseLineCorrectionInnerPanel( + props: BaseLineCorrectionInnerPanelProps, +) { + const { + register, + reset, + onAlgorithmChange, + submitHandler, + handleSubmit, + handleApplyFilter, + handleCancelFilter, + control, + algorithm, + defaultAlgorithmSelectProps, + } = useBaselineCorrection(props.filter); + const { onChange: onLivePreviewChange, ...otherLivePreviewRegisterOptions } = + register(`livePreview`); + + return ( + + + + {algorithm && algorithm?.value === 'airpls' && ( +
+ + +
+ )} + + {algorithm && + ['autoPolynomial', 'polynomial'].includes(algorithm?.value) && ( + + )} + + + handleSubmit((values) => handleApplyFilter(values))()} + onCancel={handleCancelFilter} + /> +
+ ); +} + +const MemoizedBaseLineCorrectionPanel = memo(BaseLineCorrectionInnerPanel); + +export function SimpleBaseLineCorrectionOptionsPanel() { + const filter = useFilter(Filters.baselineCorrection.id); + return ; +} diff --git a/src/component/panels/filtersPanel/Filters/BaseLineCorrectionOptionsPanel.tsx b/src/component/panels/filtersPanel/Filters/BaseLineCorrectionOptionsPanel.tsx new file mode 100644 index 000000000..c62b6ed95 --- /dev/null +++ b/src/component/panels/filtersPanel/Filters/BaseLineCorrectionOptionsPanel.tsx @@ -0,0 +1,148 @@ +import { Button, Switch } from '@blueprintjs/core'; +import { Select } from '@blueprintjs/select'; +import { Filter } from 'nmr-processing'; + +import Label from '../../../elements/Label'; +import { NumberInput2Controller } from '../../../elements/NumberInput2Controller'; +import { Sections } from '../../../elements/Sections'; + +import { FilterActionButtons } from './FilterActionButtons'; +import { HeaderContainer, StickyHeader } from './InnerFilterHeader'; +import { + baselineCorrectionsAlgorithms, + getBaselineData, + useBaselineCorrection, +} from './useBaselineCorrection'; + +import { formLabelStyle } from '.'; + +interface BaseLineCorrectionOptionsPanelProps { + filter: Filter; +} + +export default function BaseLineCorrectionOptionsPanel( + props: BaseLineCorrectionOptionsPanelProps, +) { + const { filter } = props; + + const { + register, + reset, + onAlgorithmChange, + submitHandler, + handleSubmit, + handleApplyFilter, + handleCancelFilter, + control, + algorithm, + defaultAlgorithmSelectProps, + } = useBaselineCorrection(filter); + + const { onChange: onLivePreviewFieldChange, ...livePreviewFieldOptions } = + register('livePreview'); + return ( + <> + + + { + void onLivePreviewFieldChange(event); + submitHandler(); + }} + label="Live preview" + /> + + handleSubmit((values) => handleApplyFilter(values))() + } + onCancel={handleCancelFilter} + /> + + + +
+ + + {algorithm && algorithm?.value === 'airpls' && ( + <> + + + + )} + + {algorithm && + ['autoPolynomial', 'polynomial'].includes(algorithm?.value) && ( + + )} +
+
+ + ); +} diff --git a/src/component/panels/filtersPanel/Filters/index.tsx b/src/component/panels/filtersPanel/Filters/index.tsx index fb225ce2d..58ff87f0f 100644 --- a/src/component/panels/filtersPanel/Filters/index.tsx +++ b/src/component/panels/filtersPanel/Filters/index.tsx @@ -6,18 +6,21 @@ import ApodizationOptionsPanel from './ApodizationOptionsPanel'; import PhaseCorrectionOptionsPanel from './PhaseCorrectionOptionsPanel'; import PhaseCorrectionTwoDimensionsOptionsPanel from './PhaseCorrectionTwoDimensionsOptionsPanel'; import ZeroFillingOptionsPanel from './ZeroFillingOptionsPanel'; +import BaseLineCorrectionOptionsPanel from './BaseLineCorrectionOptionsPanel'; const { apodization, phaseCorrection, phaseCorrectionTwoDimensions, zeroFilling, + baselineCorrection, } = Filters; export const filterOptionPanels = { [apodization.id]: ApodizationOptionsPanel, [phaseCorrection.id]: PhaseCorrectionOptionsPanel, [zeroFilling.id]: ZeroFillingOptionsPanel, [phaseCorrectionTwoDimensions.id]: PhaseCorrectionTwoDimensionsOptionsPanel, + [baselineCorrection.id]: BaseLineCorrectionOptionsPanel, }; export const formLabelStyle: LabelStyle = { diff --git a/src/component/panels/filtersPanel/Filters/useBaselineCorrection.tsx b/src/component/panels/filtersPanel/Filters/useBaselineCorrection.tsx new file mode 100644 index 000000000..de868dbf8 --- /dev/null +++ b/src/component/panels/filtersPanel/Filters/useBaselineCorrection.tsx @@ -0,0 +1,199 @@ +import { yupResolver } from '@hookform/resolvers/yup'; +import { Filter, BaselineCorrectionOptions } from 'nmr-processing'; +import { useCallback, useEffect, useRef } from 'react'; +import { useForm } from 'react-hook-form'; +import { useSelect } from 'react-science/ui'; +import * as Yup from 'yup'; + +import { useDispatch } from '../../../context/DispatchContext'; +import { useSyncedFilterOptions } from './useSyncedFilterOptions'; + +export const baselineCorrectionsAlgorithms = ['airPLS', 'Polynomial'].map( + (val) => ({ + label: val, + value: val.toLowerCase(), + }), +); + +interface BaseOptions { + algorithm: string; + livePreview: boolean; +} +interface AirplsOptions extends BaseOptions { + maxIterations: number; + tolerance: number; +} +interface PolynomialOptions extends BaseOptions { + degree: number; +} + +function findAlgorithmItem(algorithmName: string) { + return baselineCorrectionsAlgorithms.find( + (item) => item.value === algorithmName, + ); +} + +export function getBaselineData( + algorithm, + filterValues: BaselineCorrectionOptions, +) { + const { zones, algorithm: baseAlgorithm, ...other } = filterValues; + switch (algorithm) { + case 'airpls': { + const validation = Yup.object().shape({ + algorithm: Yup.string().required(), + livePreview: Yup.boolean().required(), + maxIterations: Yup.number().integer().min(1).required(), + tolerance: Yup.number().moreThan(0).required(), + }); + return { + resolver: yupResolver(validation), + values: { + algorithm, + livePreview: true, + maxIterations: 100, + tolerance: 0.001, + ...(baseAlgorithm === 'airpls' ? other : {}), + }, + }; + } + case 'autoPolynomial': + case 'polynomial': { + const validation = Yup.object().shape({ + algorithm: Yup.string().required(), + livePreview: Yup.boolean().required(), + degree: Yup.number().integer().min(1).max(6).required(), + }); + + return { + resolver: yupResolver(validation), + values: { + algorithm, + livePreview: true, + degree: 3, + ...(baseAlgorithm === 'polynomial' ? other : {}), + }, + }; + } + default: + return { + resolver: yupResolver( + Yup.object({ + livePreview: Yup.boolean().required(), + }), + ), + values: { livePreview: true }, + }; + } +} + +export function useBaselineCorrection(filter: Filter | null) { + const dispatch = useDispatch(); + const previousPreviewRef = useRef(true); + const { algorithm: baseAlgorithm = 'polynomial' } = filter?.value || {}; + const { + value: algorithm, + onItemSelect: onAlgorithmChange, + ...defaultAlgorithmSelectProps + } = useSelect({ + defaultSelectedItem: findAlgorithmItem(baseAlgorithm), + itemTextKey: 'label', + }); + + const { resolver, values } = getBaselineData( + algorithm?.value, + filter?.value || {}, + ); + + const { handleSubmit, control, register, reset } = useForm< + AirplsOptions | PolynomialOptions + >({ + defaultValues: values, + resolver: resolver as any, + }); + + function syncWatch(sharedFilterOptions) { + const { algorithm } = sharedFilterOptions; + const algorithmItem = findAlgorithmItem(algorithm); + if (algorithmItem) { + onAlgorithmChange(algorithmItem); + } + reset(sharedFilterOptions); + } + + const { syncFilterOptions, clearSyncFilterOptions } = + useSyncedFilterOptions(syncWatch); + + const onChange = useCallback( + (values) => { + const { livePreview, ...options } = values; + + if (livePreview || previousPreviewRef !== livePreview) { + dispatch({ + type: 'CALCULATE_BASE_LINE_CORRECTION_FILTER', + payload: { + options, + livePreview, + }, + }); + } + }, + [dispatch], + ); + + const handleApplyFilter = ( + values, + triggerSource: 'apply' | 'onChange' = 'apply', + ) => { + const { livePreview, ...options } = values; + + switch (triggerSource) { + case 'onChange': { + onChange(values); + syncFilterOptions(values); + break; + } + + case 'apply': { + dispatch({ + type: 'APPLY_BASE_LINE_CORRECTION_FILTER', + payload: { + options, + }, + }); + clearSyncFilterOptions(); + break; + } + default: + break; + } + previousPreviewRef.current = livePreview; + }; + + const handleCancelFilter = () => { + dispatch({ + type: 'RESET_SELECTED_TOOL', + }); + }; + + function submitHandler() { + void handleSubmit((values) => handleApplyFilter(values, 'onChange'))(); + } + + useEffect(() => { + void handleSubmit((values) => onChange(values))(); + }, [handleSubmit, onChange]); + + return { + algorithm, + defaultAlgorithmSelectProps, + control, + onAlgorithmChange, + handleSubmit, + register, + reset, + submitHandler, + handleApplyFilter, + handleCancelFilter, + }; +}