Skip to content

Commit

Permalink
Reworked command form elements
Browse files Browse the repository at this point in the history
  • Loading branch information
west270 committed Jun 21, 2023
1 parent e64c09b commit a0ccc34
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 252 deletions.
60 changes: 60 additions & 0 deletions src/components/FormComponents/FormAnyOrDict.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { TextField, TextFieldProps } from '@mui/material'
import { defaultTextFieldProps } from 'components/FormComponents'
import { ChangeEvent } from 'react'
import { RegisterOptions, useFormContext } from 'react-hook-form'

export type FormAnyOrDictProps = {
registerKey: string
registerOptions?: RegisterOptions
} & TextFieldProps

const FormAnyOrDict = ({ registerKey, registerOptions, ...textFieldProps }: FormAnyOrDictProps) => {
const { getFieldState, watch, setValue, register, setError, clearErrors } = useFormContext()

register(registerKey, {...registerOptions})

const currentValue = watch(registerKey)

const { error, invalid } = getFieldState(registerKey)

if(error){
textFieldProps.error = true
if(error.message) textFieldProps.helperText = error.message
}

return (
<TextField
{...defaultTextFieldProps}
{...textFieldProps}
value={ invalid ? currentValue : JSON.stringify(currentValue)}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
if(event.target.value === '') {
clearErrors(registerKey)
setValue(registerKey, undefined)
}
else try {
const value = JSON.parse(event.target.value)
if( textFieldProps.type === 'any' || typeof value === 'object' ) {
setValue(registerKey, value)
clearErrors(registerKey)
} else throw new Error('value is not valid')
} catch(e) {
if(event.target.value !== ''){
setError(registerKey, {
type: 'parseError',
message: textFieldProps.type === 'object' ?
'Invalid object'
:
'Unknown Type. Remember, variant fields must be enclosed with {} (object), [] (array), or "" (string).'
})
} else {
clearErrors(registerKey)
}
setValue(registerKey, event.target.value)
}
}}
/>
)
}

export { FormAnyOrDict }
6 changes: 2 additions & 4 deletions src/components/FormComponents/FormArray.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,10 @@ const FormArray = ({ registerKey, disabled, helperText, label, minimum, maximum,
const addItem = () => {
const tempData = getValues(registerKey) || []
if(!maximum || tempData.length < maximum)
setValue(`${registerKey}.${tempData.length}`, addValue ? addValue : null)
setValue(`${registerKey}.${tempData.length}`, addValue)
}

watch(registerKey)

const currentValue = getValues(registerKey) || []
const currentValue = watch(registerKey) || []

const error = getFieldState(registerKey).error

Expand Down
43 changes: 43 additions & 0 deletions src/components/FormComponents/FormFile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { TextField, TextFieldProps } from '@mui/material'
import { defaultTextFieldProps } from 'components/FormComponents'
import { ChangeEvent } from 'react'
import { RegisterOptions, useFormContext } from 'react-hook-form'

export type FormFileProps = {
registerKey: string
registerOptions?: RegisterOptions
} & TextFieldProps

const FormFile = ({ registerKey, registerOptions, ...textFieldProps }: FormFileProps) => {
const { getFieldState, watch, setValue, register, trigger } = useFormContext()

const currentValue = watch(registerKey)

register(registerKey, {...registerOptions})

const { error } = getFieldState(registerKey)
if(error){
textFieldProps.error = true
if(error.message) textFieldProps.helperText = error.message
}

return (
<TextField
{...defaultTextFieldProps}
{...textFieldProps}
type="file"
value={currentValue !== '' && currentValue !== undefined ? currentValue.fileName : ''}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
if(event.target.files) {
setValue(registerKey, event.target.files[0])
}
else {
setValue(registerKey, '')
}
trigger(registerKey)
}}
/>
)
}

export { FormFile }
26 changes: 16 additions & 10 deletions src/components/FormComponents/FormTextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ export type FormTextFieldProps = {
menuOptions?: (string | number)[]
} & TextFieldProps

const defaultTextFieldProps: TextFieldProps = {
FormHelperTextProps: {
sx: {ml: 0}
},
size:'small',
fullWidth: true,
InputLabelProps: {shrink: true}
}

