import { Switch } from '@blueprintjs/core'; import styled from '@emotion/styled'; import { Filters1D, Filters2D } from 'nmr-processing'; import { memo, useMemo, useRef, useState } from 'react'; import { FaRegTrashAlt } from 'react-icons/fa'; import { ObjectInspector } from 'react-inspector'; import { Button } from 'react-science/ui'; import { getFilterLabel } from '../../../../data/getFilterLabel.js'; import type { FilterEntry } from '../../../../data/types/common/FilterEntry.js'; import { useChartData } from '../../../context/ChartContext.js'; import { useDispatch } from '../../../context/DispatchContext.js'; import { useFilterSyncOptions } from '../../../context/FilterSyncOptionsContext.js'; import { useToaster } from '../../../context/ToasterContext.js'; import type { AlertButton } from '../../../elements/Alert.js'; import { useAlert } from '../../../elements/Alert.js'; import { EmptyText } from '../../../elements/EmptyText.js'; import { Sections } from '../../../elements/Sections.js'; import { useActiveSpectrum } from '../../../hooks/useActiveSpectrum.js'; import useSpectraByActiveNucleus from '../../../hooks/useSpectraPerNucleus.js'; import useSpectrum from '../../../hooks/useSpectrum.js'; import { getDefaultFilterOptions } from '../../../utility/getDefaultFilterOptions.js'; import { filterOptionPanels } from './index.js'; export const nonRemovableFilters = new Set([ 'digitalFilter', 'digitalFilter2D', ]); const readOnlyFilters = new Set([ 'digitalFilter', 'shiftX', 'exclusionZones', 'fft', 'digitalFilter2D', 'fftDimension1', 'fftDimension2', ]); const IconButton = styled(Button)` font-size: 16px; padding: 2px; `; const Filters = { ...Filters1D, ...Filters2D, }; interface FilterElementsProps { filter: FilterEntry; spectraCounter: number; onEnableChange: () => void; onFilterRestore: () => void; activeFilterID: string | null; hideFilterRestoreButton?: boolean; } function FilterElements(props: FilterElementsProps) { const dispatch = useDispatch(); const alert = useAlert(); const toaster = useToaster(); const { filter, spectraCounter, onEnableChange, activeFilterID, onFilterRestore, hideFilterRestoreButton = false, } = props; const { id, name, enabled } = filter; const label = getFilterLabel(name); function handleFilterCheck( id: string, event: React.ChangeEvent, ) { const enabled = event.target.checked; const hideLoading = toaster.showLoading({ message: `${enabled ? 'Enable' : 'Disable'} filter in progress`, }); setTimeout(() => { dispatch({ type: 'ENABLE_FILTER', payload: { id, enabled } }); hideLoading(); }, 0); onEnableChange(); } function handleDeleteFilter() { const buttons: AlertButton[] = [ { text: 'Yes', onClick: async () => { const hideLoading = await toaster.showAsyncLoading({ message: 'Delete filter process in progress', }); dispatch({ type: 'DELETE_FILTER', payload: { id } }); hideLoading(); }, intent: 'danger', }, { text: 'No' }, ]; if (spectraCounter > 1) { buttons.unshift({ text: 'Yes, for all spectra', onClick: async () => { const hideLoading = await toaster.showAsyncLoading({ message: 'Delete all spectra filter process in progress', }); dispatch({ type: 'DELETE_SPECTRA_FILTER', payload: { filterName: name }, }); hideLoading(); }, intent: spectraCounter ? 'danger' : 'none', }); } alert.showAlert({ message: ( You are about to delete this processing step {label} , Are you sure? ), buttons, }); } const isEditable = !hideFilterRestoreButton && activeFilterID !== id && !readOnlyFilters.has(name); return ( <> {isEditable && ( )} { handleDeleteFilter(); }} disabled={nonRemovableFilters.has(name)} > { if (!id) return; handleFilterCheck(id, event); }} /> ); } interface FiltersInnerProps { filters: FilterEntry[]; spectraCounter: number; activeFilterID: string | null; } function FiltersInner(props: FiltersInnerProps) { const { filters, spectraCounter, activeFilterID } = props; const { toolOptions: { selectedTool }, } = useChartData(); const [manualSection, setManualSection] = useState(null); const { updateFilterOptions } = useFilterSyncOptions(); const dispatch = useDispatch(); const toaster = useToaster(); const selectedFilterIndex = useRef(); const activeSpectrum = useActiveSpectrum(); function filterSnapShotHandler(filter: FilterEntry, index: number) { selectedFilterIndex.current = selectedFilterIndex.current && index === selectedFilterIndex.current ? null : index; if (activeFilterID) { //Clear the filter sync object updateFilterOptions(null); //Close the opened section setManualSection(null); } const hideLoading = toaster.showLoading({ message: 'Filter snapshot process in progress', }); setTimeout(() => { dispatch({ type: 'SET_FILTER_SNAPSHOT', payload: { filter, tempRollback: false }, }); hideLoading(); }, 0); } function getStyle(filter: FilterEntry, index: number) { const { id, error } = filter; if (error) { return { backgroundColor: '#ea8f8f' }; } if (activeFilterID === id) { return { backgroundColor: '#c2ea8f' }; } if ( activeFilterID && selectedFilterIndex.current != null && index > selectedFilterIndex.current ) { return { opacity: 0.5 }; } return {}; } const newFilter = useMemo(() => { const isFilterExists = filters.some((f) => f.name === selectedTool); if (!isFilterExists && (Filters as any)?.[selectedTool]) { const { name } = (Filters as any)[selectedTool]; return getDefaultFilterOptions(name); } return null; }, [filters, selectedTool]); const selectedSection = useMemo(() => { // New filter tool is selected if ((Filters as any)?.[selectedTool]) return selectedTool; //An existing filter is being edited const filter = filters.find((f) => f.id === activeFilterID); if (filter) return filter.name; // otherwise, fallback to the manual selected section return manualSection; }, [selectedTool, activeFilterID, filters, manualSection]); function toggleSection(sectionKey: string) { setManualSection((prev) => (prev === sectionKey ? null : sectionKey)); } function handleClose() { setManualSection(null); } const filtersList = [...filters]; if (newFilter && activeSpectrum) { const activeFilterIndex = filters.findIndex( (filter) => filter.id === activeFilterID, ); if (activeFilterIndex === -1) { filtersList.push(newFilter); } else { filtersList.splice(activeFilterIndex + 1, 0, newFilter); } } if (filtersList?.length === 0) { return ; } function handleReorderFilters(sourceIndex: number, targetIndex: number) { dispatch({ type: 'REORDER_FILTERS', payload: { sourceIndex, targetIndex }, }); } return ( {filtersList.map((filter, index) => { const { id, name, error, value } = filter; const FilterOptionsPanel = filterOptionPanels[name]; const enableEdit = activeFilterID === id || filter.value === null; return ( { toggleSection(id); }} isOpen={name === selectedSection} rightElement={ { filterSnapShotHandler(filter, index); }} /** Hide filter restore button when the filter is new */ hideFilterRestoreButton={value === null} /> } headerStyle={getStyle(filter, index)} sticky > {FilterOptionsPanel ? ( filterSnapShotHandler(filter, index)} /> ) : ( {value && Object.keys(value).length > 0 ? ( ) : ( )} )} ); })} ); } const emptyData = { filters: [] }; const MemoizedFilters = memo(FiltersInner); export function FiltersSectionsPanel() { const { toolOptions: { data: { activeFilterID }, }, } = useChartData(); const { filters } = useSpectrum(emptyData); const spectraCounter = useSpectraByActiveNucleus().length; return ; }