add regular form field comp with no submit button

This commit is contained in:
gingervitis
2021-01-31 00:58:27 -08:00
parent c255b81093
commit ba65aa2406
5 changed files with 196 additions and 35 deletions

View File

@@ -2,7 +2,7 @@
import React, { useState, useContext, useEffect } from 'react'; import React, { useState, useContext, useEffect } from 'react';
import { Typography } from 'antd'; import { Typography } from 'antd';
import ToggleSwitch from './form-toggleswitch'; import ToggleSwitch from './form-toggleswitch-with-submit';
import { ServerStatusContext } from '../../../utils/server-status-context'; import { ServerStatusContext } from '../../../utils/server-status-context';
import { FIELD_PROPS_NSFW, FIELD_PROPS_YP } from './constants'; import { FIELD_PROPS_NSFW, FIELD_PROPS_YP } from './constants';

View File

@@ -2,7 +2,7 @@ import React, { useState, useContext, useEffect } from 'react';
import { Button, Tooltip } from 'antd'; import { Button, Tooltip } from 'antd';
import { CopyOutlined, RedoOutlined } from '@ant-design/icons'; import { CopyOutlined, RedoOutlined } from '@ant-design/icons';
import { TEXTFIELD_TYPE_NUMBER, TEXTFIELD_TYPE_PASSWORD } from './form-textfield-nosubmit'; import { TEXTFIELD_TYPE_NUMBER, TEXTFIELD_TYPE_PASSWORD } from './form-textfield';
import TextFieldWithSubmit from './form-textfield-with-submit'; import TextFieldWithSubmit from './form-textfield-with-submit';
import { ServerStatusContext } from '../../../utils/server-status-context'; import { ServerStatusContext } from '../../../utils/server-status-context';

View File

