diff --git a/web/components/config/README.md b/web/components/config/README.md deleted file mode 100644 index b73c10234..000000000 --- a/web/components/config/README.md +++ /dev/null @@ -1,127 +0,0 @@ -# About the Config editing section - -An adventure with React, React Hooks and Ant Design forms. - -## General data flow in this React app - -### First things to note -- When the Admin app loads, the `ServerStatusContext` (in addition to checking server `/status` on a timer) makes a call to the `/serverconfig` API to get your config details. This data will be stored as **`serverConfig`** in app state, and _provided_ to the app via `useContext` hook. - -- The `serverConfig` in state is be the central source of data that pre-populates the forms. - -- The `ServerStatusContext` also provides a method for components to update the serverConfig state, called `setFieldInConfigState()`. - -- After you have updated a config value in a form field, and successfully submitted it through its endpoint, you should call `setFieldInConfigState` to update the global state with the new value. - -- Each top field of the serverConfig has its own API update endpoint. - -### Form Flow -Each form input (or group of inputs) you make, you should - 1. Get the field values that you want out of `serverConfig` from ServerStatusContext with `useContext`. - 2. Next we'll have to put these field values of interest into a `useState` in each grouping. This will help you edit the form. - 3. Because ths config data is populated asynchronously, Use a `useEffect` to check when that data has arrived before putting it into state. - 4. You will be using the state's value to populate the `defaultValue` and the `value` props of each Ant input component (`Input`, `Toggle`, `Switch`, `Select`, `Slider` are currently used). - 5. When an `onChange` event fires for each type of input component, you will update the local state of each page with the changed value. - 6. Depending on the form, an `onChange` of the input component, or a subsequent `onClick` of a submit button will take the value from local state and POST the field's API. - 7. `onSuccess` of the post, you should update the global app state with the new value. - -There are also a variety of other local states to manage the display of error/success messaging. - -## Notes about `form-textfield` and `form-togglefield` -- The text field is intentionally designed to make it difficult for the user to submit bad data. -- If you make a change on a field, a Submit buttton will show up that you have to click to update. That will be the only way you can update it. -- If you clear out a field that is marked as Required, then exit/blur the field, it will repopulate with its original value. - -- Both of these elements are specifically meant to be used with updating `serverConfig` fields, since each field requires its own endpoint. - -- Give these fields a bunch of props, and they will display labelling, some helpful UI around tips, validation messaging, as well as submit the update for you. - -- (currently undergoing re-styling and TS cleanup) - -- NOTE: you don't have to use these components. Some form groups may require a customized UX flow where you're better off using the Ant components straight up. - - -## Using Ant's `
` with `form-textfield`. -UPDATE: No more `` use! - -~~You may see that a couple of pages (currently **Public Details** and **Server Details** page), is mainly a grouping of similar Text fields.~~ - -~~These are utilizing the `` component, and these calls:~~ -~~- `const [form] = Form.useForm();`~~ -~~- `form.setFieldsValue(initialValues);`~~ - -~~It seems that a `` requires its child inputs to be in a ``, to help manage overall validation on the form before submission.~~ - -~~The `form-textfield` component was created to be used with this Form. It wraps with a ``, which I believe handles the local state change updates of the value.~~ - -## Current Refactoring: -~~While `Form` + `Form.Item` provides many useful UI features that I'd like to utilize, it's turning out to be too restricting for our uses cases.~~ - -~~I am refactoring `form-textfield` so that it does not rely on ``. But it will require some extra handling and styling of things like error states and success messaging.~~ - -### UI things -I'm in the middle of refactoring somes tyles and layout, and regorganizing some CSS. See TODO list below. - - ---- -## Potential Optimizations - -- There might be some patterns that could be overly engineered! - -- There are also a few patterns across all the form groups that repeat quite a bit. Perhaps these patterns could be consolidated into a custom hook that could handle all the steps. - - - -## Current `serverConfig` data structure (with default values) -Each of these fields has its own end point for updating. -``` -{ - streamKey: '', - instanceDetails: { - extraPageContent: '', - logo: '', - name: '', - nsfw: false, - socialHandles: [], - streamTitle: '', - summary: '', - tags: [], - title: '', - }, - ffmpegPath: '', - rtmpServerPort: '', - webServerPort: '', - s3: {}, - yp: { - enabled: false, - instanceUrl: '', - }, - videoSettings: { - latencyLevel: 4, - videoQualityVariants: [], - } -}; - -// `yp.instanceUrl` needs to be filled out before `yp.enabled` can be turned on. -``` - - -## Ginger's TODO list: - -- cleanup - - more consitent constants - - cleanup types - - cleanup style sheets..? make style module for each config page? (but what about ant deisgn overrides?) -- redesign - - label+form layout - put them into a table, table of rows?, includes responsive to stacked layout - - change Encoder preset into slider - - page headers - diff color? - - fix social handles icon in table - - things could use smaller font? - - Design, color ideas - - https://uxcandy.co/demo/label_pro/preview/demo_2/pages/forms/form-elements.html - - https://www.bootstrapdash.com/demo/corona/jquery/template/modern-vertical/pages/forms/basic_elements.html -- maybe convert common form pattern to custom hook? - diff --git a/web/components/config/cpu-usage.tsx b/web/components/config/cpu-usage.tsx index 3f3a7a06c..09acc19b9 100644 --- a/web/components/config/cpu-usage.tsx +++ b/web/components/config/cpu-usage.tsx @@ -44,15 +44,11 @@ export default function CPUUsageSelector({ defaultValue, onChange }: Props) { }; return ( -
- - CPU Usage - +
+ CPU Usage

