0

refactor forms to not use ant Form component; split server and instance details forms into their own components

This commit is contained in:
gingervitis 2021-01-28 03:08:57 -08:00
parent 04926d53e1
commit 5f70c77458
13 changed files with 403 additions and 392 deletions

View File

@ -24,9 +24,25 @@ export const SUCCESS_STATES = {
};
// CONFIG API ENDPOINTS
export const API_VIDEO_VARIANTS = '/video/streamoutputvariants';
export const API_VIDEO_SEGMENTS = '/video/streamlatencylevel';
export const API_CUSTOM_CONTENT = '/pagecontent';
export const API_FFMPEG = '/ffmpegpath';
export const API_INSTANCE_URL = '/serverurl';
export const API_LOGO = '/logo';
export const API_NSFW_SWITCH = '/nsfw';
export const API_RTMP_PORT = '/rtmpserverport';
export const API_S3_INFO = '/s3';
export const API_SERVER_SUMMARY = '/serversummary';
export const API_SERVER_TITLE = '/servertitle';
export const API_SOCIAL_HANDLES = '/socialhandles';
export const API_STREAM_KEY = '/key';
export const API_STREAM_TITLE = '/streamtitle';
export const API_TAGS = '/tags';
export const API_USERNAME = '/name';
export const API_VIDEO_SEGMENTS = '/video/streamlatencylevel';
export const API_VIDEO_VARIANTS = '/video/streamoutputvariants';
export const API_WEB_PORT = '/webserverport';
export const API_YP_SWITCH = '/directoryenabled';
export async function postConfigUpdateToAPI(args: ApiPostArgs) {
const {
@ -47,176 +63,119 @@ export async function postConfigUpdateToAPI(args: ApiPostArgs) {
}
}
// Creating this so that it'll be easier to change values in one place, rather than looking for places to change it in a sea of JSX.
// key is the input's `fieldName`
// the structure of this mirrors config data
export const TEXTFIELD_DEFAULTS = {
instanceDetails: {
// Some default props to help build out a TextField
export const TEXTFIELD_PROPS_USERNAME = {
apiPath: API_USERNAME,
configPath: 'instanceDetails',
maxLength: TEXT_MAXLENGTH,
placeholder: 'username',
label: 'User name',
tip: 'Who are you? What name do you want viewers to know you?',
};
export const TEXTFIELD_PROPS_SERVER_TITLE = {
apiPath: API_SERVER_TITLE,
maxLength: TEXT_MAXLENGTH,
placeholder: 'Owncast site name', // like "gothland"
label: 'Server Name',
tip: 'The name of your Owncast server',
};
export const TEXTFIELD_PROPS_STREAM_TITLE = {
apiPath: API_STREAM_TITLE,
maxLength: TEXT_MAXLENGTH,
placeholder: 'Doing cool things...',
label: 'Stream Title',
tip: 'What is your stream about today?',
};
export const TEXTFIELD_PROPS_SERVER_SUMMARY = {
apiPath: API_SERVER_SUMMARY,
maxLength: 500,
placeholder: 'Summary',
label: 'Summary',
tip: 'A brief blurb about what your stream is about.',
};
export const TEXTFIELD_PROPS_LOGO = {
apiPath: API_LOGO,
maxLength: 255,
placeholder: '/img/mylogo.png',
label: 'Logo',
tip: 'Path to your logo from website root. We recommend that you use a square image that is at least 256x256. (upload functionality coming soon)',
};
export const TEXTFIELD_PROPS_STREAM_KEY = {
apiPath: API_STREAM_KEY,
configPath: '',
maxLength: TEXT_MAXLENGTH,
placeholder: 'abc123',
label: 'Stream Key',
tip: 'Secret stream key',
required: true,
};
export const TEXTFIELD_PROPS_FFMPEG = {
apiPath: API_FFMPEG,
configPath: '',
maxLength: TEXT_MAXLENGTH,
placeholder: '/usr/local/bin/ffmpeg',
label: 'FFmpeg Path',
tip: 'Absolute file path of the FFMPEG application on your server',
required: true,
};
export const TEXTFIELD_PROPS_WEB_PORT = {
apiPath: API_WEB_PORT,
configPath: '',
maxLength: 6,
placeholder: '8080',
label: 'Owncast Server port',
tip: 'What port are you serving Owncast from? Default is :8080',
required: true,
};
export const TEXTFIELD_PROPS_RTMP_PORT = {
apiPath: API_RTMP_PORT,
configPath: '',
maxLength: 6,
placeholder: '1935',
label: 'RTMP port',
tip: 'What port are you receiving RTMP?',
required: true,
};
export const TEXTFIELD_PROPS_INSTANCE_URL = {
apiPath: API_INSTANCE_URL,
configPath: 'yp',
maxLength: 255,
placeholder: 'https://owncast.mysite.com',
label: 'Instance URL',
tip: 'Please provide the url to your Owncast site if you enable this Directory setting.',
};
// MISC FIELDS
export const FIELD_PROPS_TAGS = {
apiPath: API_TAGS,
configPath: 'instanceDetails',
maxLength: 24,
placeholder: 'Add a new tag',
required: true,
label: '',
tip: '',
};
// user name
name: {
apiPath: '/name',
defaultValue: '',
maxLength: TEXT_MAXLENGTH,
placeholder: 'username',
label: 'User name',
tip: 'Who are you? What name do you want viewers to know you?',
},
export const FIELD_PROPS_CUSTOM_CONTENT = {
apiPath: API_CUSTOM_CONTENT,
configPath: 'instanceDetails',
placeholder: '',
label: 'Extra page content',
tip: 'Custom markup about yourself',
};
export const FIELD_PROPS_NSFW = {
apiPath: API_NSFW_SWITCH,
configPath: 'instanceDetails',
label: 'NSFW?',
tip: "Turn this ON if you plan to steam explicit or adult content. You may want to respectfully set this flag so that unexpecting eyes won't accidentally see it from the Directory.",
};
// like "goth land"
title: {
apiPath: '/servertitle',
defaultValue: '',
maxLength: TEXT_MAXLENGTH,
placeholder: 'Owncast site name',
label: 'Server Name',
tip: 'The name of your Owncast server',
},
streamTitle: {
apiPath: '/streamtitle',
defaultValue: '',
maxLength: TEXT_MAXLENGTH,
placeholder: 'Doing cool things...',
label: 'Stream Title',
tip: 'What is your stream about today?',
},
summary: {
apiPath: '/serversummary',
defaultValue: '',
maxLength: 500,
placeholder: 'Summary',
label: 'Summary',
tip: 'A brief blurb about what your stream is about.',
},
logo: {
apiPath: '/logo',
defaultValue: '',
maxLength: 255,
placeholder: '/img/mylogo.png',
label: 'Logo',
tip: 'Path to your logo from website root. We recommend that you use a square image that is at least 256x256. (upload functionality coming soon)',
},
extraPageContent: {
apiPath: '/pagecontent',
placeholder: '',
label: 'Extra page content',
tip: 'Custom markup about yourself',
},
nsfw: {
apiPath: '/nsfw',
placeholder: '',
label: 'NSFW?',
tip: "Turn this ON if you plan to steam explicit or adult content. You may want to respectfully set this flag so that unexpecting eyes won't accidentally see it from the Directory.",
},
tags: {
apiPath: '/tags',
defaultValue: '',
maxLength: 24,
placeholder: 'Add a new tag',
label: '',
tip: '',
},
},
streamKey: {
apiPath: '/key',
defaultValue: 'abc123',
maxLength: TEXT_MAXLENGTH,
placeholder: 'abc123',
label: 'Stream Key',
tip: 'Secret stream key',
required: true,
},
ffmpegPath: {
apiPath: '/ffmpegpath',
defaultValue: '',
maxLength: TEXT_MAXLENGTH,
placeholder: '/usr/local/bin/ffmpeg',
label: 'FFmpeg Path',
tip: 'Absolute file path of the FFMPEG application on your server',
required: true,
},
webServerPort: {
apiPath: '/webserverport',
defaultValue: '8080',
maxLength: 6,
placeholder: '8080',
label: 'Owncast Server port',
tip: 'What port are you serving Owncast from? Default is :8080',
required: true,
},
rtmpServerPort: {
apiPath: '/rtmpserverport',
defaultValue: '1935',
maxLength: 6,
placeholder: '1935',
label: 'RTMP port',
tip: 'What port are you receiving RTMP?',
required: true,
},
s3: {
// tbd
},
// YP options
yp: {
instanceUrl: {
apiPath: '/serverurl',
defaultValue: 'https://owncast.mysite.com',
maxLength: 255,
placeholder: 'url',
label: 'Instance URL',
tip: 'Please provide the url to your Owncast site if you enable this Directory setting.',
},
enabled: {
apiPath: '/directoryenabled',
defaultValue: false,
maxLength: 0,
placeholder: '',
label: 'Display in the Owncast Directory?',
tip: 'Turn this ON if you want to show up in the Owncast directory at https://directory.owncast.online.',
}
},
videoSettings: {
// number slider
numberOfPlaylistItems: {
apiPath: '/webserverport', // tbd
defaultValue: 4,
maxLength: 3,
placeholder: '4',
label: 'Segment Length',
tip: '',
required: true,
minValue: 1,
maxValue: 10,
},
// number slider
segmentLengthSeconds: {
apiPath: '/webserverport', // tbd
defaultValue: 5,
maxLength: 3,
placeholder: '5',
label: 'Number of segments',
tip: '',
required: true,
minValue: 1,
maxValue: 10,
},
}
}
export const FIELD_PROPS_YP = {
apiPath: API_YP_SWITCH,
configPath: 'yp',
label: 'Display in the Owncast Directory?',
tip: 'Turn this ON if you want to show up in the Owncast directory at https://directory.owncast.online.',
};
export const ENCODER_PRESETS = [
'fast',

View File

@ -1,15 +1,16 @@
// Note: references to "yp" in the app are likely related to Owncast Directory
import React, { useContext, useEffect } from 'react';
import { Typography, Form } from 'antd';
import React, { useState, useContext, useEffect } from 'react';
import { Typography } from 'antd';
import ToggleSwitch from './form-toggleswitch';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { FIELD_PROPS_NSFW, FIELD_PROPS_YP } from './constants';
const { Title } = Typography;
export default function EditYPDetails() {
const [form] = Form.useForm();
const [formDataValues, setFormDataValues] = useState(null);
const serverStatusData = useContext(ServerStatusContext);
const { serverConfig } = serverStatusData || {};
@ -18,23 +19,19 @@ export default function EditYPDetails() {
const { nsfw } = instanceDetails;
const { enabled, instanceUrl } = yp;
const initialValues = {
...yp,
enabled,
nsfw,
};
const hasInstanceUrl = instanceUrl !== '';
useEffect(() => {
form.setFieldsValue(initialValues);
}, [yp]);
const extraProps = {
initialValues,
disabled: !hasInstanceUrl,
};
setFormDataValues({
...yp,
enabled,
nsfw,
});
}, [yp, instanceDetails]);
const hasInstanceUrl = instanceUrl !== '';
if (!formDataValues) {
return null;
}
return (
<div className="config-directory-details-form">
<Title level={3}>Owncast Directory Settings</Title>
@ -44,13 +41,18 @@ export default function EditYPDetails() {
<p style={{ backgroundColor: 'black', fontSize: '.75rem', padding: '5px' }}><em>NOTE: You will need to have a URL specified in the <code>Instance URL</code> field to be able to use this.</em></p>
<div className="config-yp-container">
<Form
form={form}
layout="vertical"
>
<ToggleSwitch fieldName="enabled" configPath="yp" {...extraProps}/>
<ToggleSwitch fieldName="nsfw" configPath="instanceDetails" {...extraProps} />
</Form>
<ToggleSwitch
fieldName="enabled"
{...FIELD_PROPS_YP}
checked={formDataValues.enabled}
disabled={!hasInstanceUrl}
/>
<ToggleSwitch
fieldName="nsfw"
{...FIELD_PROPS_NSFW}
checked={formDataValues.nsfw}
disabled={!hasInstanceUrl}
/>
</div>
</div>
);

View File

@ -0,0 +1,100 @@
import React, { useState, useContext, useEffect } from 'react';
import TextField, { TEXTFIELD_TYPE_TEXTAREA, TEXTFIELD_TYPE_URL } from './form-textfield';
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';
import configStyles from '../../../styles/config-pages.module.scss';
export default function EditInstanceDetails() {
const [formDataValues, setFormDataValues] = useState(null);
const serverStatusData = useContext(ServerStatusContext);
const { serverConfig } = serverStatusData || {};
const { instanceDetails, yp } = serverConfig;
useEffect(() => {
setFormDataValues({
...instanceDetails,
...yp,
});
}, [instanceDetails, yp]);
if (!formDataValues) {
return null;
}
// if instanceUrl is empty, we should also turn OFF the `enabled` field of directory.
const handleSubmitInstanceUrl = () => {
if (formDataValues.instanceUrl === '') {
if (yp.enabled === true) {
postConfigUpdateToAPI({
apiPath: API_YP_SWITCH,
data: { value: false },
});
}
}
}
const handleFieldChange = (fieldName: string, value: string) => {
setFormDataValues({
...formDataValues,
[fieldName]: value,
});
}
return (
<div className={configStyles.publicDetailsContainer}>
<div className={configStyles.textFieldsSection}>
<TextField
fieldName="instanceUrl"
{...TEXTFIELD_PROPS_INSTANCE_URL}
value={formDataValues.instanceUrl}
initialValue={yp.instanceUrl}
type={TEXTFIELD_TYPE_URL}
onChange={handleFieldChange}
onSubmit={handleSubmitInstanceUrl}
/>
<TextField
fieldName="title"
{...TEXTFIELD_PROPS_SERVER_TITLE}
value={formDataValues.title}
initialValue={instanceDetails.title}
onChange={handleFieldChange}
/>
<TextField
fieldName="streamTitle"
{...TEXTFIELD_PROPS_STREAM_TITLE}
value={formDataValues.streamTitle}
initialValue={instanceDetails.streamTitle}
onChange={handleFieldChange}
/>
<TextField
fieldName="name"
{...TEXTFIELD_PROPS_USERNAME}
value={formDataValues.name}
initialValue={instanceDetails.name}
onChange={handleFieldChange}
/>
<TextField
fieldName="summary"
{...TEXTFIELD_PROPS_SERVER_SUMMARY}
type={TEXTFIELD_TYPE_TEXTAREA}
value={formDataValues.summary}
initialValue={instanceDetails.summary}
onChange={handleFieldChange}
/>
<TextField
fieldName="logo"
{...TEXTFIELD_PROPS_LOGO}
value={formDataValues.logo}
initialValue={instanceDetails.logo}
onChange={handleFieldChange}
/>
</div>
</div>
);
}

View File

@ -0,0 +1,73 @@
import React, { useState, useContext, useEffect } from 'react';
import TextField, { TEXTFIELD_TYPE_NUMBER, TEXTFIELD_TYPE_PASSWORD } from './form-textfield';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { TEXTFIELD_PROPS_FFMPEG, TEXTFIELD_PROPS_RTMP_PORT, TEXTFIELD_PROPS_STREAM_KEY, TEXTFIELD_PROPS_WEB_PORT, } from './constants';
import configStyles from '../../../styles/config-pages.module.scss';
export default function EditInstanceDetails() {
const [formDataValues, setFormDataValues] = useState(null);
const serverStatusData = useContext(ServerStatusContext);
const { serverConfig } = serverStatusData || {};
const { streamKey, ffmpegPath, rtmpServerPort, webServerPort } = serverConfig;
useEffect(() => {
setFormDataValues({
streamKey, ffmpegPath, rtmpServerPort, webServerPort
});
}, [serverConfig]);
if (!formDataValues) {
return null;
}
const handleFieldChange = (fieldName: string, value: string) => {
setFormDataValues({
...formDataValues,
[fieldName]: value,
});
}
return (
<div className={configStyles.publicDetailsContainer}>
<div className={configStyles.textFieldsSection}>
<TextField
fieldName="streamKey"
{...TEXTFIELD_PROPS_STREAM_KEY}
value={formDataValues.streamKey}
initialValue={streamKey}
type={TEXTFIELD_TYPE_PASSWORD}
onChange={handleFieldChange}
/>
<TextField
fieldName="ffmpegPath"
{...TEXTFIELD_PROPS_FFMPEG}
value={formDataValues.ffmpegPath}
initialValue={ffmpegPath}
onChange={handleFieldChange}
/>
<TextField
fieldName="webServerPort"
{...TEXTFIELD_PROPS_WEB_PORT}
value={formDataValues.webServerPort}
initialValue={webServerPort}
type={TEXTFIELD_TYPE_NUMBER}
onChange={handleFieldChange}
/>
<TextField
fieldName="rtmpServerPort"
{...TEXTFIELD_PROPS_RTMP_PORT}
value={formDataValues.rtmpServerPort}
initialValue={rtmpServerPort}
type={TEXTFIELD_TYPE_NUMBER}
onChange={handleFieldChange}
/>
</div>
</div>
);
}

View File

@ -3,7 +3,7 @@ import React, { useContext, useState, useEffect } from 'react';
import { Typography, Tag, Input } from 'antd';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { TEXTFIELD_DEFAULTS, RESET_TIMEOUT, SUCCESS_STATES, postConfigUpdateToAPI } from './constants';
import { FIELD_PROPS_TAGS, RESET_TIMEOUT, SUCCESS_STATES, postConfigUpdateToAPI } from './constants';
const { Title } = Typography;
@ -17,14 +17,12 @@ export default function EditInstanceTags() {
const { instanceDetails } = serverConfig;
const { tags = [] } = instanceDetails;
const configPath = 'instanceDetails';
const {
apiPath,
maxLength,
placeholder,
} = TEXTFIELD_DEFAULTS[configPath].tags || {};
configPath,
} = FIELD_PROPS_TAGS;
let resetTimer = null;

View File

@ -1,9 +1,9 @@
import React, { useState, useContext } from 'react';
import { Button, Form, Input, InputNumber } from 'antd';
import { Button, Input, InputNumber } from 'antd';
import { FormItemProps } from 'antd/es/form';
import { TEXTFIELD_DEFAULTS, TEXT_MAXLENGTH, RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
import { RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
import { TextFieldProps } from '../../../types/config-section';
import { ServerStatusContext } from '../../../utils/server-status-context';
@ -22,37 +22,29 @@ export default function TextField(props: TextFieldProps) {
const [hasChanged, setHasChanged] = useState(false);
const [fieldValueForSubmit, setFieldValueForSubmit] = useState('');
let resetTimer = null;
const serverStatusData = useContext(ServerStatusContext);
const { setFieldInConfigState } = serverStatusData || {};
let resetTimer = null;
const {
apiPath,
configPath = '',
disabled = false,
fieldName,
handleResetValue = () => {},
initialValues = {},
placeholder,
onSubmit,
initialValue,
label,
maxLength,
onBlur,
onChange,
onSubmit,
placeholder,
required,
tip,
type,
value,
} = props;
// Keep track of what the initial value is
// Note: we're not using `initialValue` as a prop, because we expect this component to be controlled by a parent Ant <Form> which is doing a form.setFieldsValue() upstream.
const initialValue = initialValues[fieldName] || '';
// Get other static info we know about this field.
const defaultDetails = TEXTFIELD_DEFAULTS[configPath] || TEXTFIELD_DEFAULTS;
const {
apiPath = '',
maxLength = TEXT_MAXLENGTH,
label = '',
tip = '',
required = false,
} = defaultDetails[fieldName] || {};
// Clear out any validation states and messaging
const resetStates = () => {
@ -79,17 +71,19 @@ export default function TextField(props: TextFieldProps) {
}
// if an extra onChange handler was sent in as a prop, let's run that too.
if (onChange) {
onChange();
onChange(fieldName, val);
}
};
// if you blur a required field with an empty value, restore its original value
// 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 val = e.target.value;
if (required && val === '') {
handleResetValue(fieldName);
onChange(fieldName, initialValue);
}
// if an extra onBlur handler was sent in as a prop, let's run that too.
if (onBlur) {
onBlur();
@ -153,30 +147,29 @@ export default function TextField(props: TextFieldProps) {
};
}
const fieldId = `field-${fieldName}`;
return (
<div className={`textfield-container type-${type}`}>
<div className="textfield-label">{label}</div>
{ required ? <span className="required-label">*</span> : null }
<label htmlFor={fieldId} className="textfield-label">{label}</label>
<div className="textfield">
<Form.Item
name={fieldName}
hasFeedback
validateStatus={submitStatus}
help={submitStatusMessage}
required={required}
>
<Field
className={`field field-${fieldName}`}
allowClear
placeholder={placeholder}
maxLength={maxLength}
onChange={handleChange}
onBlur={handleBlur}
disabled={disabled}
{...fieldProps}
/>
</Form.Item>
id={fieldId}
className={`field ${fieldId}`}
{...fieldProps}
allowClear
placeholder={placeholder}
maxLength={maxLength}
onChange={handleChange}
onBlur={handleBlur}
disabled={disabled}
value={value}
/>
</div>
<InfoTip tip={tip} />
{submitStatus}
{submitStatusMessage}
{ hasChanged ? <Button type="primary" size="small" className="submit-button" onClick={handleSubmit}>Update</Button> : null }

View File

@ -1,18 +1,13 @@
import React, { useState, useContext } from 'react';
import { Form, Switch } from 'antd';
import { Switch } from 'antd';
import { FormItemProps } from 'antd/es/form';
import { TEXTFIELD_DEFAULTS, RESET_TIMEOUT, SUCCESS_STATES, postConfigUpdateToAPI } from './constants';
import { RESET_TIMEOUT, SUCCESS_STATES, postConfigUpdateToAPI } from './constants';
import { ToggleSwitchProps } from '../../../types/config-section';
import { ServerStatusContext } from '../../../utils/server-status-context';
import InfoTip from '../info-tip';
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 default function ToggleSwitch(props: ToggleSwitchProps) {
const [submitStatus, setSubmitStatus] = useState<FormItemProps['validateStatus']>('');
@ -24,35 +19,28 @@ export default function ToggleSwitch(props: ToggleSwitchProps) {
const { setFieldInConfigState } = serverStatusData || {};
const {
fieldName,
initialValues = {},
apiPath,
checked,
configPath = '',
disabled = false,
fieldName,
label,
tip,
} = props;
const initialValue = initialValues[fieldName] || false;
const defaultDetails = TEXTFIELD_DEFAULTS[configPath] || TEXTFIELD_DEFAULTS;
const {
apiPath = '',
label = '',
tip = '',
} = defaultDetails[fieldName] || {};
const resetStates = () => {
setSubmitStatus('');
clearTimeout(resetTimer);
resetTimer = null;
}
const handleChange = async checked => {
const handleChange = async isChecked => {
setSubmitStatus('validating');
await postConfigUpdateToAPI({
apiPath,
data: { value: checked },
data: { value: isChecked },
onSuccess: () => {
setFieldInConfigState({ fieldName, value: checked, path: configPath });
setFieldInConfigState({ fieldName, value: isChecked, path: configPath });
setSubmitStatus('success');
},
onError: (message: string) => {
@ -71,23 +59,17 @@ export default function ToggleSwitch(props: ToggleSwitchProps) {
return (
<div className="toggleswitch-container">
<div className="toggleswitch">
<Form.Item
name={fieldName}
validateStatus={submitStatus}
>
<Switch
className={`switch field-${fieldName}`}
loading={submitStatus === 'validating'}
onChange={handleChange}
checked={initialValue}
checkedChildren="ON"
unCheckedChildren="OFF"
disabled={disabled}
/>
</Form.Item>
<Switch
className={`switch field-${fieldName}`}
loading={submitStatus === 'validating'}
onChange={handleChange}
defaultChecked={checked}
checkedChildren="ON"
unCheckedChildren="OFF"
disabled={disabled}
/>
<span className="label">{label} <InfoTip tip={tip} /></span>
{submitStatus}
</div>
<div className={`status-message ${submitStatus || ''}`}>
{newStatusIcon} {newStatusMessage} {submitStatusMessage}

View File

@ -8,7 +8,7 @@ import { DeleteOutlined } from '@ant-design/icons';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { UpdateArgs, VideoVariant } from '../../../types/config-section';
import VideoVariantForm from './video-variant-form';
import { API_VIDEO_VARIANTS, DEFAULT_VARIANT_STATE, SUCCESS_STATES, RESET_TIMEOUT,postConfigUpdateToAPI } from './constants';
import { API_VIDEO_VARIANTS, DEFAULT_VARIANT_STATE, SUCCESS_STATES, RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
const { Title } = Typography;

View File

@ -5,7 +5,7 @@ import dynamic from 'next/dynamic';
import MarkdownIt from 'markdown-it';
import { ServerStatusContext } from '../utils/server-status-context';
import { TEXTFIELD_DEFAULTS, postConfigUpdateToAPI, RESET_TIMEOUT, SUCCESS_STATES} from './components/config/constants';
import { postConfigUpdateToAPI, RESET_TIMEOUT, SUCCESS_STATES, API_CUSTOM_CONTENT} from './components/config/constants';
import 'react-markdown-editor-lite/lib/index.css';
@ -29,7 +29,6 @@ export default function PageContentEditor() {
const { instanceDetails } = serverConfig;
const { extraPageContent: initialContent } = instanceDetails;
const { apiPath } = TEXTFIELD_DEFAULTS.instanceDetails.extraPageContent;
let resetTimer = null;
@ -54,10 +53,10 @@ export default function PageContentEditor() {
async function handleSave() {
setSubmitStatus('validating');
await postConfigUpdateToAPI({
apiPath,
apiPath: API_CUSTOM_CONTENT,
data: { value: content },
onSuccess: () => {
setFieldInConfigState({ fieldName: 'extraPageContent', value: content, path: apiPath });
setFieldInConfigState({ fieldName: 'extraPageContent', value: content, path: 'instanceDetails' });
setSubmitStatus('success');
},
onError: (message: string) => {

View File

@ -1,76 +1,21 @@
import React, { useContext, useEffect } from 'react';
import { Typography, Form } from 'antd';
import React from 'react';
import { Typography } from 'antd';
import Link from 'next/link';
import TextField, { TEXTFIELD_TYPE_TEXTAREA, TEXTFIELD_TYPE_URL } from './components/config/form-textfield';
import { ServerStatusContext } from '../utils/server-status-context';
import { TEXTFIELD_DEFAULTS, postConfigUpdateToAPI } from './components/config/constants';
import configStyles from '../styles/config-pages.module.scss';
import EditInstanceDetails from './components/config/edit-instance-details';
const { Title } = Typography;
export default function PublicFacingDetails() {
const [form] = Form.useForm();
const serverStatusData = useContext(ServerStatusContext);
const { serverConfig } = serverStatusData || {};
const { instanceDetails, yp } = serverConfig;
const initialValues = {
...instanceDetails,
...yp,
};
useEffect(() => {
form.setFieldsValue(initialValues);
}, [instanceDetails]);
// if instanceUrl is empty, we should also turn OFF the `enabled` field of directory.
const handleSubmitInstanceUrl = () => {
if (form.getFieldValue('instanceUrl') === '') {
if (yp.enabled === true) {
const { apiPath } = TEXTFIELD_DEFAULTS.yp.enabled;
postConfigUpdateToAPI({
apiPath,
data: { value: false },
});
}
}
}
const extraProps = {
initialValues,
configPath: 'instanceDetails',
};
return (
<>
<Title level={2}>Edit your public facing instance details</Title>
<div className={configStyles.publicDetailsContainer}>
<div className={configStyles.textFieldsSection}>
<Form
form={form}
layout="vertical"
>
<TextField
fieldName="instanceUrl"
{...extraProps}
configPath="yp"
type={TEXTFIELD_TYPE_URL}
onSubmit={handleSubmitInstanceUrl}
/>
<TextField fieldName="title" {...extraProps} />
<TextField fieldName="streamTitle" {...extraProps} />
<TextField fieldName="name" {...extraProps} />
<TextField fieldName="summary" type={TEXTFIELD_TYPE_TEXTAREA} {...extraProps} />
<TextField fieldName="logo" {...extraProps} />
</Form>
<EditInstanceDetails />
<Link href="/admin/config-page-content">
<a>Edit your extra page content here.</a>

View File

@ -1,57 +1,17 @@
import React, { useContext, useEffect } from 'react';
import { Typography, Form } from 'antd';
import TextField, { TEXTFIELD_TYPE_NUMBER, TEXTFIELD_TYPE_PASSWORD } from './components/config/form-textfield';
import { ServerStatusContext } from '../utils/server-status-context';
import { TEXTFIELD_DEFAULTS } from './components/config/constants';
import React from 'react';
import { Typography } from 'antd';
import EditServerDetails from './components/config/edit-server-details';
const { Title } = Typography;
export default function ConfigServerDetails() {
const [form] = Form.useForm();
const serverStatusData = useContext(ServerStatusContext);
const { serverConfig } = serverStatusData || {};
const { ffmpegPath, streamKey, webServerPort, rtmpServerPort } = serverConfig;
const initialValues = {
ffmpegPath,
streamKey,
webServerPort,
rtmpServerPort,
};
useEffect(() => {
form.setFieldsValue(initialValues);
}, [serverStatusData]);
const handleResetValue = (fieldName: string) => {
const defaultValue = TEXTFIELD_DEFAULTS[fieldName] && TEXTFIELD_DEFAULTS[fieldName].defaultValue || '';
form.setFieldsValue({ [fieldName]: initialValues[fieldName] || defaultValue });
};
const extraProps = {
handleResetValue,
initialValues,
configPath: '',
};
return (
<div className="config-server-details-form">
<Title level={2}>Edit your Server&apos;s details</Title>
<div className="config-public-details-container">
<Form
form={form}
layout="vertical"
>
<TextField fieldName="streamKey" type={TEXTFIELD_TYPE_PASSWORD} {...extraProps} />
<TextField fieldName="ffmpegPath" {...extraProps} />
<TextField fieldName="webServerPort" type={TEXTFIELD_TYPE_NUMBER} {...extraProps} />
<TextField fieldName="rtmpServerPort" type={TEXTFIELD_TYPE_NUMBER} {...extraProps} />
</Form>
<div className="config-server-details-container">
<EditServerDetails />
</div>
</div>
);

View File

@ -16,7 +16,6 @@ import StatisticItem from "./components/statistic"
import LogTable from "./components/log-table";
import Offline from './offline-notice';
import TextField, { TEXTFIELD_TYPE_TEXTAREA, TEXTFIELD_TYPE_URL } from './components/config/form-textfield';
import { TEXTFIELD_DEFAULTS, postConfigUpdateToAPI } from './components/config/constants';
import {
LOGS_WARN,

View File

@ -88,6 +88,7 @@ const ServerStatusProvider = ({ children }) => {
...config,
[fieldName]: value,
};
console.log({updatedConfig, fieldName, value, path})
setConfig(updatedConfig);
};