mirror of
https://github.com/owncast/owncast.git
synced 2024-10-10 19:16:02 +00:00
Merge branch 'admin-css-overhaul' into 0.0.6
This commit is contained in:
commit
5dd4d49118
@ -1,12 +1,23 @@
|
|||||||
|
// 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/globals.scss';
|
||||||
import '../styles/ant-overrides.scss';
|
import '../styles/ant-overrides.scss';
|
||||||
|
import '../styles/markdown-editor.scss';
|
||||||
|
|
||||||
|
import '../styles/main-layout.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/home.scss';
|
||||||
import '../styles/chat.scss';
|
import '../styles/chat.scss';
|
||||||
import '../styles/config.scss';
|
import '../styles/config.scss';
|
||||||
import '../styles/config-formfields.scss';
|
|
||||||
|
|
||||||
import { AppProps } from 'next/app';
|
import { AppProps } from 'next/app';
|
||||||
import ServerStatusProvider from '../utils/server-status-context';
|
import ServerStatusProvider from '../utils/server-status-context';
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { LineChart } from 'react-chartkick';
|
import { LineChart } from 'react-chartkick';
|
||||||
import 'chart.js';
|
import 'chart.js';
|
||||||
import format from 'date-fns/format';
|
import format from 'date-fns/format';
|
||||||
import styles from '../../styles/styles.module.scss';
|
|
||||||
|
|
||||||
interface TimedValue {
|
interface TimedValue {
|
||||||
time: Date;
|
time: Date;
|
||||||
@ -9,11 +8,11 @@ interface TimedValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ChartProps {
|
interface ChartProps {
|
||||||
data?: TimedValue[],
|
data?: TimedValue[];
|
||||||
title?: string,
|
title?: string;
|
||||||
color: string,
|
color: string;
|
||||||
unit: string,
|
unit: string;
|
||||||
dataCollections?: any[],
|
dataCollections?: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function createGraphDataset(dataArray) {
|
function createGraphDataset(dataArray) {
|
||||||
@ -22,7 +21,7 @@ function createGraphDataset(dataArray) {
|
|||||||
const dateObject = new Date(item.time);
|
const dateObject = new Date(item.time);
|
||||||
const dateString = format(dateObject, 'p P');
|
const dateString = format(dateObject, 'p P');
|
||||||
dataValues[dateString] = item.value;
|
dataValues[dateString] = item.value;
|
||||||
})
|
});
|
||||||
return dataValues;
|
return dataValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,18 +32,20 @@ export default function Chart({ data, title, color, unit, dataCollections }: Cha
|
|||||||
renderData.push({
|
renderData.push({
|
||||||
name: title,
|
name: title,
|
||||||
color,
|
color,
|
||||||
data: createGraphDataset(data)
|
data: createGraphDataset(data),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
dataCollections.forEach(collection => {
|
dataCollections.forEach(collection => {
|
||||||
renderData.push(
|
renderData.push({
|
||||||
{name: collection.name, data: createGraphDataset(collection.data), color: collection.color}
|
name: collection.name,
|
||||||
)
|
data: createGraphDataset(collection.data),
|
||||||
|
color: collection.color,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.lineChartContainer}>
|
<div className="line-chart-container">
|
||||||
<LineChart
|
<LineChart
|
||||||
xtitle="Time"
|
xtitle="Time"
|
||||||
ytitle={title}
|
ytitle={title}
|
||||||
|
|||||||
@ -63,7 +63,7 @@ export const TEXTFIELD_PROPS_SERVER_NAME = {
|
|||||||
};
|
};
|
||||||
export const TEXTFIELD_PROPS_STREAM_TITLE = {
|
export const TEXTFIELD_PROPS_STREAM_TITLE = {
|
||||||
apiPath: API_STREAM_TITLE,
|
apiPath: API_STREAM_TITLE,
|
||||||
maxLength: TEXT_MAXLENGTH,
|
maxLength: 100,
|
||||||
placeholder: 'Doing cool things...',
|
placeholder: 'Doing cool things...',
|
||||||
label: 'Stream Title',
|
label: 'Stream Title',
|
||||||
tip: 'What is your stream about today?',
|
tip: 'What is your stream about today?',
|
||||||
|
|||||||
@ -41,14 +41,14 @@ export default function CPUUsageSelector({ defaultValue, onChange }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-container config-video-segements-conatiner">
|
<div className="config-video-segements-conatiner">
|
||||||
<Title level={3}>CPU Usage</Title>
|
<Title level={3}>CPU Usage</Title>
|
||||||
<p>There are trade-offs when considering CPU usage blah blah more wording here.</p>
|
<p>There are trade-offs when considering CPU usage blah blah more wording here.</p>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<div className="segment-slider">
|
<div className="segment-slider-container">
|
||||||
<Slider
|
<Slider
|
||||||
tipFormatter={value => TOOLTIPS[value] }
|
tipFormatter={value => TOOLTIPS[value]}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
min={1}
|
min={1}
|
||||||
max={Object.keys(SLIDER_MARKS).length}
|
max={Object.keys(SLIDER_MARKS).length}
|
||||||
|
|||||||
@ -51,8 +51,8 @@ export default function EditInstanceDetails() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const showConfigurationRestartMessage = () => {
|
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() {
|
function generateStreamKey() {
|
||||||
let key = '';
|
let key = '';
|
||||||
|
|||||||
@ -157,7 +157,7 @@ export default function EditSocialLinks() {
|
|||||||
postUpdateToAPI(postData);
|
postUpdateToAPI(postData);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteItem = index => {
|
const handleDeleteItem = (index: number) => {
|
||||||
const postData = [...currentSocialHandles];
|
const postData = [...currentSocialHandles];
|
||||||
postData.splice(index, 1);
|
postData.splice(index, 1);
|
||||||
postUpdateToAPI(postData);
|
postUpdateToAPI(postData);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Switch, Button, Collapse, Alert } from 'antd';
|
import { Switch, Button, Collapse } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { useContext, useState, useEffect } from 'react';
|
import React, { useContext, useState, useEffect } from 'react';
|
||||||
import { UpdateArgs } from '../../../types/config-section';
|
import { UpdateArgs } from '../../../types/config-section';
|
||||||
@ -32,7 +32,7 @@ function checkSaveable(formValues: any, currentValues: any) {
|
|||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (!!endpoint && isValidUrl(endpoint) && !!accessKey && !!secret && !!bucket && !!region) {
|
if (!!endpoint && isValidUrl(endpoint) && !!accessKey && !!secret && !!bucket && !!region) {
|
||||||
if (
|
if (
|
||||||
enabled !== currentValues.enabled ||
|
enabled !== currentValues.enabled ||
|
||||||
endpoint !== currentValues.endpoint ||
|
endpoint !== currentValues.endpoint ||
|
||||||
accessKey !== currentValues.accessKey ||
|
accessKey !== currentValues.accessKey ||
|
||||||
secret !== currentValues.secret ||
|
secret !== currentValues.secret ||
|
||||||
@ -52,13 +52,12 @@ function checkSaveable(formValues: any, currentValues: any) {
|
|||||||
export default function EditStorage() {
|
export default function EditStorage() {
|
||||||
const [formDataValues, setFormDataValues] = useState(null);
|
const [formDataValues, setFormDataValues] = useState(null);
|
||||||
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||||
const [saved, setSaved] = useState<Boolean>(false);
|
|
||||||
|
|
||||||
const [shouldDisplayForm, setShouldDisplayForm] = useState(false);
|
const [shouldDisplayForm, setShouldDisplayForm] = useState(false);
|
||||||
const serverStatusData = useContext(ServerStatusContext);
|
const serverStatusData = useContext(ServerStatusContext);
|
||||||
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
|
||||||
|
|
||||||
const {message, setMessage} = useContext(AlertMessageContext);
|
const { setMessage: setAlertMessage } = useContext(AlertMessageContext);
|
||||||
|
|
||||||
const { s3 } = serverConfig;
|
const { s3 } = serverConfig;
|
||||||
const {
|
const {
|
||||||
@ -117,8 +116,9 @@ export default function EditStorage() {
|
|||||||
setFieldInConfigState({ fieldName: 's3', value: postValue, path: '' });
|
setFieldInConfigState({ fieldName: 's3', value: postValue, path: '' });
|
||||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.'));
|
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.'));
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
setSaved(true);
|
setAlertMessage(
|
||||||
setMessage('Changing your storage configuration will take place the next time you start a new stream.');
|
'Changing your storage configuration will take place the next time you start a new stream.',
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onError: (message: string) => {
|
onError: (message: string) => {
|
||||||
setSubmitStatus(createInputStatus(STATUS_ERROR, message));
|
setSubmitStatus(createInputStatus(STATUS_ERROR, message));
|
||||||
@ -131,12 +131,6 @@ export default function EditStorage() {
|
|||||||
const handleSwitchChange = (storageEnabled: boolean) => {
|
const handleSwitchChange = (storageEnabled: boolean) => {
|
||||||
setShouldDisplayForm(storageEnabled);
|
setShouldDisplayForm(storageEnabled);
|
||||||
handleFieldChange({ fieldName: 'enabled', value: 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({
|
const containerClass = classNames({
|
||||||
|
|||||||
@ -11,7 +11,7 @@ interface DropdownProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function SocialDropdown({ iconList, selectedOption, onSelected }: DropdownProps) {
|
export default function SocialDropdown({ iconList, selectedOption, onSelected }: DropdownProps) {
|
||||||
const handleSelected = value => {
|
const handleSelected = (value: string) => {
|
||||||
if (onSelected) {
|
if (onSelected) {
|
||||||
onSelected(value);
|
onSelected(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
import React, { useContext, useState, useEffect } from 'react';
|
import React, { useContext, useState, useEffect } from 'react';
|
||||||
import { Typography, Slider } from 'antd';
|
import { Typography, Slider } from 'antd';
|
||||||
import { ServerStatusContext } from '../../../utils/server-status-context';
|
import { ServerStatusContext } from '../../../utils/server-status-context';
|
||||||
|
import { API_VIDEO_SEGMENTS, RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
|
||||||
import {
|
import {
|
||||||
API_VIDEO_SEGMENTS,
|
createInputStatus,
|
||||||
SUCCESS_STATES,
|
StatusState,
|
||||||
RESET_TIMEOUT,
|
STATUS_ERROR,
|
||||||
postConfigUpdateToAPI,
|
STATUS_PROCESSING,
|
||||||
} from './constants';
|
STATUS_SUCCESS,
|
||||||
|
} from '../../../utils/input-statuses';
|
||||||
|
import FormStatusIndicator from './form-status-indicator';
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
@ -37,8 +40,10 @@ function SegmentToolTip({ value }: SegmentToolTipProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function VideoLatency() {
|
export default function VideoLatency() {
|
||||||
const [submitStatus, setSubmitStatus] = useState(null);
|
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
|
||||||
const [submitStatusMessage, setSubmitStatusMessage] = useState('');
|
|
||||||
|
// 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);
|
||||||
@ -57,13 +62,15 @@ export default function VideoLatency() {
|
|||||||
|
|
||||||
const resetStates = () => {
|
const resetStates = () => {
|
||||||
setSubmitStatus(null);
|
setSubmitStatus(null);
|
||||||
setSubmitStatusMessage('');
|
// setSubmitStatusMessage('');
|
||||||
resetTimer = null;
|
resetTimer = null;
|
||||||
clearTimeout(resetTimer);
|
clearTimeout(resetTimer);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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_SEGMENTS,
|
apiPath: API_VIDEO_SEGMENTS,
|
||||||
data: { value: postValue },
|
data: { value: postValue },
|
||||||
@ -73,34 +80,28 @@ export default function VideoLatency() {
|
|||||||
value: postValue,
|
value: postValue,
|
||||||
path: 'videoSettings',
|
path: 'videoSettings',
|
||||||
});
|
});
|
||||||
|
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Variants updated.'));
|
||||||
|
|
||||||
setSubmitStatus('success');
|
// setSubmitStatus('success');
|
||||||
setSubmitStatusMessage('Variants updated.');
|
// setSubmitStatusMessage('Variants updated.');
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
||||||
},
|
},
|
||||||
onError: (message: string) => {
|
onError: (message: string) => {
|
||||||
setSubmitStatus('error');
|
setSubmitStatus(createInputStatus(STATUS_ERROR, message));
|
||||||
setSubmitStatusMessage(message);
|
|
||||||
|
// setSubmitStatus('error');
|
||||||
|
// setSubmitStatusMessage(message);
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
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 => {
|
const handleChange = value => {
|
||||||
postUpdateToAPI(value);
|
postUpdateToAPI(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-container config-video-segements-conatiner">
|
<div className="config-video-segements-conatiner">
|
||||||
<Title level={3}>Latency Buffer</Title>
|
<Title level={3}>Latency Buffer</Title>
|
||||||
<p>
|
<p>
|
||||||
There are trade-offs when cosidering video latency and reliability. Blah blah .. better
|
There are trade-offs when cosidering video latency and reliability. Blah blah .. better
|
||||||
@ -108,7 +109,7 @@ export default function VideoLatency() {
|
|||||||
</p>
|
</p>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<div className="segment-slider">
|
<div className="segment-slider-container">
|
||||||
<Slider
|
<Slider
|
||||||
tipFormatter={value => <SegmentToolTip value={SLIDER_COMMENTS[value]} />}
|
tipFormatter={value => <SegmentToolTip value={SLIDER_COMMENTS[value]} />}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@ -119,7 +120,7 @@ export default function VideoLatency() {
|
|||||||
value={selectedOption}
|
value={selectedOption}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{statusMessage}
|
<FormStatusIndicator status={submitStatus} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
// 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, Select, Switch, Divider, Collapse } from 'antd';
|
import { Slider, Switch, Collapse } from 'antd';
|
||||||
import { FieldUpdaterFunc, CpuUsageLevel, VideoVariant } from '../../../types/config-section';
|
import { FieldUpdaterFunc, VideoVariant } from '../../../types/config-section';
|
||||||
import { DEFAULT_VARIANT_STATE } from './constants';
|
import { DEFAULT_VARIANT_STATE } from './constants';
|
||||||
import InfoTip from '../info-tip';
|
import InfoTip from '../info-tip';
|
||||||
import CPUUsageSelector from './cpu-usage';
|
import CPUUsageSelector from './cpu-usage';
|
||||||
|
|
||||||
const { Option } = Select;
|
|
||||||
const { Panel } = Collapse;
|
const { Panel } = Collapse;
|
||||||
|
|
||||||
const VIDEO_VARIANT_DEFAULTS = {
|
const VIDEO_VARIANT_DEFAULTS = {
|
||||||
@ -57,12 +56,6 @@ export default function VideoVariantForm({
|
|||||||
const handleVideoBitrateChange = (value: number) => {
|
const handleVideoBitrateChange = (value: number) => {
|
||||||
onUpdateField({ fieldName: 'videoBitrate', value });
|
onUpdateField({ fieldName: 'videoBitrate', value });
|
||||||
};
|
};
|
||||||
const handleAudioBitrateChange = (value: number) => {
|
|
||||||
onUpdateField({ fieldName: 'audioBitrate', value });
|
|
||||||
};
|
|
||||||
const handleAudioPassChange = (value: boolean) => {
|
|
||||||
onUpdateField({ fieldName: 'audioPassthrough', value });
|
|
||||||
};
|
|
||||||
const handleVideoPassChange = (value: boolean) => {
|
const handleVideoPassChange = (value: boolean) => {
|
||||||
onUpdateField({ fieldName: 'videoPassthrough', value });
|
onUpdateField({ fieldName: 'videoPassthrough', value });
|
||||||
};
|
};
|
||||||
@ -80,18 +73,12 @@ export default function VideoVariantForm({
|
|||||||
const videoBRMax = videoBitrateDefaults.max;
|
const videoBRMax = videoBitrateDefaults.max;
|
||||||
const videoBRUnit = videoBitrateDefaults.unit;
|
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 selectedVideoBRnote = `Selected: ${dataState.videoBitrate}${videoBRUnit} - it sucks`;
|
||||||
const selectedAudioBRnote = `Selected: ${dataState.audioBitrate}${audioBRUnit} - too slow`;
|
|
||||||
const selectedFramerateNote = `Selected: ${dataState.framerate}${framerateUnit} - whoa there`;
|
const selectedFramerateNote = `Selected: ${dataState.framerate}${framerateUnit} - whoa there`;
|
||||||
const selectedPresetNote = '';
|
const selectedPresetNote = '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="variant-form">
|
<div className="config-variant-form">
|
||||||
<div className="section-intro">
|
<div className="section-intro">
|
||||||
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>.
|
||||||
@ -130,6 +117,7 @@ export default function VideoVariantForm({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* VIDEO BITRATE FIELD */}
|
{/* VIDEO BITRATE FIELD */}
|
||||||
<div className={`field ${dataState.videoPassthrough ? 'disabled' : ''}`}>
|
<div className={`field ${dataState.videoPassthrough ? 'disabled' : ''}`}>
|
||||||
<p className="label">
|
<p className="label">
|
||||||
@ -192,55 +180,6 @@ export default function VideoVariantForm({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</Panel>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
// Updating a variant will post ALL the variants in an array as an update to the API.
|
// 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 React, { useContext, useState } from 'react';
|
||||||
import { Typography, Table, Modal, Button } from 'antd';
|
import { Typography, Table, Modal, Button } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
@ -20,11 +19,19 @@ import {
|
|||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
|
const CPU_USAGE_LEVEL_MAP = {
|
||||||
|
1: 'lowest',
|
||||||
|
2: 'low',
|
||||||
|
3: 'medium',
|
||||||
|
4: 'high',
|
||||||
|
5: 'highest',
|
||||||
|
};
|
||||||
|
|
||||||
export default function CurrentVariantsTable() {
|
export default function CurrentVariantsTable() {
|
||||||
const [displayModal, setDisplayModal] = useState(false);
|
const [displayModal, setDisplayModal] = useState(false);
|
||||||
const [modalProcessing, setModalProcessing] = useState(false);
|
const [modalProcessing, setModalProcessing] = useState(false);
|
||||||
const [editId, setEditId] = useState(0);
|
const [editId, setEditId] = useState(0);
|
||||||
const {setMessage} = useContext(AlertMessageContext);
|
const { setMessage } = useContext(AlertMessageContext);
|
||||||
|
|
||||||
// current data inside modal
|
// current data inside modal
|
||||||
const [modalDataState, setModalDataState] = useState(DEFAULT_VARIANT_STATE);
|
const [modalDataState, setModalDataState] = useState(DEFAULT_VARIANT_STATE);
|
||||||
@ -76,7 +83,9 @@ export default function CurrentVariantsTable() {
|
|||||||
setSubmitStatusMessage('Variants updated.');
|
setSubmitStatusMessage('Variants updated.');
|
||||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
|
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) => {
|
onError: (message: string) => {
|
||||||
setSubmitStatus('error');
|
setSubmitStatus('error');
|
||||||
@ -117,14 +126,6 @@ export default function CurrentVariantsTable() {
|
|||||||
const { icon: newStatusIcon = null, message: newStatusMessage = '' } =
|
const { icon: newStatusIcon = null, message: newStatusMessage = '' } =
|
||||||
SUCCESS_STATES[submitStatus] || {};
|
SUCCESS_STATES[submitStatus] || {};
|
||||||
|
|
||||||
const cpuUsageLevelLabelMap = {
|
|
||||||
1: 'lowest',
|
|
||||||
2: 'low',
|
|
||||||
3: 'medium',
|
|
||||||
4: 'high',
|
|
||||||
5: 'highest',
|
|
||||||
};
|
|
||||||
|
|
||||||
const videoQualityColumns: ColumnsType<VideoVariant> = [
|
const videoQualityColumns: ColumnsType<VideoVariant> = [
|
||||||
{
|
{
|
||||||
title: 'Video bitrate',
|
title: 'Video bitrate',
|
||||||
@ -137,7 +138,7 @@ export default function CurrentVariantsTable() {
|
|||||||
title: 'CPU Usage',
|
title: 'CPU Usage',
|
||||||
dataIndex: 'cpuUsageLevel',
|
dataIndex: 'cpuUsageLevel',
|
||||||
key: 'cpuUsageLevel',
|
key: 'cpuUsageLevel',
|
||||||
render: (level: string) => (!level ? 'n/a' : cpuUsageLevelLabelMap[level]),
|
render: (level: string) => (!level ? 'n/a' : CPU_USAGE_LEVEL_MAP[level]),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||||
import { Tooltip } from "antd";
|
import { Tooltip } from 'antd';
|
||||||
|
|
||||||
interface InfoTipProps {
|
interface InfoTipProps {
|
||||||
tip: string | null;
|
tip: string | null;
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
import { Table, Typography } from "antd";
|
import { Table, Typography } from 'antd';
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
export default function KeyValueTable({ title, data }: KeyValueTableProps) {
|
export default function KeyValueTable({ title, data }: KeyValueTableProps) {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: "Name",
|
title: 'Name',
|
||||||
dataIndex: "name",
|
dataIndex: 'name',
|
||||||
key: "name",
|
key: 'name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Value",
|
title: 'Value',
|
||||||
dataIndex: "value",
|
dataIndex: 'value',
|
||||||
key: "value",
|
key: 'value',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -25,6 +25,6 @@ export default function KeyValueTable({ title, data }: KeyValueTableProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface KeyValueTableProps {
|
interface KeyValueTableProps {
|
||||||
title: string,
|
title: string;
|
||||||
data: any,
|
data: any;
|
||||||
};
|
}
|
||||||
|
|||||||
@ -1,32 +1,30 @@
|
|||||||
import React from "react";
|
import React from 'react';
|
||||||
import { Table, Tag, Typography } from "antd";
|
import { Table, Tag, Typography } from 'antd';
|
||||||
import Linkify from "react-linkify";
|
import Linkify from 'react-linkify';
|
||||||
import { SortOrder } from "antd/lib/table/interface";
|
import { SortOrder } from 'antd/lib/table/interface';
|
||||||
import format from 'date-fns/format'
|
import format from 'date-fns/format';
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
function renderColumnLevel(text, entry) {
|
function renderColumnLevel(text, entry) {
|
||||||
let color = 'black';
|
let color = 'black';
|
||||||
|
|
||||||
if (entry.level === "warning") {
|
if (entry.level === 'warning') {
|
||||||
color = "orange";
|
color = 'orange';
|
||||||
} else if (entry.level === 'error') {
|
} else if (entry.level === 'error') {
|
||||||
color = "red";
|
color = 'red';
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Tag color={color}>{text}</Tag>;
|
return <Tag color={color}>{text}</Tag>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMessage(text) {
|
function renderMessage(text) {
|
||||||
return (
|
return <Linkify>{text}</Linkify>;
|
||||||
<Linkify>{text}</Linkify>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
logs: object[],
|
logs: object[];
|
||||||
pageSize: number
|
pageSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LogTable({ logs, pageSize }: Props) {
|
export default function LogTable({ logs, pageSize }: Props) {
|
||||||
@ -35,42 +33,42 @@ export default function LogTable({ logs, pageSize }: Props) {
|
|||||||
}
|
}
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: "Level",
|
title: 'Level',
|
||||||
dataIndex: "level",
|
dataIndex: 'level',
|
||||||
key: "level",
|
key: 'level',
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
text: "Info",
|
text: 'Info',
|
||||||
value: "info",
|
value: 'info',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "Warning",
|
text: 'Warning',
|
||||||
value: "warning",
|
value: 'warning',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "Error",
|
text: 'Error',
|
||||||
value: "Error",
|
value: 'Error',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
onFilter: (level, row) => row.level.indexOf(level) === 0,
|
onFilter: (level, row) => row.level.indexOf(level) === 0,
|
||||||
render: renderColumnLevel,
|
render: renderColumnLevel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Timestamp",
|
title: 'Timestamp',
|
||||||
dataIndex: "time",
|
dataIndex: 'time',
|
||||||
key: "time",
|
key: 'time',
|
||||||
render: (timestamp) => {
|
render: timestamp => {
|
||||||
const dateObject = new Date(timestamp);
|
const dateObject = new Date(timestamp);
|
||||||
return format(dateObject, 'p P');
|
return format(dateObject, 'p P');
|
||||||
},
|
},
|
||||||
sorter: (a, b) => new Date(a.time).getTime() - new Date(b.time).getTime(),
|
sorter: (a, b) => new Date(a.time).getTime() - new Date(b.time).getTime(),
|
||||||
sortDirections: ["descend", "ascend"] as SortOrder[],
|
sortDirections: ['descend', 'ascend'] as SortOrder[],
|
||||||
defaultSortOrder: "descend" as SortOrder,
|
defaultSortOrder: 'descend' as SortOrder,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Message",
|
title: 'Message',
|
||||||
dataIndex: "message",
|
dataIndex: 'message',
|
||||||
key: "message",
|
key: 'message',
|
||||||
render: renderMessage,
|
render: renderMessage,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -82,10 +80,9 @@ export default function LogTable({ logs, pageSize }: Props) {
|
|||||||
size="middle"
|
size="middle"
|
||||||
dataSource={logs}
|
dataSource={logs}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey={(row) => row.time}
|
rowKey={row => row.time}
|
||||||
pagination={{ pageSize: pageSize || 20 }}
|
pagination={{ pageSize: pageSize || 20 }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,85 +1,159 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import adminStyles from '../../styles/styles.module.scss';
|
|
||||||
|
|
||||||
export default function Logo() {
|
export default function Logo() {
|
||||||
return (
|
return (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 95.68623352050781 104.46271514892578" className={adminStyles.logoSVG}>
|
<svg
|
||||||
<g transform="matrix(1 0 0 1 -37.08803939819336 -18.940391540527344)">
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 95.68623352050781 104.46271514892578"
|
||||||
|
className="logo-svg"
|
||||||
|
>
|
||||||
|
<g transform="matrix(1 0 0 1 -37.08803939819336 -18.940391540527344)">
|
||||||
<g>
|
<g>
|
||||||
<g>
|
<g>
|
||||||
<g>
|
<g>
|
||||||
<g transform="matrix(1.0445680396949917 0 0 1.0445679172996596 36.34559138380523 18.877718021903796)">
|
<g transform="matrix(1.0445680396949917 0 0 1.0445679172996596 36.34559138380523 18.877718021903796)">
|
||||||
<g transform="matrix(1 0 0 1 0 0)">
|
<g transform="matrix(1 0 0 1 0 0)">
|
||||||
|
<defs>
|
||||||
<defs>
|
<linearGradient
|
||||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient120" gradientTransform="rotate(-90 .5 .5)">
|
x1="0"
|
||||||
<stop offset="0" stopColor="#1f2022" stopOpacity="1"/>
|
y1="0"
|
||||||
<stop offset="1" stopColor="#635e69" stopOpacity="1"/>
|
x2="0"
|
||||||
</linearGradient>
|
y2="1"
|
||||||
</defs>
|
id="gradient120"
|
||||||
<path fill="url(#gradient120)" d="M91.5 75.35Q93.05 71.15 91.65 67.7 90.35 64.5 86.65 62.3 83.2 60.3 78.3 59.4 73.85 58.6 68.6 58.7 63.55 58.85 58.8 59.8 54.25 60.75 50.8 62.2 47.4 63.65 45.5 65.35 43.6 67.15 43.5 69.05 43.35 71.3 45.8 73.9 48.05 76.3 52.1 78.6 56.15 80.9 61.05 82.55 66.3 84.3 71.4 84.8 74.7 85.1 77.55 84.9 80.65 84.6 83.3 83.6 86.15 82.5 88.15 80.55 90.4 78.4 91.5 75.35M70.6 67.5Q72.3 68.4 73.1 69.7 73.9 71.15 73.45 73 73.1 74.3 72.3 75.25 71.55 76.1 70.3 76.6 69.25 77.05 67.75 77.25 66.3 77.4 64.85 77.3 62.3 77.15 59.25 76.3 56.6 75.5 54.15 74.3 51.9 73.2 50.45 72 49.05 70.75 49.1 69.8 49.2 69 50.25 68.25 51.3 67.55 53.15 67 55 66.4 57.25 66.1 59.8 65.8 62.1 65.8 64.65 65.85 66.7 66.2 68.9 66.65 70.6 67.5Z"/>
|
gradientTransform="rotate(-90 .5 .5)"
|
||||||
</g>
|
>
|
||||||
<g transform="matrix(1 0 0 1 0 0)">
|
<stop offset="0" stopColor="#1f2022" stopOpacity="1" />
|
||||||
<defs>
|
<stop offset="1" stopColor="#635e69" stopOpacity="1" />
|
||||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient121" gradientTransform="rotate(-180 .5 .5)">
|
</linearGradient>
|
||||||
<stop offset="0" stopColor="#2087e2" stopOpacity="1"/>
|
</defs>
|
||||||
<stop offset="1" stopColor="#b63fff" stopOpacity="1"/>
|
<path
|
||||||
</linearGradient>
|
fill="url(#gradient120)"
|
||||||
</defs>
|
d="M91.5 75.35Q93.05 71.15 91.65 67.7 90.35 64.5 86.65 62.3 83.2 60.3 78.3 59.4 73.85 58.6 68.6 58.7 63.55 58.85 58.8 59.8 54.25 60.75 50.8 62.2 47.4 63.65 45.5 65.35 43.6 67.15 43.5 69.05 43.35 71.3 45.8 73.9 48.05 76.3 52.1 78.6 56.15 80.9 61.05 82.55 66.3 84.3 71.4 84.8 74.7 85.1 77.55 84.9 80.65 84.6 83.3 83.6 86.15 82.5 88.15 80.55 90.4 78.4 91.5 75.35M70.6 67.5Q72.3 68.4 73.1 69.7 73.9 71.15 73.45 73 73.1 74.3 72.3 75.25 71.55 76.1 70.3 76.6 69.25 77.05 67.75 77.25 66.3 77.4 64.85 77.3 62.3 77.15 59.25 76.3 56.6 75.5 54.15 74.3 51.9 73.2 50.45 72 49.05 70.75 49.1 69.8 49.2 69 50.25 68.25 51.3 67.55 53.15 67 55 66.4 57.25 66.1 59.8 65.8 62.1 65.8 64.65 65.85 66.7 66.2 68.9 66.65 70.6 67.5Z"
|
||||||
<path fill="url(#gradient121)" d="M66.6 15.05Q66.4 9.65 63.9 6.05 61.25 2.1 56.1 0.65 54.95 0.3 53.65 0.15 52.5 0 51.3 0.1 50.2 0.1 49.1 0.35 48.15 0.55 47 1 43.3 2.45 40.3 6.1 37.5 9.4 35.5 14.3 33.75 18.45 32.7 23.4 31.7 28.05 31.35 32.85 31.05 37.2 31.3 41.2 31.6 45.15 32.4 48.35 34 54.9 37.3 56.4 37.6 56.55 37.9 56.65L39.2 56.85Q39.45 56.85 39.95 56.8 42.05 56.6 44.7 55.05 47.25 53.5 50.05 50.8 53.05 47.9 55.85 44.05 58.8 40.05 61.1 35.6 63.8 30.35 65.25 25.3 66.75 19.75 66.6 15.05M47.55 23.15Q48.05 23.25 48.4 23.4 52.45 24.8 52.55 29.85 52.6 34 50 39.4 47.85 43.9 44.85 47.3 42.05 50.5 40.15 50.7L39.9 50.75 39.45 50.7 39.2 50.6Q37.8 49.95 37.25 46.35 36.7 42.7 37.3 38 37.95 32.75 39.75 28.8 41.9 24.1 45.05 23.25 45.6 23.1 45.85 23.1 46.25 23.05 46.65 23.05 47.05 23.05 47.55 23.15Z"/>
|
/>
|
||||||
</g>
|
|
||||||
<g transform="matrix(1 0 0 1 0 0)">
|
|
||||||
<defs>
|
|
||||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient122" gradientTransform="rotate(-90 .5 .5)">
|
|
||||||
<stop offset="0" stopColor="#100f0f" stopOpacity="1"/>
|
|
||||||
<stop offset="1" stopColor="#49261F" stopOpacity="1"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<path fill="url(#gradient122)" d="M2.7 33.6Q2.1 34.4 1.7 35.35 1.25 36.5 1.05 37.7 0 42.6 2.2 47.2 4 51 8 54.35 11.55 57.3 16 59.15 20.5 61 23.85 60.85 24.5 60.85 25.25 60.7 26 60.55 26.5 60.3 27 60.05 27.45 59.65 27.9 59.25 28.15 58.75 29.35 56.45 27.5 51.65 25.6 47 21.75 42.1 17.75 37 13.4 34.05 8.7 30.9 5.45 31.7 4.65 31.9 3.95 32.4 3.25 32.85 2.7 33.6M10.1 43.55Q10.35 43.1 10.6 42.85 10.85 42.6 11.2 42.4 11.6 42.25 11.9 42.2 13.5 41.9 15.95 43.6 18.15 45.05 20.35 47.7 22.35 50.1 23.55 52.4 24.7 54.75 24.25 55.7 24.15 55.9 24 56 23.85 56.2 23.65 56.25 23.55 56.35 23.25 56.4L22.7 56.5Q21.1 56.6 18.55 55.6 16.05 54.6 13.85 52.95 11.5 51.2 10.35 49.15 9.05 46.8 9.75 44.45 9.9 43.95 10.1 43.55Z"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(1 0 0 1 0 0)">
|
|
||||||
<defs>
|
|
||||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient123" gradientTransform="rotate(-180 .5 .5)">
|
|
||||||
<stop offset="0" stopColor="#222020" stopOpacity="1"/>
|
|
||||||
<stop offset="1" stopColor="#49261F" stopOpacity="1"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<path fill="url(#gradient123)" d="M34.95 74.2L34.75 74.2Q33.2 74.15 31.9 75.25 30.7 76.3 29.85 78.25 29.1 80 28.8 82.2 28.5 84.4 28.7 86.65 29.1 91.4 31.5 94.7 34.3 98.5 39.3 99.7L39.4 99.7 39.7 99.8 39.85 99.8Q45.3 100.85 47.15 97.75 48 96.3 48 94.05 47.95 91.9 47.2 89.35 46.45 86.75 45.1 84.15 43.75 81.5 42.05 79.35 40.25 77.1 38.45 75.75 36.55 74.35 34.95 74.2M33.55 80.4Q34.35 78.2 35.6 78.3L35.65 78.3Q36.9 78.45 38.6 80.9 40.3 83.35 41.15 86.05 42.1 89 41.55 90.75 40.9 92.6 38.35 92.25L38.3 92.25 38.25 92.2 38.1 92.2Q35.6 91.7 34.25 89.6 33.1 87.7 32.95 85 32.8 82.35 33.55 80.4Z"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(0.9999999999999999 0 0 1 0 5.684341886080802e-14)">
|
|
||||||
<defs>
|
|
||||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient124" gradientTransform="rotate(-180 .5 .5)"> <stop offset="0" stopColor="#1e1c1c" stopOpacity="1"/>
|
|
||||||
<stop offset="1" stopColor="#49261F" stopOpacity="1"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<path fill="url(#gradient124)" d="M22.7 69.65Q22.25 69.3 21.6 69.05 20.95 68.8 20.25 68.7 19.6 68.55 18.85 68.5 16.7 68.45 14.65 69.15 12.65 69.8 11.4 71.1 10.15 72.5 10.2 74.2 10.25 76.05 11.95 78.2 12.4 78.75 13.05 79.4 13.55 79.9 14.2 80.3 14.7 80.6 15.3 80.85 16 81.1 16.4 81.1 18.2 81.35 19.9 80.35 21.55 79.4 22.75 77.65 24 75.85 24.3 73.95 24.6 71.85 23.55 70.5 23.15 70 22.7 69.65M21.7 71.7Q22.15 72.3 21.9 73.3 21.7 74.25 21 75.25 20.3 76.2 19.4 76.75 18.45 77.35 17.55 77.25L17 77.15Q16.7 77.05 16.45 76.85 16.25 76.75 15.9 76.45 15.7 76.25 15.4 75.9 14.5 74.75 14.7 73.8 14.8 72.95 15.75 72.3 16.6 71.7 17.8 71.4 19 71.1 20.1 71.15L20.65 71.2 21.1 71.3Q21.3 71.4 21.45 71.5L21.7 71.7Z"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(1 0 0 1 0 0)">
|
|
||||||
<defs>
|
|
||||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient125" gradientTransform="rotate(-360 .5 .5)">
|
|
||||||
<stop offset="0" stopColor="#FFFFFF" stopOpacity="0.5"/>
|
|
||||||
<stop offset="1" stopColor="#FFFFFF" stopOpacity="0.2"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<path fill="url(#gradient125)" d="M52.6 19.25Q59.6 19.25 66.2 20.95 66.7 17.8 66.6 15.05 66.4 9.65 63.9 6.05 61.25 2.1 56.1 0.65 54.95 0.3 53.65 0.15 52.5 0 51.3 0.1 50.2 0.1 49.1 0.35 48.15 0.55 47 1 43.3 2.45 40.3 6.1 37.5 9.4 35.5 14.3 33.85 18.3 32.8 22.85 42.25 19.25 52.6 19.25Z"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(1 0 0 1 0 0)">
|
|
||||||
<defs>
|
|
||||||
<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient126" gradientTransform="rotate(-360 .5 .5)">
|
|
||||||
<stop offset="0" stopColor="#FFFFFF" stopOpacity="0.5"/>
|
|
||||||
<stop offset="1" stopColor="#FFFFFF" stopOpacity="0.2"/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<path fill="url(#gradient126)" d="M1.05 37.7Q0 42.6 2.2 47.2 2.95 48.8 4.05 50.25 7.55 41.65 14.4 34.75 14 34.45 13.4 34.05 8.7 30.9 5.45 31.7 4.65 31.9 3.95 32.4 3.25 32.85 2.7 33.6 2.1 34.4 1.7 35.35 1.25 36.5 1.05 37.7Z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(1.219512230276127 0 0 1.2195122143630526 32.82519274395008 88.56945194723018)">
|
<g transform="matrix(1 0 0 1 0 0)">
|
||||||
<path fill="#000000" fillOpacity="1" d=""/>
|
<defs>
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="0"
|
||||||
|
y2="1"
|
||||||
|
id="gradient121"
|
||||||
|
gradientTransform="rotate(-180 .5 .5)"
|
||||||
|
>
|
||||||
|
<stop offset="0" stopColor="#2087e2" stopOpacity="1" />
|
||||||
|
<stop offset="1" stopColor="#b63fff" stopOpacity="1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
fill="url(#gradient121)"
|
||||||
|
d="M66.6 15.05Q66.4 9.65 63.9 6.05 61.25 2.1 56.1 0.65 54.95 0.3 53.65 0.15 52.5 0 51.3 0.1 50.2 0.1 49.1 0.35 48.15 0.55 47 1 43.3 2.45 40.3 6.1 37.5 9.4 35.5 14.3 33.75 18.45 32.7 23.4 31.7 28.05 31.35 32.85 31.05 37.2 31.3 41.2 31.6 45.15 32.4 48.35 34 54.9 37.3 56.4 37.6 56.55 37.9 56.65L39.2 56.85Q39.45 56.85 39.95 56.8 42.05 56.6 44.7 55.05 47.25 53.5 50.05 50.8 53.05 47.9 55.85 44.05 58.8 40.05 61.1 35.6 63.8 30.35 65.25 25.3 66.75 19.75 66.6 15.05M47.55 23.15Q48.05 23.25 48.4 23.4 52.45 24.8 52.55 29.85 52.6 34 50 39.4 47.85 43.9 44.85 47.3 42.05 50.5 40.15 50.7L39.9 50.75 39.45 50.7 39.2 50.6Q37.8 49.95 37.25 46.35 36.7 42.7 37.3 38 37.95 32.75 39.75 28.8 41.9 24.1 45.05 23.25 45.6 23.1 45.85 23.1 46.25 23.05 46.65 23.05 47.05 23.05 47.55 23.15Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1 0 0 1 0 0)">
|
||||||
|
<defs>
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="0"
|
||||||
|
y2="1"
|
||||||
|
id="gradient122"
|
||||||
|
gradientTransform="rotate(-90 .5 .5)"
|
||||||
|
>
|
||||||
|
<stop offset="0" stopColor="#100f0f" stopOpacity="1" />
|
||||||
|
<stop offset="1" stopColor="#49261F" stopOpacity="1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
fill="url(#gradient122)"
|
||||||
|
d="M2.7 33.6Q2.1 34.4 1.7 35.35 1.25 36.5 1.05 37.7 0 42.6 2.2 47.2 4 51 8 54.35 11.55 57.3 16 59.15 20.5 61 23.85 60.85 24.5 60.85 25.25 60.7 26 60.55 26.5 60.3 27 60.05 27.45 59.65 27.9 59.25 28.15 58.75 29.35 56.45 27.5 51.65 25.6 47 21.75 42.1 17.75 37 13.4 34.05 8.7 30.9 5.45 31.7 4.65 31.9 3.95 32.4 3.25 32.85 2.7 33.6M10.1 43.55Q10.35 43.1 10.6 42.85 10.85 42.6 11.2 42.4 11.6 42.25 11.9 42.2 13.5 41.9 15.95 43.6 18.15 45.05 20.35 47.7 22.35 50.1 23.55 52.4 24.7 54.75 24.25 55.7 24.15 55.9 24 56 23.85 56.2 23.65 56.25 23.55 56.35 23.25 56.4L22.7 56.5Q21.1 56.6 18.55 55.6 16.05 54.6 13.85 52.95 11.5 51.2 10.35 49.15 9.05 46.8 9.75 44.45 9.9 43.95 10.1 43.55Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1 0 0 1 0 0)">
|
||||||
|
<defs>
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="0"
|
||||||
|
y2="1"
|
||||||
|
id="gradient123"
|
||||||
|
gradientTransform="rotate(-180 .5 .5)"
|
||||||
|
>
|
||||||
|
<stop offset="0" stopColor="#222020" stopOpacity="1" />
|
||||||
|
<stop offset="1" stopColor="#49261F" stopOpacity="1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
fill="url(#gradient123)"
|
||||||
|
d="M34.95 74.2L34.75 74.2Q33.2 74.15 31.9 75.25 30.7 76.3 29.85 78.25 29.1 80 28.8 82.2 28.5 84.4 28.7 86.65 29.1 91.4 31.5 94.7 34.3 98.5 39.3 99.7L39.4 99.7 39.7 99.8 39.85 99.8Q45.3 100.85 47.15 97.75 48 96.3 48 94.05 47.95 91.9 47.2 89.35 46.45 86.75 45.1 84.15 43.75 81.5 42.05 79.35 40.25 77.1 38.45 75.75 36.55 74.35 34.95 74.2M33.55 80.4Q34.35 78.2 35.6 78.3L35.65 78.3Q36.9 78.45 38.6 80.9 40.3 83.35 41.15 86.05 42.1 89 41.55 90.75 40.9 92.6 38.35 92.25L38.3 92.25 38.25 92.2 38.1 92.2Q35.6 91.7 34.25 89.6 33.1 87.7 32.95 85 32.8 82.35 33.55 80.4Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.9999999999999999 0 0 1 0 5.684341886080802e-14)">
|
||||||
|
<defs>
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="0"
|
||||||
|
y2="1"
|
||||||
|
id="gradient124"
|
||||||
|
gradientTransform="rotate(-180 .5 .5)"
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
<stop offset="0" stopColor="#1e1c1c" stopOpacity="1" />
|
||||||
|
<stop offset="1" stopColor="#49261F" stopOpacity="1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
fill="url(#gradient124)"
|
||||||
|
d="M22.7 69.65Q22.25 69.3 21.6 69.05 20.95 68.8 20.25 68.7 19.6 68.55 18.85 68.5 16.7 68.45 14.65 69.15 12.65 69.8 11.4 71.1 10.15 72.5 10.2 74.2 10.25 76.05 11.95 78.2 12.4 78.75 13.05 79.4 13.55 79.9 14.2 80.3 14.7 80.6 15.3 80.85 16 81.1 16.4 81.1 18.2 81.35 19.9 80.35 21.55 79.4 22.75 77.65 24 75.85 24.3 73.95 24.6 71.85 23.55 70.5 23.15 70 22.7 69.65M21.7 71.7Q22.15 72.3 21.9 73.3 21.7 74.25 21 75.25 20.3 76.2 19.4 76.75 18.45 77.35 17.55 77.25L17 77.15Q16.7 77.05 16.45 76.85 16.25 76.75 15.9 76.45 15.7 76.25 15.4 75.9 14.5 74.75 14.7 73.8 14.8 72.95 15.75 72.3 16.6 71.7 17.8 71.4 19 71.1 20.1 71.15L20.65 71.2 21.1 71.3Q21.3 71.4 21.45 71.5L21.7 71.7Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1 0 0 1 0 0)">
|
||||||
|
<defs>
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="0"
|
||||||
|
y2="1"
|
||||||
|
id="gradient125"
|
||||||
|
gradientTransform="rotate(-360 .5 .5)"
|
||||||
|
>
|
||||||
|
<stop offset="0" stopColor="#FFFFFF" stopOpacity="0.5" />
|
||||||
|
<stop offset="1" stopColor="#FFFFFF" stopOpacity="0.2" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
fill="url(#gradient125)"
|
||||||
|
d="M52.6 19.25Q59.6 19.25 66.2 20.95 66.7 17.8 66.6 15.05 66.4 9.65 63.9 6.05 61.25 2.1 56.1 0.65 54.95 0.3 53.65 0.15 52.5 0 51.3 0.1 50.2 0.1 49.1 0.35 48.15 0.55 47 1 43.3 2.45 40.3 6.1 37.5 9.4 35.5 14.3 33.85 18.3 32.8 22.85 42.25 19.25 52.6 19.25Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1 0 0 1 0 0)">
|
||||||
|
<defs>
|
||||||
|
<linearGradient
|
||||||
|
x1="0"
|
||||||
|
y1="0"
|
||||||
|
x2="0"
|
||||||
|
y2="1"
|
||||||
|
id="gradient126"
|
||||||
|
gradientTransform="rotate(-360 .5 .5)"
|
||||||
|
>
|
||||||
|
<stop offset="0" stopColor="#FFFFFF" stopOpacity="0.5" />
|
||||||
|
<stop offset="1" stopColor="#FFFFFF" stopOpacity="0.2" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
fill="url(#gradient126)"
|
||||||
|
d="M1.05 37.7Q0 42.6 2.2 47.2 2.95 48.8 4.05 50.25 7.55 41.65 14.4 34.75 14 34.45 13.4 34.05 8.7 30.9 5.45 31.7 4.65 31.9 3.95 32.4 3.25 32.85 2.7 33.6 2.1 34.4 1.7 35.35 1.25 36.5 1.05 37.7Z"
|
||||||
|
/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
<g transform="matrix(1.219512230276127 0 0 1.2195122143630526 32.82519274395008 88.56945194723018)">
|
||||||
|
<path fill="#000000" fillOpacity="1" d="" />
|
||||||
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Head from 'next/head'
|
import Head from 'next/head';
|
||||||
import { differenceInSeconds } from "date-fns";
|
import { differenceInSeconds } from 'date-fns';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { Layout, Menu, Popover, Alert } from 'antd';
|
import { Layout, Menu, Popover, Alert } from 'antd';
|
||||||
|
|
||||||
@ -16,11 +16,10 @@ import {
|
|||||||
QuestionCircleOutlined,
|
QuestionCircleOutlined,
|
||||||
MessageOutlined,
|
MessageOutlined,
|
||||||
ExperimentOutlined,
|
ExperimentOutlined,
|
||||||
|
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { upgradeVersionAvailable } from "../../utils/apis";
|
import { upgradeVersionAvailable } from '../../utils/apis';
|
||||||
import { parseSecondsToDurationString } from '../../utils/format'
|
import { parseSecondsToDurationString } from '../../utils/format';
|
||||||
|
|
||||||
import OwncastLogo from './logo';
|
import OwncastLogo from './logo';
|
||||||
import { ServerStatusContext } from '../../utils/server-status-context';
|
import { ServerStatusContext } from '../../utils/server-status-context';
|
||||||
@ -29,7 +28,7 @@ import { AlertMessageContext } from '../../utils/alert-message-context';
|
|||||||
import TextFieldWithSubmit from './config/form-textfield-with-submit';
|
import TextFieldWithSubmit from './config/form-textfield-with-submit';
|
||||||
import { TEXTFIELD_PROPS_STREAM_TITLE } from './config/constants';
|
import { TEXTFIELD_PROPS_STREAM_TITLE } from './config/constants';
|
||||||
|
|
||||||
import adminStyles from '../../styles/styles.module.scss';
|
import { UpdateArgs } from '../../types/config-section';
|
||||||
|
|
||||||
let performedUpgradeCheck = false;
|
let performedUpgradeCheck = false;
|
||||||
|
|
||||||
@ -37,72 +36,47 @@ export default function MainLayout(props) {
|
|||||||
const { children } = props;
|
const { children } = props;
|
||||||
|
|
||||||
const context = useContext(ServerStatusContext);
|
const context = useContext(ServerStatusContext);
|
||||||
const { serverConfig, online, broadcaster, versionNumber, streamTitle } = context || {};
|
const { serverConfig, online, broadcaster, versionNumber } = context || {};
|
||||||
const { instanceDetails } = serverConfig;
|
const { instanceDetails } = serverConfig;
|
||||||
|
|
||||||
const [currentStreamTitle, setCurrentStreamTitle] = useState(streamTitle);
|
const [currentStreamTitle, setCurrentStreamTitle] = useState('');
|
||||||
|
|
||||||
const alertMessage = useContext(AlertMessageContext);
|
const alertMessage = useContext(AlertMessageContext);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { route } = router || {};
|
const { route } = router || {};
|
||||||
|
|
||||||
const { Header, Footer, Content, Sider } = Layout;
|
const { Header, Footer, Content, Sider } = Layout;
|
||||||
const { SubMenu } = Menu;
|
const { SubMenu } = Menu;
|
||||||
|
|
||||||
// status indicator items
|
|
||||||
const streamDurationString = broadcaster ? parseSecondsToDurationString(differenceInSeconds(new Date(), new Date(broadcaster.time))) : "";
|
|
||||||
const currentThumbnail = online ? (
|
|
||||||
<img src="/thumbnail.jpg" className={adminStyles.onlineCurrentThumb} alt="current thumbnail" />
|
|
||||||
) : null;
|
|
||||||
const statusIcon = online ? <PlayCircleFilled /> : <MinusSquareFilled />;
|
|
||||||
const statusMessage = online ? `Online ${streamDurationString}` : "Offline";
|
|
||||||
const statusIndicator = (
|
|
||||||
<div className={adminStyles.statusIndicatorContainer}>
|
|
||||||
<span className={adminStyles.statusLabel}>{statusMessage}</span>
|
|
||||||
<span className={adminStyles.statusIcon}>{statusIcon}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
const statusIndicatorWithThumb = online ? (
|
|
||||||
<Popover
|
|
||||||
content={currentThumbnail}
|
|
||||||
title="Thumbnail"
|
|
||||||
trigger="hover"
|
|
||||||
>
|
|
||||||
{statusIndicator}
|
|
||||||
</Popover>
|
|
||||||
) : statusIndicator;
|
|
||||||
// ///////////////
|
|
||||||
|
|
||||||
const [upgradeVersion, setUpgradeVersion] = useState(null);
|
const [upgradeVersion, setUpgradeVersion] = useState(null);
|
||||||
const checkForUpgrade = async () => {
|
const checkForUpgrade = async () => {
|
||||||
try {
|
try {
|
||||||
const result = await upgradeVersionAvailable(versionNumber);
|
const result = await upgradeVersionAvailable(versionNumber);
|
||||||
setUpgradeVersion(result);
|
setUpgradeVersion(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("==== error", error);
|
console.log('==== error', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!performedUpgradeCheck) {
|
if (!performedUpgradeCheck) {
|
||||||
checkForUpgrade();
|
checkForUpgrade();
|
||||||
performedUpgradeCheck = true
|
performedUpgradeCheck = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentStreamTitle(streamTitle);
|
setCurrentStreamTitle(instanceDetails.streamTitle);
|
||||||
}, [streamTitle]);
|
}, [instanceDetails]);
|
||||||
|
|
||||||
const handleStreamTitleChanged = ({ value }: UpdateArgs) => {
|
const handleStreamTitleChanged = ({ value }: UpdateArgs) => {
|
||||||
setCurrentStreamTitle(value);
|
setCurrentStreamTitle(value);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
const appClass = classNames({
|
const appClass = classNames({
|
||||||
"owncast-layout": true,
|
'app-container': true,
|
||||||
[adminStyles.online]: online,
|
online,
|
||||||
});
|
});
|
||||||
|
|
||||||
const upgradeMenuItemStyle = upgradeVersion ? 'block' : 'none';
|
const upgradeMenuItemStyle = upgradeVersion ? 'block' : 'none';
|
||||||
@ -110,15 +84,36 @@ export default function MainLayout(props) {
|
|||||||
|
|
||||||
const clearAlertMessage = () => {
|
const clearAlertMessage = () => {
|
||||||
alertMessage.setMessage(null);
|
alertMessage.setMessage(null);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const headerAlertMessage = alertMessage.message ? (
|
||||||
|
<Alert message={alertMessage.message} afterClose={clearAlertMessage} banner closable />
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
// status indicator items
|
||||||
|
const streamDurationString = broadcaster
|
||||||
|
? parseSecondsToDurationString(differenceInSeconds(new Date(), new Date(broadcaster.time)))
|
||||||
|
: '';
|
||||||
|
const currentThumbnail = online ? (
|
||||||
|
<img src="/thumbnail.jpg" className="online-thumbnail" alt="current thumbnail" />
|
||||||
|
) : null;
|
||||||
|
const statusIcon = online ? <PlayCircleFilled /> : <MinusSquareFilled />;
|
||||||
|
const statusMessage = online ? `Online ${streamDurationString}` : 'Offline';
|
||||||
|
|
||||||
|
const statusIndicator = (
|
||||||
|
<div className="online-status-indicator">
|
||||||
|
<span className="status-label">{statusMessage}</span>
|
||||||
|
<span className="status-icon">{statusIcon}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
const statusIndicatorWithThumb = online ? (
|
||||||
|
<Popover content={currentThumbnail} title="Thumbnail" trigger="hover">
|
||||||
|
{statusIndicator}
|
||||||
|
</Popover>
|
||||||
|
) : (
|
||||||
|
statusIndicator
|
||||||
|
);
|
||||||
|
|
||||||
const headerAlertMessage = alertMessage.message ? ( <Alert
|
|
||||||
message={alertMessage.message}
|
|
||||||
afterClose={clearAlertMessage}
|
|
||||||
banner
|
|
||||||
closable
|
|
||||||
/>): null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout className={appClass}>
|
<Layout className={appClass}>
|
||||||
<Head>
|
<Head>
|
||||||
@ -126,47 +121,32 @@ export default function MainLayout(props) {
|
|||||||
<link rel="icon" type="image/png" sizes="32x32" href="/img/favicon/favicon-32x32.png" />
|
<link rel="icon" type="image/png" sizes="32x32" href="/img/favicon/favicon-32x32.png" />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<Sider
|
<Sider width={240} className="side-nav">
|
||||||
width={240}
|
|
||||||
className={adminStyles.sideNav}
|
|
||||||
>
|
|
||||||
<Menu
|
<Menu
|
||||||
theme="dark"
|
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"
|
||||||
>
|
>
|
||||||
<h1 className={adminStyles.owncastTitleContainer}>
|
<h1 className="owncast-title">
|
||||||
<span className={adminStyles.logoContainer}>
|
<span className="logo-container">
|
||||||
<OwncastLogo />
|
<OwncastLogo />
|
||||||
</span>
|
</span>
|
||||||
<span className={adminStyles.owncastTitle}>Owncast Admin</span>
|
<span className="title-label">Owncast Admin</span>
|
||||||
</h1>
|
</h1>
|
||||||
<Menu.Item key="home" icon={<HomeOutlined />}>
|
<Menu.Item key="home" icon={<HomeOutlined />}>
|
||||||
<Link href="/">Home</Link>
|
<Link href="/">Home</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
||||||
<Menu.Item
|
<Menu.Item key="viewer-info" icon={<LineChartOutlined />} title="Current stream">
|
||||||
key="viewer-info"
|
|
||||||
icon={<LineChartOutlined />}
|
|
||||||
title="Current stream"
|
|
||||||
>
|
|
||||||
<Link href="/viewer-info">Viewers</Link>
|
<Link href="/viewer-info">Viewers</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
||||||
<Menu.Item
|
<Menu.Item key="chat" icon={<MessageOutlined />} title="Chat utilities">
|
||||||
key="chat"
|
|
||||||
icon={<MessageOutlined />}
|
|
||||||
title="Chat utilities"
|
|
||||||
>
|
|
||||||
<Link href="/chat">Chat</Link>
|
<Link href="/chat">Chat</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
||||||
<SubMenu
|
<SubMenu key="configuration" title="Configuration" icon={<SettingOutlined />}>
|
||||||
key="configuration"
|
|
||||||
title="Configuration"
|
|
||||||
icon={<SettingOutlined />}
|
|
||||||
>
|
|
||||||
<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>
|
||||||
@ -189,11 +169,7 @@ export default function MainLayout(props) {
|
|||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</SubMenu>
|
</SubMenu>
|
||||||
|
|
||||||
<SubMenu
|
<SubMenu key="utilities-menu" icon={<ToolOutlined />} title="Utilities">
|
||||||
key="utilities-menu"
|
|
||||||
icon={<ToolOutlined />}
|
|
||||||
title="Utilities"
|
|
||||||
>
|
|
||||||
<Menu.Item key="hardware-info">
|
<Menu.Item key="hardware-info">
|
||||||
<Link href="/hardware-info">Hardware</Link>
|
<Link href="/hardware-info">Hardware</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
@ -206,11 +182,7 @@ export default function MainLayout(props) {
|
|||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</SubMenu>
|
</SubMenu>
|
||||||
<SubMenu
|
<SubMenu key="integrations-menu" icon={<ExperimentOutlined />} title="Integrations">
|
||||||
key="integrations-menu"
|
|
||||||
icon={<ExperimentOutlined />}
|
|
||||||
title="Integrations"
|
|
||||||
>
|
|
||||||
<Menu.Item key="webhooks">
|
<Menu.Item key="webhooks">
|
||||||
<Link href="/webhooks">Webhooks</Link>
|
<Link href="/webhooks">Webhooks</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
@ -218,38 +190,33 @@ export default function MainLayout(props) {
|
|||||||
<Link href="/access-tokens">Access Tokens</Link>
|
<Link href="/access-tokens">Access Tokens</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</SubMenu>
|
</SubMenu>
|
||||||
<Menu.Item
|
<Menu.Item key="help" icon={<QuestionCircleOutlined />} title="Help">
|
||||||
key="help"
|
|
||||||
icon={<QuestionCircleOutlined />}
|
|
||||||
title="Help"
|
|
||||||
>
|
|
||||||
<Link href="/help">Help</Link>
|
<Link href="/help">Help</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Sider>
|
</Sider>
|
||||||
|
|
||||||
<Layout className={adminStyles.layoutMain}>
|
<Layout className="layout-main">
|
||||||
<Header className={adminStyles.header}>
|
<Header className="layout-header">
|
||||||
<div className={adminStyles.globalStreamTitleContainer}>
|
<div className="global-stream-title-container">
|
||||||
<TextFieldWithSubmit
|
<TextFieldWithSubmit
|
||||||
apiPath="/streamtitle"
|
fieldName="streamTitle"
|
||||||
maxLength={100}
|
{...TEXTFIELD_PROPS_STREAM_TITLE}
|
||||||
className={adminStyles.globalStreamTitleInput}
|
placeholder="What you're streaming right now"
|
||||||
fieldName="streamTitle"
|
value={currentStreamTitle}
|
||||||
placeholder="What you're streaming right now"
|
initialValue={instanceDetails.streamTitle}
|
||||||
value={currentStreamTitle}
|
onChange={handleStreamTitleChanged}
|
||||||
initialValue={instanceDetails.streamTitle}
|
/>
|
||||||
onChange={handleStreamTitleChanged}
|
</div>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{statusIndicatorWithThumb}
|
{statusIndicatorWithThumb}
|
||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
{headerAlertMessage}
|
{headerAlertMessage}
|
||||||
|
|
||||||
<Content className={adminStyles.contentMain}>{children}</Content>
|
|
||||||
|
|
||||||
<Footer style={{ textAlign: "center" }}>
|
<Content className="main-content-container">{children}</Content>
|
||||||
|
|
||||||
|
<Footer className="footer-container">
|
||||||
<a href="https://owncast.online/">About Owncast v{versionNumber}</a>
|
<a href="https://owncast.online/">About Owncast v{versionNumber}</a>
|
||||||
</Footer>
|
</Footer>
|
||||||
</Layout>
|
</Layout>
|
||||||
@ -259,4 +226,4 @@ export default function MainLayout(props) {
|
|||||||
|
|
||||||
MainLayout.propTypes = {
|
MainLayout.propTypes = {
|
||||||
children: PropTypes.element.isRequired,
|
children: PropTypes.element.isRequired,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,20 +1,28 @@
|
|||||||
// Custom component for AntDesign Button that makes an api call, then displays a confirmation icon upon
|
// Custom component for AntDesign Button that makes an api call, then displays a confirmation icon upon
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Button, Tooltip } from "antd";
|
import { Button, Tooltip } from 'antd';
|
||||||
import { EyeOutlined, EyeInvisibleOutlined, CheckCircleFilled, ExclamationCircleFilled } from "@ant-design/icons";
|
import {
|
||||||
import { fetchData, UPDATE_CHAT_MESSGAE_VIZ } from "../../utils/apis";
|
EyeOutlined,
|
||||||
|
EyeInvisibleOutlined,
|
||||||
|
CheckCircleFilled,
|
||||||
|
ExclamationCircleFilled,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { fetchData, UPDATE_CHAT_MESSGAE_VIZ } from '../../utils/apis';
|
||||||
import { MessageType } from '../../types/chat';
|
import { MessageType } from '../../types/chat';
|
||||||
import { OUTCOME_TIMEOUT } from "../chat";
|
import { OUTCOME_TIMEOUT } from '../chat';
|
||||||
import { isEmptyObject } from "../../utils/format";
|
import { isEmptyObject } from '../../utils/format';
|
||||||
|
|
||||||
interface MessageToggleProps {
|
interface MessageToggleProps {
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
message: MessageType;
|
message: MessageType;
|
||||||
setMessage: (message: MessageType) => void,
|
setMessage: (message: MessageType) => void;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export default function MessageVisiblityToggle({
|
||||||
export default function MessageVisiblityToggle({ isVisible, message, setMessage }: MessageToggleProps) {
|
isVisible,
|
||||||
|
message,
|
||||||
|
setMessage,
|
||||||
|
}: MessageToggleProps) {
|
||||||
if (!message || isEmptyObject(message)) {
|
if (!message || isEmptyObject(message)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -25,16 +33,17 @@ export default function MessageVisiblityToggle({ isVisible, message, setMessage
|
|||||||
const { id: messageId } = message || {};
|
const { id: messageId } = message || {};
|
||||||
|
|
||||||
const resetOutcome = () => {
|
const resetOutcome = () => {
|
||||||
outcomeTimeout = setTimeout(() => { setOutcome(0)}, OUTCOME_TIMEOUT);
|
outcomeTimeout = setTimeout(() => {
|
||||||
|
setOutcome(0);
|
||||||
|
}, OUTCOME_TIMEOUT);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(outcomeTimeout);
|
clearTimeout(outcomeTimeout);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const updateChatMessage = async () => {
|
const updateChatMessage = async () => {
|
||||||
clearTimeout(outcomeTimeout);
|
clearTimeout(outcomeTimeout);
|
||||||
setOutcome(0);
|
setOutcome(0);
|
||||||
@ -47,7 +56,7 @@ export default function MessageVisiblityToggle({ isVisible, message, setMessage
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.success && result.message === "changed") {
|
if (result.success && result.message === 'changed') {
|
||||||
setMessage({ ...message, visible: !isVisible });
|
setMessage({ ...message, visible: !isVisible });
|
||||||
setOutcome(1);
|
setOutcome(1);
|
||||||
} else {
|
} else {
|
||||||
@ -55,14 +64,16 @@ export default function MessageVisiblityToggle({ isVisible, message, setMessage
|
|||||||
setOutcome(-1);
|
setOutcome(-1);
|
||||||
}
|
}
|
||||||
resetOutcome();
|
resetOutcome();
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
let outcomeIcon = <CheckCircleFilled style={{ color: 'transparent' }} />;
|
let outcomeIcon = <CheckCircleFilled style={{ color: 'transparent' }} />;
|
||||||
if (outcome) {
|
if (outcome) {
|
||||||
outcomeIcon = outcome > 0 ?
|
outcomeIcon =
|
||||||
<CheckCircleFilled style={{ color: 'var(--ant-success)' }} /> :
|
outcome > 0 ? (
|
||||||
<ExclamationCircleFilled style={{ color: 'var(--ant-warning)' }} />;
|
<CheckCircleFilled style={{ color: 'var(--ant-success)' }} />
|
||||||
|
) : (
|
||||||
|
<ExclamationCircleFilled style={{ color: 'var(--ant-warning)' }} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const toolTipMessage = `Click to ${isVisible ? 'hide' : 'show'} this message`;
|
const toolTipMessage = `Click to ${isVisible ? 'hide' : 'show'} this message`;
|
||||||
@ -74,10 +85,10 @@ export default function MessageVisiblityToggle({ isVisible, message, setMessage
|
|||||||
shape="circle"
|
shape="circle"
|
||||||
size="small"
|
size="small"
|
||||||
type="text"
|
type="text"
|
||||||
icon={ isVisible ? <EyeOutlined /> : <EyeInvisibleOutlined /> }
|
icon={isVisible ? <EyeOutlined /> : <EyeInvisibleOutlined />}
|
||||||
onClick={updateChatMessage}
|
onClick={updateChatMessage}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
import { Typography, Statistic, Card, Progress} from "antd";
|
import { Typography, Statistic, Card, Progress } from 'antd';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
interface StatisticItemProps {
|
interface StatisticItemProps {
|
||||||
title?: string,
|
title?: string;
|
||||||
value?: any,
|
value?: any;
|
||||||
prefix?: JSX.Element,
|
prefix?: JSX.Element;
|
||||||
color?: string,
|
color?: string;
|
||||||
progress?: boolean,
|
progress?: boolean;
|
||||||
centered?: boolean,
|
centered?: boolean;
|
||||||
formatter?: any,
|
formatter?: any;
|
||||||
};
|
}
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
title: '',
|
title: '',
|
||||||
value: 0,
|
value: 0,
|
||||||
prefix: null,
|
prefix: null,
|
||||||
color: '',
|
color: '',
|
||||||
@ -21,16 +21,19 @@ const defaultProps = {
|
|||||||
formatter: null,
|
formatter: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
function ProgressView({ title, value, prefix, color }: StatisticItemProps) {
|
function ProgressView({ title, value, prefix, color }: StatisticItemProps) {
|
||||||
const endColor = value > 90 ? 'red' : color;
|
const endColor = value > 90 ? 'red' : color;
|
||||||
const content = (
|
const content = (
|
||||||
<div>
|
<div>
|
||||||
{prefix}
|
{prefix}
|
||||||
<div><Text type="secondary">{title}</Text></div>
|
<div>
|
||||||
<div><Text type="secondary">{value}%</Text></div>
|
<Text type="secondary">{title}</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">{value}%</Text>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
return (
|
return (
|
||||||
<Progress
|
<Progress
|
||||||
type="dashboard"
|
type="dashboard"
|
||||||
@ -42,19 +45,12 @@ function ProgressView({ title, value, prefix, color }: StatisticItemProps) {
|
|||||||
}}
|
}}
|
||||||
format={percent => content}
|
format={percent => content}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
ProgressView.defaultProps = defaultProps;
|
ProgressView.defaultProps = defaultProps;
|
||||||
|
|
||||||
function StatisticView({ title, value, prefix, formatter }: StatisticItemProps) {
|
function StatisticView({ title, value, prefix, formatter }: StatisticItemProps) {
|
||||||
return (
|
return <Statistic title={title} value={value} prefix={prefix} formatter={formatter} />;
|
||||||
<Statistic
|
|
||||||
title={title}
|
|
||||||
value={value}
|
|
||||||
prefix={prefix}
|
|
||||||
formatter={formatter}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
StatisticView.defaultProps = defaultProps;
|
StatisticView.defaultProps = defaultProps;
|
||||||
|
|
||||||
@ -62,14 +58,14 @@ export default function StatisticItem(props: StatisticItemProps) {
|
|||||||
const { progress, centered } = props;
|
const { progress, centered } = props;
|
||||||
const View = progress ? ProgressView : StatisticView;
|
const View = progress ? ProgressView : StatisticView;
|
||||||
|
|
||||||
const style = centered ? {display: 'flex', alignItems: 'center', justifyContent: 'center'} : {};
|
const style = centered ? { display: 'flex', alignItems: 'center', justifyContent: 'center' } : {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card type="inner">
|
<Card type="inner">
|
||||||
<div style={style}>
|
<div style={style}>
|
||||||
<View {...props} />
|
<View {...props} />
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
StatisticItem.defaultProps = defaultProps;
|
StatisticItem.defaultProps = defaultProps;
|
||||||
|
|||||||
@ -1,8 +1,13 @@
|
|||||||
import { Result, Card } from "antd";
|
|
||||||
import { MessageTwoTone, QuestionCircleTwoTone, BookTwoTone, PlaySquareTwoTone } from '@ant-design/icons';
|
|
||||||
import OwncastLogo from "./components/logo"
|
|
||||||
import LogTable from "./components/log-table";
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { Result, Card } from 'antd';
|
||||||
|
import {
|
||||||
|
MessageTwoTone,
|
||||||
|
QuestionCircleTwoTone,
|
||||||
|
BookTwoTone,
|
||||||
|
PlaySquareTwoTone,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import OwncastLogo from './components/logo';
|
||||||
|
import LogTable from './components/log-table';
|
||||||
|
|
||||||
const { Meta } = Card;
|
const { Meta } = Card;
|
||||||
|
|
||||||
@ -10,36 +15,42 @@ export default function Offline({ logs = [] }) {
|
|||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
icon: <BookTwoTone twoToneColor="#6f42c1" />,
|
icon: <BookTwoTone twoToneColor="#6f42c1" />,
|
||||||
title: "Use your broadcasting software",
|
title: 'Use your broadcasting software',
|
||||||
content: (
|
content: (
|
||||||
<div>
|
<div>
|
||||||
<a href="https://owncast.online/docs/broadcasting/">Learn how to point your existing software to your new server and start streaming your content.</a>
|
<a href="https://owncast.online/docs/broadcasting/">
|
||||||
|
Learn how to point your existing software to your new server and start streaming your
|
||||||
|
content.
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <MessageTwoTone twoToneColor="#0366d6" />,
|
icon: <MessageTwoTone twoToneColor="#0366d6" />,
|
||||||
title: "Chat is disabled",
|
title: 'Chat is disabled',
|
||||||
content: "Chat will continue to be disabled until you begin a live stream."
|
content: 'Chat will continue to be disabled until you begin a live stream.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <PlaySquareTwoTone twoToneColor="#f9826c" />,
|
icon: <PlaySquareTwoTone twoToneColor="#f9826c" />,
|
||||||
title: "Embed your video onto other sites",
|
title: 'Embed your video onto other sites',
|
||||||
content: (
|
content: (
|
||||||
<div>
|
<div>
|
||||||
<a href="https://owncast.online/docs/embed">Learn how you can add your Owncast stream to other sites you control.</a>
|
<a href="https://owncast.online/docs/embed">
|
||||||
|
Learn how you can add your Owncast stream to other sites you control.
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <QuestionCircleTwoTone twoToneColor="#ffd33d" />,
|
icon: <QuestionCircleTwoTone twoToneColor="#ffd33d" />,
|
||||||
title: "Not sure what to do next?",
|
title: 'Not sure what to do next?',
|
||||||
content: (
|
content: (
|
||||||
<div>
|
<div>
|
||||||
If you're having issues or would like to know how to customize and configure your Owncast server visit <Link href="/help">the help page.</Link>
|
If you're having issues or would like to know how to customize and configure your
|
||||||
|
Owncast server visit <Link href="/help">the help page.</Link>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -53,19 +64,12 @@ export default function Offline({ logs = [] }) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="list-section">
|
<div className="list-section">
|
||||||
{
|
{data.map(item => (
|
||||||
data.map(item => (
|
<Card key={item.title}>
|
||||||
<Card key={item.title}>
|
<Meta avatar={item.icon} title={item.title} description={item.content} />
|
||||||
<Meta
|
</Card>
|
||||||
avatar={item.icon}
|
))}
|
||||||
title={item.title}
|
|
||||||
description={item.content}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<LogTable logs={logs} pageSize={5} />
|
<LogTable logs={logs} pageSize={5} />
|
||||||
</>
|
</>
|
||||||
|
|||||||
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 {
|
.config-page-content-form {
|
||||||
.page-content-actions {
|
.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 {
|
.segment-tip {
|
||||||
width: 10em;
|
width: 10em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -276,33 +27,6 @@
|
|||||||
display: inline-block;
|
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 {
|
// .social-option {
|
||||||
// .ant-select-item-option-content {
|
// .ant-select-item-option-content {
|
||||||
// display: flex;
|
// 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.
|
// Base styles for form-textfield, form-textfield-with-submit
|
||||||
|
|
||||||
/* 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* TEXTFIELD-CONTAINER BASE */
|
/* 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 BASE */
|
||||||
.textfield-with-submit-container {
|
.textfield-with-submit-container {
|
||||||
display: flex;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -36,48 +36,13 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// markdown editor overrides
|
.logo-svg {
|
||||||
|
height: 2rem;
|
||||||
.rc-virtual-list-scrollbar {
|
width: 2rem;
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
.rc-md-editor {
|
|
||||||
// Set the background color of the preview container
|
|
||||||
.editor-container {
|
|
||||||
background-color: #E2E8F0;
|
|
||||||
color: rgba(45,55,72,1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom CSS for formatting the preview text
|
|
||||||
.markdown-editor-preview-pane {
|
|
||||||
// color:lightgrey;
|
|
||||||
a {
|
|
||||||
color: var(--owncast-purple);;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom CSS class used to format the text of the editor
|
|
||||||
.markdown-editor-pane {
|
|
||||||
color: white !important;
|
|
||||||
background-color: black;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the background color of the editor text input
|
|
||||||
textarea {
|
|
||||||
background-color: rgb(44,44,44) !important;
|
|
||||||
color:lightgrey !important;
|
|
||||||
}
|
|
||||||
.ant-btn {
|
|
||||||
transition-duration: .15s;
|
|
||||||
transition-delay: 0s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide extra toolbar buttons.
|
p.page-description {
|
||||||
.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 {
|
margin: 1em 0;
|
||||||
display: none !important;
|
color: #ccc;
|
||||||
}
|
width: 80%;
|
||||||
}
|
}
|
||||||
|
|||||||
138
web/styles/main-layout.scss
Normal file
138
web/styles/main-layout.scss
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
.app-container {
|
||||||
|
|
||||||
|
.side-nav {
|
||||||
|
position: fixed;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: auto;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1.owncast-title {
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.logo-container {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: .35rem;
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-label {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 1rem;
|
||||||
|
color: rgba(203,213,224, 1);
|
||||||
|
font-size: 1.15rem;
|
||||||
|
font-weight: 200;
|
||||||
|
text-transform: uppercase;
|
||||||
|
line-height: normal;
|
||||||
|
letter-spacing: .05em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-main {
|
||||||
|
margin-left: 240px; // width of Ant Sider
|
||||||
|
}
|
||||||
|
.layout-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.main-content-container {
|
||||||
|
padding: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-container {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.online-status-indicator {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.online-thumbnail {
|
||||||
|
width: 12.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-label {
|
||||||
|
color: #fff;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: .75rem;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: .5rem;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
.status-icon {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
svg {
|
||||||
|
fill: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.online {
|
||||||
|
.online-status-indicator {
|
||||||
|
.status-icon {
|
||||||
|
svg {
|
||||||
|
fill: var(--online-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.status-label {
|
||||||
|
color: var(--online-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// stream title form field in header
|
||||||
|
.global-stream-title-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
.textfield-with-submit-container {
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
.input-side {
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-side {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.lower-container {
|
||||||
|
width: auto;
|
||||||
|
.lower-content {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.label-spacer,
|
||||||
|
.field-tip {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.status-container {
|
||||||
|
line-height: 1;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2em;
|
||||||
|
}
|
||||||
|
.update-button-container {
|
||||||
|
margin: 0;
|
||||||
|
margin-left: .5em;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
46
web/styles/markdown-editor.scss
Normal file
46
web/styles/markdown-editor.scss
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
// markdown editor overrides
|
||||||
|
|
||||||
|
.rc-virtual-list-scrollbar {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
.rc-md-editor {
|
||||||
|
// Set the background color of the preview container
|
||||||
|
.editor-container {
|
||||||
|
background-color: #E2E8F0;
|
||||||
|
color: rgba(45,55,72,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom CSS for formatting the preview text
|
||||||
|
.markdown-editor-preview-pane {
|
||||||
|
// color:lightgrey;
|
||||||
|
a {
|
||||||
|
color: var(--owncast-purple);;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom CSS class used to format the text of the editor
|
||||||
|
.markdown-editor-pane {
|
||||||
|
color: white !important;
|
||||||
|
background-color: black;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the background color of the editor text input
|
||||||
|
textarea {
|
||||||
|
background-color: rgb(44,44,44) !important;
|
||||||
|
color:lightgrey !important;
|
||||||
|
}
|
||||||
|
.ant-btn {
|
||||||
|
transition-duration: .15s;
|
||||||
|
transition-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,100 +0,0 @@
|
|||||||
|
|
||||||
.logoSVG {
|
|
||||||
height: 2rem;
|
|
||||||
width: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.owncastTitleContainer {
|
|
||||||
padding: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
}
|
|
||||||
.logoContainer {
|
|
||||||
background-color: #fff;
|
|
||||||
padding: .35rem;
|
|
||||||
border-radius: 9999px;
|
|
||||||
}
|
|
||||||
.owncastTitle {
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 1rem;
|
|
||||||
color: rgba(203,213,224, 1);
|
|
||||||
font-size: 1.15rem;
|
|
||||||
font-weight: 200;
|
|
||||||
text-transform: uppercase;
|
|
||||||
line-height: normal;
|
|
||||||
letter-spacing: .05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contentMain {
|
|
||||||
padding: 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sideNav {
|
|
||||||
position: fixed;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: auto;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layoutMain {
|
|
||||||
margin-left: 240px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.statusIndicatorContainer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
}
|
|
||||||
.statusIcon {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
.statusIcon svg {
|
|
||||||
fill: #999;
|
|
||||||
}
|
|
||||||
.statusLabel {
|
|
||||||
color: #fff;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: .75rem;
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: .5rem;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
.online .statusIcon svg {
|
|
||||||
fill: var(--online-color)
|
|
||||||
}
|
|
||||||
.online .statusLabel {
|
|
||||||
color: var(--online-color)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.lineChartContainer {
|
|
||||||
margin: 2em auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.configSection {
|
|
||||||
margin-bottom: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.onlineCurrentThumb {
|
|
||||||
width: 12.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.globalStreamTitleContainer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: baseline;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
@ -1,27 +1,27 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
export const AlertMessageContext = React.createContext({
|
export const AlertMessageContext = React.createContext({
|
||||||
message: null,
|
message: null,
|
||||||
setMessage: (text?: string) => {
|
setMessage: (text?: string) => {
|
||||||
return text;
|
return text;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const AlertMessageProvider = ({ children }) => {
|
const AlertMessageProvider = ({ children }) => {
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
|
|
||||||
const providerValue = {
|
|
||||||
message,
|
|
||||||
setMessage
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<AlertMessageContext.Provider value={providerValue}>{children}</AlertMessageContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AlertMessageProvider.propTypes = {
|
const providerValue = {
|
||||||
children: PropTypes.element.isRequired
|
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