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_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 { Form, Input } from 'antd';
import React, { useState, useContext } from 'react';
import { Form, Input, Tooltip } from 'antd';
import { FormItemProps } from 'antd/es/form';
import { InfoCircleOutlined } from '@ant-design/icons';
interface TextFieldProps {
onSubmit: (value: string) => void;
label: string;
defaultValue: string;
value: string;
helpInfo: string;
maxLength: number;
type: string;
}
import { TEXTFIELD_DEFAULTS, TEXT_MAXLENGTH } from './defaults';
// // do i need this?
// export const initialProps: TextFieldProps = {
// }
import { TextFieldProps } from '../../../types/config-section';
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_PASSWORD = 'password'; //Input.Password
export const TEXTFIELD_TYPE_PASSWORD = 'password'; // Input.Password
export const TEXTFIELD_TYPE_NUMBER = 'numeric';
export default function TextField(props: TextFieldProps) {
const [submitStatus, setSubmitStatus] = useState<FormItemProps['validateStatus']>('');
const [submitStatusMessage, setSubmitStatusMessage] = useState('');
const serverStatusData = useContext(ServerStatusContext);
const { setConfigField } = serverStatusData || {};
const {
label,
defaultValue,
value,
onSubmit,
helpInfo,
maxLength,
type,
fieldName,
} = 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">
<Form.Item
label={label}
name={fieldName}
hasFeedback
validateStatus="error"
help="Should be combination of numbers & alphabets"
validateStatus={submitStatus}
help={submitStatusMessage}
>
<Input placeholder="Owncast" value={value} />
<Input
className="field"
allowClear
placeholder={placeholder}
maxLength={maxLength}
onPressEnter={handleEnter}
// onBlur={handleBlur}
/>
</Form.Item>
<div className="info">
<Tooltip title={tip}>
<InfoCircleOutlined />
</Tooltip>
</div>
</div>
);
}

View File

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

View File

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

View File

@ -4,4 +4,24 @@
display: flex;
flex-direction: row;
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 {
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
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
export const VIEWERS_OVER_TIME = `${API_LOCATION}viewersOverTime`;
@ -67,6 +70,12 @@ interface 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 {
data,
method = 'GET',
@ -105,6 +114,7 @@ export async function fetchData(url: string, options?: FetchOptions) {
return {};
}
export async function getGithubRelease() {
try {
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 { STATUS, fetchData, FETCH_INTERVAL, SERVER_CONFIG } from './apis';
import { UpdateArgs } from '../types/config-section';
export const initialServerConfigState = {
streamKey: '',
@ -63,11 +64,20 @@ const ServerStatusProvider = ({ children }) => {
}
};
const setConfigField = ({ fieldName, value }) => {
const updatedConfig = {
const setConfigField = ({ fieldName, value, path }: UpdateArgs) => {
const updatedConfig = path ?
{
...config,
[path]: {
...config[path],
[fieldName]: value,
},
} :
{
...config,
[fieldName]: value,
};
setConfig(updatedConfig);
}