0

- create default values for starter fields

- add starter api urls
- try and add TS types for initial components and objects
- cleanup status indicator on layout header
- create custom textfield for config form editing
This commit is contained in:
gingervitis 2020-12-28 01:11:26 -08:00 committed by Gabe Kangas
parent f63fe9ea7b
commit f0e5bbae1f
9 changed files with 202 additions and 50 deletions

View File

@ -4,4 +4,30 @@ export const DEFAULT_NAME = 'Owncast User';
export const DEFAULT_TITLE = 'Owncast Server'; export const DEFAULT_TITLE = 'Owncast Server';
export const DEFAULT_SUMMARY = ''; export const DEFAULT_SUMMARY = '';
export const TEXT_MAXLENGTH = 255; export const TEXT_MAXLENGTH = 255;
// Creating this so that it'll be easier to change values in one place, rather than looking for places to change it in a sea of JSX.
// key is the input's `fieldName`
//serversummary
export const TEXTFIELD_DEFAULTS = {
name: {
apiPath: '/name',
defaultValue: DEFAULT_NAME,
maxLength: TEXT_MAXLENGTH,
placeholder: DEFAULT_NAME,
configPath: 'instanceDetails',
label: 'Your name',
tip: 'This is your name that shows up on things and stuff.',
},
summary: {
apiPath: '/summary',
defaultValue: DEFAULT_NAME,
maxLength: TEXT_MAXLENGTH,
placeholder: DEFAULT_NAME,
configPath: 'instanceDetails',
label: 'Summary',
tip: 'A brief blurb about what your stream is about.',
}
}

View File

@ -16,49 +16,98 @@ update vals to state, andthru api.
*/ */
import React from 'react'; import React, { useState, useContext } from 'react';
import { Form, Input } from 'antd'; import { Form, Input, Tooltip } from 'antd';
import { FormItemProps } from 'antd/es/form';
import { InfoCircleOutlined } from '@ant-design/icons';
interface TextFieldProps { import { TEXTFIELD_DEFAULTS, TEXT_MAXLENGTH } from './defaults';
onSubmit: (value: string) => void;
label: string;
defaultValue: string;
value: string;
helpInfo: string;
maxLength: number;
type: string;
}
// // do i need this? import { TextFieldProps } from '../../../types/config-section';
// export const initialProps: TextFieldProps = { import { fetchData, SERVER_CONFIG_UPDATE_URL } from '../../../utils/apis';
// } import { ServerStatusContext } from '../../../utils/server-status-context';
export const TEXTFIELD_TYPE_TEXT = 'default'; export const TEXTFIELD_TYPE_TEXT = 'default';
export const TEXTFIELD_TYPE_PASSWORD = 'password'; //Input.Password export const TEXTFIELD_TYPE_PASSWORD = 'password'; // Input.Password
export const TEXTFIELD_TYPE_NUMBER = 'numeric'; export const TEXTFIELD_TYPE_NUMBER = 'numeric';
export default function TextField(props: TextFieldProps) { export default function TextField(props: TextFieldProps) {
const [submitStatus, setSubmitStatus] = useState<FormItemProps['validateStatus']>('');
const [submitStatusMessage, setSubmitStatusMessage] = useState('');
const serverStatusData = useContext(ServerStatusContext);
const { setConfigField } = serverStatusData || {};
const { const {
label, fieldName,
defaultValue,
value,
onSubmit,
helpInfo,
maxLength,
type,
} = props; } = props;
return ( const {
apiPath = '',
defaultValue = '', // if empty
configPath = '',
maxLength = TEXT_MAXLENGTH,
placeholder = '',
label = '',
tip = '',
} = TEXTFIELD_DEFAULTS[fieldName] || {};
const postUpdateToAPI = async (postValue: any) => {
setSubmitStatus('validating');
const result = await fetchData(`${SERVER_CONFIG_UPDATE_URL}${apiPath}`, {
data: { value: postValue },
method: 'POST',
auth: true,
});
if (result.success) {
setConfigField({ fieldName, value: postValue, path: configPath });
setSubmitStatus('success');
} else {
setSubmitStatus('warning');
setSubmitStatusMessage(`There was an error: ${result.message}`);
}
};
const handleEnter = (event: React.KeyboardEvent<HTMLInputElement>) => {
const newValue = event.target.value;
if (newValue !== '') {
postUpdateToAPI(newValue);
}
}
const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
const newValue = event.target.value;
if (newValue !== '') {
console.log("blur post..", newValue)
postUpdateToAPI(newValue);
} else {
// event.target.value = value;
}
}
return (
<div className="textfield"> <div className="textfield">
<Form.Item <Form.Item
label={label} label={label}
name={fieldName}
hasFeedback hasFeedback
validateStatus="error" validateStatus={submitStatus}
help="Should be combination of numbers & alphabets" help={submitStatusMessage}
> >
<Input placeholder="Owncast" value={value} /> <Input
className="field"
allowClear
placeholder={placeholder}
maxLength={maxLength}
onPressEnter={handleEnter}
// onBlur={handleBlur}
/>
</Form.Item> </Form.Item>
<div className="info">
<Tooltip title={tip}>
<InfoCircleOutlined />
</Tooltip>
</div>
</div> </div>
); );
} }

