2021-01-30 22:25:44 -05:00
import React , { useEffect , useState , useContext } from 'react' ;
2021-01-28 03:08:57 -08:00
import { Button , Input , InputNumber } from 'antd' ;
2020-12-28 01:11:26 -08:00
import { FormItemProps } from 'antd/es/form' ;
2020-12-26 18:04:23 -08:00
2021-01-28 03:08:57 -08:00
import { RESET_TIMEOUT , postConfigUpdateToAPI } from './constants' ;
2020-12-26 18:04:23 -08:00
2021-01-29 10:26:55 -08:00
import { FieldUpdaterFunc } from '../../../types/config-section' ;
2020-12-28 01:11:26 -08:00
import { ServerStatusContext } from '../../../utils/server-status-context' ;
2021-01-03 01:54:04 -08:00
import InfoTip from '../info-tip' ;
2020-12-26 19:44:09 -08:00
export const TEXTFIELD_TYPE_TEXT = 'default' ;
2020-12-28 01:11:26 -08:00
export const TEXTFIELD_TYPE_PASSWORD = 'password' ; // Input.Password
2020-12-26 19:44:09 -08:00
export const TEXTFIELD_TYPE_NUMBER = 'numeric' ;
2020-12-29 02:51:56 -08:00
export const TEXTFIELD_TYPE_TEXTAREA = 'textarea' ;
2021-01-03 04:03:18 -08:00
export const TEXTFIELD_TYPE_URL = 'url' ;
2020-12-29 02:51:56 -08:00
2021-01-29 10:26:55 -08:00
interface TextFieldProps {
apiPath : string ;
fieldName : string ;
configPath? : string ;
disabled? : boolean ;
initialValue? : string ;
label? : string ;
maxLength? : number ;
placeholder? : string ;
required? : boolean ;
tip? : string ;
type ? : string ;
value? : string | number ;
onSubmit ? : ( ) = > void ;
onBlur ? : ( ) = > void ;
onChange? : FieldUpdaterFunc ;
}
2020-12-26 19:44:09 -08:00
export default function TextField ( props : TextFieldProps ) {
2020-12-28 01:11:26 -08:00
const [ submitStatus , setSubmitStatus ] = useState < FormItemProps [ 'validateStatus' ] > ( '' ) ;
const [ submitStatusMessage , setSubmitStatusMessage ] = useState ( '' ) ;
2020-12-29 02:51:56 -08:00
const [ hasChanged , setHasChanged ] = useState ( false ) ;
2021-01-30 22:25:44 -05:00
const [ fieldValueForSubmit , setFieldValueForSubmit ] = useState < string | number > ( '' ) ;
2020-12-28 01:11:26 -08:00
2020-12-29 02:51:56 -08:00
const serverStatusData = useContext ( ServerStatusContext ) ;
2021-01-03 01:54:04 -08:00
const { setFieldInConfigState } = serverStatusData || { } ;
2021-01-28 03:08:57 -08:00
let resetTimer = null ;
2020-12-26 19:44:09 -08:00
const {
2021-01-28 03:08:57 -08:00
apiPath ,
2021-01-03 00:26:26 -08:00
configPath = '' ,
disabled = false ,
2020-12-28 01:11:26 -08:00
fieldName ,
2021-01-28 03:08:57 -08:00
initialValue ,
label ,
maxLength ,
2021-01-03 04:10:08 -08:00
onBlur ,
onChange ,
2021-01-28 03:08:57 -08:00
onSubmit ,
placeholder ,
required ,
tip ,
2021-01-03 00:26:26 -08:00
type ,
2021-01-28 03:08:57 -08:00
value ,
2020-12-26 19:44:09 -08:00
} = props ;
2020-12-28 01:11:26 -08:00
2021-01-03 00:26:26 -08:00
// Clear out any validation states and messaging
2020-12-29 02:51:56 -08:00
const resetStates = ( ) = > {
setSubmitStatus ( '' ) ;
setHasChanged ( false ) ;
clearTimeout ( resetTimer ) ;
resetTimer = null ;
2021-01-03 23:32:47 -08:00
} ;
2020-12-29 02:51:56 -08:00
2021-01-30 22:25:44 -05:00
useEffect ( ( ) = > {
// TODO: Add native validity checks here, somehow
2021-01-03 04:03:18 -08:00
// https://developer.mozilla.org/en-US/docs/Web/API/ValidityState
2021-01-30 22:25:44 -05:00
// const hasValidity = (type !== TEXTFIELD_TYPE_NUMBER && e.target.validity.valid) || type === TEXTFIELD_TYPE_NUMBER ;
if ( ( required && ( value === '' || value === null ) ) || value === initialValue ) {
2020-12-29 02:51:56 -08:00
setHasChanged ( false ) ;
2020-12-28 01:11:26 -08:00
} else {
2021-01-03 04:03:18 -08:00
// show submit button
2020-12-29 02:51:56 -08:00
resetStates ( ) ;
setHasChanged ( true ) ;
2021-01-30 22:25:44 -05:00
setFieldValueForSubmit ( value ) ;
2020-12-28 01:11:26 -08:00
}
2021-01-30 22:25:44 -05:00
} , [ value ] ) ;
// if field is required but value is empty, or equals initial value, then don't show submit/update button. otherwise clear out any result messaging and display button.
const handleChange = ( e : any ) = > {
const val = type === TEXTFIELD_TYPE_NUMBER ? e : e.target.value ;
2021-01-03 04:10:08 -08:00
// if an extra onChange handler was sent in as a prop, let's run that too.
if ( onChange ) {
2021-01-29 10:26:55 -08:00
onChange ( { fieldName , value : val } ) ;
2021-01-03 04:10:08 -08:00
}
2020-12-29 02:51:56 -08:00
} ;
2021-01-28 03:08:57 -08:00
// if you blur a required field with an empty value, restore its original value in state (parent's state), if an onChange from parent is available.
2020-12-29 02:51:56 -08:00
const handleBlur = e = > {
2021-01-28 03:08:57 -08:00
if ( ! onChange ) {
return ;
}
2020-12-29 02:51:56 -08:00
const val = e . target . value ;
2021-01-03 00:26:26 -08:00
if ( required && val === '' ) {
2021-01-29 10:26:55 -08:00
onChange ( { fieldName , value : initialValue } ) ;
2020-12-29 02:51:56 -08:00
}
2021-01-03 04:10:08 -08:00
// if an extra onBlur handler was sent in as a prop, let's run that too.
if ( onBlur ) {
onBlur ( ) ;
}
2020-12-29 02:51:56 -08:00
} ;
// how to get current value of input
2021-01-03 01:10:38 -08:00
const handleSubmit = async ( ) = > {
2021-01-03 00:26:26 -08:00
if ( ( required && fieldValueForSubmit !== '' ) || fieldValueForSubmit !== initialValue ) {
2021-01-03 01:10:38 -08:00
setSubmitStatus ( 'validating' ) ;
await postConfigUpdateToAPI ( {
apiPath ,
data : { value : fieldValueForSubmit } ,
onSuccess : ( ) = > {
2021-01-03 01:54:04 -08:00
setFieldInConfigState ( { fieldName , value : fieldValueForSubmit , path : configPath } ) ;
2021-01-03 01:10:38 -08:00
setSubmitStatus ( 'success' ) ;
} ,
onError : ( message : string ) = > {
setSubmitStatus ( 'error' ) ;
setSubmitStatusMessage ( ` There was an error: ${ message } ` ) ;
} ,
} ) ;
resetTimer = setTimeout ( resetStates , RESET_TIMEOUT ) ;
2021-01-03 00:26:26 -08:00
// if an extra onSubmit handler was sent in as a prop, let's run that too.
if ( onSubmit ) {
onSubmit ( ) ;
}
2020-12-29 02:51:56 -08:00
}
}
2021-01-03 00:26:26 -08:00
// display the appropriate Ant text field
let Field = Input as typeof Input | typeof InputNumber | typeof Input . TextArea | typeof Input . Password ;
2020-12-29 02:51:56 -08:00
let fieldProps = { } ;
if ( type === TEXTFIELD_TYPE_TEXTAREA ) {
Field = Input . TextArea ;
fieldProps = {
autoSize : true ,
} ;
} else if ( type === TEXTFIELD_TYPE_PASSWORD ) {
Field = Input . Password ;
fieldProps = {
visibilityToggle : true ,
} ;
} else if ( type === TEXTFIELD_TYPE_NUMBER ) {
Field = InputNumber ;
2021-01-03 04:03:18 -08:00
fieldProps = {
type : 'number' ,
2021-01-09 13:12:14 -08:00
min : 1 ,
2021-01-03 04:03:18 -08:00
max : ( 10 * * maxLength ) - 1 ,
onKeyDown : ( e : React.KeyboardEvent ) = > {
if ( e . target . value . length > maxLength - 1 )
e . preventDefault ( )
return false ;
}
} ;
} else if ( type === TEXTFIELD_TYPE_URL ) {
fieldProps = {
type : 'url' ,
} ;
2020-12-28 01:11:26 -08:00
}
2021-01-28 03:08:57 -08:00
const fieldId = ` field- ${ fieldName } ` ;
2020-12-28 01:11:26 -08:00
return (
2021-01-09 13:12:14 -08:00
< div className = { ` textfield-container type- ${ type } ` } >
2021-01-28 03:08:57 -08:00
{ required ? < span className = "required-label" > * < / span > : null }
< label htmlFor = { fieldId } className = "textfield-label" > { label } < / label >
2020-12-29 02:51:56 -08:00
< div className = "textfield" >
< Field
2021-01-28 03:08:57 -08:00
id = { fieldId }
className = { ` field ${ fieldId } ` }
{ . . . fieldProps }
allowClear
placeholder = { placeholder }
maxLength = { maxLength }
onChange = { handleChange }
onBlur = { handleBlur }
disabled = { disabled }
value = { value }
/ >
2020-12-28 01:11:26 -08:00
< / div >
2021-01-27 01:46:08 -08:00
< InfoTip tip = { tip } / >
2021-01-28 03:08:57 -08:00
{ submitStatus }
{ submitStatusMessage }
2020-12-29 02:51:56 -08:00
{ hasChanged ? < Button type = "primary" size = "small" className = "submit-button" onClick = { handleSubmit } > Update < / Button > : null }
2020-12-26 19:44:09 -08:00
< / div >
) ;
}
2021-01-29 10:26:55 -08:00
TextField . defaultProps = {
configPath : '' ,
disabled : false ,
initialValue : '' ,
label : '' ,
maxLength : null ,
placeholder : '' ,
required : false ,
tip : '' ,
type : TEXTFIELD_TYPE_TEXT ,
value : '' ,
onSubmit : ( ) = > { } ,
onBlur : ( ) = > { } ,
onChange : ( ) = > { } ,
} ;