mirror of
https://github.com/owncast/owncast.git
synced 2024-10-10 19:16:02 +00:00
cleanup some forms; break out major config styles into their own files
This commit is contained in:
parent
e8c24fd2d4
commit
6e43870d41
@ -3,10 +3,18 @@ import '../styles/colors.scss';
|
||||
import '../styles/globals.scss';
|
||||
import '../styles/ant-overrides.scss';
|
||||
|
||||
import '../styles/form-textfields.scss';
|
||||
import '../styles/form-toggleswitch.scss';
|
||||
import '../styles/form-misc-elements.scss';
|
||||
import '../styles/config-socialhandles.scss';
|
||||
import '../styles/config-storage.scss';
|
||||
import '../styles/config-tags.scss';
|
||||
import '../styles/config-video-variants.scss';
|
||||
|
||||
|
||||
import '../styles/home.scss';
|
||||
import '../styles/chat.scss';
|
||||
import '../styles/config.scss';
|
||||
import '../styles/config-formfields.scss';
|
||||
|
||||
import { AppProps } from 'next/app';
|
||||
import ServerStatusProvider from '../utils/server-status-context';
|
||||
|
@ -41,14 +41,14 @@ export default function CPUUsageSelector({ defaultValue, onChange }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="module-container config-video-segements-conatiner">
|
||||
<div className="config-video-segements-conatiner">
|
||||
<Title level={3}>CPU Usage</Title>
|
||||
<p>There are trade-offs when considering CPU usage blah blah more wording here.</p>
|
||||
<br />
|
||||
<br />
|
||||
<div className="segment-slider">
|
||||
<div className="segment-slider-container">
|
||||
<Slider
|
||||
tipFormatter={value => TOOLTIPS[value] }
|
||||
tipFormatter={value => TOOLTIPS[value]}
|
||||
onChange={handleChange}
|
||||
min={1}
|
||||
max={Object.keys(SLIDER_MARKS).length}
|
||||
|
@ -51,8 +51,8 @@ export default function EditInstanceDetails() {
|
||||
};
|
||||
|
||||
const showConfigurationRestartMessage = () => {
|
||||
setMessage('Updating server settings requires a restart of your Owncast server.')
|
||||
}
|
||||
setMessage('Updating server settings requires a restart of your Owncast server.');
|
||||
};
|
||||
|
||||
function generateStreamKey() {
|
||||
let key = '';
|
||||
|
@ -157,7 +157,7 @@ export default function EditSocialLinks() {
|
||||
postUpdateToAPI(postData);
|
||||
};
|
||||
|
||||
const handleDeleteItem = index => {
|
||||
const handleDeleteItem = (index: number) => {
|
||||
const postData = [...currentSocialHandles];
|
||||
postData.splice(index, 1);
|
||||
postUpdateToAPI(postData);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Switch, Button, Collapse, Alert } from 'antd';
|
||||
import { Switch, Button, Collapse } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import React, { useContext, useState, useEffect } from 'react';
|
||||
import { UpdateArgs } from '../../../types/config-section';
|
||||
@ -32,7 +32,7 @@ function checkSaveable(formValues: any, currentValues: any) {
|
||||
if (enabled) {
|
||||
if (!!endpoint && isValidUrl(endpoint) && !!accessKey && !!secret && !!bucket && !!region) {
|
||||
if (
|
||||
enabled !== currentValues.enabled ||
|
||||
enabled !== currentValues.enabled ||
|
||||
endpoint !== currentValues.endpoint ||
|
||||
accessKey !== currentValues.accessKey ||
|
||||
secret !== currentValues.secret ||
|
||||
@ -52,13 +52,12 @@ function checkSaveable(formValues: any, currentValues: any) {
|
||||
export default function EditStorage() {
|
||||
const [formDataValues, setFormDataValues] = useState(null);
|
||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||
const [saved, setSaved] = useState<Boolean>(false);
|
||||
|
||||
const [shouldDisplayForm, setShouldDisplayForm] = useState(false);
|
||||
const serverStatusData = useContext(ServerStatusContext);
|
||||
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
||||
|
||||
const {message, setMessage} = useContext(AlertMessageContext);
|
||||
const { setMessage: setAlertMessage } = useContext(AlertMessageContext);
|
||||
|
||||
const { s3 } = serverConfig;
|
||||
const {
|
||||
@ -117,8 +116,9 @@ export default function EditStorage() {
|
||||
setFieldInConfigState({ fieldName: 's3', value: postValue, path: '' });
|
||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.'));
|
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||
setSaved(true);
|
||||
setMessage('Changing your storage configuration will take place the next time you start a new stream.');
|
||||
setAlertMessage(
|
||||
'Changing your storage configuration will take place the next time you start a new stream.',
|
||||
);
|
||||
},
|
||||
onError: (message: string) => {
|
||||
setSubmitStatus(createInputStatus(STATUS_ERROR, message));
|
||||
@ -131,12 +131,6 @@ export default function EditStorage() {
|
||||
const handleSwitchChange = (storageEnabled: boolean) => {
|
||||
setShouldDisplayForm(storageEnabled);
|
||||
handleFieldChange({ fieldName: 'enabled', value: storageEnabled });
|
||||
|
||||
// if current data in current store says s3 is enabled,
|
||||
// we should save this state
|
||||
// if (!storageEnabled && s3.enabled) {
|
||||
// handleSave();
|
||||
// }
|
||||
};
|
||||
|
||||
const containerClass = classNames({
|
||||
|
@ -11,7 +11,7 @@ interface DropdownProps {
|
||||
}
|
||||
|
||||
export default function SocialDropdown({ iconList, selectedOption, onSelected }: DropdownProps) {
|
||||
const handleSelected = value => {
|
||||
const handleSelected = (value: string) => {
|
||||
if (onSelected) {
|
||||
onSelected(value);
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
import React, { useContext, useState, useEffect } from 'react';
|
||||
import { Typography, Slider } from 'antd';
|
||||
import { ServerStatusContext } from '../../../utils/server-status-context';
|
||||
import { API_VIDEO_SEGMENTS, RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
|
||||
import {
|
||||
API_VIDEO_SEGMENTS,
|
||||
SUCCESS_STATES,
|
||||
RESET_TIMEOUT,
|
||||
postConfigUpdateToAPI,
|
||||
} from './constants';
|
||||
createInputStatus,
|
||||
StatusState,
|
||||
STATUS_ERROR,
|
||||
STATUS_PROCESSING,
|
||||
STATUS_SUCCESS,
|
||||
} from '../../../utils/input-statuses';
|
||||
import FormStatusIndicator from './form-status-indicator';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
@ -37,8 +40,10 @@ function SegmentToolTip({ value }: SegmentToolTipProps) {
|
||||
}
|
||||
|
||||
export default function VideoLatency() {
|
||||
const [submitStatus, setSubmitStatus] = useState(null);
|
||||
const [submitStatusMessage, setSubmitStatusMessage] = useState('');
|
||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||
|
||||
// const [submitStatus, setSubmitStatus] = useState(null);
|
||||
// const [submitStatusMessage, setSubmitStatusMessage] = useState('');
|
||||
const [selectedOption, setSelectedOption] = useState(null);
|
||||
|
||||
const serverStatusData = useContext(ServerStatusContext);
|
||||
@ -57,13 +62,15 @@ export default function VideoLatency() {
|
||||
|
||||
const resetStates = () => {
|
||||
setSubmitStatus(null);
|
||||
setSubmitStatusMessage('');
|
||||
// setSubmitStatusMessage('');
|
||||
resetTimer = null;
|
||||
clearTimeout(resetTimer);
|
||||
};
|
||||
|
||||
// posts all the variants at once as an array obj
|
||||
const postUpdateToAPI = async (postValue: any) => {
|
||||
setSubmitStatus(createInputStatus(STATUS_PROCESSING));
|
||||
|
||||
await postConfigUpdateToAPI({
|
||||
apiPath: API_VIDEO_SEGMENTS,
|
||||
data: { value: postValue },
|
||||
@ -73,34 +80,28 @@ export default function VideoLatency() {
|
||||
value: postValue,
|
||||
path: 'videoSettings',
|
||||
});
|
||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Variants updated.'));
|
||||
|
||||
setSubmitStatus('success');
|
||||
setSubmitStatusMessage('Variants updated.');
|
||||
// setSubmitStatus('success');
|
||||
// setSubmitStatusMessage('Variants updated.');
|
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||
},
|
||||
onError: (message: string) => {
|
||||
setSubmitStatus('error');
|
||||
setSubmitStatusMessage(message);
|
||||
setSubmitStatus(createInputStatus(STATUS_ERROR, message));
|
||||
|
||||
// setSubmitStatus('error');
|
||||
// setSubmitStatusMessage(message);
|
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const { icon: newStatusIcon = null, message: newStatusMessage = '' } =
|
||||
SUCCESS_STATES[submitStatus] || {};
|
||||
|
||||
const statusMessage = (
|
||||
<div className={`status-message ${submitStatus || ''}`}>
|
||||
{newStatusIcon} {newStatusMessage} {submitStatusMessage}
|
||||
</div>
|
||||
);
|
||||
|
||||
const handleChange = value => {
|
||||
postUpdateToAPI(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="module-container config-video-segements-conatiner">
|
||||
<div className="config-video-segements-conatiner">
|
||||
<Title level={3}>Latency Buffer</Title>
|
||||
<p>
|
||||
There are trade-offs when cosidering video latency and reliability. Blah blah .. better
|
||||
@ -108,7 +109,7 @@ export default function VideoLatency() {
|
||||
</p>
|
||||
<br />
|
||||
<br />
|
||||
<div className="segment-slider">
|
||||
<div className="segment-slider-container">
|
||||
<Slider
|
||||
tipFormatter={value => <SegmentToolTip value={SLIDER_COMMENTS[value]} />}
|
||||
onChange={handleChange}
|
||||
@ -119,7 +120,7 @@ export default function VideoLatency() {
|
||||
value={selectedOption}
|
||||
/>
|
||||
</div>
|
||||
{statusMessage}
|
||||
<FormStatusIndicator status={submitStatus} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
// This content populates the video variant modal, which is spawned from the variants table.
|
||||
import React from 'react';
|
||||
import { Slider, Select, Switch, Divider, Collapse } from 'antd';
|
||||
import { FieldUpdaterFunc, CpuUsageLevel, VideoVariant } from '../../../types/config-section';
|
||||
import { Slider, Switch, Collapse } from 'antd';
|
||||
import { FieldUpdaterFunc, VideoVariant } from '../../../types/config-section';
|
||||
import { DEFAULT_VARIANT_STATE } from './constants';
|
||||
import InfoTip from '../info-tip';
|
||||
import CPUUsageSelector from './cpu-usage';
|
||||
|
||||
const { Option } = Select;
|
||||
const { Panel } = Collapse;
|
||||
|
||||
const VIDEO_VARIANT_DEFAULTS = {
|
||||
@ -57,12 +56,6 @@ export default function VideoVariantForm({
|
||||
const handleVideoBitrateChange = (value: number) => {
|
||||
onUpdateField({ fieldName: 'videoBitrate', value });
|
||||
};
|
||||
const handleAudioBitrateChange = (value: number) => {
|
||||
onUpdateField({ fieldName: 'audioBitrate', value });
|
||||
};
|
||||
const handleAudioPassChange = (value: boolean) => {
|
||||
onUpdateField({ fieldName: 'audioPassthrough', value });
|
||||
};
|
||||
const handleVideoPassChange = (value: boolean) => {
|
||||
onUpdateField({ fieldName: 'videoPassthrough', value });
|
||||
};
|
||||
@ -80,18 +73,12 @@ export default function VideoVariantForm({
|
||||
const videoBRMax = videoBitrateDefaults.max;
|
||||
const videoBRUnit = videoBitrateDefaults.unit;
|
||||
|
||||
const audioBitrateDefaults = VIDEO_VARIANT_DEFAULTS.audioBitrate;
|
||||
const audioBRMin = audioBitrateDefaults.min;
|
||||
const audioBRMax = audioBitrateDefaults.max;
|
||||
const audioBRUnit = audioBitrateDefaults.unit;
|
||||
|
||||
const selectedVideoBRnote = `Selected: ${dataState.videoBitrate}${videoBRUnit} - it sucks`;
|
||||
const selectedAudioBRnote = `Selected: ${dataState.audioBitrate}${audioBRUnit} - too slow`;
|
||||
const selectedFramerateNote = `Selected: ${dataState.framerate}${framerateUnit} - whoa there`;
|
||||
const selectedPresetNote = '';
|
||||
|
||||
return (
|
||||
<div className="variant-form">
|
||||
<div className="config-variant-form">
|
||||
<div className="section-intro">
|
||||
Say a thing here about how this all works. Read more{' '}
|
||||
<a href="https://owncast.online/docs/configuration/">here</a>.
|
||||
@ -130,6 +117,7 @@ export default function VideoVariantForm({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* VIDEO BITRATE FIELD */}
|
||||
<div className={`field ${dataState.videoPassthrough ? 'disabled' : ''}`}>
|
||||
<p className="label">
|
||||
@ -192,55 +180,6 @@ export default function VideoVariantForm({
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* AUDIO PASSTHROUGH FIELD */}
|
||||
{/* <div className="field">
|
||||
<p className="label">
|
||||
<InfoTip tip={VIDEO_VARIANT_DEFAULTS.audioPassthrough.tip} />
|
||||
Use Audio Passthrough?
|
||||
</p>
|
||||
<div className="form-component">
|
||||
<Switch
|
||||
defaultChecked={dataState.audioPassthrough}
|
||||
checked={dataState.audioPassthrough}
|
||||
onChange={handleAudioPassChange}
|
||||
checkedChildren="Yes"
|
||||
unCheckedChildren="No"
|
||||
/>
|
||||
{dataState.audioPassthrough ? <span className="note">Same as source</span> : null}
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{/* AUDIO BITRATE FIELD */}
|
||||
{/* <div className={`field ${dataState.audioPassthrough ? 'disabled' : ''}`}>
|
||||
<p className="label">
|
||||
<InfoTip tip={VIDEO_VARIANT_DEFAULTS.audioBitrate.tip} />
|
||||
Audio Bitrate:
|
||||
</p>
|
||||
<div className="form-component">
|
||||
<Slider
|
||||
// tooltipVisible={dataState.audioPassthrough !== true}
|
||||
tipFormatter={value => `${value} ${audioBRUnit}`}
|
||||
disabled={dataState.audioPassthrough === true}
|
||||
defaultValue={dataState.audioBitrate}
|
||||
value={dataState.audioBitrate}
|
||||
onChange={handleAudioBitrateChange}
|
||||
step={audioBitrateDefaults.incrementBy}
|
||||
min={audioBRMin}
|
||||
max={audioBRMax}
|
||||
marks={{
|
||||
[audioBRMin]: `${audioBRMin} ${audioBRUnit}`,
|
||||
[audioBRMax]: `${audioBRMax} ${audioBRUnit}`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{selectedAudioBRnote ? (
|
||||
<span className="selected-value-note">{selectedAudioBRnote}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div> */}
|
||||
</Panel>
|
||||
</Collapse>
|
||||
</div>
|
||||
|
@ -1,6 +1,5 @@
|
||||
// Updating a variant will post ALL the variants in an array as an update to the API.
|
||||
|
||||
// todo : add DELETE option
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { Typography, Table, Modal, Button } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
@ -20,11 +19,19 @@ import {
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
const CPU_USAGE_LEVEL_MAP = {
|
||||
1: 'lowest',
|
||||
2: 'low',
|
||||
3: 'medium',
|
||||
4: 'high',
|
||||
5: 'highest',
|
||||
};
|
||||
|
||||
export default function CurrentVariantsTable() {
|
||||
const [displayModal, setDisplayModal] = useState(false);
|
||||
const [modalProcessing, setModalProcessing] = useState(false);
|
||||
const [editId, setEditId] = useState(0);
|
||||
const {setMessage} = useContext(AlertMessageContext);
|
||||
const { setMessage } = useContext(AlertMessageContext);
|
||||
|
||||
// current data inside modal
|
||||
const [modalDataState, setModalDataState] = useState(DEFAULT_VARIANT_STATE);
|
||||
@ -76,7 +83,9 @@ export default function CurrentVariantsTable() {
|
||||
setSubmitStatusMessage('Variants updated.');
|
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||
|
||||
setMessage('Updating your video configuration will take effect the next time you begin a new stream.');
|
||||
setMessage(
|
||||
'Updating your video configuration will take effect the next time you begin a new stream.',
|
||||
);
|
||||
},
|
||||
onError: (message: string) => {
|
||||
setSubmitStatus('error');
|
||||
@ -117,14 +126,6 @@ export default function CurrentVariantsTable() {
|
||||
const { icon: newStatusIcon = null, message: newStatusMessage = '' } =
|
||||
SUCCESS_STATES[submitStatus] || {};
|
||||
|
||||
const cpuUsageLevelLabelMap = {
|
||||
1: 'lowest',
|
||||
2: 'low',
|
||||
3: 'medium',
|
||||
4: 'high',
|
||||
5: 'highest',
|
||||
};
|
||||
|
||||
const videoQualityColumns: ColumnsType<VideoVariant> = [
|
||||
{
|
||||
title: 'Video bitrate',
|
||||
@ -137,7 +138,7 @@ export default function CurrentVariantsTable() {
|
||||
title: 'CPU Usage',
|
||||
dataIndex: 'cpuUsageLevel',
|
||||
key: 'cpuUsageLevel',
|
||||
render: (level: string) => (!level ? 'n/a' : cpuUsageLevelLabelMap[level]),
|
||||
render: (level: string) => (!level ? 'n/a' : CPU_USAGE_LEVEL_MAP[level]),
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
|
25
web/styles/config-socialhandles.scss
Normal file
25
web/styles/config-socialhandles.scss
Normal file
@ -0,0 +1,25 @@
|
||||
// styles for social handles editing section
|
||||
|
||||
.social-option,
|
||||
.social-dropdown {
|
||||
.ant-select-item-option-content,
|
||||
.ant-select-selection-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: .25em;
|
||||
line-height: normal;
|
||||
|
||||
.option-icon {
|
||||
height: 1.5em;
|
||||
width: 1.5em;
|
||||
line-height: normal;
|
||||
}
|
||||
.option-label {
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
}
|
49
web/styles/config-storage.scss
Normal file
49
web/styles/config-storage.scss
Normal file
@ -0,0 +1,49 @@
|
||||
// styles for Storage config section
|
||||
|
||||
|
||||
.edit-storage-container {
|
||||
.form-fields {
|
||||
display: none;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
&.enabled {
|
||||
.form-fields {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.button-container {
|
||||
margin: 1em 0;
|
||||
}
|
||||
.advanced-section {
|
||||
margin: 1em 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Do something special for the stream key field
|
||||
.field-streamkey-container {
|
||||
margin-bottom: 1.5em;
|
||||
.field-tip {
|
||||
color: var(--ant-warning);
|
||||
}
|
||||
.left-side {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.textfield-with-submit-container {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.streamkey-actions {
|
||||
white-space: nowrap;
|
||||
button {
|
||||
margin: .25em;
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
margin-top: 2em;
|
||||
}
|
||||
}
|
||||
}
|
37
web/styles/config-tags.scss
Normal file
37
web/styles/config-tags.scss
Normal file
@ -0,0 +1,37 @@
|
||||
// config tags block
|
||||
|
||||
.tag-current-tags {
|
||||
.ant-tag {
|
||||
margin: .1rem;
|
||||
font-size: .85rem;
|
||||
border-radius: 10em;
|
||||
padding: .25em 1em;
|
||||
background-color: rgba(255,255,255,.5);
|
||||
|
||||
.ant-tag-close-icon {
|
||||
transform: translateY(-1px);
|
||||
margin-left: .3rem;
|
||||
padding: 2px;
|
||||
border-radius: 5rem;
|
||||
border: 1px solid #eee;
|
||||
&:hover {
|
||||
border-color: #e03;
|
||||
svg {
|
||||
fill: black;
|
||||
transition: fill .3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-new-tag-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
.new-tag-input {
|
||||
width: 16em;
|
||||
}
|
||||
}
|
85
web/styles/config-video-variants.scss
Normal file
85
web/styles/config-video-variants.scss
Normal file
@ -0,0 +1,85 @@
|
||||
// styles for Video variant editor (table + modal)
|
||||
|
||||
|
||||
.config-variant-form {
|
||||
.blurb {
|
||||
margin: 1em;
|
||||
opacity: .75;
|
||||
}
|
||||
.note {
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
font-size: .75em;
|
||||
opacity: .5;
|
||||
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%;
|
||||
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 {
|
||||
font-size: .85em;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ant-collapse {
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.ant-collapse > .ant-collapse-item:last-child,
|
||||
.ant-collapse > .ant-collapse-item:last-child > .ant-collapse-header {
|
||||
border: none;
|
||||
background-color: rgba(0,0,0,.25);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.ant-collapse-content {
|
||||
background-color: rgba(0,0,0,.1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.config-video-segements-conatiner {
|
||||
|
||||
.status-message {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.variants-table {
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.delete-button {
|
||||
margin-left: .5em;
|
||||
opacity: .8;
|
||||
}
|
||||
}
|
@ -1,164 +1,7 @@
|
||||
// .config-public-details-container {
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
// align-items: flex-start;
|
||||
// flex-wrap: wrap;
|
||||
|
||||
// .text-fields {
|
||||
// margin-right: 2rem;
|
||||
// }
|
||||
// .misc-fields {
|
||||
// width: 25em;
|
||||
// }
|
||||
// .tag-editor-container,
|
||||
// .config-directory-details-form {
|
||||
// border-radius: 1em;
|
||||
// background-color: rgba(128,99,255,.1);
|
||||
// padding: 1.5em;
|
||||
// margin-bottom: 1em;
|
||||
// }
|
||||
// }
|
||||
|
||||
.module-container {
|
||||
border-radius: 1em;
|
||||
background-color: rgba(128,99,255,.1);
|
||||
padding: 1.5em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// form-textfield
|
||||
// form-textfield
|
||||
// .textfield-container {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// align-items: flex-start;
|
||||
// justify-content: flex-end;
|
||||
// position: relative;
|
||||
// width: 314px;
|
||||
|
||||
// // &.type-numeric {
|
||||
// // .ant-form-item-control {
|
||||
// // flex-direction: row;
|
||||
// // .ant-form-item-control-input {
|
||||
// // margin-right: .75rem;
|
||||
// // }
|
||||
// // }
|
||||
// // }
|
||||
// }
|
||||
// .textfield {
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
// align-items: flex-start;
|
||||
|
||||
// .field {
|
||||
// width: 18rem;
|
||||
|
||||
// &.ant-input-number {
|
||||
// width: 8em;
|
||||
// }
|
||||
|
||||
// }
|
||||
// .info-tip {
|
||||
// margin-right: .75rem;
|
||||
// }
|
||||
// .ant-form-item {
|
||||
// margin-bottom: 16px;
|
||||
// &.ant-form-item-with-help {
|
||||
// margin-bottom: 16px;
|
||||
// }
|
||||
// }
|
||||
// .ant-form-item-label label {
|
||||
// font-weight: bold;
|
||||
// color: var(--owncast-purple);
|
||||
// }
|
||||
// .ant-form-item-explain {
|
||||
// width: 70%;
|
||||
// }
|
||||
// }
|
||||
// .submit-button {
|
||||
// position: absolute;
|
||||
// right: 0;
|
||||
// bottom: .5em;
|
||||
// }
|
||||
// .ant-form-horizontal {
|
||||
// .textfield-container.type-numeric {
|
||||
// width: auto;
|
||||
|
||||
// .submit-button {
|
||||
// bottom: unset;
|
||||
// top: 0;
|
||||
// right: unset;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// form-toggleswitch
|
||||
// form-toggleswitch
|
||||
// .toggleswitch-container {
|
||||
// .status-message {
|
||||
// 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;
|
||||
// }
|
||||
// }
|
||||
|
||||
// TAGS STUFF
|
||||
// TAGS STUFF
|
||||
.tag-current-tags {
|
||||
.ant-tag {
|
||||
margin: .1rem;
|
||||
font-size: .85rem;
|
||||
border-radius: 10em;
|
||||
padding: .25em 1em;
|
||||
background-color: rgba(255,255,255,.5);
|
||||
|
||||
.ant-tag-close-icon {
|
||||
transform: translateY(-1px);
|
||||
margin-left: .3rem;
|
||||
padding: 2px;
|
||||
border-radius: 5rem;
|
||||
border: 1px solid #eee;
|
||||
&:hover {
|
||||
border-color: #e03;
|
||||
svg {
|
||||
fill: black;
|
||||
transition: fill .3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.add-new-tag-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
.new-tag-input {
|
||||
width: 16em;
|
||||
}
|
||||
}
|
||||
|
||||
.config-page-content-form {
|
||||
.page-content-actions {
|
||||
@ -174,101 +17,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.config-video-variants {
|
||||
.config-video-misc {
|
||||
margin: 2rem 0;
|
||||
// .ant-form {
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
// align-items: flex-start;
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
.variant-form {
|
||||
.blurb {
|
||||
margin: 1em;
|
||||
opacity: .75;
|
||||
}
|
||||
.note {
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
font-size: .75em;
|
||||
opacity: .5;
|
||||
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%;
|
||||
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 {
|
||||
font-size: .85em;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ant-collapse {
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.ant-collapse > .ant-collapse-item:last-child,
|
||||
.ant-collapse > .ant-collapse-item:last-child > .ant-collapse-header {
|
||||
border: none;
|
||||
background-color: rgba(0,0,0,.25);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.ant-collapse-content {
|
||||
background-color: rgba(0,0,0,.1);
|
||||
}
|
||||
}
|
||||
|
||||
.config-video-segements-conatiner {
|
||||
.segment-slider {
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
padding: 1em 2em .75em;
|
||||
background-color: black;
|
||||
border-radius: 1em;
|
||||
}
|
||||
.status-message {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.variants-table {
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.delete-button {
|
||||
margin-left: .5em;
|
||||
opacity: .8;
|
||||
}
|
||||
}
|
||||
.segment-tip {
|
||||
width: 10em;
|
||||
text-align: center;
|
||||
@ -276,33 +27,6 @@
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.social-option,
|
||||
.social-dropdown {
|
||||
// .ant-select-selector,
|
||||
// .ant-select-selection-search-input {
|
||||
// height: 40px !important;
|
||||
// }
|
||||
.ant-select-item-option-content,
|
||||
.ant-select-selection-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: .25em;
|
||||
line-height: normal;
|
||||
|
||||
.option-icon {
|
||||
height: 1.5em;
|
||||
width: 1.5em;
|
||||
line-height: normal;
|
||||
}
|
||||
.option-label {
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
// .social-option {
|
||||
// .ant-select-item-option-content {
|
||||
// display: flex;
|
||||
@ -324,57 +48,3 @@
|
||||
|
||||
|
||||
|
||||
|
||||
// EDIT STORAGE
|
||||
.edit-storage-container {
|
||||
.form-fields {
|
||||
display: none;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
&.enabled {
|
||||
.form-fields {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.button-container {
|
||||
margin: 1em 0;
|
||||
}
|
||||
.advanced-section {
|
||||
margin: 1em 0;
|
||||
}
|
||||
}
|
||||
|
||||
.field-container {
|
||||
padding: .85em 0 .5em;
|
||||
&:nth-child(even) {
|
||||
background-color: rgba(0,0,0,.25);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.field-streamkey-container {
|
||||
margin-bottom: 1.5em;
|
||||
.field-tip {
|
||||
color: var(--ant-warning);
|
||||
}
|
||||
.left-side {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.textfield-with-submit-container {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.streamkey-actions {
|
||||
white-space: nowrap;
|
||||
button {
|
||||
margin: .25em;
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
margin-top: 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
64
web/styles/form-misc-elements.scss
Normal file
64
web/styles/form-misc-elements.scss
Normal file
@ -0,0 +1,64 @@
|
||||
/* Base styles for misc helper components around forms */
|
||||
|
||||
/* STATUS-CONTAINER BASE */
|
||||
.status-container {
|
||||
&.status-success {
|
||||
color: var(--ant-success);
|
||||
}
|
||||
&.status-error {
|
||||
color: var(--ant-error);
|
||||
}
|
||||
&.status-warning {
|
||||
color: var(--ant-warning);
|
||||
}
|
||||
|
||||
&.empty {
|
||||
display: none;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
font-size: .75rem;
|
||||
.status-icon {
|
||||
display: inline-block;
|
||||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* TIP CONTAINER BASE */
|
||||
.field-tip {
|
||||
font-size: .7em;
|
||||
color: rgba(255,255,255,.5)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Ideal for wrapping each Textfield on a page with many text fields in a row. This div will alternate colors and look like a table.
|
||||
*/
|
||||
.field-container {
|
||||
padding: .85em 0 .5em;
|
||||
&:nth-child(even) {
|
||||
background-color: rgba(0,0,0,.25);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* SEGMENT SLIDER */
|
||||
.segment-slider-container {
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
padding: 1em 2em .75em;
|
||||
background-color: black;
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
|
||||
.segment-tip {
|
||||
width: 10em;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -1,37 +1,4 @@
|
||||
// Base styles for form-textfield, form-textfield-with-submit, and helper components.
|
||||
|
||||
/* STATUS-CONTAINER BASE */
|
||||
.status-container {
|
||||
&.status-success {
|
||||
color: var(--ant-success);
|
||||
}
|
||||
&.status-error {
|
||||
color: var(--ant-error);
|
||||
}
|
||||
&.status-warning {
|
||||
color: var(--ant-warning);
|
||||
}
|
||||
|
||||
&.empty {
|
||||
display: none;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
font-size: .75rem;
|
||||
.status-icon {
|
||||
display: inline-block;
|
||||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* TIP CONTAINER BASE */
|
||||
.field-tip {
|
||||
font-size: .7em;
|
||||
color: rgba(255,255,255,.5)
|
||||
}
|
||||
// Base styles for form-textfield, form-textfield-with-submit
|
||||
|
||||
|
||||
/* TEXTFIELD-CONTAINER BASE */
|
||||
@ -103,6 +70,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* TEXTFIELD-WITH-SUBMIT-CONTAINER BASE */
|
||||
/* TEXTFIELD-WITH-SUBMIT-CONTAINER BASE */
|
||||
/* TEXTFIELD-WITH-SUBMIT-CONTAINER BASE */
|
||||
.textfield-with-submit-container {
|
||||
display: flex;
|
||||
@ -169,30 +138,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
}
|
29
web/styles/form-toggleswitch.scss
Normal file
29
web/styles/form-toggleswitch.scss
Normal file
@ -0,0 +1,29 @@
|
||||
/* 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,27 +1,27 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const AlertMessageContext = React.createContext({
|
||||
message: null,
|
||||
setMessage: (text?: string) => {
|
||||
return text;
|
||||
}
|
||||
message: null,
|
||||
setMessage: (text?: string) => {
|
||||
return text;
|
||||
},
|
||||
});
|
||||
|
||||
const AlertMessageProvider = ({ children }) => {
|
||||
const [message, setMessage] = useState('');
|
||||
|
||||
const providerValue = {
|
||||
message,
|
||||
setMessage
|
||||
}
|
||||
return (
|
||||
<AlertMessageContext.Provider value={providerValue}>{children}</AlertMessageContext.Provider>
|
||||
)
|
||||
}
|
||||
const [message, setMessage] = useState('');
|
||||
|
||||
AlertMessageProvider.propTypes = {
|
||||
children: PropTypes.element.isRequired
|
||||
const providerValue = {
|
||||
message,
|
||||
setMessage,
|
||||
};
|
||||
return (
|
||||
<AlertMessageContext.Provider value={providerValue}>{children}</AlertMessageContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AlertMessageProvider;
|
||||
AlertMessageProvider.propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
};
|
||||
|
||||
export default AlertMessageProvider;
|
||||
|
Loading…
x
Reference in New Issue
Block a user