apply config form flow to edit content page

This commit is contained in:
gingervitis
2021-01-03 23:32:47 -08:00
committed by Gabe Kangas
parent 2772a8e5ec
commit e7e89556e7
9 changed files with 139 additions and 61 deletions

View File

@@ -60,7 +60,7 @@ export default function TextField(props: TextFieldProps) {
setHasChanged(false); setHasChanged(false);
clearTimeout(resetTimer); clearTimeout(resetTimer);
resetTimer = null; resetTimer = null;
} };
// if field is required but value is empty, or equals initial value, then don't show submit/update button. otherwise clear out any result messaging and display button. // if field is required but value is empty, or equals initial value, then don't show submit/update button. otherwise clear out any result messaging and display button.
const handleChange = (e: any) => { const handleChange = (e: any) => {

View File

@@ -150,6 +150,9 @@ export default function MainLayout(props) {
<Menu.Item key="config-storage"> <Menu.Item key="config-storage">
<Link href="/config-storage">Storage</Link> <Link href="/config-storage">Storage</Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="config-page-content">
<Link href="/config-page-content">Custom page content</Link>
</Menu.Item>
</SubMenu> </SubMenu>
<SubMenu <SubMenu

View File

@@ -0,0 +1,105 @@
import React, { useState, useEffect, useContext } from 'react';
import { Typography, Button } from "antd";
import { FormItemProps } from 'antd/lib/form';
import dynamic from 'next/dynamic';
import MarkdownIt from 'markdown-it';
import { ServerStatusContext } from '../utils/server-status-context';
import { TEXTFIELD_DEFAULTS, postConfigUpdateToAPI, RESET_TIMEOUT, SUCCESS_STATES} from './components/config/constants';
import 'react-markdown-editor-lite/lib/index.css';
const { Title } = Typography;
const mdParser = new MarkdownIt(/* Markdown-it options */);
const MdEditor = dynamic(() => import('react-markdown-editor-lite'), {
ssr: false,
});
export default function PageContentEditor() {
const [content, setContent] = useState('');
const [submitStatus, setSubmitStatus] = useState<FormItemProps['validateStatus']>('');
const [submitStatusMessage, setSubmitStatusMessage] = useState('');
const [hasChanged, setHasChanged] = useState(false);
const serverStatusData = useContext(ServerStatusContext);
const { serverConfig, setFieldInConfigState } = serverStatusData || {};
const { instanceDetails } = serverConfig;
const { extraPageContent: initialContent } = instanceDetails;
const { apiPath } = TEXTFIELD_DEFAULTS.instanceDetails.extraPageContent;
let resetTimer = null;
function handleEditorChange({ text }) {
setContent(text);
if (text !== initialContent && !hasChanged) {
setHasChanged(true);
} else if (text === initialContent && hasChanged) {
setHasChanged(false);
}
}
// Clear out any validation states and messaging
const resetStates = () => {
setSubmitStatus('');
setHasChanged(false);
clearTimeout(resetTimer);
resetTimer = null;
};
// posts all the tags at once as an array obj
async function handleSave() {
setSubmitStatus('validating');
await postConfigUpdateToAPI({
apiPath,
data: { value: content },
onSuccess: () => {
setFieldInConfigState({ fieldName: 'extraPageContent', value: content, path: apiPath });
setSubmitStatus('success');
},
onError: (message: string) => {
setSubmitStatus('error');
setSubmitStatusMessage(`There was an error: ${message}`);
},
});
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
}
useEffect(() => {
setContent(initialContent);
}, [instanceDetails]);
const {
icon: newStatusIcon = null,
message: newStatusMessage = '',
} = SUCCESS_STATES[submitStatus] || {};
return (
<div className="config-page-content-form">
<Title level={2}>Edit custom content</Title>
<p>Add some content about your site with the Markdown editor below. This content shows up at the bottom half of your Owncast page.</p>
<MdEditor
style={{ height: "30em" }}
value={content}
renderHTML={(c: string) => mdParser.render(c)}
onChange={handleEditorChange}
config={{
htmlClass: 'markdown-editor-preview-pane',
markdownClass: 'markdown-editor-pane',
}}
/>
<div className="page-content-actions">
{ hasChanged ? <Button type="primary" size="small" onClick={handleSave}>Save</Button> : null }
<div className={`status-message ${submitStatus || ''}`}>
{newStatusIcon} {newStatusMessage} {submitStatusMessage}
</div>
</div>
</div>
);
}

View File

@@ -39,7 +39,7 @@ export default function ConfigServerDetails() {
configPath: '', configPath: '',
}; };
return ( return (
<> <div className="config-server-details-form">
<Title level={2}>Edit your Server&apos;s details</Title> <Title level={2}>Edit your Server&apos;s details</Title>
<div className="config-public-details-container"> <div className="config-public-details-container">
@@ -53,7 +53,7 @@ export default function ConfigServerDetails() {
<TextField fieldName="rtmpServerPort" type={TEXTFIELD_TYPE_NUMBER} {...extraProps} /> <TextField fieldName="rtmpServerPort" type={TEXTFIELD_TYPE_NUMBER} {...extraProps} />
</Form> </Form>
</div> </div>
</> </div>
); );
} }

View File

@@ -1,51 +0,0 @@
import React, { useEffect } from 'react';
import MarkdownIt from 'markdown-it';
const mdParser = new MarkdownIt(/* Markdown-it options */);
import dynamic from 'next/dynamic';
import 'react-markdown-editor-lite/lib/index.css';
import { SERVER_CONFIG, fetchData, FETCH_INTERVAL, UPDATE_CHAT_MESSGAE_VIZ } from "../utils/apis";
import { Table, Typography, Tooltip, Button } from "antd";
const MdEditor = dynamic(() => import('react-markdown-editor-lite'), {
ssr: false
});
export default function PageContentEditor() {
const [content, setContent] = React.useState("");
function handleEditorChange({ html, text }) {
setContent(text);
}
function handleSave() {
console.log(content);
alert("Make API call to save here." + content)
}
async function setInitialContent() {
const serverConfig = await fetchData(SERVER_CONFIG);
const initialContent = serverConfig.instanceDetails.extraPageContent;
setContent(initialContent);
}
useEffect(() => {
setInitialContent();
}, []);
return (
<div>
<MdEditor
style={{ height: "500px" }}
value={content}
renderHTML={(content) => mdParser.render(content)}
onChange={handleEditorChange}
config={{ htmlClass: 'markdown-editor-preview-pane', markdownClass: 'markdown-editor-pane' }}
/>
<Button onClick={handleSave}>Save</Button>
</div>
);
}

View File

@@ -143,3 +143,16 @@
} }
} }
.config-page-content-form {
.page-content-actions {
margin-top: 1em;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
.status-message {
margin-left: 1em;
}
}
}

