From f0e5bbae1fe069460e368bf9520e566de6c66092 Mon Sep 17 00:00:00 2001 From: gingervitis Date: Mon, 28 Dec 2020 01:11:26 -0800 Subject: [PATCH] - 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 --- web/pages/components/config/defaults.ts | 28 ++++- .../components/config/form-textfield.tsx | 103 +++++++++++++----- .../config/public-facing-details.tsx | 26 +++-- web/pages/components/main-layout.tsx | 33 ++++-- web/styles/config.scss | 20 ++++ web/styles/styles.module.scss | 4 + web/types/config-section.ts | 14 +++ web/utils/apis.ts | 10 ++ web/utils/server-status-context.tsx | 14 ++- 9 files changed, 202 insertions(+), 50 deletions(-) create mode 100644 web/types/config-section.ts diff --git a/web/pages/components/config/defaults.ts b/web/pages/components/config/defaults.ts index 7e8cd5a68..47fe6c8ec 100644 --- a/web/pages/components/config/defaults.ts +++ b/web/pages/components/config/defaults.ts @@ -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; \ No newline at end of file +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.', + } +} \ No newline at end of file diff --git a/web/pages/components/config/form-textfield.tsx b/web/pages/components/config/form-textfield.tsx index ec8b696d3..ddf1737a7 100644 --- a/web/pages/components/config/form-textfield.tsx +++ b/web/pages/components/config/form-textfield.tsx @@ -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(''); + 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) => { + const newValue = event.target.value; + if (newValue !== '') { + postUpdateToAPI(newValue); + } + } + const handleBlur = (event: React.FocusEvent) => { + const newValue = event.target.value; + if (newValue !== '') { + console.log("blur post..", newValue) + postUpdateToAPI(newValue); + } else { + // event.target.value = value; + } + } + + return (
- + +
+ + + +
); } diff --git a/web/pages/components/config/public-facing-details.tsx b/web/pages/components/config/public-facing-details.tsx index 2b785a9ed..9e572dcdf 100644 --- a/web/pages/components/config/public-facing-details.tsx +++ b/web/pages/components/config/public-facing-details.tsx @@ -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 ( <> Edit your public facing instance details +
-
- + + +
diff --git a/web/pages/components/main-layout.tsx b/web/pages/components/main-layout.tsx index ef34c4dc5..d32bd6ccf 100644 --- a/web/pages/components/main-layout.tsx +++ b/web/pages/components/main-layout.tsx @@ -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 = ( -
- -
- ); + const currentThumbnail = online ? ( + current thumbnail + ) : 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 () => { @@ -176,12 +190,7 @@ export default function MainLayout(props) {
- -
- {statusMessage} - {statusIcon} -
-
+ {statusIndicatorWithThumb}
{children} diff --git a/web/styles/config.scss b/web/styles/config.scss index f2f0e0f0f..b9eb812f9 100644 --- a/web/styles/config.scss +++ b/web/styles/config.scss @@ -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; + } } diff --git a/web/styles/styles.module.scss b/web/styles/styles.module.scss index 9b2acec4b..6a80cdec5 100644 --- a/web/styles/styles.module.scss +++ b/web/styles/styles.module.scss @@ -86,3 +86,7 @@ .configSection { margin-bottom: 2em; } + +.onlineCurrentThumb { + width: 12.5rem; +} diff --git a/web/types/config-section.ts b/web/types/config-section.ts new file mode 100644 index 000000000..9927cc66c --- /dev/null +++ b/web/types/config-section.ts @@ -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; +} diff --git a/web/utils/apis.ts b/web/utils/apis.ts index 8b2b56783..c6bd34593 100644 --- a/web/utils/apis.ts +++ b/web/utils/apis.ts @@ -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); diff --git a/web/utils/server-status-context.tsx b/web/utils/server-status-context.tsx index 5465e136c..33bca4ff9 100644 --- a/web/utils/server-status-context.tsx +++ b/web/utils/server-status-context.tsx @@ -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); }