diff --git a/web/pages/components/config/defaults.ts b/web/pages/components/config/defaults.ts index 47fe6c8ec..eef2f697a 100644 --- a/web/pages/components/config/defaults.ts +++ b/web/pages/components/config/defaults.ts @@ -9,7 +9,7 @@ export const TEXT_MAXLENGTH = 255; // 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` -//serversummary + export const TEXTFIELD_DEFAULTS = { name: { apiPath: '/name', @@ -17,17 +17,63 @@ export const TEXTFIELD_DEFAULTS = { maxLength: TEXT_MAXLENGTH, placeholder: DEFAULT_NAME, configPath: 'instanceDetails', - label: 'Your name', + label: 'Server name', tip: 'This is your name that shows up on things and stuff.', }, summary: { - apiPath: '/summary', + apiPath: '/serversummary', defaultValue: DEFAULT_NAME, maxLength: TEXT_MAXLENGTH, placeholder: DEFAULT_NAME, configPath: 'instanceDetails', label: 'Summary', tip: 'A brief blurb about what your stream is about.', + }, + title: { + apiPath: '/servertitle', + defaultValue: DEFAULT_NAME, + maxLength: TEXT_MAXLENGTH, + placeholder: DEFAULT_NAME, + configPath: 'instanceDetails', + label: 'Server Title', + tip: 'A brief blurb about what your stream is about.', + }, + streamTitle: { + apiPath: '/streamtitle', + defaultValue: DEFAULT_NAME, + maxLength: TEXT_MAXLENGTH, + placeholder: DEFAULT_NAME, + configPath: 'instanceDetails', + label: 'Stream Title', + tip: 'The name of your stream today.', + }, + + logo: { + apiPath: '/logo', + defaultValue: DEFAULT_NAME, + maxLength: TEXT_MAXLENGTH, + placeholder: DEFAULT_NAME, + configPath: 'instanceDetails', + label: 'Stream Title', + tip: 'A brief blurb about what your stream is about.', + }, + streamKey: { + apiPath: '/key', + defaultValue: DEFAULT_NAME, + maxLength: TEXT_MAXLENGTH, + placeholder: DEFAULT_NAME, + configPath: 'instanceDetails', + label: 'Stream Key', + tip: 'Secret stream key', + }, + + pageContent: { + apiPath: '/pagecontent', + placeholder: '', + configPath: 'instanceDetails', + label: 'Stream Key', + tip: 'Custom markup about yourself', } -} \ No newline at end of file +} + diff --git a/web/pages/components/config/form-textfield.tsx b/web/pages/components/config/form-textfield.tsx index ddf1737a7..6db4f1ec9 100644 --- a/web/pages/components/config/form-textfield.tsx +++ b/web/pages/components/config/form-textfield.tsx @@ -14,10 +14,19 @@ save to local state/context. read vals from there. update vals to state, andthru api. +TODO +- no on blur +- no onEnter +- if values chnage, then show "submit" button next to it + - on blur hide submit button. on submit success, hide button, blur out? + - esc key to reset + blur? + +- if field clears, repop with orig value, if no orig vlaue, pop with default + */ import React, { useState, useContext } from 'react'; -import { Form, Input, Tooltip } from 'antd'; +import { Button, Form, Input, InputNumber, Tooltip } from 'antd'; import { FormItemProps } from 'antd/es/form'; import { InfoCircleOutlined } from '@ant-design/icons'; @@ -31,28 +40,45 @@ 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 TextField(props: TextFieldProps) { const [submitStatus, setSubmitStatus] = useState(''); const [submitStatusMessage, setSubmitStatusMessage] = useState(''); + const [hasChanged, setHasChanged] = useState(false); + const [fieldValueForSubmit, setFieldValueForSubmit] = useState(''); + + let resetTimer = null; + const serverStatusData = useContext(ServerStatusContext); const { setConfigField } = serverStatusData || {}; - - + const { fieldName, + type, + initialValues = {}, + handleResetValue, } = props; + const initialValue = initialValues[fieldName] || ''; + const { apiPath = '', - defaultValue = '', // if empty configPath = '', maxLength = TEXT_MAXLENGTH, - placeholder = '', + // placeholder = '', label = '', tip = '', } = TEXTFIELD_DEFAULTS[fieldName] || {}; + const resetStates = () => { + setSubmitStatus(''); + setHasChanged(false); + clearTimeout(resetTimer); + resetTimer = null; + } + const postUpdateToAPI = async (postValue: any) => { setSubmitStatus('validating'); const result = await fetchData(`${SERVER_CONFIG_UPDATE_URL}${apiPath}`, { @@ -63,51 +89,85 @@ export default function TextField(props: TextFieldProps) { if (result.success) { setConfigField({ fieldName, value: postValue, path: configPath }); setSubmitStatus('success'); + resetTimer = setTimeout(resetStates, 3000); } else { setSubmitStatus('warning'); setSubmitStatusMessage(`There was an error: ${result.message}`); } }; - const handleEnter = (event: React.KeyboardEvent) => { - const newValue = event.target.value; - if (newValue !== '') { - postUpdateToAPI(newValue); - } - } - const handleBlur = (event: React.FocusEvent) => { - const newValue = event.target.value; - if (newValue !== '') { - console.log("blur post..", newValue) - postUpdateToAPI(newValue); + const handleChange = e => { + const val = e.target.value; + if (val === '' || val === initialValue) { + setHasChanged(false); } else { - // event.target.value = value; + resetStates(); + setHasChanged(true); + setFieldValueForSubmit(val); + } + }; + + const handleBlur = e => { + const val = e.target.value; + if (val === '') { + handleResetValue(fieldName); + // todo: find a way to reset to initial value + } + }; + + // how to get current value of input + const handleSubmit = () => { + if (fieldValueForSubmit !== '' && fieldValueForSubmit !== initialValue) { + postUpdateToAPI(fieldValueForSubmit); } } + let Field = Input; + 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; + } + return ( -
- - - -
- - - +
+
+ + + + + + + + +
+ + { hasChanged ? : null } +
); } diff --git a/web/pages/components/config/public-facing-details.tsx b/web/pages/components/config/public-facing-details.tsx index 9e572dcdf..3f28117d0 100644 --- a/web/pages/components/config/public-facing-details.tsx +++ b/web/pages/components/config/public-facing-details.tsx @@ -1,28 +1,33 @@ import React, { useContext, useEffect } from 'react'; -import { Typography, Form, Input } from 'antd'; +import { Typography, Form } from 'antd'; -import TextField from './form-textfield'; +import TextField, { TEXTFIELD_TYPE_TEXTAREA } from './form-textfield'; import { ServerStatusContext } from '../../../utils/server-status-context'; -import { UpdateArgs } from '../../../types/config-section'; - const { Title } = Typography; export default function PublicFacingDetails() { const [form] = Form.useForm(); const serverStatusData = useContext(ServerStatusContext); - const { serverConfig, setConfigField } = serverStatusData || {}; + const { serverConfig } = serverStatusData || {}; - const { instanceDetails = {}, } = serverConfig; - - const { name, summary, title } = instanceDetails; + const { instanceDetails = {} } = serverConfig; useEffect(() => { form.setFieldsValue({...instanceDetails}); }, [instanceDetails]); - + + + const handleResetValue = (fieldName: string) => { + form.setFieldsValue({ [fieldName]: instanceDetails[fieldName]}); + } + + const extraProps = { + handleResetValue, + initialValues: instanceDetails, + }; return ( <> @@ -34,8 +39,10 @@ export default function PublicFacingDetails() { form={form} layout="vertical" > - - + + + +
diff --git a/web/styles/config.scss b/web/styles/config.scss index b9eb812f9..fed956126 100644 --- a/web/styles/config.scss +++ b/web/styles/config.scss @@ -9,19 +9,37 @@ margin-right: 1rem; } } +.textfield-container { + // width: 28rem; + display: flex; + flex-direction: column; + align-items: flex-end; + justify-content: flex-end; + position: relative; +} .textfield { display: flex; flex-direction: row; align-items: flex-start; - justify-content: flex-end; .field { - width: 20rem; + width: 18rem; } .info { - transform: translateY(.35rem); - margin-left: .5rem; + margin-right: .75rem; + } + .ant-form-item-label label { + font-weight: bold; + color: var(--owncast-purple); + } + .ant-form-item-explain { + width: 70%; } } +.submit-button { + position: absolute; + right: 0; + bottom: 1em; +} \ No newline at end of file diff --git a/web/types/config-section.ts b/web/types/config-section.ts index 9927cc66c..81ef0035a 100644 --- a/web/types/config-section.ts +++ b/web/types/config-section.ts @@ -3,7 +3,7 @@ export interface TextFieldProps { onUpdate: ({ fieldName, value }: UpdateArgs) => void; fieldName: string; - value: string; + initialValue: string; type: string; }