Skip to content

Commit 7a3953b

Browse files
refactor: change form handler on ExportOptionsModal (#4207)
Closes: #4172
1 parent 3e8d091 commit 7a3953b

6 files changed

Lines changed: 58 additions & 403 deletions

File tree

Lines changed: 56 additions & 276 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,19 @@
1-
import {
2-
Button,
3-
DialogFooter,
4-
Radio,
5-
RadioGroup,
6-
SegmentedControl,
7-
Tag,
8-
} from '@blueprintjs/core';
9-
import { yupResolver } from '@hookform/resolvers/yup';
10-
import type {
11-
AdvanceExportSettings,
12-
BasicExportSettings,
13-
ExportSettings,
14-
} from '@zakodium/nmrium-core';
15-
import { useEffect, useState } from 'react';
16-
import { Controller, useForm } from 'react-hook-form';
1+
import { DialogFooter } from '@blueprintjs/core';
2+
import { useMemo } from 'react';
3+
import { Button, Form, useForm } from 'react-science/ui';
4+
import { z } from 'zod/v4';
175

18-
import ActionButtons from '../ActionButtons.js';
19-
import { CheckController } from '../CheckController.js';
20-
import type { LabelStyle } from '../Label.js';
21-
import Label from '../Label.js';
22-
import { NumberInput2Controller } from '../NumberInput2Controller.js';
23-
import { Select2Controller } from '../Select2Controller.js';
6+
import { ExportFields } from '../../modal/setting/tanstack_general_settings/tabs/export_tab.fields.tsx';
7+
import { exportSettingsValidation } from '../../modal/setting/tanstack_general_settings/validation/export_tab_validation.ts';
248
import { StandardDialog } from '../StandardDialog.tsx';
25-
import { StyledDialogBody } from '../StyledDialogBody.js';
26-
import type { SizeItem } from '../print/pageSize.js';
27-
import { getSizesList } from '../print/pageSize.js';
9+
import { StyledDialogBody } from '../StyledDialogBody.tsx';
2810

2911
import type { BaseExportProps } from './ExportContent.js';
30-
import { units } from './units.js';
31-
import { useExportConfigurer } from './useExportConfigurer.js';
32-
import { exportOptionValidationSchema } from './utilities/exportOptionValidationSchema.js';
33-
import {
34-
getExportDefaultOptions,
35-
getExportDefaultOptionsByMode,
36-
} from './utilities/getExportOptions.js';
37-
import type { Mode } from './utilities/getModes.js';
38-
import { MODES } from './utilities/getModes.js';
12+
import { getExportDefaultOptions } from './utilities/getExportOptions.ts';
3913

4014
interface InnerExportOptionsModalProps extends BaseExportProps {
4115
onCloseDialog: () => void;
42-
confirmButtonText?: string;
16+
confirmButtonText: string;
4317
}
4418
interface ExportOptionsModalProps extends InnerExportOptionsModalProps {
4519
isOpen: boolean;
@@ -53,101 +27,36 @@ export function ExportOptionsModal(props: ExportOptionsModalProps) {
5327
return <InnerExportOptionsModal {...otherProps} />;
5428
}
5529

56-
const labelStyle: LabelStyle = {
57-
label: {
58-
color: '#232323',
59-
width: '80px',
60-
},
61-
wrapper: {
62-
display: 'flex',
63-
justifyContent: 'flex-start',
64-
},
65-
container: { margin: '5px 0' },
66-
};
30+
const validator = z.object({
31+
values: exportSettingsValidation,
32+
});
6733

6834
function InnerExportOptionsModal(props: InnerExportOptionsModalProps) {
6935
const {
7036
onCloseDialog,
71-
onExportOptionsChange,
7237
confirmButtonText,
7338
defaultExportOptions,
39+
onExportOptionsChange,
7440
} = props;
75-
const defaultValues = getExportDefaultOptions(defaultExportOptions);
76-
77-
const [mode, setMode] = useState<Mode>(defaultValues.mode);
7841

79-
const methods = useForm<ExportSettings>({
80-
defaultValues,
81-
resolver: yupResolver(exportOptionValidationSchema as any),
42+
const values = useMemo(() => {
43+
return exportSettingsValidation.encode(
44+
getExportDefaultOptions(defaultExportOptions),
45+
);
46+
}, [defaultExportOptions]);
47+
48+
const form = useForm({
49+
defaultValues: {
50+
values,
51+
},
52+
validators: {
53+
onDynamic: validator,
54+
},
55+
onSubmit: ({ value: { values } }) => {
56+
const parsedValues = exportSettingsValidation.parse(values);
57+
onExportOptionsChange(parsedValues);
58+
},
8259
});
83-
const {
84-
handleSubmit,
85-
control,
86-
watch,
87-
setValue,
88-
formState: { isValid, errors },
89-
reset,
90-
setFocus,
91-
} = methods;
92-
93-
const watchSettings = watch();
94-
const {
95-
unit,
96-
dpi = 0,
97-
layout,
98-
} = watchSettings as AdvanceExportSettings & BasicExportSettings;
99-
const {
100-
widthInPixel,
101-
heightInPixel,
102-
isAspectRatioEnabled,
103-
changeDPI,
104-
enableAspectRatio,
105-
changeSize,
106-
changeUnit,
107-
} = useExportConfigurer(watchSettings);
108-
109-
let sizesList: SizeItem[] = [];
110-
111-
if (layout) {
112-
sizesList = getSizesList(layout);
113-
}
114-
115-
function handleChangeMode(mode: any) {
116-
const options = defaultValues;
117-
setMode(mode);
118-
if (options.mode === mode) {
119-
reset(defaultValues);
120-
} else {
121-
reset(getExportDefaultOptionsByMode(mode));
122-
}
123-
}
124-
125-
useEffect(() => {
126-
const handleRenderComplete = () => {
127-
setTimeout(() => {
128-
if (mode === 'advance') {
129-
setFocus('width');
130-
} else {
131-
setFocus('dpi');
132-
}
133-
}, 0);
134-
};
135-
136-
const handleKeyDown = (event: globalThis.KeyboardEvent) => {
137-
if (event.key === 'Enter') {
138-
void handleSubmit(onExportOptionsChange)();
139-
}
140-
};
141-
142-
globalThis.addEventListener('keydown', handleKeyDown);
143-
144-
const animationFrameId = requestAnimationFrame(handleRenderComplete);
145-
146-
return () => {
147-
cancelAnimationFrame(animationFrameId);
148-
globalThis.removeEventListener('keydown', handleKeyDown);
149-
};
150-
}, [handleSubmit, mode, onExportOptionsChange, setFocus]);
15160

15261
return (
15362
<StandardDialog
@@ -158,165 +67,36 @@ function InnerExportOptionsModal(props: InnerExportOptionsModalProps) {
15867
canEscapeKeyClose
15968
autoFocus
16069
>
161-
<StyledDialogBody>
162-
<div
163-
style={{
164-
display: 'flex',
165-
justifyContent: 'center',
166-
marginBottom: '15px',
70+
<form.AppForm>
71+
<Form
72+
layout="inline"
73+
noValidate
74+
onSubmit={(event) => {
75+
event.preventDefault();
76+
void form.handleSubmit();
16777
}}
16878
>
169-
<SegmentedControl
170-
defaultValue="list"
171-
inline
172-
options={MODES}
173-
value={mode}
174-
onValueChange={handleChangeMode}
175-
/>
176-
</div>
177-
178-
<Label style={labelStyle} title="Description:">
179-
<Tag
180-
intent={
181-
!isValid && Object.keys(errors).length > 0 ? 'danger' : 'none'
79+
<StyledDialogBody>
80+
<ExportFields form={form} fields="values" />
81+
</StyledDialogBody>
82+
<DialogFooter
83+
actions={
84+
<>
85+
<Button
86+
variant="outlined"
87+
intent="danger"
88+
onClick={() => onCloseDialog?.()}
89+
>
90+
Cancel
91+
</Button>
92+
<form.SubmitButton intent="success">
93+
{confirmButtonText}
94+
</form.SubmitButton>
95+
</>
18296
}
183-
>{`${widthInPixel} px x ${heightInPixel} px @ ${dpi}DPI`}</Tag>
184-
</Label>
185-
{mode === 'basic' && (
186-
<>
187-
<Label style={labelStyle} title="Size">
188-
<Select2Controller
189-
control={control}
190-
name="size"
191-
items={sizesList}
192-
/>
193-
</Label>
194-
<Label style={labelStyle} title="Layout">
195-
<Controller
196-
name="layout"
197-
control={control}
198-
render={({ field }) => {
199-
const { value, ref, ...otherFieldProps } = field;
200-
return (
201-
<RadioGroup
202-
inline
203-
selectedValue={value}
204-
{...otherFieldProps}
205-
>
206-
<Radio label="Portrait" value="portrait" />
207-
<Radio label="Landscape" value="landscape" />
208-
</RadioGroup>
209-
);
210-
}}
211-
/>
212-
</Label>
213-
</>
214-
)}
215-
{mode === 'advance' && (
216-
<>
217-
<Label style={labelStyle} title="Size">
218-
<div style={{ display: 'flex', flexDirection: 'row' }}>
219-
<NumberInput2Controller
220-
name="width"
221-
control={control}
222-
style={{ width: 100 }}
223-
rightElement={<Tag>{unit}</Tag>}
224-
controllerProps={{ rules: { required: true } }}
225-
transformValue={(value) => {
226-
const newHeight = changeSize(value, 'height', 'width');
227-
setValue('height', newHeight);
228-
return value;
229-
}}
230-
debounceTime={250}
231-
placeholder="width"
232-
/>
233-
<div style={{ padding: '0px 5px' }}>
234-
<Button
235-
icon="link"
236-
variant="minimal"
237-
active={isAspectRatioEnabled}
238-
onClick={() => {
239-
enableAspectRatio((prevFlag) => !prevFlag);
240-
}}
241-
/>
242-
</div>
243-
<NumberInput2Controller
244-
name="height"
245-
control={control}
246-
style={{ width: 100 }}
247-
rightElement={<Tag>{unit}</Tag>}
248-
controllerProps={{ rules: { required: true } }}
249-
transformValue={(value) => {
250-
const newWidth = changeSize(value, 'width', 'height');
251-
setValue('width', newWidth);
252-
return value;
253-
}}
254-
debounceTime={250}
255-
placeholder="height"
256-
/>
257-
</div>
258-
</Label>
259-
<Label style={labelStyle} title="Units">
260-
<Select2Controller
261-
control={control}
262-
name="unit"
263-
itemTextKey="name"
264-
itemValueKey="unit"
265-
items={units}
266-
onItemSelect={({ unit }) => {
267-
const newSize = changeUnit({ unit });
268-
setValue('width', newSize.width);
269-
setValue('height', newSize.height);
270-
}}
271-
/>
272-
</Label>
273-
</>
274-
)}
275-
<Label style={labelStyle} title="DPI">
276-
<NumberInput2Controller
277-
name="dpi"
278-
control={control}
279-
style={{ width: 100 }}
280-
controllerProps={{ rules: { required: true } }}
281-
transformValue={(value) => {
282-
if (unit === 'px') {
283-
const convertedValue = changeDPI(value);
284-
setValue('width', convertedValue.width);
285-
setValue('height', convertedValue.height);
286-
}
287-
288-
return value;
289-
}}
290-
debounceTime={250}
291-
placeholder="DPI"
292-
/>
293-
</Label>
294-
</StyledDialogBody>
295-
<DialogFooter>
296-
<div
297-
style={{
298-
display: 'flex',
299-
justifyContent: 'space-between',
300-
alignItems: 'baseline',
301-
}}
302-
>
303-
<CheckController
304-
control={control}
305-
name="useDefaultSettings"
306-
style={{ fontSize: '12px' }}
307-
label="Don't show options dialog next time and use those settings"
308-
/>
309-
310-
<ActionButtons
311-
style={{ flexDirection: 'row-reverse', margin: 0 }}
312-
onDone={() => {
313-
void handleSubmit(onExportOptionsChange)();
314-
}}
315-
doneLabel={confirmButtonText}
316-
onCancel={() => onCloseDialog?.()}
31797
/>
318-
</div>
319-
</DialogFooter>
98+
</Form>
99+
</form.AppForm>
320100
</StandardDialog>
321101
);
322102
}

src/component/elements/export/units.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
import type { Unit } from '@zakodium/nmrium-core';
2-
import { units as baseUnits } from '@zakodium/nmrium-core';
32

43
import { roundNumber } from '../../utility/roundNumber.js';
54

6-
export const units = baseUnits.map((unit) => ({
7-
...unit,
8-
name: unit.name[0].toUpperCase() + unit.name.slice(1),
9-
}));
10-
115
const conversionFactors: Record<Unit, number> = {
126
in: 1, // inches
137
ft: 12, // 1 foot = 12 inches

0 commit comments

Comments
 (0)