Browse Source
- update constants - add instanceUrl field to public details; if empty, then turn off yp.enabled. - edit YP/Directory settings; hide if instanceUrl is empty - update toggleswitch logicpull/1886/head
6 changed files with 393 additions and 105 deletions
@ -0,0 +1,36 @@ |
|||||||
|
# Config |
||||||
|
|
||||||
|
|
||||||
|
TODO: explain how to use <Form> and how the custom `form-xxxx` components work together. |
||||||
|
|
||||||
|
|
||||||
|
## Misc notes |
||||||
|
- `instanceDetails` needs to be filled out before `yp.enabled` can be turned on. |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Config data structure (with default values) |
||||||
|
``` |
||||||
|
{ |
||||||
|
streamKey: '', |
||||||
|
instanceDetails: { |
||||||
|
tags: [], |
||||||
|
nsfw: false, |
||||||
|
}, |
||||||
|
yp: { |
||||||
|
enabled: false, |
||||||
|
instance: '', |
||||||
|
}, |
||||||
|
videoSettings: { |
||||||
|
videoQualityVariants: [ |
||||||
|
{ |
||||||
|
audioPassthrough: false, |
||||||
|
videoPassthrough: false, |
||||||
|
videoBitrate: 0, |
||||||
|
audioBitrate: 0, |
||||||
|
framerate: 0, |
||||||
|
}, |
||||||
|
], |
||||||
|
} |
||||||
|
}; |
||||||
|
``` |
||||||
@ -0,0 +1,60 @@ |
|||||||
|
// rename to "directory"
|
||||||
|
import React, { useContext, useEffect } from 'react'; |
||||||
|
import { Typography, Form } from 'antd'; |
||||||
|
|
||||||
|
import ToggleSwitch from './form-toggleswitch'; |
||||||
|
|
||||||
|
import { ServerStatusContext } from '../../../utils/server-status-context'; |
||||||
|
|
||||||
|
const { Title } = Typography; |
||||||
|
|
||||||
|
export default function EditYPDetails() { |
||||||
|
const [form] = Form.useForm(); |
||||||
|
|
||||||
|
const serverStatusData = useContext(ServerStatusContext); |
||||||
|
const { serverConfig } = serverStatusData || {}; |
||||||
|
|
||||||
|
const { yp, instanceDetails } = serverConfig; |
||||||
|
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, |
||||||
|
}; |
||||||
|
|
||||||
|
// TODO: DISABLE THIS SECTION UNTIL instanceURL is populated
|
||||||
|
return ( |
||||||
|
<div className="config-directory-details-form"> |
||||||
|
<Title level={3}>Owncast Directory Settings</Title> |
||||||
|
|
||||||
|
<p>Would you like to appear in the <a href="https://directory.owncast.online" target="_blank" rel="noreferrer"><strong>Owncast Directory</strong></a>?</p> |
||||||
|
|
||||||
|
<p><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> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,112 @@ |
|||||||
|
import React, { useState, useContext } from 'react'; |
||||||
|
import { Form, Switch, Tooltip } from 'antd'; |
||||||
|
import { FormItemProps } from 'antd/es/form'; |
||||||
|
|
||||||
|
import { InfoCircleOutlined } from '@ant-design/icons'; |
||||||
|
|
||||||
|
import { TEXTFIELD_DEFAULTS, RESET_TIMEOUT, SUCCESS_STATES } from './constants'; |
||||||
|
|
||||||
|
import { ToggleSwitchProps } from '../../../types/config-section'; |
||||||
|
import { fetchData, SERVER_CONFIG_UPDATE_URL } from '../../../utils/apis'; |
||||||
|
import { ServerStatusContext } from '../../../utils/server-status-context'; |
||||||
|
|
||||||
|
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']>(''); |
||||||
|
const [submitStatusMessage, setSubmitStatusMessage] = useState(''); |
||||||
|
|
||||||
|
let resetTimer = null; |
||||||
|
|
||||||
|
const serverStatusData = useContext(ServerStatusContext); |
||||||
|
const { setConfigField } = serverStatusData || {}; |
||||||
|
|
||||||
|
const { |
||||||
|
fieldName, |
||||||
|
initialValues = {}, |
||||||
|
configPath = '', |
||||||
|
disabled = false, |
||||||
|
} = 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 postUpdateToAPI = async (postValue: any) => { |
||||||
|
setSubmitStatus('validating'); |
||||||
|
const result = await fetchData(`${SERVER_CONFIG_UPDATE_URL}${apiPath}`, { |
||||||
|
data: { value: postValue }, |
||||||
|
method: 'POST', |
||||||
|
auth: true, |
||||||
|
}); |
||||||
|
|
||||||
|
if (result.success) { |
||||||
|
setConfigField({ fieldName, value: postValue, path: configPath }); |
||||||
|
setSubmitStatus('success'); |
||||||
|
} else { |
||||||
|
setSubmitStatus('error'); |
||||||
|
setSubmitStatusMessage(`There was an error: ${result.message}`); |
||||||
|
} |
||||||
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleChange = checked => { |
||||||
|
postUpdateToAPI(checked); |
||||||
|
} |
||||||
|
|
||||||
|
const { |
||||||
|
icon: newStatusIcon = null, |
||||||
|
message: newStatusMessage = '', |
||||||
|
} = SUCCESS_STATES[submitStatus] || {}; |
||||||
|
|
||||||
|
const tipComponent = tip ? ( |
||||||
|
<span className="info"> |
||||||
|
<Tooltip title={tip}> |
||||||
|
<InfoCircleOutlined /> |
||||||
|
</Tooltip> |
||||||
|
</span> |
||||||
|
) : null; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="toggleswitch-container"> |
||||||
|
<div className="toggleswitch"> |
||||||
|
<Form.Item |
||||||
|
name={fieldName} |
||||||
|
validateStatus={submitStatus} |
||||||
|
> |
||||||
|
<Switch |
||||||
|
className="switch" |
||||||
|
loading={submitStatus === 'validating'} |
||||||
|
onChange={handleChange} |
||||||
|
checked={initialValue} |
||||||
|
checkedChildren="ON" |
||||||
|
unCheckedChildren="OFF" |
||||||
|
disabled={disabled} |
||||||
|
/> |
||||||
|
</Form.Item> |
||||||
|
|
||||||
|
<span className="label">{label}</span> |
||||||
|
{tipComponent} |
||||||
|
</div> |
||||||
|
<div className={`status-message ${submitStatus || ''}`}> |
||||||
|
{newStatusIcon} {newStatusMessage} {submitStatusMessage} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
Loading…
Reference in new issue