Skip to content

Commit

Permalink
Improve error collection for the Settings page
Browse files Browse the repository at this point in the history
  • Loading branch information
toni-neurosc committed Nov 25, 2024
1 parent 34a4412 commit f562621
Show file tree
Hide file tree
Showing 23 changed files with 723 additions and 256 deletions.
66 changes: 28 additions & 38 deletions gui_dev/src/pages/Settings/Settings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { useEffect, useState } from "react";
import {
Box,
Button,
ButtonGroup,
InputAdornment,
Popover,
Stack,
Switch,
Expand All @@ -13,8 +11,7 @@ import {
} from "@mui/material";
import { Link } from "react-router-dom";
import { CollapsibleBox, TitledBox } from "@/components";
import { FrequencyRangeList } from "./FrequencyRange";
import { Dropdown } from "./Dropdown";
import { FrequencyRangeList } from "./components/FrequencyRange";
import { useSettingsStore, useStatusBarContent } from "@/stores";
import { filterObjectByKeys } from "@/utils/functions";

Expand All @@ -30,28 +27,33 @@ const BooleanField = ({ value, onChange, error }) => (
<Switch checked={value} onChange={(e) => onChange(e.target.checked)} />
);

const StringField = ({ value, onChange, label, error }) => (
<TextField
value={value}
onChange={(e) => onChange(e.target.value)}
label={label}
sx={{
"& .MuiOutlinedInput-root": {
"& fieldset": {
borderColor: error ? "error.main" : "inherit",
},
"&:hover fieldset": {
borderColor: error ? "error.main" : "primary.main",
},
"&.Mui-focused fieldset": {
borderColor: error ? "error.main" : "primary.main",
},
},
}}
/>
);
const errorStyle = {
"& .MuiOutlinedInput-root": {
"& fieldset": { borderColor: "error.main" },
"&:hover fieldset": {
borderColor: "error.main",
},
"&.Mui-focused fieldset": {
borderColor: "error.main",
},
},
};

const StringField = ({ value, onChange, label, error }) => {
const errorSx = error ? errorStyle : {};
return (
<TextField
value={value}
onChange={(e) => onChange(e.target.value)}
label={label}
sx={{ ...errorSx }}
/>
);
};

const NumberField = ({ value, onChange, label, error }) => {
const errorSx = error ? errorStyle : {};

const handleChange = (event) => {
const newValue = event.target.value;
// Only allow numbers and decimal point
Expand All @@ -66,19 +68,7 @@ const NumberField = ({ value, onChange, label, error }) => {
value={value}
onChange={handleChange}
label={label}
sx={{
"& .MuiOutlinedInput-root": {
"& fieldset": {
borderColor: error ? "error.main" : "inherit",
},
"&:hover fieldset": {
borderColor: error ? "error.main" : "primary.main",
},
"&.Mui-focused fieldset": {
borderColor: error ? "error.main" : "primary.main",
},
},
}}
sx={{ ...errorSx }}
// InputProps={{
// endAdornment: (
// <InputAdornment position="end">
Expand All @@ -98,7 +88,6 @@ const componentRegistry = {
boolean: BooleanField,
string: StringField,
number: NumberField,
Array: Dropdown,
};

const SettingsField = ({ path, Component, label, value, onChange, error }) => {
Expand Down Expand Up @@ -376,6 +365,7 @@ export const Settings = () => {
rangeOrder={frequencyRangeOrder}
onOrderChange={updateFrequencyRangeOrder}
onChange={handleChangeSettings}
errors={validationErrors}
/>
</TitledBox>
</Stack>
Expand Down
182 changes: 182 additions & 0 deletions gui_dev/src/pages/Settings/components/FrequencyRange.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import { useState } from "react";
import { TextField, Button, IconButton, Stack } from "@mui/material";
import { Add, Close } from "@mui/icons-material";
import { debounce } from "@/utils";

const NumberField = ({ ...props }) => (
<TextField
{...props}
sx={{
...(props?.sx || {}),
/* Chrome, Safari, Edge, Opera */
"& input::-webkit-outer-spin-button, & input::-webkit-inner-spin-button":
{
display: "none",
},
"& input[type=number]": {
MozAppearance: "textfield",
},
}}
/>
);

export const FrequencyRange = ({
name,
range,
onChangeName,
onChangeRange,
onRemove,
error,
}) => {
const [localName, setLocalName] = useState(name);

const debouncedChangeName = debounce((newName) => {
onChangeName(newName, name);
}, 1000);

const handleNameChange = (e) => {
const newName = e.target.value;
setLocalName(newName);
debouncedChangeName(newName);
};

const handleNameBlur = () => {
onChangeName(localName, name);
};

const handleKeyPress = (e) => {
if (e.key === "Enter") {
console.log(e.target.value, name);
onChangeName(localName, name);
}
};

const handleRangeChange = (name, field, value) => {
// onChangeRange takes the name of the range as the first argument
onChangeRange(name, { ...range, [field]: value });
};

return (
<Stack direction="row" alignItems="center" gap={1}>
<TextField
size="small"
value={localName}
fullWidth
onChange={handleNameChange}
onBlur={handleNameBlur}
onKeyPress={handleKeyPress}
/>
<NumberField
size="small"
type="number"
value={range.frequency_low_hz}
onChange={(e) =>
handleRangeChange(name, "frequency_low_hz", e.target.value)
}
label="Low Hz"
/>
<NumberField
size="small"
type="number"
value={range.frequency_high_hz}
onChange={(e) =>
handleRangeChange(name, "frequency_high_hz", e.target.value)
}
label="High Hz"
/>
<IconButton
onClick={() => onRemove(name)}
color="primary"
disableRipple
sx={{ m: 0, p: 0 }}
>
<Close />
</IconButton>
</Stack>
);
};

export const FrequencyRangeList = ({
ranges,
rangeOrder,
onChange,
onOrderChange,
errors,
}) => {
const handleChangeRange = (name, newRange) => {
const updatedRanges = { ...ranges };
updatedRanges[name] = newRange;
onChange(["frequency_ranges_hz"], updatedRanges);
};

const handleChangeName = (newName, oldName) => {
if (oldName === newName) {
return;
}

const updatedRanges = { ...ranges, [newName]: ranges[oldName] };
delete updatedRanges[oldName];
onChange(["frequency_ranges_hz"], updatedRanges);

const updatedOrder = rangeOrder.map((name) =>
name === oldName ? newName : name
);
onOrderChange(updatedOrder);
};

const handleRemove = (name) => {
const updatedRanges = { ...ranges };
delete updatedRanges[name];
onChange(["frequency_ranges_hz"], updatedRanges);

const updatedOrder = rangeOrder.filter((item) => item !== name);
onOrderChange(updatedOrder);
};

const addRange = () => {
let newName = "NewRange";
let counter = 0;
// Find first available name
while (Object.hasOwn(ranges, newName)) {
counter++;
newName = `NewRange${counter}`;
}

const updatedRanges = {
...ranges,
[newName]: {
__field_type__: "FrequencyRange",
frequency_low_hz: 1,
frequency_high_hz: 500,
},
};
onChange(["frequency_ranges_hz"], updatedRanges);

const updatedOrder = [...rangeOrder, newName];
onOrderChange(updatedOrder);
};

return (
<Stack gap={1}>
{rangeOrder.map((name, index) => (
<FrequencyRange
key={index}
id={index}
name={name}
range={ranges[name]}
onChangeName={handleChangeName}
onChangeRange={handleChangeRange}
onRemove={handleRemove}
/>
))}
<Button
variant="outlined"
startIcon={<Add />}
onClick={addRange}
sx={{ mt: 1 }}
>
Add Range
</Button>
</Stack>
);
};
6 changes: 3 additions & 3 deletions py_neuromodulation/default_settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ sharpwave_analysis_settings:
rise_steepness: false
decay_steepness: false
slope_ratio: false
filter_ranges_hz:
filter_ranges_hz: # list of [low_hz, high_hz]
- [5, 80]
- [5, 30]
detect_troughs:
Expand Down Expand Up @@ -228,8 +228,8 @@ nolds_settings:
frequency_bands: [low_beta]

mne_connectiviy_settings:
method: plv
mode: multitaper
method: plv # One of ['coh', 'cohy', 'imcoh', 'cacoh', 'mic', 'mim', 'plv', 'ciplv', 'ppc', 'pli', 'dpli','wpli', 'wpli2_debiased', 'gc', 'gc_tr']
mode: multitaper # One of ['multitaper', 'fourier', 'cwt_morlet']

bispectrum_settings:
f1s: [5, 35]
Expand Down
Loading

0 comments on commit f562621

Please sign in to comment.