apply config form flow to edit content page
This commit is contained in:
@@ -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) => {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
105
web/pages/config-page-content.tsx
Normal file
105
web/pages/config-page-content.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ export default function ConfigServerDetails() {
|
|||||||
configPath: '',
|
configPath: '',
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="config-server-details-form">
|
||||||
<Title level={2}>Edit your Server's details</Title>
|
<Title level={2}>Edit your Server'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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user