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:
1
web/.github/workflows/linter.yml
vendored
1
web/.github/workflows/linter.yml
vendored
@@ -20,4 +20,3 @@ jobs:
|
|||||||
config-path: '.eslintrc.js'
|
config-path: '.eslintrc.js'
|
||||||
ignore-path: '.eslintignore'
|
ignore-path: '.eslintignore'
|
||||||
extensions: 'ts,tsx,js,jsx'
|
extensions: 'ts,tsx,js,jsx'
|
||||||
extra-args: '--max-warnings=0'
|
|
||||||
@@ -19,8 +19,11 @@ const TOOLTIPS = {
|
|||||||
4: 'high',
|
4: 'high',
|
||||||
5: 'highest',
|
5: 'highest',
|
||||||
};
|
};
|
||||||
|
interface Props {
|
||||||
export default function CPUUsageSelector({ defaultValue, onChange }) {
|
defaultValue: number;
|
||||||
|
onChange: (arg: number) => void;
|
||||||
|
}
|
||||||
|
export default function CPUUsageSelector({ defaultValue, onChange }: Props) {
|
||||||
const [selectedOption, setSelectedOption] = useState(null);
|
const [selectedOption, setSelectedOption] = useState(null);
|
||||||
|
|
||||||
const serverStatusData = useContext(ServerStatusContext);
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
@@ -42,10 +45,14 @@ export default function CPUUsageSelector({ defaultValue, onChange }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="config-video-segements-conatiner">
|
<div className="config-video-segements-conatiner">
|
||||||
<Title level={3}>CPU Usage</Title>
|
<Title level={3} className="section-title">
|
||||||
<p>There are trade-offs when considering CPU usage blah blah more wording here.</p>
|
CPU Usage
|
||||||
<br />
|
</Title>
|
||||||
|
<p className="description">
|
||||||
|
There are trade-offs when considering CPU usage blah blah more wording here.
|
||||||
|
</p>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<div className="segment-slider-container">
|
<div className="segment-slider-container">
|
||||||
<Slider
|
<Slider
|
||||||
tipFormatter={value => TOOLTIPS[value]}
|
tipFormatter={value => TOOLTIPS[value]}
|
||||||
|
|||||||
@@ -33,9 +33,11 @@ export default function EditYPDetails() {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="config-directory-details-form">
|
<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{' '}
|
Would you like to appear in the{' '}
|
||||||
<a href="https://directory.owncast.online" target="_blank" rel="noreferrer">
|
<a href="https://directory.owncast.online" target="_blank" rel="noreferrer">
|
||||||
<strong>Owncast Directory</strong>
|
<strong>Owncast Directory</strong>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import React, { useState, useContext, useEffect } from 'react';
|
import React, { useState, useContext, useEffect } from 'react';
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
|
||||||
import TextFieldWithSubmit, {
|
import TextFieldWithSubmit, {
|
||||||
TEXTFIELD_TYPE_TEXTAREA,
|
TEXTFIELD_TYPE_TEXTAREA,
|
||||||
TEXTFIELD_TYPE_URL,
|
TEXTFIELD_TYPE_URL,
|
||||||
@@ -12,9 +14,14 @@ import {
|
|||||||
TEXTFIELD_PROPS_SERVER_SUMMARY,
|
TEXTFIELD_PROPS_SERVER_SUMMARY,
|
||||||
TEXTFIELD_PROPS_LOGO,
|
TEXTFIELD_PROPS_LOGO,
|
||||||
API_YP_SWITCH,
|
API_YP_SWITCH,
|
||||||
|
FIELD_PROPS_YP,
|
||||||
|
FIELD_PROPS_NSFW,
|
||||||
} from '../../utils/config-constants';
|
} from '../../utils/config-constants';
|
||||||
|
|
||||||
import { UpdateArgs } from '../../types/config-section';
|
import { UpdateArgs } from '../../types/config-section';
|
||||||
|
import ToggleSwitch from './form-toggleswitch-with-submit';
|
||||||
|
|
||||||
|
const { Title } = Typography;
|
||||||
|
|
||||||
export default function EditInstanceDetails() {
|
export default function EditInstanceDetails() {
|
||||||
const [formDataValues, setFormDataValues] = useState(null);
|
const [formDataValues, setFormDataValues] = useState(null);
|
||||||
@@ -22,6 +29,7 @@ export default function EditInstanceDetails() {
|
|||||||
const { serverConfig } = serverStatusData || {};
|
const { serverConfig } = serverStatusData || {};
|
||||||
|
|
||||||
const { instanceDetails, yp } = serverConfig;
|
const { instanceDetails, yp } = serverConfig;
|
||||||
|
const { instanceUrl } = yp;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFormDataValues({
|
setFormDataValues({
|
||||||
@@ -53,40 +61,74 @@ export default function EditInstanceDetails() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const hasInstanceUrl = instanceUrl !== '';
|
||||||
<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}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextFieldWithSubmit
|
return (
|
||||||
fieldName="name"
|
<div className="edit-general-settings">
|
||||||
{...TEXTFIELD_PROPS_SERVER_NAME}
|
<Title level={3} className="section-title">
|
||||||
value={formDataValues.name}
|
Configure Instance Details
|
||||||
initialValue={instanceDetails.name}
|
</Title>
|
||||||
onChange={handleFieldChange}
|
<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
|
<ToggleSwitch
|
||||||
fieldName="summary"
|
fieldName="nsfw"
|
||||||
{...TEXTFIELD_PROPS_SERVER_SUMMARY}
|
useSubmit
|
||||||
type={TEXTFIELD_TYPE_TEXTAREA}
|
{...FIELD_PROPS_NSFW}
|
||||||
value={formDataValues.summary}
|
checked={formDataValues.nsfw}
|
||||||
initialValue={instanceDetails.summary}
|
disabled={!hasInstanceUrl}
|
||||||
onChange={handleFieldChange}
|
|
||||||
/>
|
|
||||||
<TextFieldWithSubmit
|
|
||||||
fieldName="logo"
|
|
||||||
{...TEXTFIELD_PROPS_LOGO}
|
|
||||||
value={formDataValues.logo}
|
|
||||||
initialValue={instanceDetails.logo}
|
|
||||||
onChange={handleFieldChange}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,33 +1,34 @@
|
|||||||
|
// EDIT CUSTOM DETAILS ON YOUR PAGE
|
||||||
import React, { useState, useEffect, useContext } from 'react';
|
import React, { useState, useEffect, useContext } from 'react';
|
||||||
import { Typography, Button } from 'antd';
|
import { Typography, Button } from 'antd';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import MarkdownIt from 'markdown-it';
|
import MarkdownIt from 'markdown-it';
|
||||||
|
|
||||||
import { ServerStatusContext } from '../utils/server-status-context';
|
import { ServerStatusContext } from '../../utils/server-status-context';
|
||||||
import {
|
import {
|
||||||
postConfigUpdateToAPI,
|
postConfigUpdateToAPI,
|
||||||
RESET_TIMEOUT,
|
RESET_TIMEOUT,
|
||||||
API_CUSTOM_CONTENT,
|
API_CUSTOM_CONTENT,
|
||||||
} from '../utils/config-constants';
|
} from '../../utils/config-constants';
|
||||||
import {
|
import {
|
||||||
createInputStatus,
|
createInputStatus,
|
||||||
StatusState,
|
StatusState,
|
||||||
STATUS_ERROR,
|
STATUS_ERROR,
|
||||||
STATUS_PROCESSING,
|
STATUS_PROCESSING,
|
||||||
STATUS_SUCCESS,
|
STATUS_SUCCESS,
|
||||||
} from '../utils/input-statuses';
|
} from '../../utils/input-statuses';
|
||||||
import 'react-markdown-editor-lite/lib/index.css';
|
import FormStatusIndicator from './form-status-indicator';
|
||||||
import FormStatusIndicator from '../components/config/form-status-indicator';
|
|
||||||
|
|
||||||
const { Title } = Typography;
|
import 'react-markdown-editor-lite/lib/index.css';
|
||||||
|
|
||||||
const mdParser = new MarkdownIt(/* Markdown-it options */);
|
const mdParser = new MarkdownIt(/* Markdown-it options */);
|
||||||
|
|
||||||
const MdEditor = dynamic(() => import('react-markdown-editor-lite'), {
|
const MdEditor = dynamic(() => import('react-markdown-editor-lite'), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function PageContentEditor() {
|
const { Title } = Typography;
|
||||||
|
|
||||||
|
export default function EditPageContent() {
|
||||||
const [content, setContent] = useState('');
|
const [content, setContent] = useState('');
|
||||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||||
const [hasChanged, setHasChanged] = useState(false);
|
const [hasChanged, setHasChanged] = useState(false);
|
||||||
@@ -83,10 +84,12 @@ export default function PageContentEditor() {
|
|||||||
}, [instanceDetails]);
|
}, [instanceDetails]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="config-page-content-form">
|
<div className="edit-page-content">
|
||||||
<Title level={2}>Page Content</Title>
|
<Title level={3} className="section-title">
|
||||||
|
Custom Page Content
|
||||||
|
</Title>
|
||||||
|
|
||||||
<p>
|
<p className="description">
|
||||||
Edit the content of your page by using simple{' '}
|
Edit the content of your page by using simple{' '}
|
||||||
<a href="https://www.markdownguide.org/basic-syntax/">Markdown syntax</a>.
|
<a href="https://www.markdownguide.org/basic-syntax/">Markdown syntax</a>.
|
||||||
</p>
|
</p>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { useState, useContext, useEffect } from 'react';
|
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';
|
import { CopyOutlined, RedoOutlined } from '@ant-design/icons';
|
||||||
const { Panel } = Collapse;
|
|
||||||
|
|
||||||
import { TEXTFIELD_TYPE_NUMBER, TEXTFIELD_TYPE_PASSWORD } from './form-textfield';
|
import { TEXTFIELD_TYPE_NUMBER, TEXTFIELD_TYPE_PASSWORD } from './form-textfield';
|
||||||
import TextFieldWithSubmit from './form-textfield-with-submit';
|
import TextFieldWithSubmit from './form-textfield-with-submit';
|
||||||
@@ -15,9 +14,11 @@ import {
|
|||||||
TEXTFIELD_PROPS_STREAM_KEY,
|
TEXTFIELD_PROPS_STREAM_KEY,
|
||||||
TEXTFIELD_PROPS_WEB_PORT,
|
TEXTFIELD_PROPS_WEB_PORT,
|
||||||
} from '../../utils/config-constants';
|
} from '../../utils/config-constants';
|
||||||
import { fetchData, API_YP_RESET } from '../../utils/apis';
|
|
||||||
|
|
||||||
import { UpdateArgs } from '../../types/config-section';
|
import { UpdateArgs } from '../../types/config-section';
|
||||||
|
import ResetYP from './reset-yp';
|
||||||
|
|
||||||
|
const { Panel } = Collapse;
|
||||||
|
|
||||||
export default function EditInstanceDetails() {
|
export default function EditInstanceDetails() {
|
||||||
const [formDataValues, setFormDataValues] = useState(null);
|
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() {
|
function generateStreamKey() {
|
||||||
let key = '';
|
let key = '';
|
||||||
for (let i = 0; i < 3; i += 1) {
|
for (let i = 0; i < 3; i += 1) {
|
||||||
@@ -172,13 +138,14 @@ export default function EditInstanceDetails() {
|
|||||||
onChange={handleFieldChange}
|
onChange={handleFieldChange}
|
||||||
onSubmit={showConfigurationRestartMessage}
|
onSubmit={showConfigurationRestartMessage}
|
||||||
/>
|
/>
|
||||||
<Collapse>
|
|
||||||
<Panel header="Advanced Settings" key="1">
|
{yp.enabled && (
|
||||||
<div className="form-fields">
|
<Collapse className="advanced-settings">
|
||||||
|
<Panel header="Advanced Settings" key="1">
|
||||||
<ResetYP />
|
<ResetYP />
|
||||||
</div>
|
</Panel>
|
||||||
</Panel>
|
</Collapse>
|
||||||
</Collapse>
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,43 +165,36 @@ export default function EditSocialLinks() {
|
|||||||
|
|
||||||
const socialHandlesColumns: ColumnsType<SocialHandle> = [
|
const socialHandlesColumns: ColumnsType<SocialHandle> = [
|
||||||
{
|
{
|
||||||
title: '#',
|
title: 'Social Link',
|
||||||
dataIndex: 'key',
|
dataIndex: '',
|
||||||
key: 'key',
|
key: 'combo',
|
||||||
},
|
render: (data, record) => {
|
||||||
{
|
const { platform, url } = record;
|
||||||
title: 'Platform',
|
|
||||||
dataIndex: 'platform',
|
|
||||||
key: 'platform',
|
|
||||||
render: (platform: string) => {
|
|
||||||
const platformInfo = availableIconsList.find(item => item.key === platform);
|
const platformInfo = availableIconsList.find(item => item.key === platform);
|
||||||
if (!platformInfo) {
|
if (!platformInfo) {
|
||||||
return platform;
|
return platform;
|
||||||
}
|
}
|
||||||
const { icon, platform: platformName } = platformInfo;
|
const { icon, platform: platformName } = platformInfo;
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="social-handle-cell">
|
||||||
<span className="option-icon">
|
<span className="option-icon">
|
||||||
<img src={`${NEXT_PUBLIC_API_HOST}${icon}`} alt="" className="option-icon" />
|
<img src={`${NEXT_PUBLIC_API_HOST}${icon}`} alt="" className="option-icon" />
|
||||||
</span>
|
</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: '',
|
title: '',
|
||||||
dataIndex: '',
|
dataIndex: '',
|
||||||
key: 'edit',
|
key: 'edit',
|
||||||
render: (data, record, index) => {
|
render: (data, record, index) => {
|
||||||
return (
|
return (
|
||||||
<span className="actions">
|
<div className="actions">
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -219,7 +212,7 @@ export default function EditSocialLinks() {
|
|||||||
size="small"
|
size="small"
|
||||||
onClick={() => handleDeleteItem(index)}
|
onClick={() => handleDeleteItem(index)}
|
||||||
/>
|
/>
|
||||||
</span>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -231,12 +224,17 @@ export default function EditSocialLinks() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="social-links-edit-container">
|
<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} />
|
<FormStatusIndicator status={submitStatus} />
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
className="dataTable"
|
className="social-handles-table"
|
||||||
pagination={false}
|
pagination={false}
|
||||||
size="small"
|
size="small"
|
||||||
rowKey={record => record.url}
|
rowKey={record => record.url}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
import TextField from './form-textfield';
|
import TextField from './form-textfield';
|
||||||
import FormStatusIndicator from './form-status-indicator';
|
import FormStatusIndicator from './form-status-indicator';
|
||||||
import { isValidUrl } from '../../utils/urls';
|
import { isValidUrl } from '../../utils/urls';
|
||||||
|
// import ToggleSwitch from './form-toggleswitch-with-submit';
|
||||||
|
|
||||||
const { Panel } = Collapse;
|
const { Panel } = Collapse;
|
||||||
|
|
||||||
@@ -135,6 +136,7 @@ export default function EditStorage() {
|
|||||||
|
|
||||||
const containerClass = classNames({
|
const containerClass = classNames({
|
||||||
'edit-storage-container': true,
|
'edit-storage-container': true,
|
||||||
|
'form-module': true,
|
||||||
enabled: shouldDisplayForm,
|
enabled: shouldDisplayForm,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -143,6 +145,12 @@ export default function EditStorage() {
|
|||||||
return (
|
return (
|
||||||
<div className={containerClass}>
|
<div className={containerClass}>
|
||||||
<div className="enable-switch">
|
<div className="enable-switch">
|
||||||
|
{/* <ToggleSwitch
|
||||||
|
fieldName="enabled"
|
||||||
|
label="Storage Enabled"
|
||||||
|
checked={formDataValues.enabled}
|
||||||
|
onChange={handleSwitchChange}
|
||||||
|
/> */}
|
||||||
<Switch
|
<Switch
|
||||||
checked={formDataValues.enabled}
|
checked={formDataValues.enabled}
|
||||||
defaultChecked={formDataValues.enabled}
|
defaultChecked={formDataValues.enabled}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import {
|
|||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
|
const TAG_COLOR = '#5a67d8';
|
||||||
|
|
||||||
export default function EditInstanceTags() {
|
export default function EditInstanceTags() {
|
||||||
const [newTagInput, setNewTagInput] = useState<string>('');
|
const [newTagInput, setNewTagInput] = useState<string>('');
|
||||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||||
@@ -100,8 +102,12 @@ export default function EditInstanceTags() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tag-editor-container">
|
<div className="tag-editor-container">
|
||||||
<Title level={3}>Add Tags</Title>
|
<Title level={3} className="section-title">
|
||||||
<p>This is a great way to categorize your Owncast server on the Directory!</p>
|
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">
|
<div className="tag-current-tags">
|
||||||
{tags.map((tag, index) => {
|
{tags.map((tag, index) => {
|
||||||
@@ -109,7 +115,7 @@ export default function EditInstanceTags() {
|
|||||||
handleDeleteTag(index);
|
handleDeleteTag(index);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Tag closable onClose={handleClose} key={`tag-${tag}-${index}`}>
|
<Tag closable onClose={handleClose} color={TAG_COLOR} key={`tag-${tag}-${index}`}>
|
||||||
{tag}
|
{tag}
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="textfield-container lower-container">
|
<div className="formfield-container lower-container">
|
||||||
<p className="label-spacer" />
|
<p className="label-spacer" />
|
||||||
<div className="lower-content">
|
<div className="lower-content">
|
||||||
<div className="field-tip">{tip}</div>
|
<div className="field-tip">{tip}</div>
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ export default function TextField(props: TextFieldProps) {
|
|||||||
const { type: statusType } = status || {};
|
const { type: statusType } = status || {};
|
||||||
|
|
||||||
const containerClass = classNames({
|
const containerClass = classNames({
|
||||||
|
'formfield-container': true,
|
||||||
'textfield-container': true,
|
'textfield-container': true,
|
||||||
[`type-${type}`]: true,
|
[`type-${type}`]: true,
|
||||||
required,
|
required,
|
||||||
@@ -117,7 +118,7 @@ export default function TextField(props: TextFieldProps) {
|
|||||||
<div className={containerClass}>
|
<div className={containerClass}>
|
||||||
{label ? (
|
{label ? (
|
||||||
<div className="label-side">
|
<div className="label-side">
|
||||||
<label htmlFor={fieldId} className="textfield-label">
|
<label htmlFor={fieldId} className="formfield-label">
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,10 +141,7 @@ export default function TextField(props: TextFieldProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<FormStatusIndicator status={status} />
|
<FormStatusIndicator status={status} />
|
||||||
<p className="field-tip">
|
<p className="field-tip">{tip}</p>
|
||||||
{tip}
|
|
||||||
{/* <InfoTip tip={tip} /> */}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -151,9 +149,7 @@ export default function TextField(props: TextFieldProps) {
|
|||||||
|
|
||||||
TextField.defaultProps = {
|
TextField.defaultProps = {
|
||||||
className: '',
|
className: '',
|
||||||
// configPath: '',
|
|
||||||
disabled: false,
|
disabled: false,
|
||||||
// initialValue: '',
|
|
||||||
label: '',
|
label: '',
|
||||||
maxLength: 255,
|
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 React, { useState, useContext } from 'react';
|
||||||
import { Switch } from 'antd';
|
import { Switch } from 'antd';
|
||||||
import {
|
import {
|
||||||
@@ -12,7 +15,6 @@ import FormStatusIndicator from './form-status-indicator';
|
|||||||
import { RESET_TIMEOUT, postConfigUpdateToAPI } from '../../utils/config-constants';
|
import { RESET_TIMEOUT, postConfigUpdateToAPI } from '../../utils/config-constants';
|
||||||
|
|
||||||
import { ServerStatusContext } from '../../utils/server-status-context';
|
import { ServerStatusContext } from '../../utils/server-status-context';
|
||||||
import InfoTip from '../info-tip';
|
|
||||||
|
|
||||||
interface ToggleSwitchProps {
|
interface ToggleSwitchProps {
|
||||||
apiPath: string;
|
apiPath: string;
|
||||||
@@ -23,8 +25,9 @@ interface ToggleSwitchProps {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
label?: string;
|
label?: string;
|
||||||
tip?: string;
|
tip?: string;
|
||||||
|
useSubmit?: boolean;
|
||||||
|
onChange?: (arg: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ToggleSwitch(props: ToggleSwitchProps) {
|
export default function ToggleSwitch(props: ToggleSwitchProps) {
|
||||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||||
|
|
||||||
@@ -33,7 +36,17 @@ export default function ToggleSwitch(props: ToggleSwitchProps) {
|
|||||||
const serverStatusData = useContext(ServerStatusContext);
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
const { setFieldInConfigState } = serverStatusData || {};
|
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 = () => {
|
const resetStates = () => {
|
||||||
setSubmitStatus(null);
|
setSubmitStatus(null);
|
||||||
@@ -42,41 +55,52 @@ export default function ToggleSwitch(props: ToggleSwitchProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = async (isChecked: boolean) => {
|
const handleChange = async (isChecked: boolean) => {
|
||||||
setSubmitStatus(createInputStatus(STATUS_PROCESSING));
|
if (useSubmit) {
|
||||||
|
setSubmitStatus(createInputStatus(STATUS_PROCESSING));
|
||||||
|
|
||||||
await postConfigUpdateToAPI({
|
await postConfigUpdateToAPI({
|
||||||
apiPath,
|
apiPath,
|
||||||
data: { value: isChecked },
|
data: { value: isChecked },
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setFieldInConfigState({ fieldName, value: isChecked, path: configPath });
|
setFieldInConfigState({ fieldName, value: isChecked, path: configPath });
|
||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS));
|
setSubmitStatus(createInputStatus(STATUS_SUCCESS));
|
||||||
},
|
},
|
||||||
onError: (message: string) => {
|
onError: (message: string) => {
|
||||||
setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${message}`));
|
setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${message}`));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
|
}
|
||||||
|
if (onChange) {
|
||||||
|
onChange(isChecked);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loading = submitStatus !== null && submitStatus.type === STATUS_PROCESSING;
|
const loading = submitStatus !== null && submitStatus.type === STATUS_PROCESSING;
|
||||||
return (
|
return (
|
||||||
<div className="toggleswitch-container">
|
<div className="formfield-container toggleswitch-container">
|
||||||
<div className="toggleswitch">
|
{label && (
|
||||||
<Switch
|
<div className="label-side">
|
||||||
className={`switch field-${fieldName}`}
|
<span className="formfield-label">{label}</span>
|
||||||
loading={loading}
|
</div>
|
||||||
onChange={handleChange}
|
)}
|
||||||
defaultChecked={checked}
|
|
||||||
checked={checked}
|
<div className="input-side">
|
||||||
checkedChildren="ON"
|
<div className="input-group">
|
||||||
unCheckedChildren="OFF"
|
<Switch
|
||||||
disabled={disabled}
|
className={`switch field-${fieldName}`}
|
||||||
/>
|
loading={loading}
|
||||||
<span className="label">
|
onChange={handleChange}
|
||||||
{label} <InfoTip tip={tip} />
|
defaultChecked={checked}
|
||||||
</span>
|
checked={checked}
|
||||||
|
checkedChildren="ON"
|
||||||
|
unCheckedChildren="OFF"
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<FormStatusIndicator status={submitStatus} />
|
||||||
|
</div>
|
||||||
|
<p className="field-tip">{tip}</p>
|
||||||
</div>
|
</div>
|
||||||
<FormStatusIndicator status={submitStatus} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -87,4 +111,6 @@ ToggleSwitch.defaultProps = {
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
label: '',
|
label: '',
|
||||||
tip: '',
|
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;
|
const inititalSelected = selectedOption === '' ? null : selectedOption;
|
||||||
return (
|
return (
|
||||||
<div className="social-dropdown-container">
|
<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
|
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.
|
your own name. A logo will not be provided.
|
||||||
</p>
|
</p>
|
||||||
<p className="">
|
<p className="description">
|
||||||
If you DO have a logo, drop it in to the <code>/webroot/img/platformicons</code> directory
|
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
|
and update the <code>/socialHandle.go</code> list. Then restart the server and it will show
|
||||||
up in the list.
|
up in the list.
|
||||||
|
|||||||
@@ -46,9 +46,6 @@ function SegmentToolTip({ value }: SegmentToolTipProps) {
|
|||||||
|
|
||||||
export default function VideoLatency() {
|
export default function VideoLatency() {
|
||||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||||
|
|
||||||
// const [submitStatus, setSubmitStatus] = useState(null);
|
|
||||||
// const [submitStatusMessage, setSubmitStatusMessage] = useState('');
|
|
||||||
const [selectedOption, setSelectedOption] = useState(null);
|
const [selectedOption, setSelectedOption] = useState(null);
|
||||||
|
|
||||||
const serverStatusData = useContext(ServerStatusContext);
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
@@ -68,7 +65,6 @@ export default function VideoLatency() {
|
|||||||
|
|
||||||
const resetStates = () => {
|
const resetStates = () => {
|
||||||
setSubmitStatus(null);
|
setSubmitStatus(null);
|
||||||
// setSubmitStatusMessage('');
|
|
||||||
resetTimer = null;
|
resetTimer = null;
|
||||||
clearTimeout(resetTimer);
|
clearTimeout(resetTimer);
|
||||||
};
|
};
|
||||||
@@ -88,8 +84,6 @@ export default function VideoLatency() {
|
|||||||
});
|
});
|
||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Latency buffer level updated.'));
|
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Latency buffer level updated.'));
|
||||||
|
|
||||||
// setSubmitStatus('success');
|
|
||||||
// setSubmitStatusMessage('Variants updated.');
|
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
if (serverStatusData.online) {
|
if (serverStatusData.online) {
|
||||||
setMessage(
|
setMessage(
|
||||||
@@ -100,8 +94,6 @@ export default function VideoLatency() {
|
|||||||
onError: (message: string) => {
|
onError: (message: string) => {
|
||||||
setSubmitStatus(createInputStatus(STATUS_ERROR, message));
|
setSubmitStatus(createInputStatus(STATUS_ERROR, message));
|
||||||
|
|
||||||
// setSubmitStatus('error');
|
|
||||||
// setSubmitStatusMessage(message);
|
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -113,15 +105,19 @@ export default function VideoLatency() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="config-video-segements-conatiner">
|
<div className="config-video-segements-conatiner">
|
||||||
<Title level={3}>Latency Buffer</Title>
|
<Title level={3} className="section-title">
|
||||||
<p>
|
Latency Buffer
|
||||||
While it's natural to want to keep your latency as low as possible, you may experience
|
</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.
|
reduced error tolerance and stability in some environments the lower you go.
|
||||||
</p>
|
</p>
|
||||||
For interactive live streams you may want to experiment with a lower latency, for
|
<p className="description">
|
||||||
non-interactive broadcasts you may want to increase it.{' '}
|
For interactive live streams you may want to experiment with a lower latency, for
|
||||||
<a href="https://owncast.online/docs/encoding#latency-buffer">Read to learn more.</a>
|
non-interactive broadcasts you may want to increase it.{' '}
|
||||||
<p></p>
|
<a href="https://owncast.online/docs/encoding#latency-buffer">Read to learn more.</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
<div className="segment-slider-container">
|
<div className="segment-slider-container">
|
||||||
<Slider
|
<Slider
|
||||||
tipFormatter={value => <SegmentToolTip value={SLIDER_COMMENTS[value]} />}
|
tipFormatter={value => <SegmentToolTip value={SLIDER_COMMENTS[value]} />}
|
||||||
@@ -132,8 +128,8 @@ export default function VideoLatency() {
|
|||||||
defaultValue={selectedOption}
|
defaultValue={selectedOption}
|
||||||
value={selectedOption}
|
value={selectedOption}
|
||||||
/>
|
/>
|
||||||
|
<FormStatusIndicator status={submitStatus} />
|
||||||
</div>
|
</div>
|
||||||
<FormStatusIndicator status={submitStatus} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
// This content populates the video variant modal, which is spawned from the variants table.
|
// This content populates the video variant modal, which is spawned from the variants table.
|
||||||
import React from 'react';
|
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 { FieldUpdaterFunc, VideoVariant, UpdateArgs } from '../../types/config-section';
|
||||||
import TextField from './form-textfield';
|
import TextField from './form-textfield';
|
||||||
import { DEFAULT_VARIANT_STATE } from '../../utils/config-constants';
|
import { DEFAULT_VARIANT_STATE } from '../../utils/config-constants';
|
||||||
import InfoTip from '../info-tip';
|
import InfoTip from '../info-tip';
|
||||||
import CPUUsageSelector from './cpu-usage';
|
import CPUUsageSelector from './cpu-usage';
|
||||||
|
// import ToggleSwitch from './form-toggleswitch-with-submit';
|
||||||
|
|
||||||
const { Panel } = Collapse;
|
const { Panel } = Collapse;
|
||||||
|
|
||||||
@@ -55,7 +56,6 @@ const VIDEO_VARIANT_DEFAULTS = {
|
|||||||
tip: "Optionally resize this content's height.",
|
tip: "Optionally resize this content's height.",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
interface VideoVariantFormProps {
|
interface VideoVariantFormProps {
|
||||||
dataState: VideoVariant;
|
dataState: VideoVariant;
|
||||||
onUpdateField: FieldUpdaterFunc;
|
onUpdateField: FieldUpdaterFunc;
|
||||||
@@ -79,6 +79,7 @@ export default function VideoVariantForm({
|
|||||||
};
|
};
|
||||||
const handleScaledWidthChanged = (args: UpdateArgs) => {
|
const handleScaledWidthChanged = (args: UpdateArgs) => {
|
||||||
const value = Number(args.value);
|
const value = Number(args.value);
|
||||||
|
// eslint-disable-next-line no-restricted-globals
|
||||||
if (isNaN(value)) {
|
if (isNaN(value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -86,6 +87,7 @@ export default function VideoVariantForm({
|
|||||||
};
|
};
|
||||||
const handleScaledHeightChanged = (args: UpdateArgs) => {
|
const handleScaledHeightChanged = (args: UpdateArgs) => {
|
||||||
const value = Number(args.value);
|
const value = Number(args.value);
|
||||||
|
// eslint-disable-next-line no-restricted-globals
|
||||||
if (isNaN(value)) {
|
if (isNaN(value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -108,124 +110,123 @@ export default function VideoVariantForm({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="config-variant-form">
|
<div className="config-variant-form">
|
||||||
<div className="section-intro">
|
<p className="description">
|
||||||
Say a thing here about how this all works. Read more{' '}
|
Say a thing here about how this all works. Read more{' '}
|
||||||
<a href="https://owncast.online/docs/configuration/">here</a>.
|
<a href="https://owncast.online/docs/configuration/">here</a>.
|
||||||
<br />
|
</p>
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* ENCODER PRESET FIELD */}
|
<div className="row">
|
||||||
<div className="field">
|
<div>
|
||||||
<div className="form-component">
|
{/* ENCODER PRESET FIELD */}
|
||||||
<CPUUsageSelector
|
<div className="form-module cpu-usage-container">
|
||||||
defaultValue={dataState.cpuUsageLevel}
|
<CPUUsageSelector
|
||||||
onChange={handleVideoCpuUsageLevelChange}
|
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}
|
|
||||||
/>
|
/>
|
||||||
|
{selectedPresetNote && (
|
||||||
|
<span className="selected-value-note">{selectedPresetNote}</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* FRAME RATE FIELD */}
|
{/* VIDEO PASSTHROUGH FIELD */}
|
||||||
<div className="field">
|
<div style={{ display: 'none' }} className="form-module">
|
||||||
<p className="label">
|
<p className="label">
|
||||||
<InfoTip tip={VIDEO_VARIANT_DEFAULTS.framerate.tip} />
|
<InfoTip tip={VIDEO_VARIANT_DEFAULTS.videoPassthrough.tip} />
|
||||||
Frame rate:
|
Use Video Passthrough?
|
||||||
</p>
|
</p>
|
||||||
<div className="form-component">
|
<div className="form-component">
|
||||||
<Slider
|
{/* todo: change to ToggleSwitch for layout */}
|
||||||
// tooltipVisible
|
<Switch
|
||||||
tipFormatter={value => `${value} ${framerateUnit}`}
|
defaultChecked={dataState.videoPassthrough}
|
||||||
defaultValue={dataState.framerate}
|
checked={dataState.videoPassthrough}
|
||||||
value={dataState.framerate}
|
onChange={handleVideoPassChange}
|
||||||
onChange={handleFramerateChange}
|
// label="Use Video Passthrough"
|
||||||
step={framerateDefaults.incrementBy}
|
checkedChildren="Yes"
|
||||||
min={framerateMin}
|
unCheckedChildren="No"
|
||||||
max={framerateMax}
|
|
||||||
marks={{
|
|
||||||
[framerateMin]: `${framerateMin} ${framerateUnit}`,
|
|
||||||
[framerateMax]: `${framerateMax} ${framerateUnit}`,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{selectedFramerateNote ? (
|
|
||||||
<span className="selected-value-note">{selectedFramerateNote}</span>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,17 @@ import VideoVariantForm from './video-variant-form';
|
|||||||
import {
|
import {
|
||||||
API_VIDEO_VARIANTS,
|
API_VIDEO_VARIANTS,
|
||||||
DEFAULT_VARIANT_STATE,
|
DEFAULT_VARIANT_STATE,
|
||||||
SUCCESS_STATES,
|
|
||||||
RESET_TIMEOUT,
|
RESET_TIMEOUT,
|
||||||
postConfigUpdateToAPI,
|
postConfigUpdateToAPI,
|
||||||
} from '../../utils/config-constants';
|
} 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;
|
const { Title } = Typography;
|
||||||
|
|
||||||
@@ -36,8 +43,7 @@ export default function CurrentVariantsTable() {
|
|||||||
// current data inside modal
|
// current data inside modal
|
||||||
const [modalDataState, setModalDataState] = useState(DEFAULT_VARIANT_STATE);
|
const [modalDataState, setModalDataState] = useState(DEFAULT_VARIANT_STATE);
|
||||||
|
|
||||||
const [submitStatus, setSubmitStatus] = useState(null);
|
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||||
const [submitStatusMessage, setSubmitStatusMessage] = useState('');
|
|
||||||
|
|
||||||
const serverStatusData = useContext(ServerStatusContext);
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
||||||
@@ -52,7 +58,6 @@ export default function CurrentVariantsTable() {
|
|||||||
|
|
||||||
const resetStates = () => {
|
const resetStates = () => {
|
||||||
setSubmitStatus(null);
|
setSubmitStatus(null);
|
||||||
setSubmitStatusMessage('');
|
|
||||||
resetTimer = null;
|
resetTimer = null;
|
||||||
clearTimeout(resetTimer);
|
clearTimeout(resetTimer);
|
||||||
};
|
};
|
||||||
@@ -65,6 +70,8 @@ export default function CurrentVariantsTable() {
|
|||||||
|
|
||||||
// posts all the variants at once as an array obj
|
// posts all the variants at once as an array obj
|
||||||
const postUpdateToAPI = async (postValue: any) => {
|
const postUpdateToAPI = async (postValue: any) => {
|
||||||
|
setSubmitStatus(createInputStatus(STATUS_PROCESSING));
|
||||||
|
|
||||||
await postConfigUpdateToAPI({
|
await postConfigUpdateToAPI({
|
||||||
apiPath: API_VIDEO_VARIANTS,
|
apiPath: API_VIDEO_VARIANTS,
|
||||||
data: { value: postValue },
|
data: { value: postValue },
|
||||||
@@ -79,8 +86,7 @@ export default function CurrentVariantsTable() {
|
|||||||
setModalProcessing(false);
|
setModalProcessing(false);
|
||||||
handleModalCancel();
|
handleModalCancel();
|
||||||
|
|
||||||
setSubmitStatus('success');
|
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Variants updated'));
|
||||||
setSubmitStatusMessage('Variants updated.');
|
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
|
|
||||||
if (serverStatusData.online) {
|
if (serverStatusData.online) {
|
||||||
@@ -90,8 +96,7 @@ export default function CurrentVariantsTable() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (message: string) => {
|
onError: (message: string) => {
|
||||||
setSubmitStatus('error');
|
setSubmitStatus(createInputStatus(STATUS_ERROR, message));
|
||||||
setSubmitStatusMessage(message);
|
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -112,7 +117,7 @@ export default function CurrentVariantsTable() {
|
|||||||
postUpdateToAPI(postData);
|
postUpdateToAPI(postData);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteVariant = index => {
|
const handleDeleteVariant = (index: number) => {
|
||||||
const postData = [...videoQualityVariants];
|
const postData = [...videoQualityVariants];
|
||||||
postData.splice(index, 1);
|
postData.splice(index, 1);
|
||||||
postUpdateToAPI(postData);
|
postUpdateToAPI(postData);
|
||||||
@@ -125,9 +130,6 @@ export default function CurrentVariantsTable() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const { icon: newStatusIcon = null, message: newStatusMessage = '' } =
|
|
||||||
SUCCESS_STATES[submitStatus] || {};
|
|
||||||
|
|
||||||
const videoQualityColumns: ColumnsType<VideoVariant> = [
|
const videoQualityColumns: ColumnsType<VideoVariant> = [
|
||||||
{
|
{
|
||||||
title: 'Video bitrate',
|
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) => ({
|
const videoQualityVariantData = videoQualityVariants.map((variant, index) => ({
|
||||||
key: index + 1,
|
key: index + 1,
|
||||||
...variant,
|
...variant,
|
||||||
@@ -189,9 +185,11 @@ export default function CurrentVariantsTable() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Title level={3}>Stream output</Title>
|
<Title level={3} className="section-title">
|
||||||
|
Stream output
|
||||||
|
</Title>
|
||||||
|
|
||||||
{statusMessage}
|
<FormStatusIndicator status={submitStatus} />
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
className="variants-table"
|
className="variants-table"
|
||||||
@@ -207,10 +205,11 @@ export default function CurrentVariantsTable() {
|
|||||||
onOk={handleModalOk}
|
onOk={handleModalOk}
|
||||||
onCancel={handleModalCancel}
|
onCancel={handleModalCancel}
|
||||||
confirmLoading={modalProcessing}
|
confirmLoading={modalProcessing}
|
||||||
|
width={900}
|
||||||
>
|
>
|
||||||
<VideoVariantForm dataState={{ ...modalDataState }} onUpdateField={handleUpdateField} />
|
<VideoVariantForm dataState={{ ...modalDataState }} onUpdateField={handleUpdateField} />
|
||||||
|
|
||||||
{statusMessage}
|
<FormStatusIndicator status={submitStatus} />
|
||||||
</Modal>
|
</Modal>
|
||||||
<br />
|
<br />
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export default function MainLayout(props) {
|
|||||||
const { Header, Footer, Content, Sider } = Layout;
|
const { Header, Footer, Content, Sider } = Layout;
|
||||||
const { SubMenu } = Menu;
|
const { SubMenu } = Menu;
|
||||||
|
|
||||||
const [upgradeVersion, setUpgradeVersion] = useState(null);
|
const [upgradeVersion, setUpgradeVersion] = useState('');
|
||||||
const checkForUpgrade = async () => {
|
const checkForUpgrade = async () => {
|
||||||
try {
|
try {
|
||||||
const result = await upgradeVersionAvailable(versionNumber);
|
const result = await upgradeVersionAvailable(versionNumber);
|
||||||
@@ -80,7 +80,8 @@ export default function MainLayout(props) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const upgradeMenuItemStyle = upgradeVersion ? 'block' : 'none';
|
const upgradeMenuItemStyle = upgradeVersion ? 'block' : 'none';
|
||||||
const upgradeVersionString = upgradeVersion || '';
|
const upgradeVersionString = `${upgradeVersion}` || '';
|
||||||
|
const upgradeMessage = `Upgrade to v${upgradeVersionString}`;
|
||||||
|
|
||||||
const clearAlertMessage = () => {
|
const clearAlertMessage = () => {
|
||||||
alertMessage.setMessage(null);
|
alertMessage.setMessage(null);
|
||||||
@@ -123,10 +124,10 @@ export default function MainLayout(props) {
|
|||||||
|
|
||||||
<Sider width={240} className="side-nav">
|
<Sider width={240} className="side-nav">
|
||||||
<Menu
|
<Menu
|
||||||
theme="dark"
|
|
||||||
defaultSelectedKeys={[route.substring(1) || 'home']}
|
defaultSelectedKeys={[route.substring(1) || 'home']}
|
||||||
defaultOpenKeys={['current-stream-menu', 'utilities-menu', 'configuration']}
|
defaultOpenKeys={['current-stream-menu', 'utilities-menu', 'configuration']}
|
||||||
mode="inline"
|
mode="inline"
|
||||||
|
className="menu-container"
|
||||||
>
|
>
|
||||||
<h1 className="owncast-title">
|
<h1 className="owncast-title">
|
||||||
<span className="logo-container">
|
<span className="logo-container">
|
||||||
@@ -150,13 +151,6 @@ export default function MainLayout(props) {
|
|||||||
<Menu.Item key="config-public-details">
|
<Menu.Item key="config-public-details">
|
||||||
<Link href="/config-public-details">General</Link>
|
<Link href="/config-public-details">General</Link>
|
||||||
</Menu.Item>
|
</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">
|
<Menu.Item key="config-server-details">
|
||||||
<Link href="/config-server-details">Server Setup</Link>
|
<Link href="/config-server-details">Server Setup</Link>
|
||||||
@@ -177,9 +171,7 @@ export default function MainLayout(props) {
|
|||||||
<Link href="/logs">Logs</Link>
|
<Link href="/logs">Logs</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="upgrade" style={{ display: upgradeMenuItemStyle }}>
|
<Menu.Item key="upgrade" style={{ display: upgradeMenuItemStyle }}>
|
||||||
<Link href="/upgrade">
|
<Link href="/upgrade">{upgradeMessage}</Link>
|
||||||
<a>Upgrade to v{upgradeVersionString}</a>
|
|
||||||
</Link>
|
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</SubMenu>
|
</SubMenu>
|
||||||
<SubMenu key="integrations-menu" icon={<ExperimentOutlined />} title="Integrations">
|
<SubMenu key="integrations-menu" icon={<ExperimentOutlined />} title="Integrations">
|
||||||
|
|||||||
29
web/package-lock.json
generated
29
web/package-lock.json
generated
@@ -1587,6 +1587,12 @@
|
|||||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
|
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/highlight.js": {
|
||||||
|
"version": "9.12.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-9.12.4.tgz",
|
||||||
|
"integrity": "sha512-t2szdkwmg2JJyuCM20e8kR2X59WCE5Zkl4bzm1u1Oukjm79zpbiAv+QjnwLnuuV0WHEcX2NgUItu0pAMKuOPww==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/json-schema": {
|
"@types/json-schema": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
|
||||||
@@ -1598,6 +1604,23 @@
|
|||||||
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
|
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/linkify-it": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-x9OaQQTb1N2hPZ/LWJsqushexDvz7NgzuZxiRmZio44WPuolTZNHDBCrOxCzRVOMwamJRO2dWax5NbygOf1OTQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@types/markdown-it": {
|
||||||
|
"version": "12.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.0.1.tgz",
|
||||||
|
"integrity": "sha512-mHfT8j/XkPb1uLEfs0/C3se6nd+webC2kcqcy8tgcVr0GDEONv/xaQzAN+aQvkxQXk/jC0Q6mPS+0xhFwRF35g==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/highlight.js": "^9.7.0",
|
||||||
|
"@types/linkify-it": "*",
|
||||||
|
"@types/mdurl": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/mdast": {
|
"@types/mdast": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.3.tgz",
|
||||||
@@ -1606,6 +1629,12 @@
|
|||||||
"@types/unist": "*"
|
"@types/unist": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/mdurl": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "14.11.2",
|
"version": "14.11.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chart.js": "^2.9.28",
|
"@types/chart.js": "^2.9.28",
|
||||||
"@types/classnames": "^2.2.11",
|
"@types/classnames": "^2.2.11",
|
||||||
|
"@types/markdown-it": "^12.0.1",
|
||||||
"@types/node": "^14.11.2",
|
"@types/node": "^14.11.2",
|
||||||
"@types/prop-types": "^15.7.3",
|
"@types/prop-types": "^15.7.3",
|
||||||
"@types/react": "^16.9.49",
|
"@types/react": "^16.9.49",
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
// order matters!
|
// order matters!
|
||||||
import 'antd/dist/antd.css';
|
import 'antd/dist/antd.css';
|
||||||
import '../styles/colors.scss';
|
import '../styles/colors.scss';
|
||||||
import '../styles/globals.scss';
|
|
||||||
import '../styles/ant-overrides.scss';
|
import '../styles/ant-overrides.scss';
|
||||||
import '../styles/markdown-editor.scss';
|
import '../styles/markdown-editor.scss';
|
||||||
|
import '../styles/globals.scss';
|
||||||
|
|
||||||
import '../styles/main-layout.scss';
|
import '../styles/main-layout.scss';
|
||||||
|
|
||||||
import '../styles/form-textfields.scss';
|
import '../styles/form-textfields.scss';
|
||||||
import '../styles/form-toggleswitch.scss';
|
|
||||||
import '../styles/form-misc-elements.scss';
|
import '../styles/form-misc-elements.scss';
|
||||||
import '../styles/config-socialhandles.scss';
|
import '../styles/config-socialhandles.scss';
|
||||||
import '../styles/config-storage.scss';
|
import '../styles/config-storage.scss';
|
||||||
import '../styles/config-tags.scss';
|
import '../styles/config-tags.scss';
|
||||||
import '../styles/config-video-variants.scss';
|
import '../styles/config-video-variants.scss';
|
||||||
|
import '../styles/config-public-details.scss';
|
||||||
|
|
||||||
import '../styles/home.scss';
|
import '../styles/home.scss';
|
||||||
import '../styles/chat.scss';
|
import '../styles/chat.scss';
|
||||||
|
|||||||
@@ -1,26 +1,42 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
import Link from 'next/link';
|
|
||||||
|
|
||||||
import EditInstanceDetails from '../components/config/edit-instance-details';
|
import EditInstanceDetails from '../components/config/edit-instance-details';
|
||||||
import EditDirectoryDetails from '../components/config/edit-directory';
|
|
||||||
import EditInstanceTags from '../components/config/edit-tags';
|
import EditInstanceTags from '../components/config/edit-tags';
|
||||||
|
import EditSocialLinks from '../components/config/edit-social-links';
|
||||||
|
import EditPageContent from '../components/config/edit-page-content';
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
export default function PublicFacingDetails() {
|
export default function PublicFacingDetails() {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="config-public-details-page">
|
||||||
<Title level={2}>General Settings</Title>
|
<Title level={2} className="page-title">
|
||||||
<p>
|
General Settings
|
||||||
|
</Title>
|
||||||
|
<p className="description">
|
||||||
The following are displayed on your site to describe your stream and its content.{' '}
|
The following are displayed on your site to describe your stream and its content.{' '}
|
||||||
<a href="https://owncast.online/docs/website/">Learn more.</a>
|
<a href="https://owncast.online/docs/website/">Learn more.</a>
|
||||||
</p>
|
</p>
|
||||||
<div className="edit-public-details-container">
|
|
||||||
<EditInstanceDetails />
|
<div className="top-container">
|
||||||
<EditInstanceTags />
|
<div className="form-module instance-details-container">
|
||||||
<EditDirectoryDetails />
|
<EditInstanceDetails />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-module social-items-container ">
|
||||||
|
<div className="form-module tags-module">
|
||||||
|
<EditInstanceTags />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-module social-handles-container">
|
||||||
|
<EditSocialLinks />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
<div className="form-module page-content-module">
|
||||||
|
<EditPageContent />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ const { Title } = Typography;
|
|||||||
export default function ConfigServerDetails() {
|
export default function ConfigServerDetails() {
|
||||||
return (
|
return (
|
||||||
<div className="config-server-details-form">
|
<div className="config-server-details-form">
|
||||||
<Title level={2}>Server Settings</Title>
|
<Title level={2} className="page-title">
|
||||||
<p>
|
Server Settings
|
||||||
You should change your stream key from the default and keep it safe. For most people it's
|
</Title>
|
||||||
likely the other settings will not need to be changed.
|
<p className="description">
|
||||||
|
You should change your stream key from the default and keep it safe. For most people
|
||||||
|
it's likely the other settings will not need to be changed.
|
||||||
</p>
|
</p>
|
||||||
<div className="config-server-details-container">
|
<div className="form-module config-server-details-container">
|
||||||
<EditServerDetails />
|
<EditServerDetails />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,13 +7,15 @@ const { Title } = Typography;
|
|||||||
export default function ConfigStorageInfo() {
|
export default function ConfigStorageInfo() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Title level={2}>Storage</Title>
|
<Title level={2} className="page-title">
|
||||||
<p>
|
Storage
|
||||||
|
</Title>
|
||||||
|
<p className="description">
|
||||||
Owncast supports optionally using external storage providers to distribute your video. Learn
|
Owncast supports optionally using external storage providers to distribute your video. Learn
|
||||||
more about this by visiting our{' '}
|
more about this by visiting our{' '}
|
||||||
<a href="https://owncast.online/docs/storage/">Storage Documentation</a>.
|
<a href="https://owncast.online/docs/storage/">Storage Documentation</a>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p className="description">
|
||||||
Configuring this incorrectly will likely cause your video to be unplayable. Double check the
|
Configuring this incorrectly will likely cause your video to be unplayable. Double check the
|
||||||
documentation for your storage provider on how to configure the bucket you created for
|
documentation for your storage provider on how to configure the bucket you created for
|
||||||
Owncast.
|
Owncast.
|
||||||
|
|||||||
@@ -9,22 +9,24 @@ const { Title } = Typography;
|
|||||||
export default function ConfigVideoSettings() {
|
export default function ConfigVideoSettings() {
|
||||||
return (
|
return (
|
||||||
<div className="config-video-variants">
|
<div className="config-video-variants">
|
||||||
<Title level={2}>Video configuration</Title>
|
<Title level={2} className="page-title">
|
||||||
<p>
|
Video configuration
|
||||||
|
</Title>
|
||||||
|
<p className="description">
|
||||||
Before changing your video configuration{' '}
|
Before changing your video configuration{' '}
|
||||||
<a href="https://owncast.online/docs/encoding">visit the video documentation</a> to learn
|
<a href="https://owncast.online/docs/encoding">visit the video documentation</a> to learn
|
||||||
how it impacts your stream performance.
|
how it impacts your stream performance.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<div className="row">
|
||||||
<VideoVariantsTable />
|
<div className="form-module variants-table-module">
|
||||||
</p>
|
<VideoVariantsTable />
|
||||||
<br />
|
</div>
|
||||||
<hr />
|
|
||||||
<br />
|
<div className="form-module latency-module">
|
||||||
<p>
|
<VideoLatency />
|
||||||
<VideoLatency />
|
</div>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,52 +1,231 @@
|
|||||||
// GENERAL ANT OVERRIDES
|
// GENERAL ANT OVERRIDES
|
||||||
|
|
||||||
|
|
||||||
|
// RESET BG, TEXT COLORS
|
||||||
.ant-layout,
|
.ant-layout,
|
||||||
.ant-layout-footer,
|
|
||||||
.ant-menu,
|
|
||||||
.ant-menu.ant-menu-dark {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
.owncast-layout .ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected {
|
|
||||||
background-color: var(--owncast-purple);
|
|
||||||
}
|
|
||||||
|
|
||||||
// LAYOUT
|
|
||||||
.ant-layout-header,
|
.ant-layout-header,
|
||||||
.ant-layout-sider {
|
.ant-layout-sider,
|
||||||
background-color: #07050d;
|
.ant-layout-footer,
|
||||||
|
.ant-card,
|
||||||
|
.ant-collapse,
|
||||||
|
.ant-collapse-content,
|
||||||
|
.ant-table,
|
||||||
|
.ant-table-thead > tr > th,
|
||||||
|
.ant-table-small .ant-table-thead > tr > th,
|
||||||
|
th.ant-table-column-sort,
|
||||||
|
td.ant-table-column-sort,
|
||||||
|
.ant-table-tbody > tr.ant-table-row:hover > td,
|
||||||
|
.ant-table-thead th.ant-table-column-sort,
|
||||||
|
.ant-menu,
|
||||||
|
.ant-menu-submenu > .ant-menu,
|
||||||
|
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--default-text-color)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MENU
|
|
||||||
.ant-menu-dark .ant-menu-inline.ant-menu-sub {
|
h1.ant-typography,
|
||||||
// background-color: rgba(255,255,255,.05);
|
h2.ant-typography,
|
||||||
background-color: #140028;
|
h3.ant-typography,
|
||||||
|
h4.ant-typography,
|
||||||
|
h5.ant-typography,
|
||||||
|
.ant-typography,
|
||||||
|
.ant-typography h1,
|
||||||
|
.ant-typography h2,
|
||||||
|
.ant-typography h3,
|
||||||
|
.ant-typography h4,
|
||||||
|
.ant-typography h5 {
|
||||||
|
color: var(--default-text-color);
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
// CARD
|
.ant-typography.ant-typography-secondary {
|
||||||
|
color: rgba(255,255,255,.85);
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-progress-text,
|
||||||
|
.ant-progress-circle .ant-progress-text {
|
||||||
|
color: var(--default-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ANT MENU
|
||||||
|
// menu base
|
||||||
|
.ant-menu-item {
|
||||||
|
transition-duration: var(--ant-transition-duration);
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
transition-duration: var(--ant-transition-duration);
|
||||||
|
color: var(--nav-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
transition-duration: var(--ant-transition-duration);
|
||||||
|
color: var(--nav-text);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0,0,0,.15);
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// menu item selected
|
||||||
|
.ant-menu-item-selected,
|
||||||
|
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
|
||||||
|
background-color: black;
|
||||||
|
a {
|
||||||
|
color: var(--nav-selected-text);
|
||||||
|
}
|
||||||
|
.anticon {
|
||||||
|
color: var(--nav-selected-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the right border color
|
||||||
|
&:after {
|
||||||
|
border-color: var(--nav-selected-text);
|
||||||
|
transition-duration: var(--ant-transition-duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// submenu items
|
||||||
|
.ant-menu-submenu {
|
||||||
|
&> .ant-menu {
|
||||||
|
border-left: 1px solid rgba(255,255,255,.4);
|
||||||
|
background-color: rgba(0,0,0,.15);
|
||||||
|
}
|
||||||
|
.ant-menu-submenu-title {
|
||||||
|
transition-duration: var(--ant-transition-duration);
|
||||||
|
color: var(--nav-text);
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
color: var(--nav-text);
|
||||||
|
}
|
||||||
|
.ant-menu-submenu-arrow {
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
transition-duration: var(--ant-transition-duration);
|
||||||
|
background-image: linear-gradient(to right, var(--nav-text), var(--nav-text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.ant-menu-submenu-title {
|
||||||
|
color: white;
|
||||||
|
.anticon {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.ant-menu-submenu-arrow {
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
background-image: linear-gradient(to right, white, white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ANT RESULT
|
||||||
|
.ant-result-title {
|
||||||
|
color: var(--default-text-color);
|
||||||
|
}
|
||||||
|
.ant-result-subtitle {
|
||||||
|
color: var(--default-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ANT CARD
|
||||||
.ant-card {
|
.ant-card {
|
||||||
border-radius: .5em;
|
border-radius: var(--container-border-radius);
|
||||||
|
background-color: var(--container-bg-color);
|
||||||
|
color: var(--default-text-color);
|
||||||
|
}
|
||||||
|
.ant-card-bordered {
|
||||||
|
border-color: rgba(255,255,255,.25);
|
||||||
|
}
|
||||||
|
.ant-card-meta-title,
|
||||||
|
.ant-card-meta-description {
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
// INPUT
|
|
||||||
.ant-input-affix-wrapper {
|
|
||||||
// border-radius: 5px;
|
|
||||||
// background-color: rgba(255,255,255,.1);
|
|
||||||
|
|
||||||
|
|
||||||
|
// ANT INPUT
|
||||||
|
.ant-input-affix-wrapper,
|
||||||
|
.ant-input-number {
|
||||||
|
background-color: var(--textfield-bg);
|
||||||
|
border-color: var(--textfield-border);
|
||||||
|
input,
|
||||||
textarea {
|
textarea {
|
||||||
// border-radius: 5px;
|
background-color: transparent;
|
||||||
|
color: rgba(255,255,255,.85);
|
||||||
|
border-color: rgba(0,0,0,1);
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--textfield-border);
|
||||||
|
}
|
||||||
|
&:-webkit-autofill {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
input {
|
}
|
||||||
// background-color: transparent;
|
.ant-input-number:hover,
|
||||||
|
.ant-input-affix-wrapper:hover {
|
||||||
|
border-color: var(--owncast-purple-highlight);
|
||||||
|
input,
|
||||||
|
textarea {
|
||||||
|
border-color: var(--owncast-purple-highlight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.ant-input-number:focus,
|
||||||
|
.ant-input-affix-wrapper:focus,
|
||||||
|
.ant-input-affix-wrapper-focused {
|
||||||
|
border-color: var(--owncast-purple);
|
||||||
|
input,
|
||||||
|
textarea {
|
||||||
|
color: white;
|
||||||
|
border-color: var(--owncast-purple);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-input-textarea-clear-icon,
|
||||||
|
.ant-input-clear-icon {
|
||||||
|
color: rgba(255,255,255,.5);
|
||||||
|
}
|
||||||
|
textarea.ant-input {
|
||||||
|
padding-right: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
// BUTTON
|
|
||||||
.ant-btn-primary:hover, .ant-btn-primary:focus {
|
|
||||||
background-color: white;
|
|
||||||
color: #40a9ff;
|
// ANT BUTTON
|
||||||
|
.ant-btn-primary {
|
||||||
|
background-color: var(--owncast-purple);
|
||||||
|
border-color: var(--owncast-purple);
|
||||||
}
|
}
|
||||||
.ant-btn.ant-btn-primary:focus {
|
.ant-btn-primary:hover,
|
||||||
|
.ant-btn-primary:focus {
|
||||||
|
background-color: var(--form-focused);
|
||||||
|
border-color: var(--form-focused);
|
||||||
|
}
|
||||||
|
.ant-btn.ant-btn-primary:hover {
|
||||||
border-color: white;
|
border-color: white;
|
||||||
|
}
|
||||||
|
.ant-btn-primary[disabled] {
|
||||||
|
background-color: rgba(255,255,255,.2);
|
||||||
|
border-color: rgba(255,255,255,.2);
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(255,255,255,.2);
|
||||||
|
border-color: rgba(255,255,255,.2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.ant-input-affix-wrapper,
|
.ant-input-affix-wrapper,
|
||||||
.ant-btn {
|
.ant-btn {
|
||||||
@@ -54,29 +233,72 @@
|
|||||||
transition-duration: 0.15s;
|
transition-duration: 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TABLE
|
// ANT TABLE
|
||||||
.ant-table-thead > tr > th,
|
.ant-table-thead > tr > th,
|
||||||
.ant-table-small .ant-table-thead > tr > th {
|
.ant-table-small .ant-table-thead > tr > th {
|
||||||
background-color: #000;
|
transition-duration: var(--ant-transition-duration);
|
||||||
|
background-color: #112;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--owncast-purple);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-table-tbody > tr > td,
|
||||||
|
.ant-table-thead > tr > th,
|
||||||
|
.ant-table-small .ant-table-thead > tr > th {
|
||||||
|
border-color: var(--textfield-border);
|
||||||
|
}
|
||||||
|
.ant-table-tbody > tr > td {
|
||||||
|
transition-duration: var(--ant-transition-duration);
|
||||||
|
background-color: var(--textfield-bg);
|
||||||
|
}
|
||||||
|
.ant-table-tbody > tr:nth-child(odd) > td {
|
||||||
|
background-color: var(--textfield-bg);
|
||||||
|
}
|
||||||
|
.ant-empty {
|
||||||
|
color: white;
|
||||||
|
opacity: .75;
|
||||||
|
}
|
||||||
|
.ant-table-empty .ant-table-tbody > tr.ant-table-placeholder {
|
||||||
|
&:hover > td {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-table-thead th.ant-table-column-has-sorters:hover {
|
||||||
|
background-color: var(--textfield-border);
|
||||||
|
.ant-table-filter-trigger-container {
|
||||||
|
background-color: var(--textfield-border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// MODAL
|
// MODAL
|
||||||
.ant-modal-content {
|
.ant-modal-content {
|
||||||
border-radius: 6px;
|
border-radius: var(--container-border-radius);
|
||||||
|
border: 1px solid var(--owncast-purple-highlight);
|
||||||
}
|
}
|
||||||
.ant-modal-header {
|
.ant-modal-header {
|
||||||
background-color: #1c173d;
|
border-radius: var(--container-border-radius) var(--container-border-radius) 0 0;
|
||||||
border-radius: 6px 6px 0 0;
|
}
|
||||||
|
.ant-modal-close-x {
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
.ant-modal-title {
|
.ant-modal-title {
|
||||||
font-weight: bold;
|
font-weight: 500;
|
||||||
font-size: 1.5em;
|
font-size: 1.25em;
|
||||||
|
color: var(--nav-selected-text);
|
||||||
}
|
}
|
||||||
.ant-modal-body {
|
.ant-modal-body {
|
||||||
background-color: #33333c;
|
background-color: var(--nav-bg-color);
|
||||||
|
color: var(--default-text-color);
|
||||||
}
|
}
|
||||||
|
.ant-modal-header,
|
||||||
.ant-modal-footer {
|
.ant-modal-footer {
|
||||||
background-color: #222229;
|
background-color: black;
|
||||||
|
}
|
||||||
|
.ant-modal-content,
|
||||||
|
.ant-modal-header,
|
||||||
|
.ant-modal-footer {
|
||||||
|
border-color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SELECT
|
// SELECT
|
||||||
@@ -86,10 +308,59 @@
|
|||||||
|
|
||||||
|
|
||||||
// SLIDER
|
// SLIDER
|
||||||
.ant-slider-with-marks {
|
// .ant-slider-with-marks {
|
||||||
margin-right: 2em;
|
// margin-right: 2em;
|
||||||
}
|
// }
|
||||||
.ant-slider-mark-text {
|
.ant-slider-mark-text {
|
||||||
font-size: .85em;
|
font-size: .85em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ANT SWITCH
|
||||||
|
.ant-switch {
|
||||||
|
background-color: #666;
|
||||||
|
}
|
||||||
|
.ant-switch-checked {
|
||||||
|
background-color: var(--ant-success);
|
||||||
|
.ant-switch-inner {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ANT COLLAPSE
|
||||||
|
.ant-collapse {
|
||||||
|
border-color: transparent;
|
||||||
|
&> .ant-collapse-item,
|
||||||
|
.ant-collapse-content {
|
||||||
|
border-color: transparent;
|
||||||
|
&> .ant-collapse-header {
|
||||||
|
border-color: transparent;
|
||||||
|
background-color: var(--textfield-bg);
|
||||||
|
color: var(--nav-text);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ant-collapse-content {
|
||||||
|
background-color: #181231;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ANT POPOVER
|
||||||
|
.ant-popover {
|
||||||
|
|
||||||
|
}
|
||||||
|
.ant-popover-inner {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
.ant-popover-message,
|
||||||
|
.ant-popover-inner-content {
|
||||||
|
color: var(--default-text-color);
|
||||||
|
|
||||||
|
}
|
||||||
|
.ant-popover-placement-topLeft > .ant-popover-content > .ant-popover-arrow {
|
||||||
|
border-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,35 @@
|
|||||||
|
// rename to variables.scss
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--owncast-purple: rgba(90,103,216,1);
|
|
||||||
|
--default-text-color: #fff;
|
||||||
|
|
||||||
|
--owncast-purple: rgba(90,103,216,1); //5a67d8
|
||||||
--owncast-purple-highlight: #ccd;
|
--owncast-purple-highlight: #ccd;
|
||||||
|
|
||||||
--online-color: #73dd3f;
|
--online-color: #73dd3f;
|
||||||
|
|
||||||
--owncast-dark1: #1f1f21;
|
--owncast-dark1: #1f1f21;
|
||||||
|
|
||||||
|
|
||||||
--ant-error: #ff4d4f;
|
--ant-error: #ff4d4f;
|
||||||
--ant-success: #52c41a;
|
--ant-success: #52c41a;
|
||||||
--ant-warning: #faad14;
|
--ant-warning: #faad14;
|
||||||
|
--ant-transition-duration: .15s;
|
||||||
|
|
||||||
|
|
||||||
|
--container-bg-color: #1A1C24;
|
||||||
|
--container-bg-color-alt: #251c49;
|
||||||
|
--container-border-radius: 2px;
|
||||||
|
|
||||||
|
--code-purple: #82aaff;
|
||||||
|
|
||||||
|
--nav-bg-color: #1A1C24;
|
||||||
|
--nav-text: #6a76ba;
|
||||||
|
--nav-selected-text: #c48dff;
|
||||||
|
|
||||||
|
--form-focused: #8d71ff;
|
||||||
|
|
||||||
|
--textfield-border: #373640;
|
||||||
|
--textfield-bg: #100f0f;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
65
web/styles/config-public-details.scss
Normal file
65
web/styles/config-public-details.scss
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// dealiing with some general layout on the public details page
|
||||||
|
|
||||||
|
.config-public-details-page {
|
||||||
|
width: 100%;
|
||||||
|
.top-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
.social-items-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-around;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
margin: 1em 0;
|
||||||
|
width: 100%;
|
||||||
|
max-width: none;
|
||||||
|
.tags-module {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
.form-module {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
flex-direction: column;
|
||||||
|
.form-module {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.tags-module {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.instance-details-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.social-items-container {
|
||||||
|
background-color: var(--container-bg-color-alt);
|
||||||
|
padding: 0 .75em;
|
||||||
|
margin-left: 1em;
|
||||||
|
max-width: 450px;
|
||||||
|
.form-module {
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-handles-container {
|
||||||
|
min-width: 350px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.instance-details-container,
|
||||||
|
.page-content-module {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-summary {
|
||||||
|
textarea {
|
||||||
|
height: 6em !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,3 +23,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.social-links-edit-container {
|
||||||
|
.social-handles-table {
|
||||||
|
.social-handle-cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
color: rgba(255,255,255,.85);
|
||||||
|
|
||||||
|
.option-icon {
|
||||||
|
height: 2em;
|
||||||
|
width: 2em;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
.option-label {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0 0 0 1em;
|
||||||
|
line-height: 2;
|
||||||
|
font-size: .85em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
width: 6em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
|
|
||||||
.edit-storage-container {
|
.edit-storage-container {
|
||||||
|
padding: 1em;
|
||||||
.form-fields {
|
.form-fields {
|
||||||
display: none;
|
display: none;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button-container {
|
.button-container {
|
||||||
margin: 1em 0;
|
margin: 2em 0 1em 0;
|
||||||
}
|
}
|
||||||
.advanced-section {
|
.advanced-section {
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
|
|||||||
@@ -6,19 +6,24 @@
|
|||||||
font-size: .85rem;
|
font-size: .85rem;
|
||||||
border-radius: 10em;
|
border-radius: 10em;
|
||||||
padding: .25em 1em;
|
padding: .25em 1em;
|
||||||
background-color: rgba(255,255,255,.5);
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-tag-close-icon {
|
.ant-tag-close-icon {
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
margin-left: .3rem;
|
margin-left: .3rem;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
border-radius: 5rem;
|
border-radius: 5rem;
|
||||||
border: 1px solid #eee;
|
color: black;
|
||||||
|
border: 1px solid #000;
|
||||||
|
transition-duration: var(--ant-transition-duration);
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: #e03;
|
border-color: #5a67d8;
|
||||||
|
background-color: white;
|
||||||
svg {
|
svg {
|
||||||
fill: black;
|
fill: black;
|
||||||
transition: fill .3s;
|
transition: fill var(--ant-transition-duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,8 +35,5 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-top: 2em;
|
||||||
.new-tag-input {
|
|
||||||
width: 16em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,30 @@
|
|||||||
// styles for Video variant editor (table + modal)
|
// styles for Video variant editor (table + modal)
|
||||||
|
|
||||||
|
.config-video-variants {
|
||||||
|
|
||||||
|
.variants-table {
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variants-table-module {
|
||||||
|
min-width: 48%;
|
||||||
|
max-width: 600px;
|
||||||
|
margin-right: 1em
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// modal content
|
||||||
.config-variant-form {
|
.config-variant-form {
|
||||||
|
.description {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.advanced-settings {
|
||||||
|
width: 48%;
|
||||||
|
margin-left: 2em;
|
||||||
|
}
|
||||||
.blurb {
|
.blurb {
|
||||||
margin: 1em;
|
margin: 1em;
|
||||||
opacity: .75;
|
opacity: .75;
|
||||||
@@ -13,58 +36,61 @@
|
|||||||
opacity: .5;
|
opacity: .5;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
.section-intro {
|
|
||||||
margin-bottom: 2em;
|
|
||||||
}
|
|
||||||
.field {
|
|
||||||
margin-bottom: 2em;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
transform: opacity .15s;
|
|
||||||
&.disabled {
|
|
||||||
opacity: .25;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
width: 40%;
|
// .field {
|
||||||
text-align: right;
|
// margin-bottom: 2em;
|
||||||
padding-right: 2em;
|
// display: flex;
|
||||||
font-weight: bold;
|
// flex-direction: row;
|
||||||
color: var(--owncast-purple);
|
// justify-content: center;
|
||||||
}
|
// align-items: flex-start;
|
||||||
.info-tip {
|
// transform: opacity .15s;
|
||||||
margin-right: 1em;
|
// &.disabled {
|
||||||
}
|
// opacity: .25;
|
||||||
.form-component {
|
// }
|
||||||
width: 60%;
|
|
||||||
|
// .label {
|
||||||
|
// width: 40%;
|
||||||
|
// text-align: right;
|
||||||
|
// padding-right: 2em;
|
||||||
|
// font-weight: bold;
|
||||||
|
// color: var(--owncast-purple);
|
||||||
|
// }
|
||||||
|
// .info-tip {
|
||||||
|
// margin-right: 1em;
|
||||||
|
// }
|
||||||
|
// .form-component {
|
||||||
|
// width: 60%;
|
||||||
|
|
||||||
.selected-value-note {
|
// .selected-value-note {
|
||||||
font-size: .85em;
|
// font-size: .85em;
|
||||||
display: inline-block;
|
// display: inline-block;
|
||||||
text-align: center;
|
// text-align: center;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
.ant-collapse {
|
// .ant-collapse {
|
||||||
border: none;
|
// border: none;
|
||||||
border-radius: 6px;
|
// border-radius: 6px;
|
||||||
}
|
// }
|
||||||
.ant-collapse > .ant-collapse-item:last-child,
|
// .ant-collapse > .ant-collapse-item:last-child,
|
||||||
.ant-collapse > .ant-collapse-item:last-child > .ant-collapse-header {
|
// .ant-collapse > .ant-collapse-item:last-child > .ant-collapse-header {
|
||||||
border: none;
|
// border: none;
|
||||||
background-color: rgba(0,0,0,.25);
|
// background-color: rgba(0,0,0,.25);
|
||||||
border-radius: 6px;
|
// border-radius: 6px;
|
||||||
}
|
// }
|
||||||
.ant-collapse-content {
|
// .ant-collapse-content {
|
||||||
background-color: rgba(0,0,0,.1);
|
// background-color: rgba(0,0,0,.1);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.config-video-segements-conatiner {
|
.config-video-segements-conatiner {
|
||||||
|
// display: flex;
|
||||||
|
// flex-direction: row;
|
||||||
|
// justify-content: center;
|
||||||
|
// align-items: flex-start;
|
||||||
|
|
||||||
.status-message {
|
.status-message {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -82,4 +108,8 @@
|
|||||||
margin-left: .5em;
|
margin-left: .5em;
|
||||||
opacity: .8;
|
opacity: .8;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-settings {
|
||||||
|
margin-top: 2em;
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
// todo: put these somewhere else
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.edit-page-content {
|
||||||
|
|
||||||
.config-page-content-form {
|
|
||||||
.page-content-actions {
|
.page-content-actions {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -26,25 +27,3 @@
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
// .social-option {
|
|
||||||
// .ant-select-item-option-content {
|
|
||||||
// display: flex;
|
|
||||||
// flex-direction: row;
|
|
||||||
// justify-content: flex-start;
|
|
||||||
// align-items: center;
|
|
||||||
// padding: .25em;
|
|
||||||
|
|
||||||
// .option-icon {
|
|
||||||
// height: 1.75em;
|
|
||||||
// width: 1.75em;
|
|
||||||
// }
|
|
||||||
// .option-label {
|
|
||||||
// display: inline-block;
|
|
||||||
// margin-left: 1em;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
/* TIP CONTAINER BASE */
|
/* TIP CONTAINER BASE */
|
||||||
.field-tip {
|
.field-tip {
|
||||||
font-size: .7em;
|
font-size: .8em;
|
||||||
color: rgba(255,255,255,.5)
|
color: rgba(255,255,255,.5)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,9 +39,9 @@ Ideal for wrapping each Textfield on a page with many text fields in a row. This
|
|||||||
*/
|
*/
|
||||||
.field-container {
|
.field-container {
|
||||||
padding: .85em 0 .5em;
|
padding: .85em 0 .5em;
|
||||||
&:nth-child(even) {
|
// &:nth-child(even) {
|
||||||
background-color: rgba(0,0,0,.25);
|
// background-color: rgba(0,0,0,.25);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -50,8 +50,27 @@ Ideal for wrapping each Textfield on a page with many text fields in a row. This
|
|||||||
width: 90%;
|
width: 90%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 1em 2em .75em;
|
padding: 1em 2em .75em;
|
||||||
background-color: black;
|
background-color: var(--textfield-border);
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
|
.ant-slider-rail {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
.ant-slider-track {
|
||||||
|
background-color: var(--nav-text);
|
||||||
|
}
|
||||||
|
.ant-slider-mark-text,
|
||||||
|
.ant-slider-mark-text-active {
|
||||||
|
color: white;
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
.ant-slider-mark-text-active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.status-container {
|
||||||
|
width: 100%;
|
||||||
|
margin: .5em auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
// Base styles for form-textfield, form-textfield-with-submit
|
/*
|
||||||
|
Base styles for
|
||||||
|
- form-textfield,
|
||||||
|
- form-textfield-with-submit
|
||||||
|
- form-toggleswitch
|
||||||
|
|
||||||
|
Both text and toggle use this class for base layout.
|
||||||
|
*/
|
||||||
|
.formfield-container {
|
||||||
|
--form-label-container-width: 15em;
|
||||||
|
|
||||||
/* TEXTFIELD-CONTAINER BASE */
|
|
||||||
.textfield-container {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -10,14 +16,14 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
.label-side {
|
.label-side {
|
||||||
padding-right: .75em;
|
padding-right: 1.25em;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 12em;
|
width: var(--form-label-container-width);
|
||||||
margin: .2em 0;
|
margin: .2em 0;
|
||||||
}
|
}
|
||||||
.textfield-label {
|
.formfield-label {
|
||||||
font-weight: 400;
|
font-weight: 500;
|
||||||
font-size: .85em;
|
font-size: 1em;
|
||||||
color: var(--owncast-purple);
|
color: var(--owncast-purple);
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
@@ -25,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.required {
|
&.required {
|
||||||
.textfield-label {
|
.formfield-label {
|
||||||
&::before {
|
&::before {
|
||||||
content: '*';
|
content: '*';
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -97,7 +103,7 @@
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
||||||
.label-spacer {
|
.label-spacer {
|
||||||
width: 12em;
|
width: var(--form-label-container-width);
|
||||||
}
|
}
|
||||||
.lower-content {
|
.lower-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -138,3 +144,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* TOGGLE SWITCH CONTAINER BASE */
|
||||||
|
.toggleswitch-container {
|
||||||
|
margin: 2em 0 1em;
|
||||||
|
|
||||||
|
.label-side {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.status-container {
|
||||||
|
width: auto;
|
||||||
|
margin: 0 0 0 1em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
/* TOGGLE SWITCH-WITH-SUBMIT-CONTAINER BASE */
|
|
||||||
|
|
||||||
|
|
||||||
.toggleswitch-container {
|
|
||||||
|
|
||||||
.status-container {
|
|
||||||
margin-top: .25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggleswitch {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
.label {
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--owncast-purple);
|
|
||||||
}
|
|
||||||
.info-tip {
|
|
||||||
margin-left: .5rem;
|
|
||||||
svg {
|
|
||||||
fill: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ant-form-item {
|
|
||||||
margin: 0 .75rem 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "~antd/dist/antd.dark";
|
// @import "~antd/dist/antd.dark";
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
@@ -6,15 +6,20 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
|
font-family: system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
|
||||||
|
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
|
|
||||||
background-color: #1f1f21;
|
background-color: #000;
|
||||||
|
color: var(--default-text-color);;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: rgba(90,103,216,1);
|
color: var(--owncast-purple);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--default-text-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -40,13 +45,50 @@ code {
|
|||||||
height: 2rem;
|
height: 2rem;
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
}
|
}
|
||||||
.ant-btn {
|
|
||||||
transition-duration: .15s;
|
|
||||||
transition-delay: 0s;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.page-description {
|
p.description {
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
width: 80%;
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.line-chart-container {
|
||||||
|
margin: 2em auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2.ant-typography.page-title,
|
||||||
|
h3.ant-typography.page-title
|
||||||
|
{
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1.5em;
|
||||||
|
color: var(--nav-selected-text);
|
||||||
|
}
|
||||||
|
h2.section-title,
|
||||||
|
h3.section-title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-module {
|
||||||
|
// width: 100%;
|
||||||
|
// max-width: 500px;
|
||||||
|
// min-width: 300px;
|
||||||
|
margin: 1em 0;
|
||||||
|
background-color: var(--container-bg-color);
|
||||||
|
padding: 2em;
|
||||||
|
border-radius: var(--container-border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
flex-direction: column;
|
||||||
|
.form-module {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,11 @@
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
background-color: var(--nav-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-container {
|
||||||
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1.owncast-title {
|
h1.owncast-title {
|
||||||
@@ -40,6 +45,7 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
|
background-color: var(--nav-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,17 +5,24 @@
|
|||||||
display: block !important;
|
display: block !important;
|
||||||
}
|
}
|
||||||
.rc-md-editor {
|
.rc-md-editor {
|
||||||
|
border-color: black !important;
|
||||||
|
border: 1px solid black;
|
||||||
|
background-color: black !important;
|
||||||
|
.rc-md-navigation {
|
||||||
|
background-color: black;
|
||||||
|
border-color: black;
|
||||||
|
}
|
||||||
// Set the background color of the preview container
|
// Set the background color of the preview container
|
||||||
.editor-container {
|
.editor-container {
|
||||||
background-color: #E2E8F0;
|
|
||||||
color: rgba(45,55,72,1);
|
color: rgba(45,55,72,1);
|
||||||
|
background-color: rgba(226,232,240, 1) !important;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom CSS for formatting the preview text
|
// Custom CSS for formatting the preview text
|
||||||
.markdown-editor-preview-pane {
|
.markdown-editor-preview-pane {
|
||||||
// color:lightgrey;
|
|
||||||
a {
|
a {
|
||||||
color: var(--owncast-purple);;
|
color: var(--owncast-purple);
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
@@ -24,20 +31,20 @@
|
|||||||
|
|
||||||
// Custom CSS class used to format the text of the editor
|
// Custom CSS class used to format the text of the editor
|
||||||
.markdown-editor-pane {
|
.markdown-editor-pane {
|
||||||
color: white !important;
|
color: rgba(255,255,255,.85) !important;
|
||||||
|
border-color: black !important;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Set the background color of the editor text input
|
// Set the background color of the editor text input
|
||||||
textarea {
|
textarea {
|
||||||
background-color: rgb(44,44,44) !important;
|
background-color: #223 !important;
|
||||||
color:lightgrey !important;
|
color: rgba(255,255,255,.5) !important;
|
||||||
}
|
}
|
||||||
.ant-btn {
|
|
||||||
transition-duration: .15s;
|
|
||||||
transition-delay: 0s;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide extra toolbar buttons.
|
// Hide extra toolbar buttons.
|
||||||
.button-type-undo, .button-type-redo, .button-type-clear, .button-type-image, .button-type-wrap, .button-type-quote, .button-type-strikethrough, .button-type-code-inline, .button-type-code-block {
|
.button-type-undo, .button-type-redo, .button-type-clear, .button-type-image, .button-type-wrap, .button-type-quote, .button-type-strikethrough, .button-type-code-inline, .button-type-code-block {
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
// DEFAULT VALUES
|
// DEFAULT VALUES
|
||||||
import React from 'react';
|
|
||||||
import { CheckCircleFilled, ExclamationCircleFilled } from '@ant-design/icons';
|
|
||||||
import { fetchData, SERVER_CONFIG_UPDATE_URL } from './apis';
|
import { fetchData, SERVER_CONFIG_UPDATE_URL } from './apis';
|
||||||
import { ApiPostArgs, VideoVariant, SocialHandle } from '../types/config-section';
|
import { ApiPostArgs, VideoVariant, SocialHandle } from '../types/config-section';
|
||||||
|
|
||||||
@@ -8,17 +6,6 @@ export const TEXT_MAXLENGTH = 255;
|
|||||||
|
|
||||||
export const RESET_TIMEOUT = 3000;
|
export const RESET_TIMEOUT = 3000;
|
||||||
|
|
||||||
export const SUCCESS_STATES = {
|
|
||||||
success: {
|
|
||||||
icon: <CheckCircleFilled style={{ color: 'green' }} />,
|
|
||||||
message: 'Success!',
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
icon: <ExclamationCircleFilled style={{ color: 'red' }} />,
|
|
||||||
message: 'An error occurred.',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// CONFIG API ENDPOINTS
|
// CONFIG API ENDPOINTS
|
||||||
export const API_CUSTOM_CONTENT = '/pagecontent';
|
export const API_CUSTOM_CONTENT = '/pagecontent';
|
||||||
export const API_FFMPEG = '/ffmpegpath';
|
export const API_FFMPEG = '/ffmpegpath';
|
||||||
|
|||||||
Reference in New Issue
Block a user