diff --git a/web/pages/_app.tsx b/web/pages/_app.tsx
index 208b832ff..49327044c 100644
--- a/web/pages/_app.tsx
+++ b/web/pages/_app.tsx
@@ -1,12 +1,23 @@
+// order matters!
import 'antd/dist/antd.css';
import '../styles/colors.scss';
import '../styles/globals.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/chat.scss';
import '../styles/config.scss';
-import '../styles/config-formfields.scss';
import { AppProps } from 'next/app';
import ServerStatusProvider from '../utils/server-status-context';
diff --git a/web/pages/components/chart.tsx b/web/pages/components/chart.tsx
index 57445d35a..e324a7c4d 100644
--- a/web/pages/components/chart.tsx
+++ b/web/pages/components/chart.tsx
@@ -1,7 +1,6 @@
import { LineChart } from 'react-chartkick';
import 'chart.js';
import format from 'date-fns/format';
-import styles from '../../styles/styles.module.scss';
interface TimedValue {
time: Date;
@@ -9,11 +8,11 @@ interface TimedValue {
}
interface ChartProps {
- data?: TimedValue[],
- title?: string,
- color: string,
- unit: string,
- dataCollections?: any[],
+ data?: TimedValue[];
+ title?: string;
+ color: string;
+ unit: string;
+ dataCollections?: any[];
}
function createGraphDataset(dataArray) {
@@ -22,7 +21,7 @@ function createGraphDataset(dataArray) {
const dateObject = new Date(item.time);
const dateString = format(dateObject, 'p P');
dataValues[dateString] = item.value;
- })
+ });
return dataValues;
}
@@ -33,18 +32,20 @@ export default function Chart({ data, title, color, unit, dataCollections }: Cha
renderData.push({
name: title,
color,
- data: createGraphDataset(data)
+ data: createGraphDataset(data),
});
}
dataCollections.forEach(collection => {
- renderData.push(
- {name: collection.name, data: createGraphDataset(collection.data), color: collection.color}
- )
+ renderData.push({
+ name: collection.name,
+ data: createGraphDataset(collection.data),
+ color: collection.color,
+ });
});
return (
-
+
+
CPU Usage
There are trade-offs when considering CPU usage blah blah more wording here.
-
+
TOOLTIPS[value] }
+ tipFormatter={value => TOOLTIPS[value]}
onChange={handleChange}
min={1}
max={Object.keys(SLIDER_MARKS).length}
diff --git a/web/pages/components/config/edit-server-details.tsx b/web/pages/components/config/edit-server-details.tsx
index 82389086f..f09f0d51d 100644
--- a/web/pages/components/config/edit-server-details.tsx
+++ b/web/pages/components/config/edit-server-details.tsx
@@ -51,8 +51,8 @@ export default function EditInstanceDetails() {
};
const showConfigurationRestartMessage = () => {
- setMessage('Updating server settings requires a restart of your Owncast server.')
- }
+ setMessage('Updating server settings requires a restart of your Owncast server.');
+ };
function generateStreamKey() {
let key = '';
diff --git a/web/pages/components/config/edit-social-links.tsx b/web/pages/components/config/edit-social-links.tsx
index 4c5f6f92c..af9096f4d 100644
--- a/web/pages/components/config/edit-social-links.tsx
+++ b/web/pages/components/config/edit-social-links.tsx
@@ -157,7 +157,7 @@ export default function EditSocialLinks() {
postUpdateToAPI(postData);
};
- const handleDeleteItem = index => {
+ const handleDeleteItem = (index: number) => {
const postData = [...currentSocialHandles];
postData.splice(index, 1);
postUpdateToAPI(postData);
diff --git a/web/pages/components/config/edit-storage.tsx b/web/pages/components/config/edit-storage.tsx
index 2a12a6846..ae9efba22 100644
--- a/web/pages/components/config/edit-storage.tsx
+++ b/web/pages/components/config/edit-storage.tsx
@@ -1,4 +1,4 @@
-import { Switch, Button, Collapse, Alert } from 'antd';
+import { Switch, Button, Collapse } from 'antd';
import classNames from 'classnames';
import React, { useContext, useState, useEffect } from 'react';
import { UpdateArgs } from '../../../types/config-section';
@@ -32,7 +32,7 @@ function checkSaveable(formValues: any, currentValues: any) {
if (enabled) {
if (!!endpoint && isValidUrl(endpoint) && !!accessKey && !!secret && !!bucket && !!region) {
if (
- enabled !== currentValues.enabled ||
+ enabled !== currentValues.enabled ||
endpoint !== currentValues.endpoint ||
accessKey !== currentValues.accessKey ||
secret !== currentValues.secret ||
@@ -52,13 +52,12 @@ function checkSaveable(formValues: any, currentValues: any) {
export default function EditStorage() {
const [formDataValues, setFormDataValues] = useState(null);
const [submitStatus, setSubmitStatus] = useState(null);
- const [saved, setSaved] = useState(false);
const [shouldDisplayForm, setShouldDisplayForm] = useState(false);
const serverStatusData = useContext(ServerStatusContext);
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
- const {message, setMessage} = useContext(AlertMessageContext);
+ const { setMessage: setAlertMessage } = useContext(AlertMessageContext);
const { s3 } = serverConfig;
const {
@@ -117,8 +116,9 @@ export default function EditStorage() {
setFieldInConfigState({ fieldName: 's3', value: postValue, path: '' });
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.'));
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
- setSaved(true);
- setMessage('Changing your storage configuration will take place the next time you start a new stream.');
+ setAlertMessage(
+ 'Changing your storage configuration will take place the next time you start a new stream.',
+ );
},
onError: (message: string) => {
setSubmitStatus(createInputStatus(STATUS_ERROR, message));
@@ -131,12 +131,6 @@ export default function EditStorage() {
const handleSwitchChange = (storageEnabled: boolean) => {
setShouldDisplayForm(storageEnabled);
handleFieldChange({ fieldName: 'enabled', value: storageEnabled });
-
- // if current data in current store says s3 is enabled,
- // we should save this state
- // if (!storageEnabled && s3.enabled) {
- // handleSave();
- // }
};
const containerClass = classNames({
diff --git a/web/pages/components/config/social-icons-dropdown.tsx b/web/pages/components/config/social-icons-dropdown.tsx
index fac0c5374..767c8ea88 100644
--- a/web/pages/components/config/social-icons-dropdown.tsx
+++ b/web/pages/components/config/social-icons-dropdown.tsx
@@ -11,7 +11,7 @@ interface DropdownProps {
}
export default function SocialDropdown({ iconList, selectedOption, onSelected }: DropdownProps) {
- const handleSelected = value => {
+ const handleSelected = (value: string) => {
if (onSelected) {
onSelected(value);
}
diff --git a/web/pages/components/config/video-latency.tsx b/web/pages/components/config/video-latency.tsx
index d337cc43e..82870d643 100644
--- a/web/pages/components/config/video-latency.tsx
+++ b/web/pages/components/config/video-latency.tsx
@@ -1,12 +1,15 @@
import React, { useContext, useState, useEffect } from 'react';
import { Typography, Slider } from 'antd';
import { ServerStatusContext } from '../../../utils/server-status-context';
+import { API_VIDEO_SEGMENTS, RESET_TIMEOUT, postConfigUpdateToAPI } from './constants';
import {
- API_VIDEO_SEGMENTS,
- SUCCESS_STATES,
- RESET_TIMEOUT,
- postConfigUpdateToAPI,
-} from './constants';
+ createInputStatus,
+ StatusState,
+ STATUS_ERROR,
+ STATUS_PROCESSING,
+ STATUS_SUCCESS,
+} from '../../../utils/input-statuses';
+import FormStatusIndicator from './form-status-indicator';
const { Title } = Typography;
@@ -37,8 +40,10 @@ function SegmentToolTip({ value }: SegmentToolTipProps) {
}
export default function VideoLatency() {
- const [submitStatus, setSubmitStatus] = useState(null);
- const [submitStatusMessage, setSubmitStatusMessage] = useState('');
+ const [submitStatus, setSubmitStatus] = useState(null);
+
+ // const [submitStatus, setSubmitStatus] = useState(null);
+ // const [submitStatusMessage, setSubmitStatusMessage] = useState('');
const [selectedOption, setSelectedOption] = useState(null);
const serverStatusData = useContext(ServerStatusContext);
@@ -57,13 +62,15 @@ export default function VideoLatency() {
const resetStates = () => {
setSubmitStatus(null);
- setSubmitStatusMessage('');
+ // setSubmitStatusMessage('');
resetTimer = null;
clearTimeout(resetTimer);
};
// posts all the variants at once as an array obj
const postUpdateToAPI = async (postValue: any) => {
+ setSubmitStatus(createInputStatus(STATUS_PROCESSING));
+
await postConfigUpdateToAPI({
apiPath: API_VIDEO_SEGMENTS,
data: { value: postValue },
@@ -73,34 +80,28 @@ export default function VideoLatency() {
value: postValue,
path: 'videoSettings',
});
+ setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Variants updated.'));
- setSubmitStatus('success');
- setSubmitStatusMessage('Variants updated.');
+ // setSubmitStatus('success');
+ // setSubmitStatusMessage('Variants updated.');
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
},
onError: (message: string) => {
- setSubmitStatus('error');
- setSubmitStatusMessage(message);
+ setSubmitStatus(createInputStatus(STATUS_ERROR, message));
+
+ // setSubmitStatus('error');
+ // setSubmitStatusMessage(message);
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
},
});
};
- const { icon: newStatusIcon = null, message: newStatusMessage = '' } =
- SUCCESS_STATES[submitStatus] || {};
-
- const statusMessage = (
-
- {newStatusIcon} {newStatusMessage} {submitStatusMessage}
-
- );
-
const handleChange = value => {
postUpdateToAPI(value);
};
return (
-
+
Latency Buffer
There are trade-offs when cosidering video latency and reliability. Blah blah .. better
@@ -108,7 +109,7 @@ export default function VideoLatency() {
-
+
}
onChange={handleChange}
@@ -119,7 +120,7 @@ export default function VideoLatency() {
value={selectedOption}
/>
- {statusMessage}
+
);
}
diff --git a/web/pages/components/config/video-variant-form.tsx b/web/pages/components/config/video-variant-form.tsx
index e82a37788..c5e7b5e83 100644
--- a/web/pages/components/config/video-variant-form.tsx
+++ b/web/pages/components/config/video-variant-form.tsx
@@ -1,12 +1,11 @@
// This content populates the video variant modal, which is spawned from the variants table.
import React from 'react';
-import { Slider, Select, Switch, Divider, Collapse } from 'antd';
-import { FieldUpdaterFunc, CpuUsageLevel, VideoVariant } from '../../../types/config-section';
+import { Slider, Switch, Collapse } from 'antd';
+import { FieldUpdaterFunc, VideoVariant } from '../../../types/config-section';
import { DEFAULT_VARIANT_STATE } from './constants';
import InfoTip from '../info-tip';
import CPUUsageSelector from './cpu-usage';
-const { Option } = Select;
const { Panel } = Collapse;
const VIDEO_VARIANT_DEFAULTS = {
@@ -57,12 +56,6 @@ export default function VideoVariantForm({
const handleVideoBitrateChange = (value: number) => {
onUpdateField({ fieldName: 'videoBitrate', value });
};
- const handleAudioBitrateChange = (value: number) => {
- onUpdateField({ fieldName: 'audioBitrate', value });
- };
- const handleAudioPassChange = (value: boolean) => {
- onUpdateField({ fieldName: 'audioPassthrough', value });
- };
const handleVideoPassChange = (value: boolean) => {
onUpdateField({ fieldName: 'videoPassthrough', value });
};
@@ -80,18 +73,12 @@ export default function VideoVariantForm({
const videoBRMax = videoBitrateDefaults.max;
const videoBRUnit = videoBitrateDefaults.unit;
- const audioBitrateDefaults = VIDEO_VARIANT_DEFAULTS.audioBitrate;
- const audioBRMin = audioBitrateDefaults.min;
- const audioBRMax = audioBitrateDefaults.max;
- const audioBRUnit = audioBitrateDefaults.unit;
-
const selectedVideoBRnote = `Selected: ${dataState.videoBitrate}${videoBRUnit} - it sucks`;
- const selectedAudioBRnote = `Selected: ${dataState.audioBitrate}${audioBRUnit} - too slow`;
const selectedFramerateNote = `Selected: ${dataState.framerate}${framerateUnit} - whoa there`;
const selectedPresetNote = '';
return (
-
+
Say a thing here about how this all works. Read more{' '}
here.
@@ -130,6 +117,7 @@ export default function VideoVariantForm({
+
{/* VIDEO BITRATE FIELD */}
@@ -192,55 +180,6 @@ export default function VideoVariantForm({
) : null}
-
-
-
- {/* AUDIO PASSTHROUGH FIELD */}
- {/*
-
-
- Use Audio Passthrough?
-
-
-
- {dataState.audioPassthrough ? Same as source : null}
-
-
*/}
-
- {/* AUDIO BITRATE FIELD */}
- {/*
-
-
- Audio Bitrate:
-
-
- `${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 ? (
- {selectedAudioBRnote}
- ) : null}
-
-
*/}
diff --git a/web/pages/components/config/video-variants-table.tsx b/web/pages/components/config/video-variants-table.tsx
index 83420fb05..e6bb72a39 100644
--- a/web/pages/components/config/video-variants-table.tsx
+++ b/web/pages/components/config/video-variants-table.tsx
@@ -1,6 +1,5 @@
// Updating a variant will post ALL the variants in an array as an update to the API.
-// todo : add DELETE option
import React, { useContext, useState } from 'react';
import { Typography, Table, Modal, Button } from 'antd';
import { ColumnsType } from 'antd/lib/table';
@@ -20,11 +19,19 @@ import {
const { Title } = Typography;
+const CPU_USAGE_LEVEL_MAP = {
+ 1: 'lowest',
+ 2: 'low',
+ 3: 'medium',
+ 4: 'high',
+ 5: 'highest',
+};
+
export default function CurrentVariantsTable() {
const [displayModal, setDisplayModal] = useState(false);
const [modalProcessing, setModalProcessing] = useState(false);
const [editId, setEditId] = useState(0);
- const {setMessage} = useContext(AlertMessageContext);
+ const { setMessage } = useContext(AlertMessageContext);
// current data inside modal
const [modalDataState, setModalDataState] = useState(DEFAULT_VARIANT_STATE);
@@ -76,7 +83,9 @@ export default function CurrentVariantsTable() {
setSubmitStatusMessage('Variants updated.');
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
- setMessage('Updating your video configuration will take effect the next time you begin a new stream.');
+ setMessage(
+ 'Updating your video configuration will take effect the next time you begin a new stream.',
+ );
},
onError: (message: string) => {
setSubmitStatus('error');
@@ -117,14 +126,6 @@ export default function CurrentVariantsTable() {
const { icon: newStatusIcon = null, message: newStatusMessage = '' } =
SUCCESS_STATES[submitStatus] || {};
- const cpuUsageLevelLabelMap = {
- 1: 'lowest',
- 2: 'low',
- 3: 'medium',
- 4: 'high',
- 5: 'highest',
- };
-
const videoQualityColumns: ColumnsType = [
{
title: 'Video bitrate',
@@ -137,7 +138,7 @@ export default function CurrentVariantsTable() {
title: 'CPU Usage',
dataIndex: 'cpuUsageLevel',
key: 'cpuUsageLevel',
- render: (level: string) => (!level ? 'n/a' : cpuUsageLevelLabelMap[level]),
+ render: (level: string) => (!level ? 'n/a' : CPU_USAGE_LEVEL_MAP[level]),
},
{
title: '',
diff --git a/web/pages/components/info-tip.tsx b/web/pages/components/info-tip.tsx
index 28b02e48d..f5b847bb2 100644
--- a/web/pages/components/info-tip.tsx
+++ b/web/pages/components/info-tip.tsx
@@ -1,5 +1,5 @@
-import { InfoCircleOutlined } from "@ant-design/icons";
-import { Tooltip } from "antd";
+import { InfoCircleOutlined } from '@ant-design/icons';
+import { Tooltip } from 'antd';
interface InfoTipProps {
tip: string | null;
diff --git a/web/pages/components/key-value-table.tsx b/web/pages/components/key-value-table.tsx
index 28e620ead..91bb05e03 100644
--- a/web/pages/components/key-value-table.tsx
+++ b/web/pages/components/key-value-table.tsx
@@ -1,18 +1,18 @@
-import { Table, Typography } from "antd";
+import { Table, Typography } from 'antd';
const { Title } = Typography;
export default function KeyValueTable({ title, data }: KeyValueTableProps) {
const columns = [
{
- title: "Name",
- dataIndex: "name",
- key: "name",
+ title: 'Name',
+ dataIndex: 'name',
+ key: 'name',
},
{
- title: "Value",
- dataIndex: "value",
- key: "value",
+ title: 'Value',
+ dataIndex: 'value',
+ key: 'value',
},
];
@@ -25,6 +25,6 @@ export default function KeyValueTable({ title, data }: KeyValueTableProps) {
}
interface KeyValueTableProps {
- title: string,
- data: any,
-};
\ No newline at end of file
+ title: string;
+ data: any;
+}
diff --git a/web/pages/components/log-table.tsx b/web/pages/components/log-table.tsx
index 74c819c7e..603d6af56 100644
--- a/web/pages/components/log-table.tsx
+++ b/web/pages/components/log-table.tsx
@@ -1,32 +1,30 @@
-import React from "react";
-import { Table, Tag, Typography } from "antd";
-import Linkify from "react-linkify";
-import { SortOrder } from "antd/lib/table/interface";
-import format from 'date-fns/format'
+import React from 'react';
+import { Table, Tag, Typography } from 'antd';
+import Linkify from 'react-linkify';
+import { SortOrder } from 'antd/lib/table/interface';
+import format from 'date-fns/format';
const { Title } = Typography;
function renderColumnLevel(text, entry) {
let color = 'black';
- if (entry.level === "warning") {
- color = "orange";
+ if (entry.level === 'warning') {
+ color = 'orange';
} else if (entry.level === 'error') {
- color = "red";
+ color = 'red';
}
return {text};
}
function renderMessage(text) {
- return (
- {text}
- )
+ return {text};
}
interface Props {
- logs: object[],
- pageSize: number
+ logs: object[];
+ pageSize: number;
}
export default function LogTable({ logs, pageSize }: Props) {
@@ -35,42 +33,42 @@ export default function LogTable({ logs, pageSize }: Props) {
}
const columns = [
{
- title: "Level",
- dataIndex: "level",
- key: "level",
+ title: 'Level',
+ dataIndex: 'level',
+ key: 'level',
filters: [
{
- text: "Info",
- value: "info",
+ text: 'Info',
+ value: 'info',
},
{
- text: "Warning",
- value: "warning",
+ text: 'Warning',
+ value: 'warning',
},
{
- text: "Error",
- value: "Error",
+ text: 'Error',
+ value: 'Error',
},
],
onFilter: (level, row) => row.level.indexOf(level) === 0,
render: renderColumnLevel,
},
{
- title: "Timestamp",
- dataIndex: "time",
- key: "time",
- render: (timestamp) => {
+ title: 'Timestamp',
+ dataIndex: 'time',
+ key: 'time',
+ render: timestamp => {
const dateObject = new Date(timestamp);
return format(dateObject, 'p P');
},
sorter: (a, b) => new Date(a.time).getTime() - new Date(b.time).getTime(),
- sortDirections: ["descend", "ascend"] as SortOrder[],
- defaultSortOrder: "descend" as SortOrder,
+ sortDirections: ['descend', 'ascend'] as SortOrder[],
+ defaultSortOrder: 'descend' as SortOrder,
},
{
- title: "Message",
- dataIndex: "message",
- key: "message",
+ title: 'Message',
+ dataIndex: 'message',
+ key: 'message',
render: renderMessage,
},
];
@@ -82,10 +80,9 @@ export default function LogTable({ logs, pageSize }: Props) {
size="middle"
dataSource={logs}
columns={columns}
- rowKey={(row) => row.time}
+ rowKey={row => row.time}
pagination={{ pageSize: pageSize || 20 }}
/>
);
}
-
diff --git a/web/pages/components/logo.tsx b/web/pages/components/logo.tsx
index cb1a49732..eb2721a12 100644
--- a/web/pages/components/logo.tsx
+++ b/web/pages/components/logo.tsx
@@ -1,85 +1,159 @@
import React from 'react';
-import adminStyles from '../../styles/styles.module.scss';
export default function Logo() {
return (
-
);
-}
\ No newline at end of file
+}
diff --git a/web/pages/components/main-layout.tsx b/web/pages/components/main-layout.tsx
index d8e948c7e..095622b9e 100644
--- a/web/pages/components/main-layout.tsx
+++ b/web/pages/components/main-layout.tsx
@@ -1,8 +1,8 @@
import React, { useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import Link from 'next/link';
-import Head from 'next/head'
-import { differenceInSeconds } from "date-fns";
+import Head from 'next/head';
+import { differenceInSeconds } from 'date-fns';
import { useRouter } from 'next/router';
import { Layout, Menu, Popover, Alert } from 'antd';
@@ -16,11 +16,10 @@ import {
QuestionCircleOutlined,
MessageOutlined,
ExperimentOutlined,
-
} from '@ant-design/icons';
import classNames from 'classnames';
-import { upgradeVersionAvailable } from "../../utils/apis";
-import { parseSecondsToDurationString } from '../../utils/format'
+import { upgradeVersionAvailable } from '../../utils/apis';
+import { parseSecondsToDurationString } from '../../utils/format';
import OwncastLogo from './logo';
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 { TEXTFIELD_PROPS_STREAM_TITLE } from './config/constants';
-import adminStyles from '../../styles/styles.module.scss';
+import { UpdateArgs } from '../../types/config-section';
let performedUpgradeCheck = false;
@@ -37,72 +36,47 @@ export default function MainLayout(props) {
const { children } = props;
const context = useContext(ServerStatusContext);
- const { serverConfig, online, broadcaster, versionNumber, streamTitle } = context || {};
+ const { serverConfig, online, broadcaster, versionNumber } = context || {};
const { instanceDetails } = serverConfig;
- const [currentStreamTitle, setCurrentStreamTitle] = useState(streamTitle);
+ const [currentStreamTitle, setCurrentStreamTitle] = useState('');
const alertMessage = useContext(AlertMessageContext);
-
+
const router = useRouter();
const { route } = router || {};
const { Header, Footer, Content, Sider } = Layout;
const { SubMenu } = Menu;
- // status indicator items
- const streamDurationString = broadcaster ? parseSecondsToDurationString(differenceInSeconds(new Date(), new Date(broadcaster.time))) : "";
- const currentThumbnail = online ? (
-

- ) : null;
- const statusIcon = online ?
:
;
- const statusMessage = online ? `Online ${streamDurationString}` : "Offline";
- const statusIndicator = (
-
- {statusMessage}
- {statusIcon}
-
- );
- const statusIndicatorWithThumb = online ? (
-
- {statusIndicator}
-
- ) : statusIndicator;
- // ///////////////
-
const [upgradeVersion, setUpgradeVersion] = useState(null);
const checkForUpgrade = async () => {
try {
const result = await upgradeVersionAvailable(versionNumber);
setUpgradeVersion(result);
} catch (error) {
- console.log("==== error", error);
+ console.log('==== error', error);
}
};
useEffect(() => {
if (!performedUpgradeCheck) {
checkForUpgrade();
- performedUpgradeCheck = true
+ performedUpgradeCheck = true;
}
});
useEffect(() => {
- setCurrentStreamTitle(streamTitle);
- }, [streamTitle]);
+ setCurrentStreamTitle(instanceDetails.streamTitle);
+ }, [instanceDetails]);
const handleStreamTitleChanged = ({ value }: UpdateArgs) => {
setCurrentStreamTitle(value);
- }
-
+ };
const appClass = classNames({
- "owncast-layout": true,
- [adminStyles.online]: online,
+ 'app-container': true,
+ online,
});
const upgradeMenuItemStyle = upgradeVersion ? 'block' : 'none';
@@ -110,15 +84,36 @@ export default function MainLayout(props) {
const clearAlertMessage = () => {
alertMessage.setMessage(null);
- }
+ };
+
+ const headerAlertMessage = alertMessage.message ? (
+
+ ) : null;
+
+ // status indicator items
+ const streamDurationString = broadcaster
+ ? parseSecondsToDurationString(differenceInSeconds(new Date(), new Date(broadcaster.time)))
+ : '';
+ const currentThumbnail = online ? (
+

+ ) : null;
+ const statusIcon = online ?
:
;
+ const statusMessage = online ? `Online ${streamDurationString}` : 'Offline';
+
+ const statusIndicator = (
+
+ {statusMessage}
+ {statusIcon}
+
+ );
+ const statusIndicatorWithThumb = online ? (
+
+ {statusIndicator}
+
+ ) : (
+ statusIndicator
+ );
- const headerAlertMessage = alertMessage.message ? (
): null;
-
return (
@@ -126,47 +121,32 @@ export default function MainLayout(props) {
-
+
-
-
-
-
-
+
+
+
+
+
+
{statusIndicatorWithThumb}
{headerAlertMessage}
-
- {children}
-
@@ -259,4 +226,4 @@ export default function MainLayout(props) {
MainLayout.propTypes = {
children: PropTypes.element.isRequired,
-};
\ No newline at end of file
+};
diff --git a/web/pages/components/message-visiblity-toggle.tsx b/web/pages/components/message-visiblity-toggle.tsx
index c202e8d1e..bd4fdf64c 100644
--- a/web/pages/components/message-visiblity-toggle.tsx
+++ b/web/pages/components/message-visiblity-toggle.tsx
@@ -1,20 +1,28 @@
-// Custom component for AntDesign Button that makes an api call, then displays a confirmation icon upon
-import React, { useState, useEffect } from "react";
-import { Button, Tooltip } from "antd";
-import { EyeOutlined, EyeInvisibleOutlined, CheckCircleFilled, ExclamationCircleFilled } from "@ant-design/icons";
-import { fetchData, UPDATE_CHAT_MESSGAE_VIZ } from "../../utils/apis";
+// Custom component for AntDesign Button that makes an api call, then displays a confirmation icon upon
+import React, { useState, useEffect } from 'react';
+import { Button, Tooltip } from 'antd';
+import {
+ EyeOutlined,
+ EyeInvisibleOutlined,
+ CheckCircleFilled,
+ ExclamationCircleFilled,
+} from '@ant-design/icons';
+import { fetchData, UPDATE_CHAT_MESSGAE_VIZ } from '../../utils/apis';
import { MessageType } from '../../types/chat';
-import { OUTCOME_TIMEOUT } from "../chat";
-import { isEmptyObject } from "../../utils/format";
+import { OUTCOME_TIMEOUT } from '../chat';
+import { isEmptyObject } from '../../utils/format';
interface MessageToggleProps {
isVisible: boolean;
message: MessageType;
- setMessage: (message: MessageType) => void,
-};
+ setMessage: (message: MessageType) => void;
+}
-
-export default function MessageVisiblityToggle({ isVisible, message, setMessage }: MessageToggleProps) {
+export default function MessageVisiblityToggle({
+ isVisible,
+ message,
+ setMessage,
+}: MessageToggleProps) {
if (!message || isEmptyObject(message)) {
return null;
}
@@ -25,16 +33,17 @@ export default function MessageVisiblityToggle({ isVisible, message, setMessage
const { id: messageId } = message || {};
const resetOutcome = () => {
- outcomeTimeout = setTimeout(() => { setOutcome(0)}, OUTCOME_TIMEOUT);
+ outcomeTimeout = setTimeout(() => {
+ setOutcome(0);
+ }, OUTCOME_TIMEOUT);
};
-
+
useEffect(() => {
return () => {
clearTimeout(outcomeTimeout);
};
});
-
const updateChatMessage = async () => {
clearTimeout(outcomeTimeout);
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 });
setOutcome(1);
} else {
@@ -55,14 +64,16 @@ export default function MessageVisiblityToggle({ isVisible, message, setMessage
setOutcome(-1);
}
resetOutcome();
- }
-
+ };
let outcomeIcon = ;
if (outcome) {
- outcomeIcon = outcome > 0 ?
- :
- ;
+ outcomeIcon =
+ outcome > 0 ? (
+
+ ) : (
+
+ );
}
const toolTipMessage = `Click to ${isVisible ? 'hide' : 'show'} this message`;
@@ -74,10 +85,10 @@ export default function MessageVisiblityToggle({ isVisible, message, setMessage
shape="circle"
size="small"
type="text"
- icon={ isVisible ? : }
+ icon={isVisible ? : }
onClick={updateChatMessage}
/>
);
-}
\ No newline at end of file
+}
diff --git a/web/pages/components/statistic.tsx b/web/pages/components/statistic.tsx
index 62383efe1..a6edd9da9 100644
--- a/web/pages/components/statistic.tsx
+++ b/web/pages/components/statistic.tsx
@@ -1,18 +1,18 @@
-import { Typography, Statistic, Card, Progress} from "antd";
+import { Typography, Statistic, Card, Progress } from 'antd';
const { Text } = Typography;
interface StatisticItemProps {
- title?: string,
- value?: any,
- prefix?: JSX.Element,
- color?: string,
- progress?: boolean,
- centered?: boolean,
- formatter?: any,
-};
+ title?: string;
+ value?: any;
+ prefix?: JSX.Element;
+ color?: string;
+ progress?: boolean;
+ centered?: boolean;
+ formatter?: any;
+}
const defaultProps = {
- title: '',
+ title: '',
value: 0,
prefix: null,
color: '',
@@ -21,16 +21,19 @@ const defaultProps = {
formatter: null,
};
-
function ProgressView({ title, value, prefix, color }: StatisticItemProps) {
const endColor = value > 90 ? 'red' : color;
const content = (
- {prefix}
-
{title}
-
{value}%
+ {prefix}
+
+ {title}
+
+
+ {value}%
+
- )
+ );
return (
- {
- data.map(item => (
-
-
-
- ))
- }
+ {data.map(item => (
+
+
+
+ ))}
-
>
diff --git a/web/styles/config-socialhandles.scss b/web/styles/config-socialhandles.scss
new file mode 100644
index 000000000..7cb9591d7
--- /dev/null
+++ b/web/styles/config-socialhandles.scss
@@ -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;
+ }
+ }
+}
diff --git a/web/styles/config-storage.scss b/web/styles/config-storage.scss
new file mode 100644
index 000000000..3f3993509
--- /dev/null
+++ b/web/styles/config-storage.scss
@@ -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;
+ }
+ }
+}
diff --git a/web/styles/config-tags.scss b/web/styles/config-tags.scss
new file mode 100644
index 000000000..f1d176693
--- /dev/null
+++ b/web/styles/config-tags.scss
@@ -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;
+ }
+}
diff --git a/web/styles/config-video-variants.scss b/web/styles/config-video-variants.scss
new file mode 100644
index 000000000..a985d70f2
--- /dev/null
+++ b/web/styles/config-video-variants.scss
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/web/styles/config.scss b/web/styles/config.scss
index 769433842..af2986587 100644
--- a/web/styles/config.scss
+++ b/web/styles/config.scss
@@ -1,164 +1,7 @@
-// .config-public-details-container {
-// display: flex;
-// flex-direction: row;
-// align-items: flex-start;
-// flex-wrap: wrap;
-
-// .text-fields {
-// margin-right: 2rem;
-// }
-// .misc-fields {
-// width: 25em;
-// }
-// .tag-editor-container,
-// .config-directory-details-form {
-// border-radius: 1em;
-// background-color: rgba(128,99,255,.1);
-// padding: 1.5em;
-// margin-bottom: 1em;
-// }
-// }
-
-.module-container {
- border-radius: 1em;
- background-color: rgba(128,99,255,.1);
- padding: 1.5em;
- margin-bottom: 1em;
-}
-// form-textfield
-// form-textfield
-// .textfield-container {
-// display: flex;
-// flex-direction: column;
-// align-items: flex-start;
-// justify-content: flex-end;
-// position: relative;
-// width: 314px;
-
-// // &.type-numeric {
-// // .ant-form-item-control {
-// // flex-direction: row;
-// // .ant-form-item-control-input {
-// // margin-right: .75rem;
-// // }
-// // }
-// // }
-// }
-// .textfield {
-// display: flex;
-// flex-direction: row;
-// align-items: flex-start;
-
-// .field {
-// width: 18rem;
-
-// &.ant-input-number {
-// width: 8em;
-// }
-
-// }
-// .info-tip {
-// margin-right: .75rem;
-// }
-// .ant-form-item {
-// margin-bottom: 16px;
-// &.ant-form-item-with-help {
-// margin-bottom: 16px;
-// }
-// }
-// .ant-form-item-label label {
-// font-weight: bold;
-// color: var(--owncast-purple);
-// }
-// .ant-form-item-explain {
-// width: 70%;
-// }
-// }
-// .submit-button {
-// position: absolute;
-// right: 0;
-// bottom: .5em;
-// }
-// .ant-form-horizontal {
-// .textfield-container.type-numeric {
-// width: auto;
-
-// .submit-button {
-// bottom: unset;
-// top: 0;
-// right: unset;
-// }
-// }
-// }
-
-
-// form-toggleswitch
-// form-toggleswitch
-// .toggleswitch-container {
-// .status-message {
-// margin-top: .25rem;
-// }
-// }
-// .toggleswitch {
-// display: flex;
-// flex-direction: row;
-// align-items: center;
-// justify-content: flex-start;
-// .label {
-// font-weight: bold;
-// color: var(--owncast-purple);
-// }
-// .info-tip {
-// margin-left: .5rem;
-// svg {
-// fill: white;
-// }
-// }
-// .ant-form-item {
-// margin: 0 .75rem 0 0;
-// }
-// }
-
-// TAGS STUFF
-// TAGS STUFF
-.tag-current-tags {
- .ant-tag {
- margin: .1rem;
- font-size: .85rem;
- border-radius: 10em;
- padding: .25em 1em;
- background-color: rgba(255,255,255,.5);
-
- .ant-tag-close-icon {
- transform: translateY(-1px);
- margin-left: .3rem;
- padding: 2px;
- border-radius: 5rem;
- border: 1px solid #eee;
- &:hover {
- border-color: #e03;
- svg {
- fill: black;
- transition: fill .3s;
- }
- }
- }
- }
-}
-.add-new-tag-section {
- display: flex;
- flex-direction: row;
- justify-content: flex-start;
- align-items: center;
-
- .new-tag-input {
- width: 16em;
- }
-}
.config-page-content-form {
.page-content-actions {
@@ -174,101 +17,9 @@
}
}
-.config-video-variants {
- .config-video-misc {
- margin: 2rem 0;
- // .ant-form {
- // display: flex;
- // flex-direction: row;
- // align-items: flex-start;
- // }
- }
-}
-.variant-form {
- .blurb {
- margin: 1em;
- opacity: .75;
- }
- .note {
- display: inline-block;
- margin-left: 1em;
- font-size: .75em;
- opacity: .5;
- font-style: italic;
- }
- .section-intro {
- margin-bottom: 2em;
- }
- .field {
- margin-bottom: 2em;
- display: flex;
- flex-direction: row;
- justify-content: center;
- align-items: flex-start;
- transform: opacity .15s;
- &.disabled {
- opacity: .25;
- }
- .label {
- width: 40%;
- text-align: right;
- padding-right: 2em;
- font-weight: bold;
- color: var(--owncast-purple);
- }
- .info-tip {
- margin-right: 1em;
- }
- .form-component {
- width: 60%;
-
- .selected-value-note {
- font-size: .85em;
- display: inline-block;
- text-align: center;
- }
- }
- }
- .ant-collapse {
- border: none;
- border-radius: 6px;
- }
- .ant-collapse > .ant-collapse-item:last-child,
- .ant-collapse > .ant-collapse-item:last-child > .ant-collapse-header {
- border: none;
- background-color: rgba(0,0,0,.25);
- border-radius: 6px;
- }
- .ant-collapse-content {
- background-color: rgba(0,0,0,.1);
- }
-}
-.config-video-segements-conatiner {
- .segment-slider {
- width: 90%;
- margin: auto;
- padding: 1em 2em .75em;
- background-color: black;
- border-radius: 1em;
- }
- .status-message {
- text-align: center;
- }
-}
-.variants-table {
- .actions {
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .delete-button {
- margin-left: .5em;
- opacity: .8;
- }
-}
.segment-tip {
width: 10em;
text-align: center;
@@ -276,33 +27,6 @@
display: inline-block;
}
-.social-option,
-.social-dropdown {
- // .ant-select-selector,
- // .ant-select-selection-search-input {
- // height: 40px !important;
- // }
- .ant-select-item-option-content,
- .ant-select-selection-item {
- display: flex;
- flex-direction: row;
- justify-content: flex-start;
- align-items: center;
- padding: .25em;
- line-height: normal;
-
- .option-icon {
- height: 1.5em;
- width: 1.5em;
- line-height: normal;
- }
- .option-label {
- display: inline-block;
- margin-left: 1em;
- line-height: normal;
- }
- }
-}
// .social-option {
// .ant-select-item-option-content {
// display: flex;
@@ -324,57 +48,3 @@
-
-// EDIT STORAGE
-.edit-storage-container {
- .form-fields {
- display: none;
- margin-bottom: 1em;
- }
- &.enabled {
- .form-fields {
- display: block;
- }
- }
-
- .button-container {
- margin: 1em 0;
- }
- .advanced-section {
- margin: 1em 0;
- }
-}
-
-.field-container {
- padding: .85em 0 .5em;
- &:nth-child(even) {
- background-color: rgba(0,0,0,.25);
- }
-}
-
-
-
-.field-streamkey-container {
- margin-bottom: 1.5em;
- .field-tip {
- color: var(--ant-warning);
- }
- .left-side {
- display: flex;
- flex-direction: row;
- align-items: flex-start;
- }
- .textfield-with-submit-container {
- margin-bottom: 0;
- }
-
- .streamkey-actions {
- white-space: nowrap;
- button {
- margin: .25em;
- }
- @media (max-width: 800px) {
- margin-top: 2em;
- }
- }
-}
diff --git a/web/styles/form-misc-elements.scss b/web/styles/form-misc-elements.scss
new file mode 100644
index 000000000..dd2abd7cb
--- /dev/null
+++ b/web/styles/form-misc-elements.scss
@@ -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;
+}
+
diff --git a/web/styles/config-formfields.scss b/web/styles/form-textfields.scss
similarity index 67%
rename from web/styles/config-formfields.scss
rename to web/styles/form-textfields.scss
index 97b2227c7..29ffbe7f7 100644
--- a/web/styles/config-formfields.scss
+++ b/web/styles/form-textfields.scss
@@ -1,37 +1,4 @@
-// Base styles for form-textfield, form-textfield-with-submit, and helper components.
-
-/* STATUS-CONTAINER BASE */
-.status-container {
- &.status-success {
- color: var(--ant-success);
- }
- &.status-error {
- color: var(--ant-error);
- }
- &.status-warning {
- color: var(--ant-warning);
- }
-
- &.empty {
- display: none;
- }
- display: flex;
- flex-direction: row;
- justify-content: flex-start;
- align-items: center;
- font-size: .75rem;
- .status-icon {
- display: inline-block;
- margin-right: .5em;
- }
-}
-
-
-/* TIP CONTAINER BASE */
-.field-tip {
- font-size: .7em;
- color: rgba(255,255,255,.5)
-}
+// Base styles for form-textfield, form-textfield-with-submit
/* TEXTFIELD-CONTAINER BASE */
@@ -103,6 +70,8 @@
}
}
+/* TEXTFIELD-WITH-SUBMIT-CONTAINER BASE */
+/* TEXTFIELD-WITH-SUBMIT-CONTAINER BASE */
/* TEXTFIELD-WITH-SUBMIT-CONTAINER BASE */
.textfield-with-submit-container {
display: flex;
@@ -169,30 +138,3 @@
}
}
}
-
-/* TOGGLE SWITCH-WITH-SUBMIT-CONTAINER BASE */
-.toggleswitch-container {
- .status-container {
- margin-top: .25rem;
- }
-
- .toggleswitch {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: flex-start;
- .label {
- font-weight: bold;
- color: var(--owncast-purple);
- }
- .info-tip {
- margin-left: .5rem;
- svg {
- fill: white;
- }
- }
- .ant-form-item {
- margin: 0 .75rem 0 0;
- }
- }
-}
diff --git a/web/styles/form-toggleswitch.scss b/web/styles/form-toggleswitch.scss
new file mode 100644
index 000000000..b3abd2080
--- /dev/null
+++ b/web/styles/form-toggleswitch.scss
@@ -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;
+ }
+ }
+}
diff --git a/web/styles/globals.scss b/web/styles/globals.scss
index 6c0317df4..308933e18 100644
--- a/web/styles/globals.scss
+++ b/web/styles/globals.scss
@@ -36,48 +36,13 @@ code {
}
-// 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;
+.logo-svg {
+ height: 2rem;
+ width: 2rem;
}
- // 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;
- }
+p.page-description {
+ margin: 1em 0;
+ color: #ccc;
+ width: 80%;
}
diff --git a/web/styles/main-layout.scss b/web/styles/main-layout.scss
new file mode 100644
index 000000000..8cde3017d
--- /dev/null
+++ b/web/styles/main-layout.scss
@@ -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;
+ }
+ }
+ }
+}
+
diff --git a/web/styles/markdown-editor.scss b/web/styles/markdown-editor.scss
new file mode 100644
index 000000000..74186233c
--- /dev/null
+++ b/web/styles/markdown-editor.scss
@@ -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;
+ }
+}
diff --git a/web/styles/styles.module.scss b/web/styles/styles.module.scss
deleted file mode 100644
index 4d2af6e14..000000000
--- a/web/styles/styles.module.scss
+++ /dev/null
@@ -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%;
-}
diff --git a/web/utils/alert-message-context.tsx b/web/utils/alert-message-context.tsx
index 1d1a13194..3cf81fc71 100644
--- a/web/utils/alert-message-context.tsx
+++ b/web/utils/alert-message-context.tsx
@@ -1,27 +1,27 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState } from 'react';
import PropTypes from 'prop-types';
export const AlertMessageContext = React.createContext({
- message: null,
- setMessage: (text?: string) => {
- return text;
- }
+ message: null,
+ setMessage: (text?: string) => {
+ return text;
+ },
});
const AlertMessageProvider = ({ children }) => {
- const [message, setMessage] = useState('');
-
- const providerValue = {
- message,
- setMessage
- }
- return (
-
{children}
- )
-}
+ const [message, setMessage] = useState('');
-AlertMessageProvider.propTypes = {
- children: PropTypes.element.isRequired
+ const providerValue = {
+ message,
+ setMessage,
+ };
+ return (
+
{children}
+ );
};
-export default AlertMessageProvider;
\ No newline at end of file
+AlertMessageProvider.propTypes = {
+ children: PropTypes.element.isRequired,
+};
+
+export default AlertMessageProvider;