@@ -3,16 +3,18 @@ import React, { useContext, useState, useEffect } from 'react';
import { Typography, Tag, Input } from 'antd'; import { Typography, Tag, Input } from 'antd';
import { ServerStatusContext } from '../../../utils/server-status-context'; import { ServerStatusContext } from '../../../utils/server-status-context';
import { FIELD_PROPS_TAGS, RESET_TIMEOUT, SUCCESS_STATES, postConfigUpdateToAPI } from './constants'; import { FIELD_PROPS_TAGS, RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
import TextField from './form-textfield-nosubmit'; import TextField from './form-textfield';
import { UpdateArgs } from '../../../types/config-section'; import { UpdateArgs } from '../../../types/config-section';
import { createInputStatus, StatusState, STATUS_ERROR, STATUS_PROCESSING, STATUS_SUCCESS, STATUS_WARNING } from '../../../utils/input-statuses';
const { Title } = Typography; const { Title } = Typography;
export default function EditInstanceTags() { export default function EditInstanceTags() {
const [newTagInput, setNewTagInput] = useState(''); const [newTagInput, setNewTagInput] = useState('');
const [submitStatus, setSubmitStatus] = useState(null); const [fieldStatus, setFieldStatus] = useState<StatusState>(null);
const [submitStatusMessage, setSubmitStatusMessage] = useState(''); // const [submitStatus, setSubmitStatus] = useState(null);
// const [submitStatusMessage, setSubmitStatusMessage] = useState('');
const serverStatusData = useContext(ServerStatusContext); const serverStatusData = useContext(ServerStatusContext);
const { serverConfig, setFieldInConfigState } = serverStatusData || {}; const { serverConfig, setFieldInConfigState } = serverStatusData || {};
@@ -35,36 +37,46 @@ export default function EditInstanceTags() {
}, []); }, []);
const resetStates = () => { const resetStates = () => {
setSubmitStatus(null); // setSubmitStatus(null);
setSubmitStatusMessage(''); // setSubmitStatusMessage('');
setFieldStatus(null);
resetTimer = null; resetTimer = null;
clearTimeout(resetTimer); clearTimeout(resetTimer);
} }
// posts all the tags at once as an array obj // posts all the tags at once as an array obj
const postUpdateToAPI = async (postValue: any) => { const postUpdateToAPI = async (postValue: any) => {
setFieldStatus(createInputStatus(STATUS_PROCESSING));
await postConfigUpdateToAPI({ await postConfigUpdateToAPI({
apiPath, apiPath,
data: { value: postValue }, data: { value: postValue },
onSuccess: () => { onSuccess: () => {
setFieldInConfigState({ fieldName: 'tags', value: postValue, path: configPath }); setFieldInConfigState({ fieldName: 'tags', value: postValue, path: configPath });
setSubmitStatus('success'); setFieldStatus(createInputStatus(STATUS_SUCCESS, 'Tags updated.'));
setSubmitStatusMessage('Tags updated.');
// setSubmitStatus('success');
// setSubmitStatusMessage('Tags updated.');
setNewTagInput(''); setNewTagInput('');
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
}, },
onError: (message: string) => { onError: (message: string) => {
setSubmitStatus('error'); setFieldStatus(createInputStatus(STATUS_ERROR, message));
setSubmitStatusMessage(message);
// setSubmitStatus('error');
// setSubmitStatusMessage(message);
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
}, },
}); });
}; };
const handleInputChange = ({ value }: UpdateArgs) => { const handleInputChange = ({ value }: UpdateArgs) => {
if (submitStatusMessage !== '') { if (!fieldStatus) {
setSubmitStatusMessage(''); setFieldStatus(null);
} }
// if (submitStatusMessage !== '') {
// setSubmitStatusMessage('');
// }
setNewTagInput(value); setNewTagInput(value);
}; };
@@ -73,11 +85,15 @@ export default function EditInstanceTags() {
resetStates(); resetStates();
const newTag = newTagInput.trim(); const newTag = newTagInput.trim();
if (newTag === '') { if (newTag === '') {
setSubmitStatusMessage('Please enter a tag'); setFieldStatus(createInputStatus(STATUS_WARNING, 'Please enter a tag'));
// setSubmitStatusMessage('Please enter a tag');
return; return;
} }
if (tags.some(tag => tag.toLowerCase() === newTag.toLowerCase())) { if (tags.some(tag => tag.toLowerCase() === newTag.toLowerCase())) {
setSubmitStatusMessage('This tag is already used!'); setFieldStatus(createInputStatus(STATUS_WARNING, 'This tag is already used!'));
// setSubmitStatusMessage('This tag is already used!');
return; return;
} }
@@ -92,10 +108,10 @@ export default function EditInstanceTags() {
postUpdateToAPI(updatedTags); postUpdateToAPI(updatedTags);
} }
const { // const {
icon: newStatusIcon = null, // icon: newStatusIcon = null,
message: newStatusMessage = '', // message: newStatusMessage = '',
} = SUCCESS_STATES[submitStatus] || {}; // } = fieldStatus || {};
return ( return (
<div className="tag-editor-container"> <div className="tag-editor-container">
@@ -113,9 +129,9 @@ export default function EditInstanceTags() {
); );
})} })}
</div> </div>
<div className={`status-message ${submitStatus || ''}`}> {/* <div className={`status-message ${submitStatus || ''}`}>
{newStatusIcon} {newStatusMessage} {submitStatusMessage} {newStatusIcon} {newStatusMessage} {submitStatusMessage}
</div> </div> */}
<div className="add-new-tag-section"> <div className="add-new-tag-section">
<TextField <TextField
fieldName="tag-input" fieldName="tag-input"
@@ -125,19 +141,9 @@ export default function EditInstanceTags() {
onPressEnter={handleSubmitNewTag} onPressEnter={handleSubmitNewTag}
maxLength={maxLength} maxLength={maxLength}
placeholder={placeholder} placeholder={placeholder}
status={fieldStatus}
message={`${newStatusIcon} ${newStatusMessage} ${submitStatusMessage}`} // message={`${newStatusIcon} ${newStatusMessage} ${submitStatusMessage}`}
/> />
{/* <Input
type="text"
className="new-tag-input"
value={newTagInput}
onChange={handleInputChange}
onPressEnter={handleSubmitNewTag}
maxLength={maxLength}
placeholder={placeholder}
allowClear
/> */}
</div> </div>
</div> </div>
); );

