From 255c55a4998ccfc7926793564c8d725c5244f273 Mon Sep 17 00:00:00 2001 From: hamed musallam Date: Wed, 13 Aug 2025 11:57:23 +0200 Subject: [PATCH] feat: create auto-processing pipelines modal UI --- .../CreateAutoProcessingPipelineModal.tsx | 231 ++++++++++++++++++ .../panels/filtersPanel/FilterPanel.tsx | 3 + 2 files changed, 234 insertions(+) create mode 100644 src/component/modal/CreateAutoProcessingPipelineModal.tsx diff --git a/src/component/modal/CreateAutoProcessingPipelineModal.tsx b/src/component/modal/CreateAutoProcessingPipelineModal.tsx new file mode 100644 index 0000000000..dd2a1962ca --- /dev/null +++ b/src/component/modal/CreateAutoProcessingPipelineModal.tsx @@ -0,0 +1,231 @@ +import { Checkbox, Dialog, DialogFooter } from '@blueprintjs/core'; +import styled from '@emotion/styled'; +import { yupResolver } from '@hookform/resolvers/yup/src/yup.js'; +import type { Filter1DOptions, Filter2DOptions } from '@zakodium/nmr-types'; +import dlv from 'dlv'; +import { useCallback, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { FaList } from 'react-icons/fa'; +import { ObjectInspector } from 'react-inspector'; +import { Toolbar, useOnOff } from 'react-science/ui'; +import * as Yup from 'yup'; + +import { getFilterLabel } from '../../data/getFilterLabel.ts'; +import Button from '../elements/Button.js'; +import { EmptyText } from '../elements/EmptyText.tsx'; +import { GroupPane } from '../elements/GroupPane.tsx'; +import { Input2Controller } from '../elements/Input2Controller.tsx'; +import Label from '../elements/Label.tsx'; +import { Sections } from '../elements/Sections.tsx'; +import { StyledDialogBody } from '../elements/StyledDialogBody.js'; +import useSpectrum from '../hooks/useSpectrum.ts'; + +const Container = styled.div` + display: flex; + flex-direction: row; + height: 100%; +`; +const FiltersContainer = styled.div` + flex: 1; + height: 100%; +`; +const PipelineOptionsContainer = styled.div` + flex: 1; + display: flex; + flex-direction: column; + padding: 0.9em; +`; + +const selectors = [ + { + jpath: ['info', 'nucleus'], + label: 'Is it the same nucleus / nuclei', + }, + { + jpath: ['info', 'experiment'], + label: 'Is it the same experiment', + }, + { + jpath: ['info', 'pulseSequence'], + label: 'Is it the same pulse sequence', + }, +]; + +type AutoProcessingFilterEntry = Omit< + Filter1DOptions | Filter2DOptions, + 'value' +>; + +interface Selector { + jpath: string[]; + value: string | number | string[] | number[]; +} + +interface AutProcessingPipeline { + id: string; + label?: string; + enabled: boolean; // by default value is true + selectors: Selector[]; // ordered by hierarchy + filters: AutoProcessingFilterEntry[]; +} + +type AutoProcessingItem = Required< + Omit +>; + +const defaultValues = { + filters: [ + { + id: '1', + name: 'digitalFilter', + enabled: true, + settings: {}, + }, + { + id: '2', + name: 'apodization', + enabled: false, + settings: {}, + }, + { + id: '3', + name: 'zeroFilling', + enabled: true, + settings: {}, + }, + { + id: '4', + name: 'fft', + enabled: true, + settings: {}, + }, + { + id: '5', + name: 'phaseCorrection', + enabled: true, + settings: {}, + }, + ], +}; + +const defaultPipeline: AutoProcessingItem = { + label: '', + selectors: [], + filters: defaultValues.filters.slice(0) as AutoProcessingFilterEntry[], +}; + +const filterSettingsPanels = {}; + +const validationSchema = Yup.object().shape({ + label: Yup.string().required(), + selectors: Yup.array().required(), + filters: Yup.array().required().min(1), +}); + +interface CreateAutoProcessingPipelineModalProps { + onClose?: (element?: string) => void; +} + +export default function CreateAutoProcessingPipelineModal({ + onClose = () => null, +}: CreateAutoProcessingPipelineModalProps) { + const [isOpenDialog, openDialog, closeDialog] = useOnOff(false); + const spectrum = useSpectrum(defaultValues); + const [selectedSections, openSection] = useState>({}); + const { handleSubmit, control } = useForm({ + defaultValues: defaultPipeline, + resolver: yupResolver(validationSchema), + }); + + const submitHandler = useCallback((values) => {}, []); + + function handleReorderFilters(sourceIndex, targetIndex) {} + + const { filters } = spectrum; + + return ( + <> + } + tooltip="Create an auto-processing pipeline" + onClick={openDialog} + /> + { + onClose(); + closeDialog(); + }} + style={{ width: 1200, maxWidth: 1000, height: 600 }} + title="New auto-processing pipeline" + > + + + + + {filters.map((filter, index) => { + const { id, name, error, value } = filter; + const FilterSettingsPanel = filterSettingsPanels[filter.name]; + return ( + { + openSection((prevSections) => { + return { + ...prevSections, + [name]: !(name in prevSections) + ? true + : !prevSections[name], + }; + }); + }} + > + {FilterSettingsPanel ? ( +
Settings panel
+ ) : ( + + {value && Object.keys(value).length > 0 ? ( + + ) : ( + + )} + + )} +
+ ); + })} +
+
+ + + + {selectors.map((selector) => { + const value = dlv(spectrum, selector.jpath) || ''; + return ( + + ); + })} + + +
+
+ + Save + +
+ + ); +} diff --git a/src/component/panels/filtersPanel/FilterPanel.tsx b/src/component/panels/filtersPanel/FilterPanel.tsx index 3d30f34174..cc022e3ae1 100644 --- a/src/component/panels/filtersPanel/FilterPanel.tsx +++ b/src/component/panels/filtersPanel/FilterPanel.tsx @@ -3,6 +3,7 @@ import { useToaster } from '../../context/ToasterContext.js'; import type { AlertButton } from '../../elements/Alert.js'; import { useAlert } from '../../elements/Alert.js'; import useSpectrum from '../../hooks/useSpectrum.js'; +import CreateAutoProcessingPipelineModal from '../../modal/CreateAutoProcessingPipelineModal.tsx'; import { TablePanel } from '../extra/BasicPanelStyle.js'; import DefaultPanelHeader from '../header/DefaultPanelHeader.js'; @@ -43,7 +44,9 @@ export default function FiltersPanel() { onDelete={handleDeleteFilter} total={filters?.length} hideCounter + rightButtons={[{ component: }]} /> +