mirror of
https://github.com/owncast/owncast.git
synced 2024-12-18 07:12:33 +03:00
Merge branch 'develop' into gw/2021-05-22/admin-width
This commit is contained in:
commit
e5db35590c
15 changed files with 145 additions and 55 deletions
|
@ -13,7 +13,7 @@ import {
|
||||||
OTHER_SOCIAL_HANDLE_OPTION,
|
OTHER_SOCIAL_HANDLE_OPTION,
|
||||||
} from '../../utils/config-constants';
|
} from '../../utils/config-constants';
|
||||||
import { SocialHandle, UpdateArgs } from '../../types/config-section';
|
import { SocialHandle, UpdateArgs } from '../../types/config-section';
|
||||||
import isValidUrl from '../../utils/urls';
|
import isValidUrl, { DEFAULT_TEXTFIELD_URL_PATTERN } from '../../utils/urls';
|
||||||
import TextField from './form-textfield';
|
import TextField from './form-textfield';
|
||||||
import { createInputStatus, STATUS_ERROR, STATUS_SUCCESS } from '../../utils/input-statuses';
|
import { createInputStatus, STATUS_ERROR, STATUS_SUCCESS } from '../../utils/input-statuses';
|
||||||
import FormStatusIndicator from './form-status-indicator';
|
import FormStatusIndicator from './form-status-indicator';
|
||||||
|
@ -62,6 +62,10 @@ export default function EditSocialLinks() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isPredefinedSocial = (platform: string) => {
|
||||||
|
return availableIconsList.find(item => item.key === platform) || false;
|
||||||
|
};
|
||||||
|
|
||||||
const selectedOther =
|
const selectedOther =
|
||||||
modalDataState.platform !== '' &&
|
modalDataState.platform !== '' &&
|
||||||
!availableIconsList.find(item => item.key === modalDataState.platform);
|
!availableIconsList.find(item => item.key === modalDataState.platform);
|
||||||
|
@ -172,9 +176,20 @@ export default function EditSocialLinks() {
|
||||||
key: 'combo',
|
key: 'combo',
|
||||||
render: (data, record) => {
|
render: (data, record) => {
|
||||||
const { platform, url } = record;
|
const { platform, url } = record;
|
||||||
const platformInfo = availableIconsList.find(item => item.key === platform);
|
const platformInfo = isPredefinedSocial(platform);
|
||||||
|
|
||||||
|
// custom platform case
|
||||||
if (!platformInfo) {
|
if (!platformInfo) {
|
||||||
return platform;
|
return (
|
||||||
|
<div className="social-handle-cell">
|
||||||
|
<p className="option-label">
|
||||||
|
<strong>{platform}</strong>
|
||||||
|
<span className="handle-url" title={url}>
|
||||||
|
{url}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const { icon, platform: platformName } = platformInfo;
|
const { icon, platform: platformName } = platformInfo;
|
||||||
const iconUrl = NEXT_PUBLIC_API_HOST + `${icon.slice(1)}`;
|
const iconUrl = NEXT_PUBLIC_API_HOST + `${icon.slice(1)}`;
|
||||||
|
@ -186,7 +201,9 @@ export default function EditSocialLinks() {
|
||||||
</span>
|
</span>
|
||||||
<p className="option-label">
|
<p className="option-label">
|
||||||
<strong>{platformName}</strong>
|
<strong>{platformName}</strong>
|
||||||
<span className="handle-url" title={url}>{url}</span>
|
<span className="handle-url" title={url}>
|
||||||
|
{url}
|
||||||
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -201,9 +218,13 @@ export default function EditSocialLinks() {
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
const platformInfo = currentSocialHandles[index];
|
||||||
setEditId(index);
|
setEditId(index);
|
||||||
setModalDataState({ ...currentSocialHandles[index] });
|
setModalDataState({ ...platformInfo });
|
||||||
setDisplayModal(true);
|
setDisplayModal(true);
|
||||||
|
if (!isPredefinedSocial(platformInfo.platform)) {
|
||||||
|
setDisplayOther(true);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
|
@ -251,7 +272,7 @@ export default function EditSocialLinks() {
|
||||||
className="social-handles-table"
|
className="social-handles-table"
|
||||||
pagination={false}
|
pagination={false}
|
||||||
size="small"
|
size="small"
|
||||||
rowKey={record => record.url}
|
rowKey={record => `${record.platform}-${record.url}`}
|
||||||
columns={socialHandlesColumns}
|
columns={socialHandlesColumns}
|
||||||
dataSource={currentSocialHandles}
|
dataSource={currentSocialHandles}
|
||||||
/>
|
/>
|
||||||
|
@ -278,6 +299,9 @@ export default function EditSocialLinks() {
|
||||||
placeholder={PLACEHOLDERS[modalDataState.platform] || 'Url to page'}
|
placeholder={PLACEHOLDERS[modalDataState.platform] || 'Url to page'}
|
||||||
value={modalDataState.url}
|
value={modalDataState.url}
|
||||||
onChange={handleUrlChange}
|
onChange={handleUrlChange}
|
||||||
|
useTrim
|
||||||
|
type="url"
|
||||||
|
pattern={DEFAULT_TEXTFIELD_URL_PATTERN}
|
||||||
/>
|
/>
|
||||||
<FormStatusIndicator status={submitStatus} />
|
<FormStatusIndicator status={submitStatus} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -41,6 +41,7 @@ export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) {
|
||||||
apiPath,
|
apiPath,
|
||||||
configPath = '',
|
configPath = '',
|
||||||
initialValue,
|
initialValue,
|
||||||
|
useTrim,
|
||||||
...textFieldProps // rest of props
|
...textFieldProps // rest of props
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
@ -70,7 +71,10 @@ export default function TextFieldWithSubmit(props: TextFieldWithSubmitProps) {
|
||||||
// if field is required but value is empty, or equals initial value, then don't show submit/update button. otherwise clear out any result messaging and display button.
|
// if field is required but value is empty, or equals initial value, then don't show submit/update button. otherwise clear out any result messaging and display button.
|
||||||
const handleChange = ({ fieldName: changedFieldName, value: changedValue }: UpdateArgs) => {
|
const handleChange = ({ fieldName: changedFieldName, value: changedValue }: UpdateArgs) => {
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange({ fieldName: changedFieldName, value: changedValue });
|
onChange({
|
||||||
|
fieldName: changedFieldName,
|
||||||
|
value: useTrim ? changedValue.trim() : changedValue,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,13 @@ export interface TextFieldProps {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
label?: string;
|
label?: string;
|
||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
|
pattern?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
status?: StatusState;
|
status?: StatusState;
|
||||||
tip?: string;
|
tip?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
|
useTrim?: boolean;
|
||||||
value?: string | number;
|
value?: string | number;
|
||||||
onBlur?: FieldUpdaterFunc;
|
onBlur?: FieldUpdaterFunc;
|
||||||
onChange?: FieldUpdaterFunc;
|
onChange?: FieldUpdaterFunc;
|
||||||
|
@ -42,20 +44,21 @@ export default function TextField(props: TextFieldProps) {
|
||||||
onBlur,
|
onBlur,
|
||||||
onChange,
|
onChange,
|
||||||
onPressEnter,
|
onPressEnter,
|
||||||
|
pattern,
|
||||||
placeholder,
|
placeholder,
|
||||||
required,
|
required,
|
||||||
status,
|
status,
|
||||||
tip,
|
tip,
|
||||||
type,
|
type,
|
||||||
|
useTrim,
|
||||||
value,
|
value,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
// if field is required but value is empty, or equals initial value, then don't show submit/update button. otherwise clear out any result messaging and display button.
|
|
||||||
const handleChange = (e: any) => {
|
const handleChange = (e: any) => {
|
||||||
const val = type === TEXTFIELD_TYPE_NUMBER ? e : e.target.value;
|
|
||||||
// if an extra onChange handler was sent in as a prop, let's run that too.
|
// if an extra onChange handler was sent in as a prop, let's run that too.
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange({ fieldName, value: val });
|
const val = type === TEXTFIELD_TYPE_NUMBER ? e : e.target.value;
|
||||||
|
onChange({ fieldName, value: useTrim ? val.trim() : val });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -100,6 +103,7 @@ export default function TextField(props: TextFieldProps) {
|
||||||
} else if (type === TEXTFIELD_TYPE_URL) {
|
} else if (type === TEXTFIELD_TYPE_URL) {
|
||||||
fieldProps = {
|
fieldProps = {
|
||||||
type: 'url',
|
type: 'url',
|
||||||
|
pattern,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +118,7 @@ export default function TextField(props: TextFieldProps) {
|
||||||
required,
|
required,
|
||||||
[`status-${statusType}`]: status,
|
[`status-${statusType}`]: status,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={containerClass}>
|
<div className={containerClass}>
|
||||||
{label ? (
|
{label ? (
|
||||||
|
@ -130,7 +135,7 @@ export default function TextField(props: TextFieldProps) {
|
||||||
id={fieldId}
|
id={fieldId}
|
||||||
className={`field ${className} ${fieldId}`}
|
className={`field ${className} ${fieldId}`}
|
||||||
{...fieldProps}
|
{...fieldProps}
|
||||||
allowClear
|
{...(type !== TEXTFIELD_TYPE_NUMBER && { allowClear: true })}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
maxLength={maxLength}
|
maxLength={maxLength}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
|
|
@ -120,7 +120,7 @@ export default function CodecSelector() {
|
||||||
<Title level={3} className="section-title">
|
<Title level={3} className="section-title">
|
||||||
Video Codec
|
Video Codec
|
||||||
</Title>
|
</Title>
|
||||||
<p className="description">
|
<div className="description">
|
||||||
If you have access to specific hardware with the drivers and software installed for them,
|
If you have access to specific hardware with the drivers and software installed for them,
|
||||||
you may be able to improve your video encoding performance.
|
you may be able to improve your video encoding performance.
|
||||||
<p>
|
<p>
|
||||||
|
@ -133,7 +133,7 @@ export default function CodecSelector() {
|
||||||
unplayable.
|
unplayable.
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</div>
|
||||||
<div className="segment-slider-container">
|
<div className="segment-slider-container">
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={`Are you sure you want to change your video codec to ${pendingSaveCodec} and understand what this means?`}
|
title={`Are you sure you want to change your video codec to ${pendingSaveCodec} and understand what this means?`}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import React, { useState, useEffect, useContext } from 'react';
|
||||||
import { Table, Space, Button, Modal, Checkbox, Input, Typography } from 'antd';
|
import { Table, Space, Button, Modal, Checkbox, Input, Typography } from 'antd';
|
||||||
import { ServerStatusContext } from '../utils/server-status-context';
|
import { ServerStatusContext } from '../utils/server-status-context';
|
||||||
import { DeleteOutlined } from '@ant-design/icons';
|
import { DeleteOutlined } from '@ant-design/icons';
|
||||||
import isValidUrl from '../utils/urls';
|
import isValidUrl, { DEFAULT_TEXTFIELD_URL_PATTERN } from '../utils/urls';
|
||||||
import FormStatusIndicator from '../components/config/form-status-indicator';
|
import FormStatusIndicator from '../components/config/form-status-indicator';
|
||||||
import {
|
import {
|
||||||
createInputStatus,
|
createInputStatus,
|
||||||
|
@ -41,12 +41,12 @@ function NewActionModal(props: Props) {
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
onOk(actionUrl, actionTitle, actionDescription, actionIcon, actionColor, openExternally);
|
onOk(actionUrl, actionTitle, actionDescription, actionIcon, actionColor, openExternally);
|
||||||
setActionUrl('')
|
setActionUrl('');
|
||||||
setActionTitle('')
|
setActionTitle('');
|
||||||
setActionDescription('')
|
setActionDescription('');
|
||||||
setActionIcon('')
|
setActionIcon('');
|
||||||
setActionColor('')
|
setActionColor('');
|
||||||
setOpenExternally(false)
|
setOpenExternally(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function canSave(): Boolean {
|
function canSave(): Boolean {
|
||||||
|
@ -91,7 +91,9 @@ function NewActionModal(props: Props) {
|
||||||
value={actionUrl}
|
value={actionUrl}
|
||||||
required
|
required
|
||||||
placeholder="https://myserver.com/action (required)"
|
placeholder="https://myserver.com/action (required)"
|
||||||
onChange={input => setActionUrl(input.currentTarget.value)}
|
onChange={input => setActionUrl(input.currentTarget.value.trim())}
|
||||||
|
type="url"
|
||||||
|
pattern={DEFAULT_TEXTFIELD_URL_PATTERN}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
@ -289,11 +291,22 @@ export default function Actions() {
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph>
|
<Paragraph>
|
||||||
Read more about how to use actions, with examples, at{' '}
|
Read more about how to use actions, with examples, at{' '}
|
||||||
<a href="https://owncast.online/thirdparty/?source=admin" target="_blank"
|
<a
|
||||||
rel="noopener noreferrer">our documentation</a>.
|
href="https://owncast.online/thirdparty/?source=admin"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
our documentation
|
||||||
|
</a>
|
||||||
|
.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
<Table rowKey="id" columns={columns} dataSource={actions} pagination={false} />
|
<Table
|
||||||
|
rowKey={record => `${record.title}-${record.url}`}
|
||||||
|
columns={columns}
|
||||||
|
dataSource={actions}
|
||||||
|
pagination={false}
|
||||||
|
/>
|
||||||
<br />
|
<br />
|
||||||
<Button type="primary" onClick={showCreateModal}>
|
<Button type="primary" onClick={showCreateModal}>
|
||||||
Create New Action
|
Create New Action
|
||||||
|
|
|
@ -37,7 +37,7 @@ export default function ConfigVideoSettings() {
|
||||||
<VideoLatency />
|
<VideoLatency />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Collapse className="advanced-settings">
|
<Collapse className="advanced-settings codec-module">
|
||||||
<Panel header="Advanced Settings" key="1">
|
<Panel header="Advanced Settings" key="1">
|
||||||
<div className="form-module variants-table-module">
|
<div className="form-module variants-table-module">
|
||||||
<VideoCodecSelector />
|
<VideoCodecSelector />
|
||||||
|
|
|
@ -75,7 +75,7 @@ export default function HardwareInfo() {
|
||||||
<Col>
|
<Col>
|
||||||
<StatisticItem
|
<StatisticItem
|
||||||
title={series[0].name}
|
title={series[0].name}
|
||||||
value={`${currentCPUUsage}`}
|
value={`${currentCPUUsage || 0}`}
|
||||||
prefix={<LaptopOutlined style={{ color: series[0].color }} />}
|
prefix={<LaptopOutlined style={{ color: series[0].color }} />}
|
||||||
color={series[0].color}
|
color={series[0].color}
|
||||||
progress
|
progress
|
||||||
|
@ -85,7 +85,7 @@ export default function HardwareInfo() {
|
||||||
<Col>
|
<Col>
|
||||||
<StatisticItem
|
<StatisticItem
|
||||||
title={series[1].name}
|
title={series[1].name}
|
||||||
value={`${currentRamUsage}`}
|
value={`${currentRamUsage || 0}`}
|
||||||
prefix={<BulbOutlined style={{ color: series[1].color }} />}
|
prefix={<BulbOutlined style={{ color: series[1].color }} />}
|
||||||
color={series[1].color}
|
color={series[1].color}
|
||||||
progress
|
progress
|
||||||
|
@ -95,7 +95,7 @@ export default function HardwareInfo() {
|
||||||
<Col>
|
<Col>
|
||||||
<StatisticItem
|
<StatisticItem
|
||||||
title={series[2].name}
|
title={series[2].name}
|
||||||
value={`${currentDiskUsage}`}
|
value={`${currentDiskUsage || 0}`}
|
||||||
prefix={<SaveOutlined style={{ color: series[2].color }} />}
|
prefix={<SaveOutlined style={{ color: series[2].color }} />}
|
||||||
color={series[2].color}
|
color={series[2].color}
|
||||||
progress
|
progress
|
||||||
|
|
|
@ -205,8 +205,8 @@ export default function Help() {
|
||||||
<Title level={2}>Common tasks</Title>
|
<Title level={2}>Common tasks</Title>
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
{questions.map(question => (
|
{questions.map(question => (
|
||||||
<Col xs={24} lg={12}>
|
<Col xs={24} lg={12} key={question.title}>
|
||||||
<Card key={question.title}>
|
<Card>
|
||||||
<Meta avatar={question.icon} title={question.title} description={question.content} />
|
<Meta avatar={question.icon} title={question.title} description={question.content} />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -216,8 +216,8 @@ export default function Help() {
|
||||||
<Title level={2}>Other</Title>
|
<Title level={2}>Other</Title>
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
{otherResources.map(question => (
|
{otherResources.map(question => (
|
||||||
<Col xs={24} lg={12}>
|
<Col xs={24} lg={12} key={question.title}>
|
||||||
<Card key={question.title}>
|
<Card>
|
||||||
<Meta avatar={question.icon} title={question.title} description={question.content} />
|
<Meta avatar={question.icon} title={question.title} description={question.content} />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -23,7 +23,15 @@ function AssetTable(assets) {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return <Table dataSource={data} columns={columns} rowKey="id" size="large" pagination={false} />;
|
return (
|
||||||
|
<Table
|
||||||
|
dataSource={data}
|
||||||
|
columns={columns}
|
||||||
|
rowKey={record => record.id}
|
||||||
|
size="large"
|
||||||
|
pagination={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Logs() {
|
export default function Logs() {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
Col,
|
Col,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { DeleteOutlined } from '@ant-design/icons';
|
import { DeleteOutlined } from '@ant-design/icons';
|
||||||
import isValidUrl from '../utils/urls';
|
import isValidUrl, { DEFAULT_TEXTFIELD_URL_PATTERN } from '../utils/urls';
|
||||||
|
|
||||||
import { fetchData, DELETE_WEBHOOK, CREATE_WEBHOOK, WEBHOOKS } from '../utils/apis';
|
import { fetchData, DELETE_WEBHOOK, CREATE_WEBHOOK, WEBHOOKS } from '../utils/apis';
|
||||||
|
|
||||||
|
@ -86,7 +86,11 @@ function NewWebhookModal(props: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkboxes = events.map(function (singleEvent) {
|
const checkboxes = events.map(function (singleEvent) {
|
||||||
return (<Col span={8} key={singleEvent.value}><Checkbox value={singleEvent.value}>{singleEvent.label}</Checkbox></Col>)
|
return (
|
||||||
|
<Col span={8} key={singleEvent.value}>
|
||||||
|
<Checkbox value={singleEvent.value}>{singleEvent.label}</Checkbox>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -101,15 +105,15 @@ function NewWebhookModal(props: Props) {
|
||||||
<Input
|
<Input
|
||||||
value={webhookUrl}
|
value={webhookUrl}
|
||||||
placeholder="https://myserver.com/webhook"
|
placeholder="https://myserver.com/webhook"
|
||||||
onChange={input => setWebhookUrl(input.currentTarget.value)}
|
onChange={input => setWebhookUrl(input.currentTarget.value.trim())}
|
||||||
|
type="url"
|
||||||
|
pattern={DEFAULT_TEXTFIELD_URL_PATTERN}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>Select the events that will be sent to this webhook.</p>
|
<p>Select the events that will be sent to this webhook.</p>
|
||||||
<Checkbox.Group style={{ width: '100%' }} value={selectedEvents} onChange={onChange}>
|
<Checkbox.Group style={{ width: '100%' }} value={selectedEvents} onChange={onChange}>
|
||||||
<Row>
|
<Row>{checkboxes}</Row>
|
||||||
{checkboxes}
|
|
||||||
</Row>
|
|
||||||
</Checkbox.Group>
|
</Checkbox.Group>
|
||||||
<p>
|
<p>
|
||||||
<Button type="primary" onClick={selectAll}>
|
<Button type="primary" onClick={selectAll}>
|
||||||
|
@ -225,7 +229,12 @@ export default function Webhooks() {
|
||||||
.
|
.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
<Table rowKey="id" columns={columns} dataSource={webhooks} pagination={false} />
|
<Table
|
||||||
|
rowKey={record => record.id}
|
||||||
|
columns={columns}
|
||||||
|
dataSource={webhooks}
|
||||||
|
pagination={false}
|
||||||
|
/>
|
||||||
<br />
|
<br />
|
||||||
<Button type="primary" onClick={showCreateModal}>
|
<Button type="primary" onClick={showCreateModal}>
|
||||||
Create Webhook
|
Create Webhook
|
||||||
|
|
|
@ -62,3 +62,8 @@
|
||||||
.read-more-subtext {
|
.read-more-subtext {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
.codec-module {
|
||||||
|
.ant-collapse-content-active {
|
||||||
|
background-color: var(--white-15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ a {
|
||||||
|
|
||||||
p,
|
p,
|
||||||
p.description,
|
p.description,
|
||||||
|
.description,
|
||||||
.ant-typography {
|
.ant-typography {
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
|
@ -65,6 +66,10 @@ strong {
|
||||||
margin: 2em auto;
|
margin: 2em auto;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
border: 1px solid var(--gray-dark);
|
border: 1px solid var(--gray-dark);
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-module {
|
.form-module {
|
||||||
|
@ -106,3 +111,11 @@ strong {
|
||||||
font-size: 0.92em;
|
font-size: 0.92em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
&:not(:focus) {
|
||||||
|
&:invalid {
|
||||||
|
color: var(--ant-error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// DEFAULT VALUES
|
// DEFAULT VALUES
|
||||||
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';
|
||||||
|
import { TEXTFIELD_TYPE_URL } from '../components/config/form-textfield';
|
||||||
|
import { DEFAULT_TEXTFIELD_URL_PATTERN } from './urls';
|
||||||
|
|
||||||
export const TEXT_MAXLENGTH = 255;
|
export const TEXT_MAXLENGTH = 255;
|
||||||
|
|
||||||
|
@ -76,8 +78,7 @@ export const TEXTFIELD_PROPS_SERVER_WELCOME_MESSAGE = {
|
||||||
maxLength: 500,
|
maxLength: 500,
|
||||||
placeholder: '',
|
placeholder: '',
|
||||||
label: 'Welcome Message',
|
label: 'Welcome Message',
|
||||||
tip:
|
tip: 'A system chat message sent to viewers when they first connect to chat. Leave blank to disable.',
|
||||||
'A system chat message sent to viewers when they first connect to chat. Leave blank to disable.',
|
|
||||||
};
|
};
|
||||||
export const TEXTFIELD_PROPS_LOGO = {
|
export const TEXTFIELD_PROPS_LOGO = {
|
||||||
apiPath: API_LOGO,
|
apiPath: API_LOGO,
|
||||||
|
@ -85,8 +86,7 @@ export const TEXTFIELD_PROPS_LOGO = {
|
||||||
maxLength: 255,
|
maxLength: 255,
|
||||||
placeholder: '/img/mylogo.png',
|
placeholder: '/img/mylogo.png',
|
||||||
label: 'Logo',
|
label: 'Logo',
|
||||||
tip:
|
tip: 'Upload your logo if you have one. We recommend that you use a square image that is at least 256x256.',
|
||||||
'Upload your logo if you have one. We recommend that you use a square image that is at least 256x256.',
|
|
||||||
};
|
};
|
||||||
export const TEXTFIELD_PROPS_STREAM_KEY = {
|
export const TEXTFIELD_PROPS_STREAM_KEY = {
|
||||||
apiPath: API_STREAM_KEY,
|
apiPath: API_STREAM_KEY,
|
||||||
|
@ -131,6 +131,9 @@ export const TEXTFIELD_PROPS_INSTANCE_URL = {
|
||||||
placeholder: 'https://owncast.mysite.com',
|
placeholder: 'https://owncast.mysite.com',
|
||||||
label: 'Server URL',
|
label: 'Server URL',
|
||||||
tip: 'The full url to your Owncast server.',
|
tip: 'The full url to your Owncast server.',
|
||||||
|
type: TEXTFIELD_TYPE_URL,
|
||||||
|
pattern: DEFAULT_TEXTFIELD_URL_PATTERN,
|
||||||
|
useTrim: true,
|
||||||
};
|
};
|
||||||
// MISC FIELDS
|
// MISC FIELDS
|
||||||
export const FIELD_PROPS_TAGS = {
|
export const FIELD_PROPS_TAGS = {
|
||||||
|
@ -147,8 +150,7 @@ export const FIELD_PROPS_NSFW = {
|
||||||
apiPath: API_NSFW_SWITCH,
|
apiPath: API_NSFW_SWITCH,
|
||||||
configPath: 'instanceDetails',
|
configPath: 'instanceDetails',
|
||||||
label: 'NSFW?',
|
label: 'NSFW?',
|
||||||
tip:
|
tip: "Turn this ON if you plan to steam explicit or adult content. Please respectfully set this flag so unexpected eyes won't accidentally see it in the Directory.",
|
||||||
"Turn this ON if you plan to steam explicit or adult content. Please respectfully set this flag so unexpected eyes won't accidentally see it in the Directory.",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FIELD_PROPS_YP = {
|
export const FIELD_PROPS_YP = {
|
||||||
|
@ -217,8 +219,7 @@ export const FRAMERATE_DEFAULTS = {
|
||||||
defaultValue: 24,
|
defaultValue: 24,
|
||||||
unit: 'fps',
|
unit: 'fps',
|
||||||
incrementBy: null,
|
incrementBy: null,
|
||||||
tip:
|
tip: 'Reducing your framerate will decrease the amount of video that needs to be encoded and sent to your viewers, saving CPU and bandwidth at the expense of smoothness. A lower value is generally is fine for most content.',
|
||||||
'Reducing your framerate will decrease the amount of video that needs to be encoded and sent to your viewers, saving CPU and bandwidth at the expense of smoothness. A lower value is generally is fine for most content.',
|
|
||||||
};
|
};
|
||||||
export const FRAMERATE_SLIDER_MARKS = {
|
export const FRAMERATE_SLIDER_MARKS = {
|
||||||
[FRAMERATE_DEFAULTS.min]: `${FRAMERATE_DEFAULTS.min} ${FRAMERATE_DEFAULTS.unit}`,
|
[FRAMERATE_DEFAULTS.min]: `${FRAMERATE_DEFAULTS.min} ${FRAMERATE_DEFAULTS.unit}`,
|
||||||
|
@ -247,7 +248,7 @@ export const VIDEO_BITRATE_DEFAULTS = {
|
||||||
export const VIDEO_NAME_DEFAULTS = {
|
export const VIDEO_NAME_DEFAULTS = {
|
||||||
fieldName: 'name',
|
fieldName: 'name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
maxLength: 12,
|
maxLength: 15,
|
||||||
placeholder: 'HD or Low',
|
placeholder: 'HD or Low',
|
||||||
tip: 'Human-readable name for for displaying in the player.',
|
tip: 'Human-readable name for for displaying in the player.',
|
||||||
};
|
};
|
||||||
|
@ -313,7 +314,10 @@ export const S3_TEXT_FIELDS_INFO = {
|
||||||
label: 'Endpoint',
|
label: 'Endpoint',
|
||||||
maxLength: 255,
|
maxLength: 255,
|
||||||
placeholder: 'https://your.s3.provider.endpoint.com',
|
placeholder: 'https://your.s3.provider.endpoint.com',
|
||||||
tip: 'The full URL endpoint your storage provider gave you.',
|
tip: 'The full URL (with "https://") endpoint from your storage provider.',
|
||||||
|
useTrim: true,
|
||||||
|
type: TEXTFIELD_TYPE_URL,
|
||||||
|
pattern: DEFAULT_TEXTFIELD_URL_PATTERN,
|
||||||
},
|
},
|
||||||
region: {
|
region: {
|
||||||
fieldName: 'region',
|
fieldName: 'region',
|
||||||
|
@ -334,7 +338,9 @@ export const S3_TEXT_FIELDS_INFO = {
|
||||||
label: 'Serving Endpoint',
|
label: 'Serving Endpoint',
|
||||||
maxLength: 255,
|
maxLength: 255,
|
||||||
placeholder: 'http://cdn.ss3.provider.endpoint.com',
|
placeholder: 'http://cdn.ss3.provider.endpoint.com',
|
||||||
tip:
|
tip: 'Optional URL that content should be accessed from instead of the default. Used with CDNs and specific storage providers. Generally not required.',
|
||||||
'Optional URL that content should be accessed from instead of the default. Used with CDNs and specific storage providers. Generally not required.',
|
type: TEXTFIELD_TYPE_URL,
|
||||||
|
pattern: DEFAULT_TEXTFIELD_URL_PATTERN,
|
||||||
|
useTrim: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// to use with <input type="url"> fields, as the default pattern only checks for `:`,
|
||||||
|
export const DEFAULT_TEXTFIELD_URL_PATTERN = 'https?://.*';
|
||||||
|
|
||||||
export default function isValidUrl(url: string): boolean {
|
export default function isValidUrl(url: string): boolean {
|
||||||
const validProtocols = ['http:', 'https:'];
|
const validProtocols = ['http:', 'https:'];
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue