Admin css overhaul pt2 (#19)
* tweaks to offline state in admin viewers page If stream is offline, hide current viewers statistic and viewers table. Also, change wording for describing max viewers. * take out ant dark stylesheet, organize ant color overrides * remove ant dark css; cleanup ant overrides; format public-detail page * combine toggleswitch component style with textfield so layout can be shared * fix toggleswitch status message placement * - update styles for modals, collapses - move reset dir into its own component - assorted style cleanups ans consistencies * hide entire advanced section for resetyp if no yp * temp adjustments to video modal * temp comment out toggle switch use for later' * address PR comments * lint * update type * allow warnings during lint Co-authored-by: nebunez <uoj2y7wak869@opayq.net>
This commit is contained in:
@@ -19,8 +19,11 @@ const TOOLTIPS = {
|
||||
4: 'high',
|
||||
5: 'highest',
|
||||
};
|
||||
|
||||
export default function CPUUsageSelector({ defaultValue, onChange }) {
|
||||
interface Props {
|
||||
defaultValue: number;
|
||||
onChange: (arg: number) => void;
|
||||
}
|
||||
export default function CPUUsageSelector({ defaultValue, onChange }: Props) {
|
||||
const [selectedOption, setSelectedOption] = useState(null);
|
||||
|
||||
const serverStatusData = useContext(ServerStatusContext);
|
||||
@@ -42,10 +45,14 @@ export default function CPUUsageSelector({ defaultValue, onChange }) {
|
||||
|
||||
return (
|
||||
<div className="config-video-segements-conatiner">
|
||||
<Title level={3}>CPU Usage</Title>
|
||||
<p>There are trade-offs when considering CPU usage blah blah more wording here.</p>
|
||||
<br />
|
||||
<Title level={3} className="section-title">
|
||||
CPU Usage
|
||||
</Title>
|
||||
<p className="description">
|
||||
There are trade-offs when considering CPU usage blah blah more wording here.
|
||||
</p>
|
||||
<br />
|
||||
|
||||
<div className="segment-slider-container">
|
||||
<Slider
|
||||
tipFormatter={value => TOOLTIPS[value]}
|
||||
|
||||
@@ -33,9 +33,11 @@ export default function EditYPDetails() {
|
||||
}
|
||||
return (
|
||||
<div className="config-directory-details-form">
|
||||
<Title level={3}>Owncast Directory Settings</Title>
|
||||
<Title level={3} className="section-title">
|
||||
Owncast Directory Settings
|
||||
</Title>
|
||||
|
||||
<p>
|
||||
<p className="description">
|
||||
Would you like to appear in the{' '}
|
||||
<a href="https://directory.owncast.online" target="_blank" rel="noreferrer">
|
||||
<strong>Owncast Directory</strong>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import React, { useState, useContext, useEffect } from 'react';
|
||||
import { Typography } from 'antd';
|
||||
|
||||
import TextFieldWithSubmit, {
|
||||
TEXTFIELD_TYPE_TEXTAREA,
|
||||
TEXTFIELD_TYPE_URL,
|
||||
@@ -12,9 +14,14 @@ import {
|
||||
TEXTFIELD_PROPS_SERVER_SUMMARY,
|
||||
TEXTFIELD_PROPS_LOGO,
|
||||
API_YP_SWITCH,
|
||||
FIELD_PROPS_YP,
|
||||
FIELD_PROPS_NSFW,
|
||||
} from '../../utils/config-constants';
|
||||
|
||||
import { UpdateArgs } from '../../types/config-section';
|
||||
import ToggleSwitch from './form-toggleswitch-with-submit';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
export default function EditInstanceDetails() {
|
||||
const [formDataValues, setFormDataValues] = useState(null);
|
||||
@@ -22,6 +29,7 @@ export default function EditInstanceDetails() {
|
||||
const { serverConfig } = serverStatusData || {};
|
||||
|
||||
const { instanceDetails, yp } = serverConfig;
|
||||
const { instanceUrl } = yp;
|
||||
|
||||
useEffect(() => {
|
||||
setFormDataValues({
|
||||
@@ -53,40 +61,74 @@ export default function EditInstanceDetails() {
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`publicDetailsContainer`}>
|
||||
<div className={`textFieldsSection`}>
|
||||
<TextFieldWithSubmit
|
||||
fieldName="instanceUrl"
|
||||
{...TEXTFIELD_PROPS_INSTANCE_URL}
|
||||
value={formDataValues.instanceUrl}
|
||||
initialValue={yp.instanceUrl}
|
||||
type={TEXTFIELD_TYPE_URL}
|
||||
onChange={handleFieldChange}
|
||||
onSubmit={handleSubmitInstanceUrl}
|
||||
/>
|
||||
const hasInstanceUrl = instanceUrl !== '';
|
||||
|
||||
<TextFieldWithSubmit
|
||||
fieldName="name"
|
||||
{...TEXTFIELD_PROPS_SERVER_NAME}
|
||||
value={formDataValues.name}
|
||||
initialValue={instanceDetails.name}
|
||||
onChange={handleFieldChange}
|
||||
return (
|
||||
<div className="edit-general-settings">
|
||||
<Title level={3} className="section-title">
|
||||
Configure Instance Details
|
||||
</Title>
|
||||
<br />
|
||||
|
||||
<TextFieldWithSubmit
|
||||
fieldName="instanceUrl"
|
||||
{...TEXTFIELD_PROPS_INSTANCE_URL}
|
||||
value={formDataValues.instanceUrl}
|
||||
initialValue={yp.instanceUrl}
|
||||
type={TEXTFIELD_TYPE_URL}
|
||||
onChange={handleFieldChange}
|
||||
onSubmit={handleSubmitInstanceUrl}
|
||||
/>
|
||||
|
||||
<TextFieldWithSubmit
|
||||
fieldName="name"
|
||||
{...TEXTFIELD_PROPS_SERVER_NAME}
|
||||
value={formDataValues.name}
|
||||
initialValue={instanceDetails.name}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
<TextFieldWithSubmit
|
||||
fieldName="summary"
|
||||
{...TEXTFIELD_PROPS_SERVER_SUMMARY}
|
||||
type={TEXTFIELD_TYPE_TEXTAREA}
|
||||
value={formDataValues.summary}
|
||||
initialValue={instanceDetails.summary}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
<TextFieldWithSubmit
|
||||
fieldName="logo"
|
||||
{...TEXTFIELD_PROPS_LOGO}
|
||||
value={formDataValues.logo}
|
||||
initialValue={instanceDetails.logo}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
|
||||
<br />
|
||||
|
||||
<Title level={3} className="section-title">
|
||||
Owncast Directory Settings
|
||||
</Title>
|
||||
<p className="description">
|
||||
Would you like to appear in the{' '}
|
||||
<a href="https://directory.owncast.online" target="_blank" rel="noreferrer">
|
||||
<strong>Owncast Directory</strong>
|
||||
</a>
|
||||
?
|
||||
</p>
|
||||
<div className="config-yp-container">
|
||||
<ToggleSwitch
|
||||
fieldName="enabled"
|
||||
useSubmit
|
||||
{...FIELD_PROPS_YP}
|
||||
checked={formDataValues.enabled}
|
||||
disabled={!hasInstanceUrl}
|
||||
/>
|
||||
<TextFieldWithSubmit
|
||||
fieldName="summary"
|
||||
{...TEXTFIELD_PROPS_SERVER_SUMMARY}
|
||||
type={TEXTFIELD_TYPE_TEXTAREA}
|
||||
value={formDataValues.summary}
|
||||
initialValue={instanceDetails.summary}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
<TextFieldWithSubmit
|
||||
fieldName="logo"
|
||||
{...TEXTFIELD_PROPS_LOGO}
|
||||
value={formDataValues.logo}
|
||||
initialValue={instanceDetails.logo}
|
||||
onChange={handleFieldChange}
|
||||
<ToggleSwitch
|
||||
fieldName="nsfw"
|
||||
useSubmit
|
||||
{...FIELD_PROPS_NSFW}
|
||||
checked={formDataValues.nsfw}
|
||||
disabled={!hasInstanceUrl}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
118
web/components/config/edit-page-content.tsx
Normal file
118
web/components/config/edit-page-content.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
// EDIT CUSTOM DETAILS ON YOUR PAGE
|
||||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import { Typography, Button } from 'antd';
|
||||
import dynamic from 'next/dynamic';
|
||||
import MarkdownIt from 'markdown-it';
|
||||
|
||||
import { ServerStatusContext } from '../../utils/server-status-context';
|
||||
import {
|
||||
postConfigUpdateToAPI,
|
||||
RESET_TIMEOUT,
|
||||
API_CUSTOM_CONTENT,
|
||||
} from '../../utils/config-constants';
|
||||
import {
|
||||
createInputStatus,
|
||||
StatusState,
|
||||
STATUS_ERROR,
|
||||
STATUS_PROCESSING,
|
||||
STATUS_SUCCESS,
|
||||
} from '../../utils/input-statuses';
|
||||
import FormStatusIndicator from './form-status-indicator';
|
||||
|
||||
import 'react-markdown-editor-lite/lib/index.css';
|
||||
|
||||
const mdParser = new MarkdownIt(/* Markdown-it options */);
|
||||
const MdEditor = dynamic(() => import('react-markdown-editor-lite'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
export default function EditPageContent() {
|
||||
const [content, setContent] = useState('');
|
||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||
const [hasChanged, setHasChanged] = useState(false);
|
||||
|
||||
const serverStatusData = useContext(ServerStatusContext);
|
||||
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
||||
|
||||
const { instanceDetails } = serverConfig;
|
||||
const { extraPageContent: initialContent } = instanceDetails;
|
||||
|
||||
let resetTimer = null;
|
||||
|
||||
function handleEditorChange({ text }) {
|
||||
setContent(text);
|
||||
if (text !== initialContent && !hasChanged) {
|
||||
setHasChanged(true);
|
||||
} else if (text === initialContent && hasChanged) {
|
||||
setHasChanged(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear out any validation states and messaging
|
||||
const resetStates = () => {
|
||||
setSubmitStatus(null);
|
||||
setHasChanged(false);
|
||||
clearTimeout(resetTimer);
|
||||
resetTimer = null;
|
||||
};
|
||||
|
||||
// posts all the tags at once as an array obj
|
||||
async function handleSave() {
|
||||
setSubmitStatus(createInputStatus(STATUS_PROCESSING));
|
||||
await postConfigUpdateToAPI({
|
||||
apiPath: API_CUSTOM_CONTENT,
|
||||
data: { value: content },
|
||||
onSuccess: (message: string) => {
|
||||
setFieldInConfigState({
|
||||
fieldName: 'extraPageContent',
|
||||
value: content,
|
||||
path: 'instanceDetails',
|
||||
});
|
||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, message));
|
||||
},
|
||||
onError: (message: string) => {
|
||||
setSubmitStatus(createInputStatus(STATUS_ERROR, message));
|
||||
},
|
||||
});
|
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setContent(initialContent);
|
||||
}, [instanceDetails]);
|
||||
|
||||
return (
|
||||
<div className="edit-page-content">
|
||||
<Title level={3} className="section-title">
|
||||
Custom Page Content
|
||||
</Title>
|
||||
|
||||
<p className="description">
|
||||
Edit the content of your page by using simple{' '}
|
||||
<a href="https://www.markdownguide.org/basic-syntax/">Markdown syntax</a>.
|
||||
</p>
|
||||
|
||||
<MdEditor
|
||||
style={{ height: '30em' }}
|
||||
value={content}
|
||||
renderHTML={(c: string) => mdParser.render(c)}
|
||||
onChange={handleEditorChange}
|
||||
config={{
|
||||
htmlClass: 'markdown-editor-preview-pane',
|
||||
markdownClass: 'markdown-editor-pane',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="page-content-actions">
|
||||
{hasChanged ? (
|
||||
<Button type="primary" onClick={handleSave}>
|
||||
Save
|
||||
</Button>
|
||||
) : null}
|
||||
<FormStatusIndicator status={submitStatus} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useState, useContext, useEffect } from 'react';
|
||||
import { Button, Tooltip, Collapse, Popconfirm } from 'antd';
|
||||
import { Button, Tooltip, Collapse } from 'antd';
|
||||
import { CopyOutlined, RedoOutlined } from '@ant-design/icons';
|
||||
const { Panel } = Collapse;
|
||||
|
||||
import { TEXTFIELD_TYPE_NUMBER, TEXTFIELD_TYPE_PASSWORD } from './form-textfield';
|
||||
import TextFieldWithSubmit from './form-textfield-with-submit';
|
||||
@@ -15,9 +14,11 @@ import {
|
||||
TEXTFIELD_PROPS_STREAM_KEY,
|
||||
TEXTFIELD_PROPS_WEB_PORT,
|
||||
} from '../../utils/config-constants';
|
||||
import { fetchData, API_YP_RESET } from '../../utils/apis';
|
||||
|
||||
import { UpdateArgs } from '../../types/config-section';
|
||||
import ResetYP from './reset-yp';
|
||||
|
||||
const { Panel } = Collapse;
|
||||
|
||||
export default function EditInstanceDetails() {
|
||||
const [formDataValues, setFormDataValues] = useState(null);
|
||||
@@ -68,41 +69,6 @@ export default function EditInstanceDetails() {
|
||||
}
|
||||
};
|
||||
|
||||
const resetDirectoryRegistration = async () => {
|
||||
try {
|
||||
await fetchData(API_YP_RESET);
|
||||
setMessage('');
|
||||
} catch (error) {
|
||||
alert(error);
|
||||
}
|
||||
};
|
||||
|
||||
function ResetYP() {
|
||||
// TODO: Uncomment this after it's styled.
|
||||
// if (yp.enabled) {
|
||||
return (
|
||||
<div className="field-container">
|
||||
Reset Directory:
|
||||
<Popconfirm
|
||||
placement="topLeft"
|
||||
title={'Are you sure you want to reset your connection to the Owncast directory?'}
|
||||
onConfirm={resetDirectoryRegistration}
|
||||
okText="Yes"
|
||||
cancelText="No"
|
||||
>
|
||||
<Button>Reset Directory Connection</Button>
|
||||
</Popconfirm>
|
||||
<p>
|
||||
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 live
|
||||
it will try and re-register your server with the directory from scratch.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
// }
|
||||
// return null;
|
||||
}
|
||||
|
||||
function generateStreamKey() {
|
||||
let key = '';
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
@@ -172,13 +138,14 @@ export default function EditInstanceDetails() {
|
||||
onChange={handleFieldChange}
|
||||
onSubmit={showConfigurationRestartMessage}
|
||||
/>
|
||||
<Collapse>
|
||||
<Panel header="Advanced Settings" key="1">
|
||||
<div className="form-fields">
|
||||
|
||||
{yp.enabled && (
|
||||
<Collapse className="advanced-settings">
|
||||
<Panel header="Advanced Settings" key="1">
|
||||
<ResetYP />
|
||||
</div>
|
||||
</Panel>
|
||||
</Collapse>
|
||||
</Panel>
|
||||
</Collapse>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -165,43 +165,36 @@ export default function EditSocialLinks() {
|
||||
|
||||
const socialHandlesColumns: ColumnsType<SocialHandle> = [
|
||||
{
|
||||
title: '#',
|
||||
dataIndex: 'key',
|
||||
key: 'key',
|
||||
},
|
||||
{
|
||||
title: 'Platform',
|
||||
dataIndex: 'platform',
|
||||
key: 'platform',
|
||||
render: (platform: string) => {
|
||||
title: 'Social Link',
|
||||
dataIndex: '',
|
||||
key: 'combo',
|
||||
render: (data, record) => {
|
||||
const { platform, url } = record;
|
||||
const platformInfo = availableIconsList.find(item => item.key === platform);
|
||||
if (!platformInfo) {
|
||||
return platform;
|
||||
}
|
||||
const { icon, platform: platformName } = platformInfo;
|
||||
return (
|
||||
<>
|
||||
<div className="social-handle-cell">
|
||||
<span className="option-icon">
|
||||
<img src={`${NEXT_PUBLIC_API_HOST}${icon}`} alt="" className="option-icon" />
|
||||
</span>
|
||||
<span className="option-label">{platformName}</span>
|
||||
</>
|
||||
<p className="option-label">
|
||||
<strong>{platformName}</strong>
|
||||
<span>{url}</span>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Url Link',
|
||||
dataIndex: 'url',
|
||||
key: 'url',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: '',
|
||||
key: 'edit',
|
||||
render: (data, record, index) => {
|
||||
return (
|
||||
<span className="actions">
|
||||
<div className="actions">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
@@ -219,7 +212,7 @@ export default function EditSocialLinks() {
|
||||
size="small"
|
||||
onClick={() => handleDeleteItem(index)}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -231,12 +224,17 @@ export default function EditSocialLinks() {
|
||||
|
||||
return (
|
||||
<div className="social-links-edit-container">
|
||||
<p>Add all your social media handles and links to your other profiles here.</p>
|
||||
<Title level={3} className="section-title">
|
||||
Your Social Handles
|
||||
</Title>
|
||||
<p className="description">
|
||||
Add all your social media handles and links to your other profiles here.
|
||||
</p>
|
||||
|
||||
<FormStatusIndicator status={submitStatus} />
|
||||
|
||||
<Table
|
||||
className="dataTable"
|
||||
className="social-handles-table"
|
||||
pagination={false}
|
||||
size="small"
|
||||
rowKey={record => record.url}
|
||||
|
||||
@@ -21,6 +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';
|
||||
|
||||
const { Panel } = Collapse;
|
||||
|
||||
@@ -135,6 +136,7 @@ export default function EditStorage() {
|
||||
|
||||
const containerClass = classNames({
|
||||
'edit-storage-container': true,
|
||||
'form-module': true,
|
||||
enabled: shouldDisplayForm,
|
||||
});
|
||||
|
||||
@@ -143,6 +145,12 @@ export default function EditStorage() {
|
||||
return (
|
||||
<div className={containerClass}>
|
||||
<div className="enable-switch">
|
||||
{/* <ToggleSwitch
|
||||
fieldName="enabled"
|
||||
label="Storage Enabled"
|
||||
checked={formDataValues.enabled}
|
||||
onChange={handleSwitchChange}
|
||||
/> */}
|
||||
<Switch
|
||||
checked={formDataValues.enabled}
|
||||
defaultChecked={formDataValues.enabled}
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
const TAG_COLOR = '#5a67d8';
|
||||
|
||||
export default function EditInstanceTags() {
|
||||
const [newTagInput, setNewTagInput] = useState<string>('');
|
||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||
@@ -100,8 +102,12 @@ export default function EditInstanceTags() {
|
||||
|
||||
return (
|
||||
<div className="tag-editor-container">
|
||||
<Title level={3}>Add Tags</Title>
|
||||
<p>This is a great way to categorize your Owncast server on the Directory!</p>
|
||||
<Title level={3} className="section-title">
|
||||
Add Tags
|
||||
</Title>
|
||||
<p className="description">
|
||||
This is a great way to categorize your Owncast server on the Directory!
|
||||
</p>
|
||||
|
||||
<div className="tag-current-tags">
|
||||
{tags.map((tag, index) => {
|
||||
@@ -109,7 +115,7 @@ export default function EditInstanceTags() {
|
||||
handleDeleteTag(index);
|
||||
};
|
||||
return (
|
||||
<Tag closable onClose={handleClose} key={`tag-${tag}-${index}`}>
|
||||
<Tag closable onClose={handleClose} color={TAG_COLOR} key={`tag-${tag}-${index}`}>
|
||||
{tag}
|
||||
</Tag>
|
||||
);
|
||||
|
||||
@@ -120,7 +120,7 @@ export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) {
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="textfield-container lower-container">
|
||||
<div className="formfield-container lower-container">
|
||||
<p className="label-spacer" />
|
||||
<div className="lower-content">
|
||||
<div className="field-tip">{tip}</div>
|
||||
|
||||
@@ -108,6 +108,7 @@ export default function TextField(props: TextFieldProps) {
|
||||
const { type: statusType } = status || {};
|
||||
|
||||
const containerClass = classNames({
|
||||
'formfield-container': true,
|
||||
'textfield-container': true,
|
||||
[`type-${type}`]: true,
|
||||
required,
|
||||
@@ -117,7 +118,7 @@ export default function TextField(props: TextFieldProps) {
|
||||
<div className={containerClass}>
|
||||
{label ? (
|
||||
<div className="label-side">
|
||||
<label htmlFor={fieldId} className="textfield-label">
|
||||
<label htmlFor={fieldId} className="formfield-label">
|
||||
{label}
|
||||
</label>
|
||||
</div>
|
||||
@@ -140,10 +141,7 @@ export default function TextField(props: TextFieldProps) {
|
||||
/>
|
||||
</div>
|
||||
<FormStatusIndicator status={status} />
|
||||
<p className="field-tip">
|
||||
{tip}
|
||||
{/* <InfoTip tip={tip} /> */}
|
||||
</p>
|
||||
<p className="field-tip">{tip}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -151,9 +149,7 @@ export default function TextField(props: TextFieldProps) {
|
||||
|
||||
TextField.defaultProps = {
|
||||
className: '',
|
||||
// configPath: '',
|
||||
disabled: false,
|
||||
// initialValue: '',
|
||||
label: '',
|
||||
maxLength: 255,
|
||||
|
||||
|
||||
@@ -1,3 +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.
|
||||
|
||||
import React, { useState, useContext } from 'react';
|
||||
import { Switch } from 'antd';
|
||||
import {
|
||||
@@ -12,7 +15,6 @@ import FormStatusIndicator from './form-status-indicator';
|
||||
import { RESET_TIMEOUT, postConfigUpdateToAPI } from '../../utils/config-constants';
|
||||
|
||||
import { ServerStatusContext } from '../../utils/server-status-context';
|
||||
import InfoTip from '../info-tip';
|
||||
|
||||
interface ToggleSwitchProps {
|
||||
apiPath: string;
|
||||
@@ -23,8 +25,9 @@ interface ToggleSwitchProps {
|
||||
disabled?: boolean;
|
||||
label?: string;
|
||||
tip?: string;
|
||||
useSubmit?: boolean;
|
||||
onChange?: (arg: boolean) => void;
|
||||
}
|
||||
|
||||
export default function ToggleSwitch(props: ToggleSwitchProps) {
|
||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||
|
||||
@@ -33,7 +36,17 @@ export default function ToggleSwitch(props: ToggleSwitchProps) {
|
||||
const serverStatusData = useContext(ServerStatusContext);
|
||||
const { setFieldInConfigState } = serverStatusData || {};
|
||||
|
||||
const { apiPath, checked, configPath = '', disabled = false, fieldName, label, tip } = props;
|
||||
const {
|
||||
apiPath,
|
||||
checked,
|
||||
configPath = '',
|
||||
disabled = false,
|
||||
fieldName,
|
||||
label,
|
||||
tip,
|
||||
useSubmit,
|
||||
onChange,
|
||||
} = props;
|
||||
|
||||
const resetStates = () => {
|
||||
setSubmitStatus(null);
|
||||
@@ -42,41 +55,52 @@ export default function ToggleSwitch(props: ToggleSwitchProps) {
|
||||
};
|
||||
|
||||
const handleChange = async (isChecked: boolean) => {
|
||||
setSubmitStatus(createInputStatus(STATUS_PROCESSING));
|
||||
if (useSubmit) {
|
||||
setSubmitStatus(createInputStatus(STATUS_PROCESSING));
|
||||
|
||||
await postConfigUpdateToAPI({
|
||||
apiPath,
|
||||
data: { value: isChecked },
|
||||
onSuccess: () => {
|
||||
setFieldInConfigState({ fieldName, value: isChecked, path: configPath });
|
||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS));
|
||||
},
|
||||
onError: (message: string) => {
|
||||
setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${message}`));
|
||||
},
|
||||
});
|
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||
await postConfigUpdateToAPI({
|
||||
apiPath,
|
||||
data: { value: isChecked },
|
||||
onSuccess: () => {
|
||||
setFieldInConfigState({ fieldName, value: isChecked, path: configPath });
|
||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS));
|
||||
},
|
||||
onError: (message: string) => {
|
||||
setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${message}`));
|
||||
},
|
||||
});
|
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||
}
|
||||
if (onChange) {
|
||||
onChange(isChecked);
|
||||
}
|
||||
};
|
||||
|
||||
const loading = submitStatus !== null && submitStatus.type === STATUS_PROCESSING;
|
||||
return (
|
||||
<div className="toggleswitch-container">
|
||||
<div className="toggleswitch">
|
||||
<Switch
|
||||
className={`switch field-${fieldName}`}
|
||||
loading={loading}
|
||||
onChange={handleChange}
|
||||
defaultChecked={checked}
|
||||
checked={checked}
|
||||
checkedChildren="ON"
|
||||
unCheckedChildren="OFF"
|
||||
disabled={disabled}
|
||||
/>
|
||||
<span className="label">
|
||||
{label} <InfoTip tip={tip} />
|
||||
</span>
|
||||
<div className="formfield-container toggleswitch-container">
|
||||
{label && (
|
||||
<div className="label-side">
|
||||
<span className="formfield-label">{label}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="input-side">
|
||||
<div className="input-group">
|
||||
<Switch
|
||||
className={`switch field-${fieldName}`}
|
||||
loading={loading}
|
||||
onChange={handleChange}
|
||||
defaultChecked={checked}
|
||||
checked={checked}
|
||||
checkedChildren="ON"
|
||||
unCheckedChildren="OFF"
|
||||
disabled={disabled}
|
||||
/>
|
||||
<FormStatusIndicator status={submitStatus} />
|
||||
</div>
|
||||
<p className="field-tip">{tip}</p>
|
||||
</div>
|
||||
<FormStatusIndicator status={submitStatus} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -87,4 +111,6 @@ ToggleSwitch.defaultProps = {
|
||||
disabled: false,
|
||||
label: '',
|
||||
tip: '',
|
||||
useSubmit: false,
|
||||
onChange: null,
|
||||
};
|
||||
|
||||
42
web/components/config/reset-yp.tsx
Normal file
42
web/components/config/reset-yp.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Popconfirm, Button, Typography } from 'antd';
|
||||
import { useContext } from 'react';
|
||||
import { AlertMessageContext } from '../../utils/alert-message-context';
|
||||
|
||||
import { API_YP_RESET, fetchData } from '../../utils/apis';
|
||||
|
||||
export default function ResetYP() {
|
||||
const { setMessage } = useContext(AlertMessageContext);
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
const resetDirectoryRegistration = async () => {
|
||||
try {
|
||||
await fetchData(API_YP_RESET);
|
||||
setMessage('');
|
||||
} catch (error) {
|
||||
alert(error);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Title level={3} className="section-title">
|
||||
Reset Directory
|
||||
</Title>
|
||||
<p className="description">
|
||||
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
|
||||
live it will try and re-register your server with the directory from scratch.
|
||||
</p>
|
||||
|
||||
<Popconfirm
|
||||
placement="topLeft"
|
||||
title="Are you sure you want to reset your connection to the Owncast directory?"
|
||||
onConfirm={resetDirectoryRegistration}
|
||||
okText="Yes"
|
||||
cancelText="No"
|
||||
>
|
||||
<Button type="primary">Reset Directory Connection</Button>
|
||||
</Popconfirm>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -19,11 +19,11 @@ export default function SocialDropdown({ iconList, selectedOption, onSelected }:
|
||||
const inititalSelected = selectedOption === '' ? null : selectedOption;
|
||||
return (
|
||||
<div className="social-dropdown-container">
|
||||
<p className="">
|
||||
<p className="description">
|
||||
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.
|
||||
</p>
|
||||
<p className="">
|
||||
<p className="description">
|
||||
If you DO have a logo, drop it in to the <code>/webroot/img/platformicons</code> directory
|
||||
and update the <code>/socialHandle.go</code> list. Then restart the server and it will show
|
||||
up in the list.
|
||||
|
||||
@@ -46,9 +46,6 @@ function SegmentToolTip({ value }: SegmentToolTipProps) {
|
||||
|
||||
export default function VideoLatency() {
|
||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||
|
||||
// const [submitStatus, setSubmitStatus] = useState(null);
|
||||
// const [submitStatusMessage, setSubmitStatusMessage] = useState('');
|
||||
const [selectedOption, setSelectedOption] = useState(null);
|
||||
|
||||
const serverStatusData = useContext(ServerStatusContext);
|
||||
@@ -68,7 +65,6 @@ export default function VideoLatency() {
|
||||
|
||||
const resetStates = () => {
|
||||
setSubmitStatus(null);
|
||||
// setSubmitStatusMessage('');
|
||||
resetTimer = null;
|
||||
clearTimeout(resetTimer);
|
||||
};
|
||||
@@ -88,8 +84,6 @@ export default function VideoLatency() {
|
||||
});
|
||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Latency buffer level updated.'));
|
||||
|
||||
// setSubmitStatus('success');
|
||||
// setSubmitStatusMessage('Variants updated.');
|
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||
if (serverStatusData.online) {
|
||||
setMessage(
|
||||
@@ -100,8 +94,6 @@ export default function VideoLatency() {
|
||||
onError: (message: string) => {
|
||||
setSubmitStatus(createInputStatus(STATUS_ERROR, message));
|
||||
|
||||
// setSubmitStatus('error');
|
||||
// setSubmitStatusMessage(message);
|
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||
},
|
||||
});
|
||||
@@ -113,15 +105,19 @@ export default function VideoLatency() {
|
||||
|
||||
return (
|
||||
<div className="config-video-segements-conatiner">
|
||||
<Title level={3}>Latency Buffer</Title>
|
||||
<p>
|
||||
While it's natural to want to keep your latency as low as possible, you may experience
|
||||
<Title level={3} className="section-title">
|
||||
Latency Buffer
|
||||
</Title>
|
||||
<p className="description">
|
||||
While it's natural to want to keep your latency as low as possible, you may experience
|
||||
reduced error tolerance and stability in some environments the lower you go.
|
||||
</p>
|
||||
For interactive live streams you may want to experiment with a lower latency, for
|
||||
non-interactive broadcasts you may want to increase it.{' '}
|
||||
<a href="https://owncast.online/docs/encoding#latency-buffer">Read to learn more.</a>
|
||||
<p></p>
|
||||
<p className="description">
|
||||
For interactive live streams you may want to experiment with a lower latency, for
|
||||
non-interactive broadcasts you may want to increase it.{' '}
|
||||
<a href="https://owncast.online/docs/encoding#latency-buffer">Read to learn more.</a>
|
||||
</p>
|
||||
|
||||
<div className="segment-slider-container">
|
||||
<Slider
|
||||
tipFormatter={value => <SegmentToolTip value={SLIDER_COMMENTS[value]} />}
|
||||
@@ -132,8 +128,8 @@ export default function VideoLatency() {
|
||||
defaultValue={selectedOption}
|
||||
value={selectedOption}
|
||||
/>
|
||||
<FormStatusIndicator status={submitStatus} />
|
||||
</div>
|
||||
<FormStatusIndicator status={submitStatus} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// This content populates the video variant modal, which is spawned from the variants table.
|
||||
import React from 'react';
|
||||
import { Slider, Switch, Collapse } from 'antd';
|
||||
import { Slider, Switch, 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';
|
||||
|
||||
const { Panel } = Collapse;
|
||||
|
||||
@@ -55,7 +56,6 @@ const VIDEO_VARIANT_DEFAULTS = {
|
||||
tip: "Optionally resize this content's height.",
|
||||
},
|
||||
};
|
||||
|
||||
interface VideoVariantFormProps {
|
||||
dataState: VideoVariant;
|
||||
onUpdateField: FieldUpdaterFunc;
|
||||
@@ -79,6 +79,7 @@ export default function VideoVariantForm({
|
||||
};
|
||||
const handleScaledWidthChanged = (args: UpdateArgs) => {
|
||||
const value = Number(args.value);
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
if (isNaN(value)) {
|
||||
return;
|
||||
}
|
||||
@@ -86,6 +87,7 @@ export default function VideoVariantForm({
|
||||
};
|
||||
const handleScaledHeightChanged = (args: UpdateArgs) => {
|
||||
const value = Number(args.value);
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
if (isNaN(value)) {
|
||||
return;
|
||||
}
|
||||
@@ -108,124 +110,123 @@ export default function VideoVariantForm({
|
||||
|
||||
return (
|
||||
<div className="config-variant-form">
|
||||
<div className="section-intro">
|
||||
<p className="description">
|
||||
Say a thing here about how this all works. Read more{' '}
|
||||
<a href="https://owncast.online/docs/configuration/">here</a>.
|
||||
<br />
|
||||
<br />
|
||||
</div>
|
||||
</p>
|
||||
|
||||
{/* ENCODER PRESET FIELD */}
|
||||
<div className="field">
|
||||
<div className="form-component">
|
||||
<CPUUsageSelector
|
||||
defaultValue={dataState.cpuUsageLevel}
|
||||
onChange={handleVideoCpuUsageLevelChange}
|
||||
/>
|
||||
{selectedPresetNote ? (
|
||||
<span className="selected-value-note">{selectedPresetNote}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* VIDEO PASSTHROUGH FIELD */}
|
||||
<div style={{ display: 'none' }}>
|
||||
<div className="field">
|
||||
<p className="label">
|
||||
<InfoTip tip={VIDEO_VARIANT_DEFAULTS.videoPassthrough.tip} />
|
||||
Use Video Passthrough?
|
||||
</p>
|
||||
<div className="form-component">
|
||||
<Switch
|
||||
defaultChecked={dataState.videoPassthrough}
|
||||
checked={dataState.videoPassthrough}
|
||||
onChange={handleVideoPassChange}
|
||||
checkedChildren="Yes"
|
||||
unCheckedChildren="No"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* VIDEO BITRATE FIELD */}
|
||||
<div className={`field ${dataState.videoPassthrough ? 'disabled' : ''}`}>
|
||||
<p className="label">
|
||||
<InfoTip tip={VIDEO_VARIANT_DEFAULTS.videoBitrate.tip} />
|
||||
Video Bitrate:
|
||||
</p>
|
||||
<div className="form-component">
|
||||
<Slider
|
||||
tipFormatter={value => `${value} ${videoBRUnit}`}
|
||||
disabled={dataState.videoPassthrough === true}
|
||||
defaultValue={dataState.videoBitrate}
|
||||
value={dataState.videoBitrate}
|
||||
onChange={handleVideoBitrateChange}
|
||||
step={videoBitrateDefaults.incrementBy}
|
||||
min={videoBRMin}
|
||||
max={videoBRMax}
|
||||
marks={{
|
||||
[videoBRMin]: `${videoBRMin} ${videoBRUnit}`,
|
||||
[videoBRMax]: `${videoBRMax} ${videoBRUnit}`,
|
||||
}}
|
||||
/>
|
||||
{selectedVideoBRnote ? (
|
||||
<span className="selected-value-note">{selectedVideoBRnote}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Collapse>
|
||||
<Panel header="Advanced Settings" key="1">
|
||||
<div className="section-intro">
|
||||
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 <strong>or</strong> the height to keep your aspect ratio.
|
||||
</div>
|
||||
<div className="field">
|
||||
<TextField
|
||||
type="number"
|
||||
{...VIDEO_VARIANT_DEFAULTS.scaledWidth}
|
||||
value={dataState.scaledWidth}
|
||||
onChange={handleScaledWidthChanged}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<TextField
|
||||
type="number"
|
||||
{...VIDEO_VARIANT_DEFAULTS.scaledHeight}
|
||||
value={dataState.scaledHeight}
|
||||
onChange={handleScaledHeightChanged}
|
||||
<div className="row">
|
||||
<div>
|
||||
{/* ENCODER PRESET FIELD */}
|
||||
<div className="form-module cpu-usage-container">
|
||||
<CPUUsageSelector
|
||||
defaultValue={dataState.cpuUsageLevel}
|
||||
onChange={handleVideoCpuUsageLevelChange}
|
||||
/>
|
||||
{selectedPresetNote && (
|
||||
<span className="selected-value-note">{selectedPresetNote}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* FRAME RATE FIELD */}
|
||||
<div className="field">
|
||||
{/* VIDEO PASSTHROUGH FIELD */}
|
||||
<div style={{ display: 'none' }} className="form-module">
|
||||
<p className="label">
|
||||
<InfoTip tip={VIDEO_VARIANT_DEFAULTS.framerate.tip} />
|
||||
Frame rate:
|
||||
<InfoTip tip={VIDEO_VARIANT_DEFAULTS.videoPassthrough.tip} />
|
||||
Use Video Passthrough?
|
||||
</p>
|
||||
<div className="form-component">
|
||||
<Slider
|
||||
// tooltipVisible
|
||||
tipFormatter={value => `${value} ${framerateUnit}`}
|
||||
defaultValue={dataState.framerate}
|
||||
value={dataState.framerate}
|
||||
onChange={handleFramerateChange}
|
||||
step={framerateDefaults.incrementBy}
|
||||
min={framerateMin}
|
||||
max={framerateMax}
|
||||
marks={{
|
||||
[framerateMin]: `${framerateMin} ${framerateUnit}`,
|
||||
[framerateMax]: `${framerateMax} ${framerateUnit}`,
|
||||
}}
|
||||
{/* todo: change to ToggleSwitch for layout */}
|
||||
<Switch
|
||||
defaultChecked={dataState.videoPassthrough}
|
||||
checked={dataState.videoPassthrough}
|
||||
onChange={handleVideoPassChange}
|
||||
// label="Use Video Passthrough"
|
||||
checkedChildren="Yes"
|
||||
unCheckedChildren="No"
|
||||
/>
|
||||
{selectedFramerateNote ? (
|
||||
<span className="selected-value-note">{selectedFramerateNote}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</Collapse>
|
||||
|
||||
{/* VIDEO BITRATE FIELD */}
|
||||
<div className={`form-module ${dataState.videoPassthrough ? 'disabled' : ''}`}>
|
||||
<Typography.Title level={3} className="section-title">
|
||||
Video Bitrate
|
||||
</Typography.Title>
|
||||
<p className="description">{VIDEO_VARIANT_DEFAULTS.videoBitrate.tip}</p>
|
||||
<div className="segment-slider-container">
|
||||
<Slider
|
||||
tipFormatter={value => `${value} ${videoBRUnit}`}
|
||||
disabled={dataState.videoPassthrough === true}
|
||||
defaultValue={dataState.videoBitrate}
|
||||
value={dataState.videoBitrate}
|
||||
onChange={handleVideoBitrateChange}
|
||||
step={videoBitrateDefaults.incrementBy}
|
||||
min={videoBRMin}
|
||||
max={videoBRMax}
|
||||
marks={{
|
||||
[videoBRMin]: `${videoBRMin} ${videoBRUnit}`,
|
||||
[videoBRMax]: `${videoBRMax} ${videoBRUnit}`,
|
||||
}}
|
||||
/>
|
||||
{selectedVideoBRnote && (
|
||||
<span className="selected-value-note">{selectedVideoBRnote}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Collapse className="advanced-settings">
|
||||
<Panel header="Advanced Settings" key="1">
|
||||
<div className="section-intro">
|
||||
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 <strong>or</strong> the height to keep your aspect ratio.
|
||||
</div>
|
||||
<div className="field">
|
||||
<TextField
|
||||
type="number"
|
||||
{...VIDEO_VARIANT_DEFAULTS.scaledWidth}
|
||||
value={dataState.scaledWidth}
|
||||
onChange={handleScaledWidthChanged}
|
||||
/>
|
||||
</div>
|
||||
<div className="field">
|
||||
<TextField
|
||||
type="number"
|
||||
{...VIDEO_VARIANT_DEFAULTS.scaledHeight}
|
||||
value={dataState.scaledHeight}
|
||||
onChange={handleScaledHeightChanged}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* FRAME RATE FIELD */}
|
||||
<div className="field">
|
||||
<p className="label">
|
||||
<InfoTip tip={VIDEO_VARIANT_DEFAULTS.framerate.tip} />
|
||||
Frame rate:
|
||||
</p>
|
||||
<div className="segment-slider-container form-component">
|
||||
<Slider
|
||||
// tooltipVisible
|
||||
tipFormatter={value => `${value} ${framerateUnit}`}
|
||||
defaultValue={dataState.framerate}
|
||||
value={dataState.framerate}
|
||||
onChange={handleFramerateChange}
|
||||
step={framerateDefaults.incrementBy}
|
||||
min={framerateMin}
|
||||
max={framerateMax}
|
||||
marks={{
|
||||
[framerateMin]: `${framerateMin} ${framerateUnit}`,
|
||||
[framerateMax]: `${framerateMax} ${framerateUnit}`,
|
||||
}}
|
||||
/>
|
||||
{selectedFramerateNote ? (
|
||||
<span className="selected-value-note">{selectedFramerateNote}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</Collapse>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,10 +12,17 @@ import VideoVariantForm from './video-variant-form';
|
||||
import {
|
||||
API_VIDEO_VARIANTS,
|
||||
DEFAULT_VARIANT_STATE,
|
||||
SUCCESS_STATES,
|
||||
RESET_TIMEOUT,
|
||||
postConfigUpdateToAPI,
|
||||
} from '../../utils/config-constants';
|
||||
import {
|
||||
createInputStatus,
|
||||
StatusState,
|
||||
STATUS_ERROR,
|
||||
STATUS_PROCESSING,
|
||||
STATUS_SUCCESS,
|
||||
} from '../../utils/input-statuses';
|
||||
import FormStatusIndicator from './form-status-indicator';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
@@ -36,8 +43,7 @@ export default function CurrentVariantsTable() {
|
||||
// current data inside modal
|
||||
const [modalDataState, setModalDataState] = useState(DEFAULT_VARIANT_STATE);
|
||||
|
||||
const [submitStatus, setSubmitStatus] = useState(null);
|
||||
const [submitStatusMessage, setSubmitStatusMessage] = useState('');
|
||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||
|
||||
const serverStatusData = useContext(ServerStatusContext);
|
||||
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
||||
@@ -52,7 +58,6 @@ export default function CurrentVariantsTable() {
|
||||
|
||||
const resetStates = () => {
|
||||
setSubmitStatus(null);
|
||||
setSubmitStatusMessage('');
|
||||
resetTimer = null;
|
||||
clearTimeout(resetTimer);
|
||||
};
|
||||
@@ -65,6 +70,8 @@ export default function CurrentVariantsTable() {
|
||||
|
||||
// posts all the variants at once as an array obj
|
||||
const postUpdateToAPI = async (postValue: any) => {
|
||||
setSubmitStatus(createInputStatus(STATUS_PROCESSING));
|
||||
|
||||
await postConfigUpdateToAPI({
|
||||
apiPath: API_VIDEO_VARIANTS,
|
||||
data: { value: postValue },
|
||||
@@ -79,8 +86,7 @@ export default function CurrentVariantsTable() {
|
||||
setModalProcessing(false);
|
||||
handleModalCancel();
|
||||
|
||||
setSubmitStatus('success');
|
||||
setSubmitStatusMessage('Variants updated.');
|
||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Variants updated'));
|
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||
|
||||
if (serverStatusData.online) {
|
||||
@@ -90,8 +96,7 @@ export default function CurrentVariantsTable() {
|
||||
}
|
||||
},
|
||||
onError: (message: string) => {
|
||||
setSubmitStatus('error');
|
||||
setSubmitStatusMessage(message);
|
||||
setSubmitStatus(createInputStatus(STATUS_ERROR, message));
|
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||
},
|
||||
});
|
||||
@@ -112,7 +117,7 @@ export default function CurrentVariantsTable() {
|
||||
postUpdateToAPI(postData);
|
||||
};
|
||||
|
||||
const handleDeleteVariant = index => {
|
||||
const handleDeleteVariant = (index: number) => {
|
||||
const postData = [...videoQualityVariants];
|
||||
postData.splice(index, 1);
|
||||
postUpdateToAPI(postData);
|
||||
@@ -125,9 +130,6 @@ export default function CurrentVariantsTable() {
|
||||
});
|
||||
};
|
||||
|
||||
const { icon: newStatusIcon = null, message: newStatusMessage = '' } =
|
||||
SUCCESS_STATES[submitStatus] || {};
|
||||
|
||||
const videoQualityColumns: ColumnsType<VideoVariant> = [
|
||||
{
|
||||
title: 'Video bitrate',
|
||||
@@ -176,12 +178,6 @@ export default function CurrentVariantsTable() {
|
||||
},
|
||||
];
|
||||
|
||||
const statusMessage = (
|
||||
<div className={`status-message ${submitStatus || ''}`}>
|
||||
{newStatusIcon} {newStatusMessage} {submitStatusMessage}
|
||||
</div>
|
||||
);
|
||||
|
||||
const videoQualityVariantData = videoQualityVariants.map((variant, index) => ({
|
||||
key: index + 1,
|
||||
...variant,
|
||||
@@ -189,9 +185,11 @@ export default function CurrentVariantsTable() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title level={3}>Stream output</Title>
|
||||
<Title level={3} className="section-title">
|
||||
Stream output
|
||||
</Title>
|
||||
|
||||
{statusMessage}
|
||||
<FormStatusIndicator status={submitStatus} />
|
||||
|
||||
<Table
|
||||
className="variants-table"
|
||||
@@ -207,10 +205,11 @@ export default function CurrentVariantsTable() {
|
||||
onOk={handleModalOk}
|
||||
onCancel={handleModalCancel}
|
||||
confirmLoading={modalProcessing}
|
||||
width={900}
|
||||
>
|
||||
<VideoVariantForm dataState={{ ...modalDataState }} onUpdateField={handleUpdateField} />
|
||||
|
||||
{statusMessage}
|
||||
<FormStatusIndicator status={submitStatus} />
|
||||
</Modal>
|
||||
<br />
|
||||
<Button
|
||||
|
||||
@@ -49,7 +49,7 @@ export default function MainLayout(props) {
|
||||
const { Header, Footer, Content, Sider } = Layout;
|
||||
const { SubMenu } = Menu;
|
||||
|
||||
const [upgradeVersion, setUpgradeVersion] = useState(null);
|
||||
const [upgradeVersion, setUpgradeVersion] = useState('');
|
||||
const checkForUpgrade = async () => {
|
||||
try {
|
||||
const result = await upgradeVersionAvailable(versionNumber);
|
||||
@@ -80,7 +80,8 @@ export default function MainLayout(props) {
|
||||
});
|
||||
|
||||
const upgradeMenuItemStyle = upgradeVersion ? 'block' : 'none';
|
||||
const upgradeVersionString = upgradeVersion || '';
|
||||
const upgradeVersionString = `${upgradeVersion}` || '';
|
||||
const upgradeMessage = `Upgrade to v${upgradeVersionString}`;
|
||||
|
||||
const clearAlertMessage = () => {
|
||||
alertMessage.setMessage(null);
|
||||
@@ -123,10 +124,10 @@ export default function MainLayout(props) {
|
||||
|
||||
<Sider width={240} className="side-nav">
|
||||
<Menu
|
||||
theme="dark"
|
||||
defaultSelectedKeys={[route.substring(1) || 'home']}
|
||||
defaultOpenKeys={['current-stream-menu', 'utilities-menu', 'configuration']}
|
||||
mode="inline"
|
||||
className="menu-container"
|
||||
>
|
||||
<h1 className="owncast-title">
|
||||
<span className="logo-container">
|
||||
@@ -150,13 +151,6 @@ export default function MainLayout(props) {
|
||||
<Menu.Item key="config-public-details">
|
||||
<Link href="/config-public-details">General</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="config-social-items">
|
||||
<Link href="/config-social-items">Social Links</Link>
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Item key="config-page-content">
|
||||
<Link href="/config-page-content">Page Content</Link>
|
||||
</Menu.Item>
|
||||
|
||||
<Menu.Item key="config-server-details">
|
||||
<Link href="/config-server-details">Server Setup</Link>
|
||||
@@ -177,9 +171,7 @@ export default function MainLayout(props) {
|
||||
<Link href="/logs">Logs</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="upgrade" style={{ display: upgradeMenuItemStyle }}>
|
||||
<Link href="/upgrade">
|
||||
<a>Upgrade to v{upgradeVersionString}</a>
|
||||
</Link>
|
||||
<Link href="/upgrade">{upgradeMessage}</Link>
|
||||
</Menu.Item>
|
||||
</SubMenu>
|
||||
<SubMenu key="integrations-menu" icon={<ExperimentOutlined />} title="Integrations">
|
||||
|
||||
Reference in New Issue
Block a user