View File

@ -1,31 +1,41 @@
import React, { useContext } from 'react'; import React, { useContext, useEffect } from 'react';
import { Typography, Input, Form } from 'antd'; import { Typography, Form, Input } from 'antd';
import TextField from './form-textfield'; import TextField from './form-textfield';
import { ServerStatusContext } from '../../../utils/server-status-context'; import { ServerStatusContext } from '../../../utils/server-status-context';
import { UpdateArgs } from '../../../types/config-section';
const { Title } = Typography; const { Title } = Typography;
export default function PublicFacingDetails() { export default function PublicFacingDetails() {
const [form] = Form.useForm();
const serverStatusData = useContext(ServerStatusContext); const serverStatusData = useContext(ServerStatusContext);
const { serverConfig, setConfigField } = serverStatusData || {}; const { serverConfig, setConfigField } = serverStatusData || {};
const { instanceDetails = {}, } = serverConfig; const { instanceDetails = {}, } = serverConfig;
const { name, summary, title } = instanceDetails; const { name, summary, title } = instanceDetails;
useEffect(() => {
form.setFieldsValue({...instanceDetails});
}, [instanceDetails]);
return ( return (
<> <>
<Title level={2}>Edit your public facing instance details</Title> <Title level={2}>Edit your public facing instance details</Title>
<div className="config-public-details-container"> <div className="config-public-details-container">
<div className="text-fields"> <div className="text-fields">
<Form name="text-fields" labelCol={{ span: 8 }} wrapperCol={{ span: 16 }}> <Form
<TextField form={form}
label="name" layout="vertical"
value={name} >
/> <TextField fieldName="name" />
<TextField fieldName="summary" />
</Form> </Form>
</div> </div>
<div className="misc-optionals"> <div className="misc-optionals">

View File

@ -41,15 +41,29 @@ export default function MainLayout(props) {
const { Header, Footer, Content, Sider } = Layout; const { Header, Footer, Content, Sider } = Layout;
const { SubMenu } = Menu; const { SubMenu } = Menu;
// status indicator items
const streamDurationString = online ? parseSecondsToDurationString(differenceInSeconds(new Date(), new Date(broadcaster.time))) : ""; const streamDurationString = online ? parseSecondsToDurationString(differenceInSeconds(new Date(), new Date(broadcaster.time))) : "";
const currentThumbnail = online ? (
const content = ( <img src="/thumbnail.jpg" className={adminStyles.onlineCurrentThumb} alt="current thumbnail" />
<div> ) : null;
<img src="/thumbnail.jpg" width="200px" />
</div>
);
const statusIcon = online ? <PlayCircleFilled /> : <MinusSquareFilled />; const statusIcon = online ? <PlayCircleFilled /> : <MinusSquareFilled />;
const statusMessage = online ? `Online ${streamDurationString}` : "Offline"; 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 () => {
@ -176,12 +190,7 @@ export default function MainLayout(props) {
<Layout className={adminStyles.layoutMain}> <Layout className={adminStyles.layoutMain}>
<Header className={adminStyles.header}> <Header className={adminStyles.header}>
<Popover content={content} title="Thumbnail" trigger="hover"> {statusIndicatorWithThumb}
<div className={adminStyles.statusIndicatorContainer}>
<span className={adminStyles.statusLabel}>{statusMessage}</span>
<span className={adminStyles.statusIcon}>{statusIcon}</span>
</div>
</Popover>
</Header> </Header>
<Content className={adminStyles.contentMain}>{children}</Content> <Content className={adminStyles.contentMain}>{children}</Content>

View File

@ -4,4 +4,24 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: flex-start; align-items: flex-start;
.text-fields {
margin-right: 1rem;
}
}
.textfield {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-end;
.field {
width: 20rem;
}
.info {
transform: translateY(.35rem);
margin-left: .5rem;
}
} }

View File

@ -86,3 +86,7 @@
.configSection { .configSection {
margin-bottom: 2em; margin-bottom: 2em;
} }
.onlineCurrentThumb {
width: 12.5rem;
}

View File

@ -0,0 +1,14 @@
// TS types for elements on the Config pages
export interface TextFieldProps {
onUpdate: ({ fieldName, value }: UpdateArgs) => void;
fieldName: string;
value: string;
type: string;
}
export interface UpdateArgs {
fieldName: string;
value: string;
path?: string;
}

View File

@ -19,6 +19,9 @@ export const STREAMKEY_CHANGE = `${API_LOCATION}changekey`;
// Current server config // Current server config
export const SERVER_CONFIG = `${API_LOCATION}serverconfig`; export const SERVER_CONFIG = `${API_LOCATION}serverconfig`;
// Base url to update config settings
export const SERVER_CONFIG_UPDATE_URL = `${API_LOCATION}config`;
// Get viewer count over time // Get viewer count over time
export const VIEWERS_OVER_TIME = `${API_LOCATION}viewersOverTime`; export const VIEWERS_OVER_TIME = `${API_LOCATION}viewersOverTime`;
@ -67,6 +70,12 @@ interface FetchOptions {
}; };
export async function fetchData(url: string, options?: FetchOptions) { export async function fetchData(url: string, options?: FetchOptions) {
// TEMP
export const TEMP_UPDATER_API = LOGS_ALL;
const GITHUB_RELEASE_URL = "https://api.github.com/repos/owncast/owncast/releases/latest";
export async function fetchData(url: string, options?: object) {
const { const {
data, data,
method = 'GET', method = 'GET',
@ -105,6 +114,7 @@ export async function fetchData(url: string, options?: FetchOptions) {
return {}; return {};
} }
export async function getGithubRelease() { export async function getGithubRelease() {
try { try {
const response = await fetch(GITHUB_RELEASE_URL); const response = await fetch(GITHUB_RELEASE_URL);

View File

@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { STATUS, fetchData, FETCH_INTERVAL, SERVER_CONFIG } from './apis'; import { STATUS, fetchData, FETCH_INTERVAL, SERVER_CONFIG } from './apis';
import { UpdateArgs } from '../types/config-section';
export const initialServerConfigState = { export const initialServerConfigState = {
streamKey: '', streamKey: '',
@ -63,11 +64,20 @@ const ServerStatusProvider = ({ children }) => {
} }
}; };
const setConfigField = ({ fieldName, value }) => { const setConfigField = ({ fieldName, value, path }: UpdateArgs) => {
const updatedConfig = { const updatedConfig = path ?
{
...config,
[path]: {
...config[path],
[fieldName]: value,
},
} :
{
...config, ...config,
[fieldName]: value, [fieldName]: value,
}; };
setConfig(updatedConfig); setConfig(updatedConfig);
} }