- 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:
parent
f63fe9ea7b
commit
f0e5bbae1f
@ -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.',
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -86,3 +86,7 @@
|
||||
.configSection {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.onlineCurrentThumb {
|
||||
width: 12.5rem;
|
||||
}
|
||||
|
14
web/types/config-section.ts
Normal file
14
web/types/config-section.ts
Normal 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;
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user