const FormTextField = ({ registerKey, registerOptions, menuOptions, ...textFieldProps }: FormTextFieldProps) => {
const { register, getFieldState, watch, } = useFormContext()
const [showPassword, setShowPassword] = useMountedState(false)
const defaultTextFieldProps: TextFieldProps = {
FormHelperTextProps: {
sx: {ml: 0}
},
size:'small',
fullWidth: true,
InputLabelProps: {shrink: true}
}

const currentValue = watch(registerKey)

Expand Down Expand Up @@ -61,8 +62,13 @@ const FormTextField = ({ registerKey, registerOptions, menuOptions, ...textField
{menuOptions.map((value) => <MenuItem key={value} value={value}>{value}</MenuItem>)}
</TextField>
) : (
<TextField {...defaultTextFieldProps} value={(currentValue === 0 || currentValue) ? currentValue : ''} {...textFieldProps} {...register(registerKey, registerOptions)} />
<TextField
{...defaultTextFieldProps}
value={(currentValue === 0 || currentValue) ? currentValue : ''}
{...textFieldProps}
{...register(registerKey, registerOptions)}
/>
)
}

export { FormTextField }
export { defaultTextFieldProps, FormTextField }
3 changes: 2 additions & 1 deletion src/components/FormComponents/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './FormAnyOrDict'
export * from './FormArray'
export * from './FormCheckbox'
export * from './FormFile'
export * from './FormTextField'

60 changes: 26 additions & 34 deletions src/pages/CommandView/CommandFormFields.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { TabContext, TabList } from '@mui/lab'
import { Alert, Box, Grid, MenuItem, Tab, TextField, TextFieldProps } from '@mui/material'
import { Alert, Box, Grid, Tab, TextFieldProps } from '@mui/material'
import { Divider } from 'components/Divider'
import { FormTextField } from 'components/FormComponents'
import { useMountedState } from 'hooks/useMountedState'
import { ParameterElement } from 'pages/CommandView'
import { useFormContext } from 'react-hook-form'
Expand All @@ -16,11 +17,15 @@ const CommandFormFields = ({ parameters, instances, parentKey }: { parameters: P
fullWidth: true,
}

const { register, formState: { errors } } = useFormContext()
const { formState: { errors } } = useFormContext()
const getKey = (key: string) => (
parentKey ? [parentKey, key].join('.') : key
)


const instancesNames: (string | number)[] | undefined = instances.length === 1 ? undefined : []
if(instancesNames) instances.sort((a, b) =>
(a.name < b.name ? -1 : 1)
).forEach(instance => instancesNames.push(instance.name))

const requiredParams = parameters.filter((param: Parameter) => (!param.optional)) || []
const optionalParams = parameters.filter((param: Parameter) => (param.optional)) || []
Expand Down Expand Up @@ -52,52 +57,39 @@ const CommandFormFields = ({ parameters, instances, parentKey }: { parameters: P
rowSpacing={2}
>
<Grid key="systemName" minWidth="150px" xs={1} item>
<TextField label="System Name" {...textFieldProps} {...register(getKey('system'))} />
<FormTextField label="System Name" {...textFieldProps} registerKey={getKey('system')} />
</Grid>
<Grid key="systemVersion" minWidth="150px" xs={1} item>
<TextField label="System Version" {...textFieldProps} {...register(getKey('system_version'))} />
<FormTextField label="System Version" {...textFieldProps} registerKey={getKey('system_version')} />
</Grid>
<Grid key="commandName" minWidth="150px" xs={1} item>
<TextField label="Command Name" {...textFieldProps} {...register(getKey('command'))} />
<FormTextField label="Command Name" {...textFieldProps} registerKey={getKey('command')} />
</Grid>
<Grid key="instanceName" minWidth="150px" xs={1} item>
{
instances.length === 1 ?
<TextField label="Instance Name" {...textFieldProps} {...register(getKey('instance_name'))} />
:
<TextField
label="Instance Name"
type="string"
size="small"
error={!!errors['instance_name']}
helperText={errors['instance_name']?.message || ''}
select
defaultValue={''}
fullWidth
{...register(getKey('instance_name'), {required: 'Instance is required'})}
>
{(instances.sort((a, b) =>
(a.name < b.name ? -1 : 1)
).map((instance, index) => (
<MenuItem key={`${instance.name}MenuItem${index}`} value={instance.name} >
{instance.name}
</MenuItem>)
))}
</TextField>
}
<FormTextField
label="Instance Name"
{...textFieldProps}
disabled={instances.length <= 1}
registerKey={getKey('instance_name')}
registerOptions={{required: {value: true, message: 'Instance Name is required'}}}
menuOptions={instancesNames}
/>
</Grid>
</Grid>
<TextField
<FormTextField
{...textFieldProps}
disabled={false}
multiline
label="Comment"
error={!!errors['comment']}
helperText={errors['comment']?.message || ''}
maxRows={3}
{...register(getKey('comment'), {
maxLength: {value: 140, message: 'Maximum comment length is 140 characters'}
})}
registerKey={getKey('comment')}
registerOptions={
{
maxLength: {value: 140, message: 'Maximum comment length is 140 characters'}
}
}
/>
</>
)
Expand Down
15 changes: 8 additions & 7 deletions src/pages/CommandView/CommandView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,15 @@ const CommandView = ({isJob} : {isJob?: boolean}) => {
job,
isJob
])

const getCommandFromSystem = (sys: System) => {
let tempCommand: Command | undefined = undefined
tempCommand = sys.commands.find((cmd: Command) => (cmd.name===commandName))
setCommand && setCommand(tempCommand as AugmentedCommand | undefined)
}

if(system && Object.hasOwn(system, 'commands') && !command) getCommandFromSystem(system as System)
useEffect(() => {
const getCommandFromSystem = (sys: System) => {
let tempCommand: Command | undefined = undefined
tempCommand = sys.commands.find((cmd: Command) => (cmd.name===commandName))
setCommand && setCommand(tempCommand as AugmentedCommand | undefined)
}
if(system && Object.hasOwn(system, 'commands') && !command) getCommandFromSystem(system as System)
}, [command, commandName, setCommand, system])


if (!system || !command || error) {
Expand Down
79 changes: 18 additions & 61 deletions src/pages/CommandView/ParameterElements/ParamArray.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Alert, Box, Button, FormHelperText, Stack, Typography, } from '@mui/material'
import { FormArray } from 'components/FormComponents'
import { ParameterElement } from 'pages/CommandView/ParameterElements'
import { useFormContext } from 'react-hook-form'
import { Parameter } from 'types/backend-types'
Expand All @@ -9,78 +9,35 @@ interface ParamTextFieldProps {
}

const ParamArray = ({ parameter, registerKey }: ParamTextFieldProps) => {
const { getValues, setValue, watch, setError, clearErrors, getFieldState } = useFormContext()

const addItem = () => {
const tempData = getValues(registerKey) || []
if(!parameter.maximum || tempData.length < parameter.maximum)
setValue(`${registerKey}.${tempData.length}`, null)
}

const removeItem = (index: number) => {
const tempData = getValues(registerKey) || []
if(!parameter.minimum || tempData.length > parameter.minimum) tempData.splice(index, 1)
setValue(registerKey, tempData)
}
const { watch, setError, clearErrors, getFieldState } = useFormContext()

// triggers rerender when adding or removing
watch(registerKey)
const currentValue = watch(registerKey)

const {error} = getFieldState(registerKey)

const currentValue = getValues(registerKey) || []

if(parameter.maximum && currentValue.length > parameter.maximum) {
if(error?.type !== 'arrayLength') setError(registerKey, { type: 'arrayLength', message: `remove items max length is: ${parameter.maximum}` })
} else if(parameter.minimum && currentValue.length < parameter.minimum) {
if(error?.type !== 'arrayLength') setError(registerKey, { type: 'arrayLength', message: `add items min length is: ${parameter.minimum}` })
} else if(error?.type === 'arrayLength') clearErrors(registerKey)

const getFieldJsx = (index: number, registerKey: string,): JSX.Element => {
return (
<ParameterElement parameter={parameter} parentKey={registerKey} ignoreMulti={true} arrayIndex={index} />
)
}

return (
<Box p={1} sx={{border: `solid 1px ${error?.type === 'arrayLength'? 'red' : 'gray'}`, borderRadius: 2}}>
{parameter.display_name && <Typography variant="h3" >{parameter.display_name}</Typography>}
<Stack pt={1} rowGap={2}>
{currentValue.map((value: unknown, index: number) => {
return (
<Stack
key={`${registerKey}-${index}`}
direction="row" spacing={1}
alignItems="start"
justifyContent="space-between"
>
<ParameterElement parameter={parameter} parentKey={registerKey} ignoreMulti={true} arrayIndex={index} />
<Button
variant="contained"
color="secondary"
onClick={() => removeItem(index)}
size="small"
disabled={!!parameter.minimum && currentValue.length === parameter.minimum}
>
Remove
</Button>
</Stack>
)
})}
</Stack>
{parameter.description &&
<FormHelperText sx={{pt: 2}} id={`${registerKey}-helperText`}>
{ error?.type === 'arrayLength' ?
<Alert sx={{border: 'solid 0px gray'}} severity="error" variant="outlined" >{error.message}</Alert>
:
parameter.description
}
</FormHelperText>
}
<Button
sx={{float: 'right', mt: parameter.description? 0 : 1}}
variant="contained"
color="secondary"
onClick={addItem}
disabled={!!parameter.maximum && currentValue.length === parameter.maximum}
>
Add
</Button>
</Box>
<FormArray
registerKey={registerKey}
getFieldJsx={getFieldJsx}
maximum={parameter.maximum}
minimum={parameter.minimum}
label={parameter.display_name}
helperText={parameter.description}
addValue={parameter.nullable ? null : undefined}
/>
)
}

Expand Down
Loading

0 comments on commit a0ccc34

Please sign in to comment.