- There are trade-offs when considering CPU usage blah blah more wording here. + Reduce the to improve server performance, or increase it to improve video quality.

-
-
TOOLTIPS[value]} @@ -63,6 +59,7 @@ export default function CPUUsageSelector({ defaultValue, onChange }: Props) { defaultValue={selectedOption} value={selectedOption} /> +

Selected: {TOOLTIPS[selectedOption]}

); diff --git a/web/components/config/edit-directory.tsx b/web/components/config/edit-directory.tsx index 033908daf..558a4e900 100644 --- a/web/components/config/edit-directory.tsx +++ b/web/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-with-submit'; +import ToggleSwitch from './form-toggleswitch'; import { ServerStatusContext } from '../../utils/server-status-context'; import { FIELD_PROPS_NSFW, FIELD_PROPS_YP } from '../../utils/config-constants'; diff --git a/web/components/config/edit-instance-details.tsx b/web/components/config/edit-instance-details.tsx index 92f280223..e23eada5f 100644 --- a/web/components/config/edit-instance-details.tsx +++ b/web/components/config/edit-instance-details.tsx @@ -17,9 +17,10 @@ import { FIELD_PROPS_YP, FIELD_PROPS_NSFW, } from '../../utils/config-constants'; +import { NEXT_PUBLIC_API_HOST } from '../../utils/apis'; import { UpdateArgs } from '../../types/config-section'; -import ToggleSwitch from './form-toggleswitch-with-submit'; +import ToggleSwitch from './form-toggleswitch'; const { Title } = Typography; @@ -102,7 +103,9 @@ export default function EditInstanceDetails() { initialValue={instanceDetails.logo} onChange={handleFieldChange} /> - + {instanceDetails.logo && ( + uploaded logo + )}
diff --git a/web/components/config/edit-page-content.tsx b/web/components/config/edit-page-content.tsx index ee12a403d..0f07b45cf 100644 --- a/web/components/config/edit-page-content.tsx +++ b/web/components/config/edit-page-content.tsx @@ -104,13 +104,13 @@ export default function EditPageContent() { markdownClass: 'markdown-editor-pane', }} /> - + <br /> <div className="page-content-actions"> - {hasChanged ? ( + {hasChanged && ( <Button type="primary" onClick={handleSave}> Save </Button> - ) : null} + )} <FormStatusIndicator status={submitStatus} /> </div> </div> diff --git a/web/components/config/edit-server-details.tsx b/web/components/config/edit-server-details.tsx index 92541625d..c13acfc9b 100644 --- a/web/components/config/edit-server-details.tsx +++ b/web/components/config/edit-server-details.tsx @@ -86,7 +86,7 @@ export default function EditInstanceDetails() { } return ( - <div className="edit-public-details-container"> + <div className="edit-server-details-container"> <div className="field-container field-streamkey-container"> <div className="left-side"> <TextFieldWithSubmit diff --git a/web/components/config/edit-social-links.tsx b/web/components/config/edit-social-links.tsx index 6e5a75e18..570a5ec40 100644 --- a/web/components/config/edit-social-links.tsx +++ b/web/components/config/edit-social-links.tsx @@ -196,7 +196,6 @@ export default function EditSocialLinks() { return ( <div className="actions"> <Button - type="primary" size="small" onClick={() => { setEditId(index); @@ -222,6 +221,19 @@ export default function EditSocialLinks() { disabled: !isValidUrl(modalDataState.url), }; + const otherField = ( + <div className="other-field-container formfield-container"> + <div className="label-side" /> + <div className="input-side"> + <Input + placeholder="Other platform name" + defaultValue={modalDataState.platform} + onChange={handleOtherNameChange} + /> + </div> + </div> + ); + return ( <div className="social-links-edit-container"> <Title level={3} className="section-title"> @@ -250,30 +262,23 @@ export default function EditSocialLinks() { confirmLoading={modalProcessing} okButtonProps={okButtonProps} > - <SocialDropdown - iconList={availableIconsList} - selectedOption={selectedOther ? OTHER_SOCIAL_HANDLE_OPTION : modalDataState.platform} - onSelected={handleDropdownSelect} - /> - {displayOther ? ( - <> - <Input - placeholder="Other" - defaultValue={modalDataState.platform} - onChange={handleOtherNameChange} - /> - <br /> - </> - ) : null} - <br /> - <TextField - fieldName="social-url" - label="URL" - placeholder={PLACEHOLDERS[modalDataState.platform] || 'Url to page'} - value={modalDataState.url} - onChange={handleUrlChange} - /> - <FormStatusIndicator status={submitStatus} /> + <div className="social-handle-modal-content"> + <SocialDropdown + iconList={availableIconsList} + selectedOption={selectedOther ? OTHER_SOCIAL_HANDLE_OPTION : modalDataState.platform} + onSelected={handleDropdownSelect} + /> + {displayOther && otherField} + <br /> + <TextField + fieldName="social-url" + label="URL" + placeholder={PLACEHOLDERS[modalDataState.platform] || 'Url to page'} + value={modalDataState.url} + onChange={handleUrlChange} + /> + <FormStatusIndicator status={submitStatus} /> + </div> </Modal> <br /> <Button diff --git a/web/components/config/edit-storage.tsx b/web/components/config/edit-storage.tsx index 3abb5e967..5ddff5054 100644 --- a/web/components/config/edit-storage.tsx +++ b/web/components/config/edit-storage.tsx @@ -1,4 +1,4 @@ -import { Switch, Button, Collapse } from 'antd'; +import { Button, Collapse } from 'antd'; import classNames from 'classnames'; import React, { useContext, useState, useEffect } from 'react'; import { UpdateArgs } from '../../types/config-section'; @@ -21,7 +21,7 @@ import { import TextField from './form-textfield'; import FormStatusIndicator from './form-status-indicator'; import { isValidUrl } from '../../utils/urls'; -// import ToggleSwitch from './form-toggleswitch-with-submit'; +import ToggleSwitch from './form-toggleswitch'; const { Panel } = Collapse; @@ -145,20 +145,21 @@ export default function EditStorage() { return ( <div className={containerClass}> <div className="enable-switch"> - {/* <ToggleSwitch + <ToggleSwitch + apiPath="" fieldName="enabled" label="Storage Enabled" checked={formDataValues.enabled} onChange={handleSwitchChange} - /> */} - <Switch + /> + {/* <Switch checked={formDataValues.enabled} defaultChecked={formDataValues.enabled} onChange={handleSwitchChange} checkedChildren="ON" unCheckedChildren="OFF" />{' '} - Enabled + Enabled */} </div> <div className="form-fields"> diff --git a/web/components/config/form-toggleswitch-with-submit.tsx b/web/components/config/form-toggleswitch.tsx similarity index 94% rename from web/components/config/form-toggleswitch-with-submit.tsx rename to web/components/config/form-toggleswitch.tsx index 0ee264d13..1a05ad65b 100644 --- a/web/components/config/form-toggleswitch-with-submit.tsx +++ b/web/components/config/form-toggleswitch.tsx @@ -1,5 +1,6 @@ // This is a wrapper for the Ant Switch component. -// onChange of the switch, it will automatically post a change to the config api. +// This one is styled to match the form-textfield component. +// If `useSubmit` is true then it will automatically post to the config API onChange. import React, { useState, useContext } from 'react'; import { Switch } from 'antd'; @@ -17,9 +18,9 @@ import { RESET_TIMEOUT, postConfigUpdateToAPI } from '../../utils/config-constan import { ServerStatusContext } from '../../utils/server-status-context'; interface ToggleSwitchProps { - apiPath: string; fieldName: string; + apiPath?: string; checked?: boolean; configPath?: string; disabled?: boolean; @@ -106,6 +107,7 @@ export default function ToggleSwitch(props: ToggleSwitchProps) { } ToggleSwitch.defaultProps = { + apiPath: '', checked: false, configPath: '', disabled: false, diff --git a/web/components/config/reset-yp.tsx b/web/components/config/reset-yp.tsx index 97d3ad79d..e40f12da6 100644 --- a/web/components/config/reset-yp.tsx +++ b/web/components/config/reset-yp.tsx @@ -1,27 +1,46 @@ import { Popconfirm, Button, Typography } from 'antd'; -import { useContext } from 'react'; +import { useContext, useState } from 'react'; import { AlertMessageContext } from '../../utils/alert-message-context'; import { API_YP_RESET, fetchData } from '../../utils/apis'; +import { RESET_TIMEOUT } from '../../utils/config-constants'; +import { + createInputStatus, + STATUS_ERROR, + STATUS_PROCESSING, + STATUS_SUCCESS, +} from '../../utils/input-statuses'; +import FormStatusIndicator from './form-status-indicator'; export default function ResetYP() { const { setMessage } = useContext(AlertMessageContext); - const { Title } = Typography; + const [submitStatus, setSubmitStatus] = useState(null); + let resetTimer = null; + const resetStates = () => { + setSubmitStatus(null); + resetTimer = null; + clearTimeout(resetTimer); + }; const resetDirectoryRegistration = async () => { + setSubmitStatus(createInputStatus(STATUS_PROCESSING)); try { await fetchData(API_YP_RESET); setMessage(''); + setSubmitStatus(createInputStatus(STATUS_SUCCESS)); + resetTimer = setTimeout(resetStates, RESET_TIMEOUT); } catch (error) { - alert(error); + setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${error}`)); + resetTimer = setTimeout(resetStates, RESET_TIMEOUT); } }; + return ( <> - <Title level={3} className="section-title"> + <Typography.Title level={3} className="section-title"> Reset Directory - +

If you are experiencing issues with your listing on the Owncast Directory and were asked to "reset" your connection to the service, you can do that here. The next time you go @@ -37,6 +56,9 @@ export default function ResetYP() { > +

+ +

); } diff --git a/web/components/config/social-icons-dropdown.tsx b/web/components/config/social-icons-dropdown.tsx index dec12cdf0..df1fab8dd 100644 --- a/web/components/config/social-icons-dropdown.tsx +++ b/web/components/config/social-icons-dropdown.tsx @@ -23,39 +23,41 @@ export default function SocialDropdown({ iconList, selectedOption, onSelected }: If you are looking for a platform name not on this list, please select Other and type in your own name. A logo will not be provided.

-

- If you DO have a logo, drop it in to the /webroot/img/platformicons directory - and update the /socialHandle.go list. Then restart the server and it will show - up in the list. -

- + {iconList.map(item => { + const { platform, icon, key } = item; + return ( + + + + + {platform} + + ); + })} + + Other... - ); - })} - - Other... - - + +
+ ); } diff --git a/web/components/config/video-latency.tsx b/web/components/config/video-latency.tsx index 39b7e1e3c..e7ab44f0f 100644 --- a/web/components/config/video-latency.tsx +++ b/web/components/config/video-latency.tsx @@ -36,14 +36,6 @@ const SLIDER_COMMENTS = { 6: 'Highest latency, highest error tolerance', }; -interface SegmentToolTipProps { - value: string; -} - -function SegmentToolTip({ value }: SegmentToolTipProps) { - return {value}; -} - export default function VideoLatency() { const [submitStatus, setSubmitStatus] = useState(null); const [selectedOption, setSelectedOption] = useState(null); @@ -104,7 +96,7 @@ export default function VideoLatency() { }; return ( -
+
Latency Buffer @@ -120,7 +112,7 @@ export default function VideoLatency() {
} + tipFormatter={value => SLIDER_COMMENTS[value]} onChange={handleChange} min={1} max={6} @@ -128,6 +120,7 @@ export default function VideoLatency() { defaultValue={selectedOption} value={selectedOption} /> +

{SLIDER_COMMENTS[selectedOption]}

diff --git a/web/components/config/video-variant-form.tsx b/web/components/config/video-variant-form.tsx index 872818ece..3ec73c566 100644 --- a/web/components/config/video-variant-form.tsx +++ b/web/components/config/video-variant-form.tsx @@ -1,12 +1,11 @@ // This content populates the video variant modal, which is spawned from the variants table. import React from 'react'; -import { Slider, Switch, Collapse, Typography } from 'antd'; +import { Row, Col, Slider, Collapse, Typography } from 'antd'; import { FieldUpdaterFunc, VideoVariant, UpdateArgs } from '../../types/config-section'; import TextField from './form-textfield'; import { DEFAULT_VARIANT_STATE } from '../../utils/config-constants'; -import InfoTip from '../info-tip'; import CPUUsageSelector from './cpu-usage'; -// import ToggleSwitch from './form-toggleswitch-with-submit'; +import ToggleSwitch from './form-toggleswitch'; const { Panel } = Collapse; @@ -17,7 +16,8 @@ const VIDEO_VARIANT_DEFAULTS = { defaultValue: 24, unit: 'fps', incrementBy: null, - tip: 'You prob wont need to touch this unless youre a hardcore gamer and need all the bitties', + tip: + 'Reducing your framerate will decrease the amount of video that needs to be encoded and sent to your viewers, saving CPU and bandwidth at the expense of smoothness. A lower value is generally is fine for most content.', }, videoBitrate: { min: 600, @@ -25,7 +25,7 @@ const VIDEO_VARIANT_DEFAULTS = { defaultValue: 1200, unit: 'kbps', incrementBy: 100, - tip: 'This is importatnt yo', + tip: 'The overall quality of your stream is generally impacted most by bitrate.', }, audioBitrate: { min: 600, @@ -118,9 +118,9 @@ export default function VideoVariantForm({ const selectedVideoBRnote = () => { let note = `Selected: ${dataState.videoBitrate}${videoBRUnit}`; - if (dataState.videoBitrate < 3000) { + if (dataState.videoBitrate < 2000) { note = `${note} - Good for low bandwidth environments.`; - } else if (dataState.videoBitrate < 4500) { + } else if (dataState.videoBitrate < 3500) { note = `${note} - Good for most bandwidth environments.`; } else { note = `${note} - Good for high bandwidth environments.`; @@ -147,52 +147,47 @@ export default function VideoVariantForm({ } return note; }; - const selectedPresetNote = ''; return (

- Say a thing here about how this all works. Read more{' '} - here. + Learn more about how each of these settings + can impact the performance of your server.

-
-
+ + {/* ENCODER PRESET FIELD */}
- {selectedPresetNote && ( - {selectedPresetNote} - )} -
- - {/* VIDEO PASSTHROUGH FIELD */} -
-

- - Use Video Passthrough? +

+ Read more about CPU usage.

-
- {/* todo: change to ToggleSwitch for layout */} - -
+ {/* VIDEO PASSTHROUGH FIELD - currently disabled */} +
+ +
+ + + {/* VIDEO BITRATE FIELD */} -
- - Video Bitrate - +
+ Video Bitrate

{VIDEO_VARIANT_DEFAULTS.videoBitrate.tip}

- {selectedVideoBRnote()} +

{selectedVideoBRnote()}

+

+ Read more about bitrates. +

-
- - -
- Resizing your content will take additional resources on your server. If you wish to - optionally resize your output for this stream variant then you should either set the - width or the height to keep your aspect ratio. -
-
- -
-
- -
+ +
+ + +

+ Resizing your content will take additional resources on your server. If you wish to + optionally resize your content for this stream output then you should either set the + width or the height to keep your aspect ratio.{' '} + Read more about resolutions. +

- {/* FRAME RATE FIELD */} -
-

- - Frame rate: -

-
- `${value} ${framerateUnit}`} - defaultValue={dataState.framerate} - value={dataState.framerate} - onChange={handleFramerateChange} - step={framerateDefaults.incrementBy} - min={framerateMin} - max={framerateMax} - marks={framerateMarks} - /> - {selectedFramerateNote()} -
+ + + + {/* FRAME RATE FIELD */} +
+ Frame rate +

{VIDEO_VARIANT_DEFAULTS.framerate.tip}

+
+ `${value} ${framerateUnit}`} + defaultValue={dataState.framerate} + value={dataState.framerate} + onChange={handleFramerateChange} + step={framerateDefaults.incrementBy} + min={framerateMin} + max={framerateMax} + marks={framerateMarks} + /> +

{selectedFramerateNote()}

- - -
+

+ Read more about framerates. +

+
+
+
); } diff --git a/web/components/config/video-variants-table.tsx b/web/components/config/video-variants-table.tsx index d8e1a2a11..366908847 100644 --- a/web/components/config/video-variants-table.tsx +++ b/web/components/config/video-variants-table.tsx @@ -153,7 +153,6 @@ export default function CurrentVariantsTable() { return (