Skip to content

Commit 937569f

Browse files
feat: create auto-processing pipelines modal UI
1 parent 85d5187 commit 937569f

2 files changed

Lines changed: 234 additions & 0 deletions

File tree

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import { Checkbox, Dialog, DialogFooter } from '@blueprintjs/core';
2+
import styled from '@emotion/styled';
3+
import { yupResolver } from '@hookform/resolvers/yup/src/yup.js';
4+
import type { Filter1DOptions, Filter2DOptions } from '@zakodium/nmr-types';
5+
import dlv from 'dlv';
6+
import { useCallback, useState } from 'react';
7+
import { useForm } from 'react-hook-form';
8+
import { FaList } from 'react-icons/fa';
9+
import { ObjectInspector } from 'react-inspector';
10+
import { Toolbar, useOnOff } from 'react-science/ui';
11+
import * as Yup from 'yup';
12+
13+
import { getFilterLabel } from '../../data/getFilterLabel.ts';
14+
import Button from '../elements/Button.js';
15+
import { EmptyText } from '../elements/EmptyText.tsx';
16+
import { GroupPane } from '../elements/GroupPane.tsx';
17+
import { Input2Controller } from '../elements/Input2Controller.tsx';
18+
import Label from '../elements/Label.tsx';
19+
import { Sections } from '../elements/Sections.tsx';
20+
import { StyledDialogBody } from '../elements/StyledDialogBody.js';
21+
import useSpectrum from '../hooks/useSpectrum.ts';
22+
23+
const Container = styled.div`
24+
display: flex;
25+
flex-direction: row;
26+
height: 100%;
27+
`;
28+
const FiltersContainer = styled.div`
29+
flex: 1;
30+
height: 100%;
31+
`;
32+
const PipelineOptionsContainer = styled.div`
33+
flex: 1;
34+
display: flex;
35+
flex-direction: column;
36+
padding: 0.9em;
37+
`;
38+
39+
const selectors = [
40+
{
41+
jpath: ['info', 'nucleus'],
42+
label: 'Is it the same nucleus / nuclei',
43+
},
44+
{
45+
jpath: ['info', 'experiment'],
46+
label: 'Is it the same experiment',
47+
},
48+
{
49+
jpath: ['info', 'pulseSequence'],
50+
label: 'Is it the same pulse sequence',
51+
},
52+
];
53+
54+
export type AutoProcessingFilterEntry = Omit<
55+
Filter1DOptions | Filter2DOptions,
56+
'value'
57+
>;
58+
59+
interface Selector {
60+
jpath: string[];
61+
value: string | number | string[] | number[];
62+
}
63+
64+
export interface AutProcessingPipeline {
65+
id: string;
66+
label?: string;
67+
enabled: boolean; // by default value is true
68+
selectors: Selector[]; // ordered by hierarchy
69+
filters: AutoProcessingFilterEntry[];
70+
}
71+
72+
type AutoProcessingItem = Required<
73+
Omit<AutProcessingPipeline, 'id' | 'enabled'>
74+
>;
75+
76+
const defaultValues = {
77+
filters: [
78+
{
79+
id: '1',
80+
name: 'digitalFilter',
81+
enabled: true,
82+
settings: {},
83+
},
84+
{
85+
id: '2',
86+
name: 'apodization',
87+
enabled: false,
88+
settings: {},
89+
},
90+
{
91+
id: '3',
92+
name: 'zeroFilling',
93+
enabled: true,
94+
settings: {},
95+
},
96+
{
97+
id: '4',
98+
name: 'fft',
99+
enabled: true,
100+
settings: {},
101+
},
102+
{
103+
id: '5',
104+
name: 'phaseCorrection',
105+
enabled: true,
106+
settings: {},
107+
},
108+
],
109+
};
110+
111+
const defaultPipeline: AutoProcessingItem = {
112+
label: '',
113+
selectors: [],
114+
filters: defaultValues.filters.slice(0) as AutoProcessingFilterEntry[],
115+
};
116+
117+
const filterSettingsPanels = {};
118+
119+
const validationSchema = Yup.object().shape({
120+
label: Yup.string().required(),
121+
selectors: Yup.array().required(),
122+
filters: Yup.array().required().min(1),
123+
});
124+
125+
interface CreateAutoProcessingPipelineModalProps {
126+
onClose?: (element?: string) => void;
127+
}
128+
129+
export default function CreateAutoProcessingPipelineModal({
130+
onClose = () => null,
131+
}: CreateAutoProcessingPipelineModalProps) {
132+
const [isOpenDialog, openDialog, closeDialog] = useOnOff(false);
133+
const spectrum = useSpectrum(defaultValues);
134+
const [selectedSections, openSection] = useState<Record<string, boolean>>({});
135+
const { handleSubmit, control } = useForm({
136+
defaultValues: defaultPipeline,
137+
resolver: yupResolver(validationSchema),
138+
});
139+
140+
const submitHandler = useCallback((values) => {}, []);
141+
142+
function handleReorderFilters(sourceIndex, targetIndex) {}
143+
144+
const { filters } = spectrum;
145+
146+
return (
147+
<>
148+
<Toolbar.Item
149+
icon={<FaList />}
150+
tooltip="Create an auto-processing pipeline"
151+
onClick={openDialog}
152+
/>
153+
<Dialog
154+
isOpen={isOpenDialog}
155+
onClose={() => {
156+
onClose();
157+
closeDialog();
158+
}}
159+
style={{ width: 1200, maxWidth: 1000, height: 600 }}
160+
title="New auto-processing pipeline"
161+
>
162+
<StyledDialogBody>
163+
<Container>
164+
<FiltersContainer>
165+
<Sections isOverflow renderActiveSectionContentOnly>
166+
{filters.map((filter, index) => {
167+
const { id, name, error, value } = filter;
168+
const FilterSettingsPanel = filterSettingsPanels[filter.name];
169+
return (
170+
<Sections.Item
171+
index={index}
172+
key={id}
173+
id={name}
174+
dragLabel={getFilterLabel(name)}
175+
onReorder={handleReorderFilters}
176+
title={error || getFilterLabel(name)}
177+
serial={index + 1}
178+
isOpen={selectedSections[name]}
179+
sticky
180+
onClick={() => {
181+
openSection((prevSections) => {
182+
return {
183+
...prevSections,
184+
[name]: !(name in prevSections)
185+
? true
186+
: !prevSections[name],
187+
};
188+
});
189+
}}
190+
>
191+
{FilterSettingsPanel ? (
192+
<div>Settings panel</div>
193+
) : (
194+
<Sections.Body>
195+
{value && Object.keys(value).length > 0 ? (
196+
<ObjectInspector data={value} />
197+
) : (
198+
<EmptyText text=" No Settings available" />
199+
)}
200+
</Sections.Body>
201+
)}
202+
</Sections.Item>
203+
);
204+
})}
205+
</Sections>
206+
</FiltersContainer>
207+
<PipelineOptionsContainer>
208+
<Label title="Label" style={{ wrapper: { flex: 1 } }}>
209+
<Input2Controller control={control} name="label" fill />
210+
</Label>
211+
<GroupPane text="Selectors criteria">
212+
{selectors.map((selector) => {
213+
const value = dlv(spectrum, selector.jpath) || '';
214+
return (
215+
<Checkbox
216+
key={selector.jpath.join('.')}
217+
label={`${selector.label} [${value}]`}
218+
/>
219+
);
220+
})}
221+
</GroupPane>
222+
</PipelineOptionsContainer>
223+
</Container>
224+
</StyledDialogBody>
225+
<DialogFooter className="footer-container">
226+
<Button.Done onClick={handleSubmit(submitHandler)}>Save</Button.Done>
227+
</DialogFooter>
228+
</Dialog>
229+
</>
230+
);
231+
}

src/component/panels/filtersPanel/FilterPanel.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useToaster } from '../../context/ToasterContext.js';
33
import type { AlertButton } from '../../elements/Alert.js';
44
import { useAlert } from '../../elements/Alert.js';
55
import useSpectrum from '../../hooks/useSpectrum.js';
6+
import CreateAutoProcessingPipelineModal from '../../modal/CreateAutoProcessingPipelineModal.tsx';
67
import { TablePanel } from '../extra/BasicPanelStyle.js';
78
import DefaultPanelHeader from '../header/DefaultPanelHeader.js';
89

@@ -43,7 +44,9 @@ export default function FiltersPanel() {
4344
onDelete={handleDeleteFilter}
4445
total={filters?.length}
4546
hideCounter
47+
rightButtons={[{ component: <CreateAutoProcessingPipelineModal /> }]}
4648
/>
49+
4750
<div className="inner-container">
4851
<FiltersSectionsPanel />
4952
</div>

0 commit comments

Comments
 (0)