update readme; more style tweaks
This commit is contained in:
@@ -1,10 +1,44 @@
|
|||||||
# About the Config editing section
|
# Tips for creating a new Admin form
|
||||||
|
|
||||||
An adventure with React, React Hooks and Ant Design forms.
|
### Layout
|
||||||
|
- Give your page or form a title. Feel free to use Ant Design's `<Title>` component.
|
||||||
|
- Give your form a description inside of a `<p className="description" />` tag.
|
||||||
|
|
||||||
## General data flow in this React app
|
- Use some Ant Design `Row` and `Col`'s to layout your forms if you want to spread them out into responsive columns.
|
||||||
|
|
||||||
|
- Use the `form-module` CSS class if you want to add a visual separation to a grouping of items.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Form fields
|
||||||
|
- Feel free to use the pre-styled `<TextField>` text form field or the `<ToggleSwitch>` compnent, in a group of form fields together. These have been styled and laid out to match each other.
|
||||||
|
|
||||||
|
- `Slider`'s - If your form uses an Ant Slider component, follow this recommended markup of CSS classes to maintain a consistent look and feel to other Sliders in the app.
|
||||||
|
```
|
||||||
|
<div className="segment-slider-container">
|
||||||
|
<Slider ...props />
|
||||||
|
<p className="selected-value-note">{selected value}</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Submit Statuses
|
||||||
|
- It would be nice to display indicators of success/warnings to let users know if something has been successfully updated on the server. It has a lot of steps (sorry, but it could probably be optimized), but it'll provide a consistent way to display messaging.
|
||||||
|
|
||||||
|
- See `reset-yp.tsx` for an example of using `submitStatus` with `useState()` and the `<FormStatusIndicator>` component to achieve this.
|
||||||
|
|
||||||
|
### Styling
|
||||||
|
- This admin site chooses to have a generally Dark color palette, but with colors that are different from Ant design's _dark_ stylesheet, so that style sheet is not included. This results in a very large `ant-overrides.scss` file to reset colors on frequently used Ant components in the system. If you find yourself a new Ant Component that has not yet been used in this app, feel free to add a reset style for that component to the overrides stylesheet.
|
||||||
|
|
||||||
|
- Take a look at `variables.scss` CSS file if you want to give some elements custom css colors.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
---
|
||||||
|
# Creating Admin forms the Config section
|
||||||
|
First things first..
|
||||||
|
|
||||||
|
## General Config 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.
|
- 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 `serverConfig` in state is be the central source of data that pre-populates the forms.
|
||||||
@@ -13,10 +47,14 @@ An adventure with React, React Hooks and Ant Design forms.
|
|||||||
|
|
||||||
- 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.
|
- 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
|
## Suggested Config Form Flow
|
||||||
Each form input (or group of inputs) you make, you should
|
- *NOTE: Each top field of the serverConfig has its own API update endpoint.*
|
||||||
|
|
||||||
|
|
||||||
|
There many steps here, but they are highly suggested to ensure that Config values are updated and displayed properly throughout the entire admin form.
|
||||||
|
|
||||||
|
For 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`.
|
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.
|
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.
|
3. Because ths config data is populated asynchronously, Use a `useEffect` to check when that data has arrived before putting it into state.
|
||||||
@@ -27,7 +65,18 @@ Each form input (or group of inputs) you make, you should
|
|||||||
|
|
||||||
There are also a variety of other local states to manage the display of error/success messaging.
|
There are also a variety of other local states to manage the display of error/success messaging.
|
||||||
|
|
||||||
## Notes about `form-textfield` and `form-togglefield`
|
- It is recommended that you use `form-textfield-with-submit` and `form-toggleswitch`(with `useSubmit=true`) Components to edit Config fields.
|
||||||
|
|
||||||
|
Examples of Config form groups where individual form fields submitting to the update API include:
|
||||||
|
- `edit-instance-details.tsx`
|
||||||
|
- `edit-server-details.tsx`
|
||||||
|
|
||||||
|
Examples of Config form groups where there is 1 submit button for the entire group include:
|
||||||
|
- `edit-storage.tsx`
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
#### Notes about `form-textfield-with-submit` and `form-togglefield` (with useSubmit=true)
|
||||||
- The text field is intentionally designed to make it difficult for the user to submit bad data.
|
- 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 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.
|
- If you clear out a field that is marked as Required, then exit/blur the field, it will repopulate with its original value.
|
||||||
@@ -40,7 +89,3 @@ There are also a variety of other local states to manage the display of error/su
|
|||||||
|
|
||||||
- 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.
|
- 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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
segment-slider-container
|
|
||||||
selected-value-note
|
|
||||||
@@ -51,7 +51,6 @@ export default function CPUUsageSelector({ defaultValue, onChange }: Props) {
|
|||||||
</p>
|
</p>
|
||||||
<div className="segment-slider-container">
|
<div className="segment-slider-container">
|
||||||
<Slider
|
<Slider
|
||||||
tooltipVisible={false}
|
|
||||||
tipFormatter={value => TOOLTIPS[value]}
|
tipFormatter={value => TOOLTIPS[value]}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
min={1}
|
min={1}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export default function EditInstanceDetails() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="edit-public-details-container">
|
<div className="edit-server-details-container">
|
||||||
<div className="field-container field-streamkey-container">
|
<div className="field-container field-streamkey-container">
|
||||||
<div className="left-side">
|
<div className="left-side">
|
||||||
<TextFieldWithSubmit
|
<TextFieldWithSubmit
|
||||||
|
|||||||
@@ -1,27 +1,46 @@
|
|||||||
import { Popconfirm, Button, Typography } from 'antd';
|
import { Popconfirm, Button, Typography } from 'antd';
|
||||||
import { useContext } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
import { AlertMessageContext } from '../../utils/alert-message-context';
|
import { AlertMessageContext } from '../../utils/alert-message-context';
|
||||||
|
|
||||||
import { API_YP_RESET, fetchData } from '../../utils/apis';
|
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() {
|
export default function ResetYP() {
|
||||||
const { setMessage } = useContext(AlertMessageContext);
|
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 () => {
|
const resetDirectoryRegistration = async () => {
|
||||||
|
setSubmitStatus(createInputStatus(STATUS_PROCESSING));
|
||||||
try {
|
try {
|
||||||
await fetchData(API_YP_RESET);
|
await fetchData(API_YP_RESET);
|
||||||
setMessage('');
|
setMessage('');
|
||||||
|
setSubmitStatus(createInputStatus(STATUS_SUCCESS));
|
||||||
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert(error);
|
setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${error}`));
|
||||||
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Title level={3} className="section-title">
|
<Typography.Title level={3} className="section-title">
|
||||||
Reset Directory
|
Reset Directory
|
||||||
</Title>
|
</Typography.Title>
|
||||||
<p className="description">
|
<p className="description">
|
||||||
If you are experiencing issues with your listing on the Owncast Directory and were asked to
|
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
|
"reset" your connection to the service, you can do that here. The next time you go
|
||||||
@@ -37,6 +56,9 @@ export default function ResetYP() {
|
|||||||
>
|
>
|
||||||
<Button type="primary">Reset Directory Connection</Button>
|
<Button type="primary">Reset Directory Connection</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
|
<p>
|
||||||
|
<FormStatusIndicator status={submitStatus} />
|
||||||
|
</p>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,14 +36,6 @@ const SLIDER_COMMENTS = {
|
|||||||
6: 'Highest latency, highest error tolerance',
|
6: 'Highest latency, highest error tolerance',
|
||||||
};
|
};
|
||||||
|
|
||||||
interface SegmentToolTipProps {
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function SegmentToolTip({ value }: SegmentToolTipProps) {
|
|
||||||
return <span className="segment-tip">{value}</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function VideoLatency() {
|
export default function VideoLatency() {
|
||||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||||
const [selectedOption, setSelectedOption] = useState(null);
|
const [selectedOption, setSelectedOption] = useState(null);
|
||||||
@@ -120,7 +112,7 @@ export default function VideoLatency() {
|
|||||||
|
|
||||||
<div className="segment-slider-container">
|
<div className="segment-slider-container">
|
||||||
<Slider
|
<Slider
|
||||||
tooltipVisible={false}
|
tipFormatter={value => SLIDER_COMMENTS[value]}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
min={1}
|
min={1}
|
||||||
max={6}
|
max={6}
|
||||||
|
|||||||
@@ -230,7 +230,6 @@ export default function VideoVariantForm({
|
|||||||
<p className="description">{VIDEO_VARIANT_DEFAULTS.framerate.tip}</p>
|
<p className="description">{VIDEO_VARIANT_DEFAULTS.framerate.tip}</p>
|
||||||
<div className="segment-slider-container">
|
<div className="segment-slider-container">
|
||||||
<Slider
|
<Slider
|
||||||
// tooltipVisible
|
|
||||||
tipFormatter={value => `${value} ${framerateUnit}`}
|
tipFormatter={value => `${value} ${framerateUnit}`}
|
||||||
defaultValue={dataState.framerate}
|
defaultValue={dataState.framerate}
|
||||||
value={dataState.framerate}
|
value={dataState.framerate}
|
||||||
|
|||||||
@@ -472,7 +472,7 @@ textarea.ant-input {
|
|||||||
|
|
||||||
// ANT POPOVER
|
// ANT POPOVER
|
||||||
.ant-popover-inner {
|
.ant-popover-inner {
|
||||||
background-color: var(--black);
|
background-color: var(--gray);
|
||||||
}
|
}
|
||||||
.ant-popover-message,
|
.ant-popover-message,
|
||||||
.ant-popover-inner-content {
|
.ant-popover-inner-content {
|
||||||
@@ -480,7 +480,7 @@ textarea.ant-input {
|
|||||||
|
|
||||||
}
|
}
|
||||||
.ant-popover-placement-topLeft > .ant-popover-content > .ant-popover-arrow {
|
.ant-popover-placement-topLeft > .ant-popover-content > .ant-popover-arrow {
|
||||||
border-color: var(--black);
|
border-color: var(--gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.edit-server-details-container {
|
||||||
|
|
||||||
// Do something special for the stream key field
|
// Do something special for the stream key field
|
||||||
.field-streamkey-container {
|
.field-streamkey-container {
|
||||||
margin-bottom: 1.5em;
|
margin-bottom: 1.5em;
|
||||||
@@ -48,3 +50,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.advanced-settings {
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user