mirror of
https://github.com/owncast/owncast.git
synced 2024-10-10 19:16:02 +00:00
add regular form field comp with no submit button
This commit is contained in:
parent
c255b81093
commit
ba65aa2406
@ -2,7 +2,7 @@
|
||||
import React, { useState, useContext, useEffect } from 'react';
|
||||
import { Typography } from 'antd';
|
||||
|
||||
import ToggleSwitch from './form-toggleswitch';
|
||||
import ToggleSwitch from './form-toggleswitch-with-submit';
|
||||
|
||||
import { ServerStatusContext } from '../../../utils/server-status-context';
|
||||
import { FIELD_PROPS_NSFW, FIELD_PROPS_YP } from './constants';
|
||||
|
@ -2,7 +2,7 @@ import React, { useState, useContext, useEffect } from 'react';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
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 { ServerStatusContext } from '../../../utils/server-status-context';
|
||||
|
@ -3,16 +3,18 @@ import React, { useContext, useState, useEffect } from 'react';
|
||||
import { Typography, Tag, Input } from 'antd';
|
||||
|
||||
import { ServerStatusContext } from '../../../utils/server-status-context';
|
||||
import { FIELD_PROPS_TAGS, RESET_TIMEOUT, SUCCESS_STATES, postConfigUpdateToAPI } from './constants';
|
||||
import TextField from './form-textfield-nosubmit';
|
||||
import { FIELD_PROPS_TAGS, RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
|
||||
import TextField from './form-textfield';
|
||||
import { UpdateArgs } from '../../../types/config-section';
|
||||
import { createInputStatus, StatusState, STATUS_ERROR, STATUS_PROCESSING, STATUS_SUCCESS, STATUS_WARNING } from '../../../utils/input-statuses';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
export default function EditInstanceTags() {
|
||||
const [newTagInput, setNewTagInput] = useState('');
|
||||
const [submitStatus, setSubmitStatus] = useState(null);
|
||||
const [submitStatusMessage, setSubmitStatusMessage] = useState('');
|
||||
const [fieldStatus, setFieldStatus] = useState<StatusState>(null);
|
||||
// const [submitStatus, setSubmitStatus] = useState(null);
|
||||
// const [submitStatusMessage, setSubmitStatusMessage] = useState('');
|
||||
const serverStatusData = useContext(ServerStatusContext);
|
||||
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
||||
|
||||
@ -35,36 +37,46 @@ export default function EditInstanceTags() {
|
||||
}, []);
|
||||
|
||||
const resetStates = () => {
|
||||
setSubmitStatus(null);
|
||||
setSubmitStatusMessage('');
|
||||
// setSubmitStatus(null);
|
||||
// setSubmitStatusMessage('');
|
||||
setFieldStatus(null);
|
||||
resetTimer = null;
|
||||
clearTimeout(resetTimer);
|
||||
}
|
||||
|
||||
// posts all the tags at once as an array obj
|
||||
const postUpdateToAPI = async (postValue: any) => {
|
||||
setFieldStatus(createInputStatus(STATUS_PROCESSING));
|
||||
|
||||
await postConfigUpdateToAPI({
|
||||
apiPath,
|
||||
data: { value: postValue },
|
||||
onSuccess: () => {
|
||||
setFieldInConfigState({ fieldName: 'tags', value: postValue, path: configPath });
|
||||
setSubmitStatus('success');
|
||||
setSubmitStatusMessage('Tags updated.');
|
||||
setFieldStatus(createInputStatus(STATUS_SUCCESS, 'Tags updated.'));
|
||||
|
||||
// setSubmitStatus('success');
|
||||
// setSubmitStatusMessage('Tags updated.');
|
||||
setNewTagInput('');
|
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||
},
|
||||
onError: (message: string) => {
|
||||
setSubmitStatus('error');
|
||||
setSubmitStatusMessage(message);
|
||||
setFieldStatus(createInputStatus(STATUS_ERROR, message));
|
||||
|
||||
// setSubmitStatus('error');
|
||||
// setSubmitStatusMessage(message);
|
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleInputChange = ({ value }: UpdateArgs) => {
|
||||
if (submitStatusMessage !== '') {
|
||||
setSubmitStatusMessage('');
|
||||
if (!fieldStatus) {
|
||||
setFieldStatus(null);
|
||||
}
|
||||
// if (submitStatusMessage !== '') {
|
||||
// setSubmitStatusMessage('');
|
||||
// }
|
||||
setNewTagInput(value);
|
||||
};
|
||||
|
||||
@ -73,11 +85,15 @@ export default function EditInstanceTags() {
|
||||
resetStates();
|
||||
const newTag = newTagInput.trim();
|
||||
if (newTag === '') {
|
||||
setSubmitStatusMessage('Please enter a tag');
|
||||
setFieldStatus(createInputStatus(STATUS_WARNING, 'Please enter a tag'));
|
||||
|
||||
// setSubmitStatusMessage('Please enter a tag');
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@ -92,10 +108,10 @@ export default function EditInstanceTags() {
|
||||
postUpdateToAPI(updatedTags);
|
||||
}
|
||||
|
||||
const {
|
||||
icon: newStatusIcon = null,
|
||||
message: newStatusMessage = '',
|
||||
} = SUCCESS_STATES[submitStatus] || {};
|
||||
// const {
|
||||
// icon: newStatusIcon = null,
|
||||
// message: newStatusMessage = '',
|
||||
// } = fieldStatus || {};
|
||||
|
||||
return (
|
||||
<div className="tag-editor-container">
|
||||
@ -113,9 +129,9 @@ export default function EditInstanceTags() {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className={`status-message ${submitStatus || ''}`}>
|
||||
{/* <div className={`status-message ${submitStatus || ''}`}>
|
||||
{newStatusIcon} {newStatusMessage} {submitStatusMessage}
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="add-new-tag-section">
|
||||
<TextField
|
||||
fieldName="tag-input"
|
||||
@ -125,19 +141,9 @@ export default function EditInstanceTags() {
|
||||
onPressEnter={handleSubmitNewTag}
|
||||
maxLength={maxLength}
|
||||
placeholder={placeholder}
|
||||
|
||||
message={`${newStatusIcon} ${newStatusMessage} ${submitStatusMessage}`}
|
||||
status={fieldStatus}
|
||||
// message={`${newStatusIcon} ${newStatusMessage} ${submitStatusMessage}`}
|
||||
/>
|
||||
{/* <Input
|
||||
type="text"
|
||||
className="new-tag-input"
|
||||
value={newTagInput}
|
||||
onChange={handleInputChange}
|
||||
onPressEnter={handleSubmitNewTag}
|
||||
maxLength={maxLength}
|
||||
placeholder={placeholder}
|
||||
allowClear
|
||||
/> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ import { Button } from 'antd';
|
||||
import { RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
|
||||
|
||||
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 { UpdateArgs } from '../../../types/config-section';
|
||||
|
||||
|
155
web/pages/components/config/form-textfield.tsx
Normal file
155
web/pages/components/config/form-textfield.tsx
Normal 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: () => {},
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user