fix(striker-ui): generate fence parameter inputs

main
Tsu-ba-me 2 years ago
parent d9685250b6
commit 4a0d4b5cb3
  1. 157
      striker-ui/components/AddFenceDeviceForm.tsx
  2. 11
      striker-ui/types/APIFence.d.ts

@ -1,13 +1,19 @@
import { Box } from '@mui/material';
import { FC, useMemo, useState } from 'react';
import useIsFirstRender from '../hooks/useIsFirstRender';
import useProtectedState from '../hooks/useProtectedState';
import { Box, Switch } from '@mui/material';
import { FC, ReactElement, ReactNode, useMemo, useState } from 'react';
import api from '../lib/api';
import handleAPIError from '../lib/handleAPIError';
import Autocomplete from './Autocomplete';
import ContainedButton from './ContainedButton';
import FlexBox from './FlexBox';
import handleAPIError from '../lib/handleAPIError';
import InputWithRef from './InputWithRef';
import OutlinedInputWithLabel from './OutlinedInputWithLabel';
import { ExpandablePanel } from './Panels';
import SelectWithLabel from './SelectWithLabel';
import Spinner from './Spinner';
import { BodyText } from './Text';
import useIsFirstRender from '../hooks/useIsFirstRender';
import useProtectedState from '../hooks/useProtectedState';
type FenceDeviceAutocompleteOption = {
fenceDeviceDescription: string;
@ -15,13 +21,54 @@ type FenceDeviceAutocompleteOption = {
label: string;
};
type FenceParameterInputBuilder = (args: {
id: string;
isChecked?: boolean;
isRequired?: boolean;
label?: string;
selectOptions?: string[];
value?: string;
}) => ReactElement;
const MAP_TO_INPUT_BUILDER: Partial<
Record<Exclude<FenceParameterType, 'string'>, FenceParameterInputBuilder>
> & { string: FenceParameterInputBuilder } = {
boolean: ({ id, isChecked = false, label }) => (
<FlexBox key={`${id}-wrapper`} row>
<BodyText flexGrow={1}>{label}</BodyText>
<Switch checked={isChecked} edge="end" id={id} />
</FlexBox>
),
select: ({ id, isRequired, label, selectOptions = [], value = '' }) => (
<InputWithRef
key={`${id}-wrapper`}
input={
<SelectWithLabel
id={id}
label={label}
selectItems={selectOptions}
selectProps={{ value }}
/>
}
required={isRequired}
/>
),
string: ({ id, isRequired, label = '', value }) => (
<InputWithRef
key={`${id}-wrapper`}
input={<OutlinedInputWithLabel id={id} label={label} value={value} />}
required={isRequired}
/>
),
};
const AddFenceDeivceForm: FC = () => {
const isFirstRender = useIsFirstRender();
const [fenceDeviceTemplate, setFenceDeviceTemplate] = useProtectedState<
APIFenceTemplate | undefined
>(undefined);
const [inputFenceDeviceTypeValue, setInputFenceDeviceTypeValue] =
const [fenceDeviceTypeValue, setInputFenceDeviceTypeValue] =
useState<FenceDeviceAutocompleteOption | null>(null);
const [isLoadingTemplate, setIsLoadingTemplate] =
useProtectedState<boolean>(true);
@ -47,7 +94,7 @@ const AddFenceDeivceForm: FC = () => {
[fenceDeviceTemplate],
);
const autocompleteFenceDeviceType = useMemo(
const fenceDeviceTypeElement = useMemo(
() => (
<Autocomplete
id="add-fence-device-pick-type"
@ -89,20 +136,106 @@ const AddFenceDeivceForm: FC = () => {
<BodyText selected={false}>{fenceDeviceDescription}</BodyText>
</Box>
)}
value={inputFenceDeviceTypeValue}
value={fenceDeviceTypeValue}
/>
),
[fenceDeviceTypeOptions, inputFenceDeviceTypeValue],
[fenceDeviceTypeOptions, fenceDeviceTypeValue],
);
const fenceParameterElements = useMemo(() => {
let result: ReactNode;
if (fenceDeviceTemplate && fenceDeviceTypeValue) {
const { fenceDeviceId } = fenceDeviceTypeValue;
const { parameters: fenceDeviceParameters } =
fenceDeviceTemplate[fenceDeviceId];
const { optional: optionalInputs, required: requiredInputs } =
Object.entries(fenceDeviceParameters).reduce<{
optional: ReactElement[];
required: ReactElement[];
}>(
(
previous,
[
parameterId,
{
content_type: contentType,
default: parameterDefault,
options: parameterSelectOptions,
required: isRequired,
},
],
) => {
const { optional, required } = previous;
const buildInput =
MAP_TO_INPUT_BUILDER[contentType] ?? MAP_TO_INPUT_BUILDER.string;
const fenceJoinParameterId = `${fenceDeviceId}-${parameterId}`;
const parameterIsRequired = isRequired === '1';
const parameterInput = buildInput({
id: fenceJoinParameterId,
isChecked: parameterDefault === '1',
isRequired: parameterIsRequired,
label: parameterId,
selectOptions: parameterSelectOptions,
value: parameterDefault,
});
if (parameterIsRequired) {
required.push(parameterInput);
} else {
optional.push(parameterInput);
}
return previous;
},
{
optional: [],
required: [
MAP_TO_INPUT_BUILDER.string({
id: `${fenceDeviceId}-name`,
isRequired: true,
label: 'Fence device name',
}),
],
},
);
result = (
<>
<ExpandablePanel expandInitially header="Required parameters">
<FlexBox margin=".4em 0">{requiredInputs}</FlexBox>
</ExpandablePanel>
<ExpandablePanel header="Optional parameters">
<FlexBox margin=".4em 0">{optionalInputs}</FlexBox>
</ExpandablePanel>
</>
);
}
return result;
}, [fenceDeviceTemplate, fenceDeviceTypeValue]);
const formContent = useMemo(
() =>
isLoadingTemplate ? (
<Spinner mt={0} />
) : (
<>{autocompleteFenceDeviceType}</>
<FlexBox
component="form"
onSubmit={(event) => {
event.preventDefault();
}}
sx={{ '& > div': { marginBottom: 0 } }}
>
{fenceDeviceTypeElement}
{fenceParameterElements}
<FlexBox row justifyContent="flex-end">
<ContainedButton type="submit">Add fence device</ContainedButton>
</FlexBox>
</FlexBox>
),
[autocompleteFenceDeviceType, isLoadingTemplate],
[fenceDeviceTypeElement, fenceParameterElements, isLoadingTemplate],
);
if (isFirstRender) {
@ -119,7 +252,7 @@ const AddFenceDeivceForm: FC = () => {
});
}
return <FlexBox>{formContent}</FlexBox>;
return <>{formContent}</>;
};
export default AddFenceDeivceForm;

@ -1,11 +1,18 @@
type FenceParameterType =
| 'boolean'
| 'integer'
| 'second'
| 'select'
| 'string';
type APIFenceTemplate = {
[fenceId: string]: {
actions: string[];
description: string;
parameters: {
[parameterId: string]: {
content_type: 'boolean' | 'integer' | 'second' | 'select' | 'string';
default: string;
content_type: FenceParameterType;
default?: string;
deprecated: number;
description: string;
obsoletes: number;

Loading…
Cancel
Save