Replace all class components with function components and move server-state
polling to @tanstack/react-query.
- add @tanstack/react-query; generate query helpers via the hey-api
@tanstack/react-query plugin (src/client/@tanstack)
- QueryClientProvider + one-time client.setConfig({ baseUrl: '' }) in index.tsx
- App polls statistics with useQuery(getStatisticsOptions, refetchInterval),
DataProcessingPlot uses getPreviewPlotOptions; manual setInterval polling gone
- memo() on presentational children; with TanStack structural sharing this
re-renders a child only when its own statistics slice changes
- PreviewImage stays imperative (binary JPEG -> object URL) using useEffect +
a ref for the object-URL lifecycle
- fix AzIntSettings correction checkboxes that mutated state in place (relied on
the poll re-render); they now use setState
- drop dead code uncovered during the port (unused upload/deactivate handlers
and imports in ImageFormatSettings, DetectorSettings, ROI)
Build (tsc + vite) passes; dev server transforms all entry modules.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
161 lines
6.0 KiB
TypeScript
161 lines
6.0 KiB
TypeScript
import {ChangeEvent, memo, useEffect, useState} from 'react';
|
|
|
|
import Paper from '@mui/material/Paper';
|
|
import {FormControlLabel, Checkbox, Stack, Radio, RadioGroup, Typography} from "@mui/material";
|
|
import NumberTextField from "./NumberTextField";
|
|
import {azim_int_settings} from "../client";
|
|
import _ from "lodash";
|
|
import ButtonWithSnackbar from "./ButtonWithSnackbar";
|
|
import FormControl from "@mui/material/FormControl";
|
|
|
|
type MyProps = {
|
|
s?: azim_int_settings
|
|
};
|
|
|
|
const default_azim_int_settings : azim_int_settings = {
|
|
solid_angle_corr: true,
|
|
polarization_corr: true,
|
|
high_q_recipA: 5,
|
|
low_q_recipA: 0.1,
|
|
q_spacing: 0.1,
|
|
azimuthal_bins: 1,
|
|
force_cpu: false
|
|
}
|
|
|
|
function AzIntSettings({s: serverS}: MyProps) {
|
|
const [s, setS] = useState<azim_int_settings>(default_azim_int_settings);
|
|
const [lastDownloadedS, setLastDownloadedS] = useState<azim_int_settings>(default_azim_int_settings);
|
|
const [downloadCounter, setDownloadCounter] = useState(0);
|
|
const [lowQError, setLowQError] = useState(false);
|
|
const [highQError, setHighQError] = useState(false);
|
|
const [qSpacingError, setQSpacingError] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if ((serverS !== undefined) && !_.isEqual(serverS, lastDownloadedS)) {
|
|
setS(serverS);
|
|
setLastDownloadedS(serverS);
|
|
setDownloadCounter(c => c + 1);
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [serverS]);
|
|
|
|
return <Paper style={{textAlign: 'center'}} sx={{ width: '100%'}}>
|
|
<br/>
|
|
<Stack spacing={3} sx={{
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
marginLeft: '8%',
|
|
marginRight: '8%',
|
|
}}>
|
|
<div><strong>Azimuthal integration settings </strong></div>
|
|
<Stack spacing={2} direction="row">
|
|
<NumberTextField
|
|
default={1.0}
|
|
start_val={s.q_spacing}
|
|
label={"Q spacing"}
|
|
min={1e-5}
|
|
units={"A-1"}
|
|
float={true}
|
|
counter={downloadCounter}
|
|
callback={(val: number, err: boolean) => {
|
|
setS(prev => ({...prev, q_spacing: val}));
|
|
setQSpacingError(err);
|
|
}}
|
|
fullWidth/>
|
|
|
|
<NumberTextField
|
|
default={0.1}
|
|
start_val={s.low_q_recipA}
|
|
label={"Low Q"}
|
|
min={1e-5}
|
|
max={10.0}
|
|
units={"A-1"}
|
|
float={true}
|
|
counter={downloadCounter}
|
|
callback={(val: number, err: boolean) => {
|
|
setS(prev => ({...prev, low_q_recipA: val}));
|
|
setLowQError(err);
|
|
}}
|
|
fullWidth/>
|
|
|
|
<NumberTextField
|
|
default={5}
|
|
start_val={s.high_q_recipA}
|
|
label={"High Q"}
|
|
min={1e-5}
|
|
max={10.0}
|
|
units={"A-1"}
|
|
float={true}
|
|
counter={downloadCounter}
|
|
callback={(val: number, err: boolean) => {
|
|
setS(prev => ({...prev, high_q_recipA: val}));
|
|
setHighQError(err);
|
|
}}
|
|
fullWidth/>
|
|
</Stack>
|
|
<FormControl>
|
|
<FormControlLabel control={
|
|
<Checkbox
|
|
checked={s.solid_angle_corr}
|
|
onChange={
|
|
(event: ChangeEvent<HTMLInputElement>) => {
|
|
setS(prev => ({...prev, solid_angle_corr: event.target.checked}));
|
|
}}
|
|
/>} label="Solid angle correction"/>
|
|
<FormControlLabel control={
|
|
<Checkbox
|
|
checked={s.polarization_corr}
|
|
onChange={
|
|
(event: ChangeEvent<HTMLInputElement>) => {
|
|
setS(prev => ({...prev, polarization_corr: event.target.checked}));
|
|
}}
|
|
/>} label="Polarization correction"/>
|
|
<FormControlLabel control={
|
|
<Checkbox
|
|
checked={s.force_cpu ?? false}
|
|
onChange={
|
|
(event: ChangeEvent<HTMLInputElement>) => {
|
|
setS(prev => ({...prev, force_cpu: event.target.checked}));
|
|
}}
|
|
/>} label="Force CPU calculation in FPGA workflow"/>
|
|
</FormControl>
|
|
<Typography>Azimuthal bins</Typography>
|
|
<FormControl component="fieldset">
|
|
<RadioGroup
|
|
row
|
|
value={s.azimuthal_bins}
|
|
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
|
setS(prev => ({...prev, azimuthal_bins: Number(event.target.value)}));
|
|
}}
|
|
>
|
|
{[1, 2, 4, 8, 16, 32, 64, 128].map((value) => (
|
|
<FormControlLabel
|
|
key={value}
|
|
value={value}
|
|
control={<Radio/>}
|
|
label={value.toString()}
|
|
/>
|
|
))}
|
|
</RadioGroup>
|
|
</FormControl>
|
|
|
|
<ButtonWithSnackbar
|
|
color={"primary"}
|
|
path={"/config/azim_int"}
|
|
input={JSON.stringify(s)}
|
|
method={"PUT"}
|
|
text={"Upload"}
|
|
disabled={
|
|
(s.high_q_recipA <= s.low_q_recipA)
|
|
|| highQError
|
|
|| lowQError
|
|
|| qSpacingError
|
|
}
|
|
/>
|
|
</Stack>
|
|
<br/>
|
|
</Paper>
|
|
}
|
|
|
|
export default memo(AzIntSettings);
|