diff --git a/web/pages/components/config/edit-directory.tsx b/web/pages/components/config/edit-directory.tsx
index fcd4664a7..08608cf10 100644
--- a/web/pages/components/config/edit-directory.tsx
+++ b/web/pages/components/config/edit-directory.tsx
@@ -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';
diff --git a/web/pages/components/config/edit-instance-details.tsx b/web/pages/components/config/edit-instance-details.tsx
index 3ec5129d2..af2821836 100644
--- a/web/pages/components/config/edit-instance-details.tsx
+++ b/web/pages/components/config/edit-instance-details.tsx
@@ -1,5 +1,5 @@
import React, { useState, useContext, useEffect } from 'react';
-import TextField, { TEXTFIELD_TYPE_TEXTAREA, TEXTFIELD_TYPE_URL } from './form-textfield';
+import TextFieldWithSubmit, { TEXTFIELD_TYPE_TEXTAREA, TEXTFIELD_TYPE_URL } from './form-textfield-with-submit';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { postConfigUpdateToAPI, TEXTFIELD_PROPS_USERNAME, TEXTFIELD_PROPS_INSTANCE_URL, TEXTFIELD_PROPS_SERVER_TITLE, TEXTFIELD_PROPS_STREAM_TITLE, TEXTFIELD_PROPS_SERVER_SUMMARY, TEXTFIELD_PROPS_LOGO, API_YP_SWITCH } from './constants';
@@ -47,7 +47,7 @@ export default function EditInstanceDetails() {
return (
-
-
-
-
-
-
-
-
-
- (null);
+ // const [submitStatus, setSubmitStatus] = useState(null);
+ // const [submitStatusMessage, setSubmitStatusMessage] = useState('');
const serverStatusData = useContext(ServerStatusContext);
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
@@ -33,37 +37,47 @@ 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 = e => {
- if (submitStatusMessage !== '') {
- setSubmitStatusMessage('');
+ const handleInputChange = ({ value }: UpdateArgs) => {
+ if (!fieldStatus) {
+ setFieldStatus(null);
}
- setNewTagInput(e.target.value);
+ // if (submitStatusMessage !== '') {
+ // setSubmitStatusMessage('');
+ // }
+ setNewTagInput(value);
};
// send to api and do stuff
@@ -71,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;
}
@@ -90,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 (
@@ -111,19 +129,20 @@ export default function EditInstanceTags() {
);
})}
-
+ {/*
{newStatusIcon} {newStatusMessage} {submitStatusMessage}
-
+
*/}
-
diff --git a/web/pages/components/config/form-textfield-with-submit.tsx b/web/pages/components/config/form-textfield-with-submit.tsx
new file mode 100644
index 000000000..5044225b3
--- /dev/null
+++ b/web/pages/components/config/form-textfield-with-submit.tsx
@@ -0,0 +1,137 @@
+import React, { useEffect, useState, useContext } from 'react';
+import { Button } from 'antd';
+
+import { RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
+
+import { ServerStatusContext } from '../../../utils/server-status-context';
+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';
+
+export const TEXTFIELD_TYPE_TEXT = 'default';
+export const TEXTFIELD_TYPE_PASSWORD = 'password'; // Input.Password
+export const TEXTFIELD_TYPE_NUMBER = 'numeric';
+export const TEXTFIELD_TYPE_TEXTAREA = 'textarea';
+export const TEXTFIELD_TYPE_URL = 'url';
+
+interface TextFieldWithSubmitProps extends TextFieldProps {
+ apiPath: string;
+ configPath?: string;
+ initialValue?: string;
+}
+
+export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) {
+ const [fieldStatus, setFieldStatus] = useState
(null);
+
+ const [hasChanged, setHasChanged] = useState(false);
+ const [fieldValueForSubmit, setFieldValueForSubmit] = useState('');
+
+ const serverStatusData = useContext(ServerStatusContext);
+ const { setFieldInConfigState } = serverStatusData || {};
+
+ let resetTimer = null;
+
+ const {
+ apiPath,
+ configPath = '',
+ initialValue,
+ ...textFieldProps // rest of props
+ } = props;
+
+ const {
+ fieldName,
+ required,
+ status,
+ // type,
+ value,
+ onChange,
+ // onBlur,
+ onSubmit,
+ } = textFieldProps;
+
+ // Clear out any validation states and messaging
+ const resetStates = () => {
+ setFieldStatus(null);
+ setHasChanged(false);
+ clearTimeout(resetTimer);
+ resetTimer = null;
+ };
+
+ useEffect(() => {
+ // TODO: Add native validity checks here, somehow
+ // https://developer.mozilla.org/en-US/docs/Web/API/ValidityState
+ // const hasValidity = (type !== TEXTFIELD_TYPE_NUMBER && e.target.validity.valid) || type === TEXTFIELD_TYPE_NUMBER ;
+ if ((required && (value === '' || value === null)) || value === initialValue) {
+ setHasChanged(false);
+ } else {
+ // show submit button
+ resetStates();
+ setHasChanged(true);
+ setFieldValueForSubmit(value);
+ }
+ }, [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 = ({ fieldName: changedFieldName, value: changedValue }: UpdateArgs) => {
+ if (onChange) {
+ onChange({ fieldName: changedFieldName, value: changedValue });
+ }
+ };
+
+ // 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 = ({ value: changedValue }: UpdateArgs) => {
+ if (onChange && required && changedValue === '') {
+ onChange({ fieldName, value: initialValue });
+ }
+ };
+
+ // how to get current value of input
+ const handleSubmit = async () => {
+ if ((required && fieldValueForSubmit !== '') || fieldValueForSubmit !== initialValue) {
+ setFieldStatus(createInputStatus(STATUS_PROCESSING));
+
+ // setSubmitStatus('validating');
+
+ await postConfigUpdateToAPI({
+ apiPath,
+ data: { value: fieldValueForSubmit },
+ onSuccess: () => {
+ setFieldInConfigState({ fieldName, value: fieldValueForSubmit, path: configPath });
+ setFieldStatus(createInputStatus(STATUS_SUCCESS));
+ // setSubmitStatus('success');
+ },
+ onError: (message: string) => {
+ setFieldStatus(createInputStatus(STATUS_ERROR, `There was an error: ${message}`));
+
+ // setSubmitStatus('error');
+ // setSubmitStatusMessage(`There was an error: ${message}`);
+ },
+ });
+ resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
+
+ // if an extra onSubmit handler was sent in as a prop, let's run that too.
+ if (onSubmit) {
+ onSubmit();
+ }
+ }
+ }
+
+ return (
+
+
+
+ { hasChanged ? : null }
+
+ );
+}
+
+TextFieldWithSubmit.defaultProps = {
+ configPath: '',
+ initialValue: '',
+};
diff --git a/web/pages/components/config/form-textfield.tsx b/web/pages/components/config/form-textfield.tsx
index 95c82cae0..cdb6e6ca1 100644
--- a/web/pages/components/config/form-textfield.tsx
+++ b/web/pages/components/config/form-textfield.tsx
@@ -1,94 +1,57 @@
-import React, { useEffect, useState, useContext } from 'react';
-import { Button, Input, InputNumber } from 'antd';
-import { FormItemProps } from 'antd/es/form';
-
-import { RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
-
+import React from 'react';
+import { Input, InputNumber } from 'antd';
import { FieldUpdaterFunc } from '../../../types/config-section';
-import { ServerStatusContext } from '../../../utils/server-status-context';
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';
-export const TEXTFIELD_TYPE_TEXTAREA = 'textarea';
+export const TEXTFIELD_TYPE_NUMBER = 'numeric'; // InputNumber
+export const TEXTFIELD_TYPE_TEXTAREA = 'textarea'; // Input.TextArea
export const TEXTFIELD_TYPE_URL = 'url';
-interface TextFieldProps {
- apiPath: string;
+export interface TextFieldProps {
fieldName: string;
+
+ onSubmit?: () => void;
+ onPressEnter?: () => void;
- configPath?: string;
+ className?: string;
disabled?: boolean;
- initialValue?: string;
label?: string;
maxLength?: number;
placeholder?: string;
required?: boolean;
+ status?: StatusState;
tip?: string;
type?: string;
value?: string | number;
- onSubmit?: () => void;
- onBlur?: () => void;
+ onBlur?: FieldUpdaterFunc;
onChange?: FieldUpdaterFunc;
}
export default function TextField(props: TextFieldProps) {
- const [submitStatus, setSubmitStatus] = useState('');
- const [submitStatusMessage, setSubmitStatusMessage] = useState('');
- const [hasChanged, setHasChanged] = useState(false);
- const [fieldValueForSubmit, setFieldValueForSubmit] = useState('');
-
- const serverStatusData = useContext(ServerStatusContext);
- const { setFieldInConfigState } = serverStatusData || {};
-
- let resetTimer = null;
-
const {
- apiPath,
- configPath = '',
- disabled = false,
+ className,
+ disabled,
fieldName,
- initialValue,
label,
maxLength,
onBlur,
onChange,
- onSubmit,
+ onPressEnter,
placeholder,
required,
+ status,
tip,
type,
value,
} = props;
- // Clear out any validation states and messaging
- const resetStates = () => {
- setSubmitStatus('');
- setHasChanged(false);
- clearTimeout(resetTimer);
- resetTimer = null;
- };
-
- useEffect(() => {
- // TODO: Add native validity checks here, somehow
- // https://developer.mozilla.org/en-US/docs/Web/API/ValidityState
- // const hasValidity = (type !== TEXTFIELD_TYPE_NUMBER && e.target.validity.valid) || type === TEXTFIELD_TYPE_NUMBER ;
- if ((required && (value === '' || value === null)) || value === initialValue) {
- setHasChanged(false);
- } else {
- // show submit button
- resetStates();
- setHasChanged(true);
- setFieldValueForSubmit(value);
- }
- }, [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;
-
// if an extra onChange handler was sent in as a prop, let's run that too.
if (onChange) {
onChange({ fieldName, value: val });
@@ -96,45 +59,19 @@ export default function TextField(props: TextFieldProps) {
};
// 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 => {
- if (!onChange) {
- return;
- }
+ const handleBlur = (e: any) => {
const val = e.target.value;
- if (required && val === '') {
- onChange({ fieldName, value: initialValue });
- }
- // if an extra onBlur handler was sent in as a prop, let's run that too.
if (onBlur) {
- onBlur();
+ onBlur({ value: val });
}
};
- // how to get current value of input
- const handleSubmit = async () => {
- if ((required && fieldValueForSubmit !== '') || fieldValueForSubmit !== initialValue) {
- setSubmitStatus('validating');
-
- await postConfigUpdateToAPI({
- apiPath,
- data: { value: fieldValueForSubmit },
- onSuccess: () => {
- setFieldInConfigState({ fieldName, value: fieldValueForSubmit, path: configPath });
- setSubmitStatus('success');
- },
- onError: (message: string) => {
- setSubmitStatus('error');
- setSubmitStatusMessage(`There was an error: ${message}`);
- },
- });
- resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
-
- // if an extra onSubmit handler was sent in as a prop, let's run that too.
- if (onSubmit) {
- onSubmit();
- }
+ 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;
@@ -157,7 +94,7 @@ export default function TextField(props: TextFieldProps) {
max: (10**maxLength) - 1,
onKeyDown: (e: React.KeyboardEvent) => {
if (e.target.value.length > maxLength - 1 )
- e.preventDefault()
+ e.preventDefault();
return false;
}
};
@@ -169,46 +106,50 @@ export default function TextField(props: TextFieldProps) {
const fieldId = `field-${fieldName}`;
- return (
+ const { icon: statusIcon, message: statusMessage } = status || {};
+
+ return (
{ required ?
* : null }
- {submitStatus}
- {submitStatusMessage}
-
- { hasChanged ?
: null }
-
+ { status ? statusMessage : null }
+ { status ? statusIcon : null }
);
}
TextField.defaultProps = {
- configPath: '',
+ className: '',
+ // configPath: '',
disabled: false,
- initialValue: '',
+ // initialValue: '',
label: '',
maxLength: null,
+
placeholder: '',
required: false,
+ status: null,
tip: '',
type: TEXTFIELD_TYPE_TEXT,
value: '',
onSubmit: () => {},
onBlur: () => {},
onChange: () => {},
+ onPressEnter: () => {},
};
diff --git a/web/pages/components/config/form-toggleswitch.tsx b/web/pages/components/config/form-toggleswitch-with-submit.tsx
similarity index 100%
rename from web/pages/components/config/form-toggleswitch.tsx
rename to web/pages/components/config/form-toggleswitch-with-submit.tsx
diff --git a/web/pages/index.tsx b/web/pages/index.tsx
index 13adcd505..9df1805e2 100644
--- a/web/pages/index.tsx
+++ b/web/pages/index.tsx
@@ -15,8 +15,8 @@ import { ServerStatusContext } from "../utils/server-status-context";
import StatisticItem from "./components/statistic"
import LogTable from "./components/log-table";
import Offline from './offline-notice';
-import TextField from './components/config/form-textfield';
-import { API_STREAM_TITLE, postConfigUpdateToAPI, TEXTFIELD_PROPS_STREAM_TITLE } from './components/config/constants';
+import TextFieldWithSubmit from './components/config/form-textfield-with-submit';
+import { TEXTFIELD_PROPS_STREAM_TITLE } from './components/config/constants';
import {
LOGS_WARN,
@@ -158,7 +158,7 @@ export default function Home() {
- ,
+ message: 'Success!',
+ },
+ [STATUS_ERROR]: {
+ icon: ,
+ message: 'An error occurred.',
+ },
+ [STATUS_INVALID]: {
+ icon: ,
+ message: 'An error occurred.',
+ },
+ [STATUS_PROCESSING]: {
+ icon: ,
+ message: '',
+ },
+ [STATUS_WARNING]: {
+ icon: ,
+ message: '',
+ },
+};
+
+// Don't like any of the default messages in INPUT_STATES? Create a state with custom message by providing an icon style with your message.
+export function createInputStatus(type: InputStatusTypes, message?: string): StatusState {
+ if (!type || !INPUT_STATES[type]) {
+ return null;
+ }
+ if (message === null) {
+ return INPUT_STATES[type];
+ }
+ return {
+ icon: INPUT_STATES[type].icon,
+ message,
+ };
+}