diff options
-rw-r--r-- | modern/src/settings/ComputedAttributePage.js | 181 | ||||
-rw-r--r-- | web/l10n/en.json | 3 |
2 files changed, 120 insertions, 64 deletions
diff --git a/modern/src/settings/ComputedAttributePage.js b/modern/src/settings/ComputedAttributePage.js index c9170650..287a4340 100644 --- a/modern/src/settings/ComputedAttributePage.js +++ b/modern/src/settings/ComputedAttributePage.js @@ -11,6 +11,8 @@ import { TextField, createFilterOptions, Autocomplete, + Button, + Snackbar, } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; @@ -18,6 +20,9 @@ import EditItemView from './components/EditItemView'; import { useTranslation } from '../common/components/LocalizationProvider'; import usePositionAttributes from '../common/attributes/usePositionAttributes'; import SettingsMenu from './components/SettingsMenu'; +import SelectField from '../common/components/SelectField'; +import { useCatch } from '../reactHelper'; +import { snackBarDurationLongMs } from '../common/util/duration'; const useStyles = makeStyles((theme) => ({ details: { @@ -35,6 +40,8 @@ const ComputedAttributePage = () => { const positionAttributes = usePositionAttributes(t); const [item, setItem] = useState(); + const [deviceId, setDeviceId] = useState(); + const [result, setResult] = useState(); const options = Object.entries(positionAttributes).filter(([, value]) => !value.property).map(([key, value]) => ({ key, @@ -46,6 +53,21 @@ const ComputedAttributePage = () => { stringify: (option) => option.name, }); + const testAttribute = useCatch(async () => { + const query = new URLSearchParams({ deviceId }); + const url = `/api/attributes/computed/test?${query.toString()}`; + const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(item), + }); + if (response.ok) { + setResult(await response.text()); + } else { + throw Error(await response.text()); + } + }); + const validate = () => item && item.description && item.expression; return ( @@ -58,71 +80,102 @@ const ComputedAttributePage = () => { breadcrumbs={['settingsTitle', 'sharedComputedAttribute']} > {item && ( - <Accordion defaultExpanded> - <AccordionSummary expandIcon={<ExpandMoreIcon />}> - <Typography variant="subtitle1"> - {t('sharedRequired')} - </Typography> - </AccordionSummary> - <AccordionDetails className={classes.details}> - <TextField - value={item.description || ''} - onChange={(event) => setItem({ ...item, description: event.target.value })} - label={t('sharedDescription')} - /> - <Autocomplete - value={options.find((option) => option.key === item.attribute) || item.attribute} - onChange={(_, option) => { - const attribute = option ? option.key || option : null; - if (option && option.type) { - setItem({ ...item, attribute, type: option.type }); - } else { - setItem({ ...item, attribute }); - } - }} - filterOptions={(options, params) => { - const filtered = filter(options, params); - if (params.inputValue) { - filtered.push({ - key: params.inputValue, - name: params.inputValue, - }); - } - return filtered; - }} - options={options} - getOptionLabel={(option) => option.name || option} - renderOption={(props, option) => ( - <li {...props}> - {option.name} - </li> - )} - renderInput={(params) => ( - <TextField {...params} label={t('sharedAttribute')} /> - )} - freeSolo - /> - <TextField - value={item.expression || ''} - onChange={(event) => setItem({ ...item, expression: event.target.value })} - label={t('sharedExpression')} - multiline - rows={4} - /> - <FormControl disabled={item.attribute in positionAttributes}> - <InputLabel>{t('sharedType')}</InputLabel> - <Select - label={t('sharedType')} - value={item.type || ''} - onChange={(event) => setItem({ ...item, type: event.target.value })} + <> + <Accordion defaultExpanded> + <AccordionSummary expandIcon={<ExpandMoreIcon />}> + <Typography variant="subtitle1"> + {t('sharedRequired')} + </Typography> + </AccordionSummary> + <AccordionDetails className={classes.details}> + <TextField + value={item.description || ''} + onChange={(e) => setItem({ ...item, description: e.target.value })} + label={t('sharedDescription')} + /> + <Autocomplete + value={options.find((option) => option.key === item.attribute) || item.attribute} + onChange={(_, option) => { + const attribute = option ? option.key || option : null; + if (option && option.type) { + setItem({ ...item, attribute, type: option.type }); + } else { + setItem({ ...item, attribute }); + } + }} + filterOptions={(options, params) => { + const filtered = filter(options, params); + if (params.inputValue) { + filtered.push({ + key: params.inputValue, + name: params.inputValue, + }); + } + return filtered; + }} + options={options} + getOptionLabel={(option) => option.name || option} + renderOption={(props, option) => ( + <li {...props}> + {option.name} + </li> + )} + renderInput={(params) => ( + <TextField {...params} label={t('sharedAttribute')} /> + )} + freeSolo + /> + <TextField + value={item.expression || ''} + onChange={(e) => setItem({ ...item, expression: e.target.value })} + label={t('sharedExpression')} + multiline + rows={4} + /> + <FormControl disabled={item.attribute in positionAttributes}> + <InputLabel>{t('sharedType')}</InputLabel> + <Select + label={t('sharedType')} + value={item.type || ''} + onChange={(e) => setItem({ ...item, type: e.target.value })} + > + <MenuItem value="string">{t('sharedTypeString')}</MenuItem> + <MenuItem value="number">{t('sharedTypeNumber')}</MenuItem> + <MenuItem value="boolean">{t('sharedTypeBoolean')}</MenuItem> + </Select> + </FormControl> + </AccordionDetails> + </Accordion> + <Accordion> + <AccordionSummary expandIcon={<ExpandMoreIcon />}> + <Typography variant="subtitle1"> + {t('sharedTest')} + </Typography> + </AccordionSummary> + <AccordionDetails className={classes.details}> + <SelectField + value={deviceId || 0} + onChange={(e) => setDeviceId(Number(e.target.value))} + endpoint="/api/devices" + label={t('sharedDevice')} + /> + <Button + variant="outlined" + color="primary" + onClick={testAttribute} + disabled={!deviceId} > - <MenuItem value="string">{t('sharedTypeString')}</MenuItem> - <MenuItem value="number">{t('sharedTypeNumber')}</MenuItem> - <MenuItem value="boolean">{t('sharedTypeBoolean')}</MenuItem> - </Select> - </FormControl> - </AccordionDetails> - </Accordion> + {t('sharedTestExpression')} + </Button> + <Snackbar + open={!!result} + onClose={() => setResult(null)} + autoHideDuration={snackBarDurationLongMs} + message={result} + /> + </AccordionDetails> + </Accordion> + </> )} </EditItemView> ); diff --git a/web/l10n/en.json b/web/l10n/en.json index 6d655354..4db574e2 100644 --- a/web/l10n/en.json +++ b/web/l10n/en.json @@ -57,7 +57,10 @@ "sharedCheckComputedAttribute": "Check Computed Attribute", "sharedExpression": "Expression", "sharedDevice": "Device", + "sharedTest": "Test", "sharedTestNotification": "Send Test Notification", + "sharedTestChannels": "Test Channels", + "sharedTestExpression": "Test Expression", "sharedCalendar": "Calendar", "sharedCalendars": "Calendars", "sharedFile": "File", |