View File

@@ -4,7 +4,7 @@ import { Button } from 'antd';
import { RESET_TIMEOUT, postConfigUpdateToAPI } from './constants'; import { RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
import { ServerStatusContext } from '../../../utils/server-status-context'; import { ServerStatusContext } from '../../../utils/server-status-context';
import TextField, { TextFieldProps } from './form-textfield-nosubmit'; import TextField, { TextFieldProps } from './form-textfield';
import { createInputStatus, StatusState, STATUS_ERROR, STATUS_PROCESSING, STATUS_SUCCESS } from '../../../utils/input-statuses'; import { createInputStatus, StatusState, STATUS_ERROR, STATUS_PROCESSING, STATUS_SUCCESS } from '../../../utils/input-statuses';
import { UpdateArgs } from '../../../types/config-section'; import { UpdateArgs } from '../../../types/config-section';

View File

@@ -0,0 +1,155 @@
import React from 'react';
import { Input, InputNumber } from 'antd';
import { FieldUpdaterFunc } from '../../../types/config-section';
import InfoTip from '../info-tip';
import { StatusState } from '../../../utils/input-statuses';
export const TEXTFIELD_TYPE_TEXT = 'default';
export const TEXTFIELD_TYPE_PASSWORD = 'password'; // Input.Password
export const TEXTFIELD_TYPE_NUMBER = 'numeric'; // InputNumber
export const TEXTFIELD_TYPE_TEXTAREA = 'textarea'; // Input.TextArea
export const TEXTFIELD_TYPE_URL = 'url';
export interface TextFieldProps {
fieldName: string;
onSubmit?: () => void;
onPressEnter?: () => void;
className?: string;
disabled?: boolean;
label?: string;
maxLength?: number;
placeholder?: string;
required?: boolean;
status?: StatusState;
tip?: string;
type?: string;
value?: string | number;
onBlur?: FieldUpdaterFunc;
onChange?: FieldUpdaterFunc;
}
export default function TextField(props: TextFieldProps) {
const {
className,
disabled,
fieldName,
label,
maxLength,
onBlur,
onChange,
onPressEnter,
placeholder,
required,
status,
tip,
type,
value,
} = props;
// 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;
// if an extra onChange handler was sent in as a prop, let's run that too.
if (onChange) {
onChange({ fieldName, value: val });
}
};
// 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.
const handleBlur = (e: any) => {
const val = e.target.value;
if (onBlur) {
onBlur({ value: val });
}
};
const handlePressEnter = () => {
if (onPressEnter) {
onPressEnter();
}
}
// display the appropriate Ant text field
let Field = Input as typeof Input | typeof InputNumber | typeof Input.TextArea | typeof Input.Password;
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;
fieldProps = {
type: 'number',
min: 1,
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',
};
}
const fieldId = `field-${fieldName}`;
const { icon: statusIcon, message: statusMessage } = status || {};
return (
<div className={`textfield-container type-${type}`}>
{ required ? <span className="required-label">*</span> : null }
<label htmlFor={fieldId} className="textfield-label">{label}</label>
<div className="textfield">
<Field
id={fieldId}
className={`field ${className} ${fieldId}`}
{...fieldProps}
allowClear
placeholder={placeholder}
maxLength={maxLength}
onChange={handleChange}
onBlur={handleBlur}
onPressEnter={handlePressEnter}
disabled={disabled}
value={value}
/>
</div>
<InfoTip tip={tip} />
{ status ? statusMessage : null }
{ status ? statusIcon : null }
</div>
);
}
TextField.defaultProps = {
className: '',
// configPath: '',
disabled: false,
// initialValue: '',
label: '',
maxLength: null,
placeholder: '',
required: false,
status: null,
tip: '',
type: TEXTFIELD_TYPE_TEXT,
value: '',
onSubmit: () => {},
onBlur: () => {},
onChange: () => {},
onPressEnter: () => {},
};