View File

@@ -80,16 +80,16 @@ code {
.rc-md-editor { .rc-md-editor {
// Set the background color of the preview container // Set the background color of the preview container
.editor-container { .editor-container {
background-color: black; background-color: #E2E8F0;
color: rgba(45,55,72,1);
} }
// Custom CSS for formatting the preview text // Custom CSS for formatting the preview text
.markdown-editor-preview-pane { .markdown-editor-preview-pane {
color:lightgrey; // color:lightgrey;
a { a {
color: $owncast-purple; color: $owncast-purple;
} }
h1 { h1 {
font-size: 2em; font-size: 2em;
} }
@@ -97,12 +97,15 @@ code {
// Custom CSS class used to format the text of the editor // Custom CSS class used to format the text of the editor
.markdown-editor-pane { .markdown-editor-pane {
color:lightgrey !important; color: white !important;
background-color: black;
font-family: monospace;
} }
// Set the background color of the editor text input // Set the background color of the editor text input
textarea { textarea {
background-color: rgb(44,44,44) !important; background-color: rgb(44,44,44) !important;
color:lightgrey !important;
} }
// Hide extra toolbar buttons. // Hide extra toolbar buttons.

View File

@@ -9,6 +9,8 @@ export interface TextFieldProps {
required?: boolean; required?: boolean;
disabled?: boolean; disabled?: boolean;
onSubmit?: () => void; onSubmit?: () => void;
onBlur?: () => void;
onChange?: () => void;
} }
export interface ToggleSwitchProps { export interface ToggleSwitchProps {
@@ -49,7 +51,7 @@ export interface ConfigInstanceDetailsFields {
export interface VideoVariant { export interface VideoVariant {
audioBitrate: number; audioBitrate: number;
audioPassthrough: number; audioPassthrough: false | number;
encoderPreset: 'ultrafast' | 'superfast' | 'veryfast' | 'faster' | 'fast'; encoderPreset: 'ultrafast' | 'superfast' | 'veryfast' | 'faster' | 'fast';
framerate: number; framerate: number;
videoBitrate: number; videoBitrate: number;

View File

@@ -25,6 +25,8 @@ export const initialServerConfigState: ConfigDetails = {
instanceUrl: '', instanceUrl: '',
}, },
videoSettings: { videoSettings: {
numberOfPlaylistItems: 5,
segmentLengthSeconds: 4,
videoQualityVariants: [ videoQualityVariants: [
{ {
audioPassthrough: false, audioPassthrough: false,
@@ -32,6 +34,7 @@ export const initialServerConfigState: ConfigDetails = {
videoBitrate: 0, videoBitrate: 0,
audioBitrate: 0, audioBitrate: 0,
framerate: 0, framerate: 0,
encoderPreset: 'veryfast',
}, },
], ],
} }
@@ -93,7 +96,7 @@ const ServerStatusProvider = ({ children }) => {
}; };
setConfig(updatedConfig); setConfig(updatedConfig);
} };
useEffect(() => { useEffect(() => {
@@ -108,7 +111,7 @@ const ServerStatusProvider = ({ children }) => {
return () => { return () => {
clearInterval(getStatusIntervalId); clearInterval(getStatusIntervalId);
} }
}, []) }, []);
const providerValue = { const providerValue = {
...status, ...status,