Upload logo (#54)

* add upload component for logo

* - move upload logo functionlity to its own component
- style upload logo component
- display current logo from server
- implement submit button on logo updater, to submit new logo to api after update
- add some submit status indicator

* update edit-logo component

Logo now posts correctly to owncast api endpoint. This update includes file type
validation and removes the submit button, since the ant.d Upload component
already handles the post logic.

* remove submit-button style for logo upload

Co-authored-by: gingervitis <omqmail@gmail.com>
This commit is contained in:
nebunez 2021-03-21 19:07:12 -04:00 committed by GitHub
parent ab0cf2ad9d
commit 3031f8144e
4 changed files with 144 additions and 19 deletions

View File

@ -13,15 +13,14 @@ import {
TEXTFIELD_PROPS_SERVER_NAME,
TEXTFIELD_PROPS_SERVER_SUMMARY,
TEXTFIELD_PROPS_SERVER_WELCOME_MESSAGE,
TEXTFIELD_PROPS_LOGO,
API_YP_SWITCH,
FIELD_PROPS_YP,
FIELD_PROPS_NSFW,
} from '../../utils/config-constants';
import { NEXT_PUBLIC_API_HOST } from '../../utils/apis';
import { UpdateArgs } from '../../types/config-section';
import ToggleSwitch from './form-toggleswitch';
import EditLogo from './edit-logo';
const { Title } = Typography;
@ -106,16 +105,11 @@ export default function EditInstanceDetails() {
initialValue={instanceDetails.welcomeMessage}
onChange={handleFieldChange}
/>
<TextFieldWithSubmit
fieldName="logo"
{...TEXTFIELD_PROPS_LOGO}
value={formDataValues.logo}
initialValue={instanceDetails.logo}
onChange={handleFieldChange}
/>
{instanceDetails.logo && <img src="/logo" alt="uploaded logo" className="logo-preview" />}
<br />
{/* Logo section */}
<EditLogo />
<br />
<p className="description">
Increase your audience by appearing in the{' '}
<a href="https://directory.owncast.online" target="_blank" rel="noreferrer">

View File

@ -0,0 +1,122 @@
import { Button, Upload } from 'antd';
import { RcFile } from 'antd/lib/upload/interface';
import { LoadingOutlined, UploadOutlined } from '@ant-design/icons';
import React, { useState, useContext } from 'react';
import FormStatusIndicator from './form-status-indicator';
import { ServerStatusContext } from '../../utils/server-status-context';
import {
postConfigUpdateToAPI,
RESET_TIMEOUT,
TEXTFIELD_PROPS_LOGO,
} from '../../utils/config-constants';
import {
createInputStatus,
StatusState,
STATUS_ERROR,
STATUS_PROCESSING,
STATUS_SUCCESS,
} from '../../utils/input-statuses';
const ACCEPTED_FILE_TYPES = ['image/svg+xml', 'image/png', 'image/jpeg', 'image/gif'];
function getBase64(img: File | Blob, callback: (imageUrl: string | ArrayBuffer) => void) {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img);
}
export default function EditLogo() {
const [logoUrl, setlogoUrl] = useState(null);
const [loading, setLoading] = useState(false);
const serverStatusData = useContext(ServerStatusContext);
const { setFieldInConfigState, serverConfig } = serverStatusData || {};
const currentLogo = serverConfig?.instanceDetails?.logo;
const [submitStatus, setSubmitStatus] = useState<StatusState>(null);
let resetTimer = null;
const { apiPath, tip } = TEXTFIELD_PROPS_LOGO;
// Clear out any validation states and messaging
const resetStates = () => {
setSubmitStatus(null);
clearTimeout(resetTimer);
resetTimer = null;
};
// validate file type and create base64 encoded img
const beforeUpload = (file: RcFile) => {
setLoading(true);
// eslint-disable-next-line consistent-return
return new Promise<void>((res, rej) => {
if (!ACCEPTED_FILE_TYPES.includes(file.type)) {
const msg = `File type is not supported: ${file.type}`;
setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${msg}`));
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
setLoading(false);
return rej();
}
getBase64(file, (url: string) => {
setlogoUrl(url);
return res();
});
});
};
// Post new logo to api
const handleLogoUpdate = async () => {
if (logoUrl !== currentLogo) {
setSubmitStatus(createInputStatus(STATUS_PROCESSING));
await postConfigUpdateToAPI({
apiPath,
data: { value: logoUrl },
onSuccess: () => {
setFieldInConfigState({ fieldName: 'logo', value: logoUrl, path: '' });
setSubmitStatus(createInputStatus(STATUS_SUCCESS));
setLoading(false);
},
onError: (msg: string) => {
setSubmitStatus(createInputStatus(STATUS_ERROR, `There was an error: ${msg}`));
setLoading(false);
},
});
resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
}
};
return (
<div className="formfield-container logo-upload-container">
<div className="label-side">
<span className="formfield-label">Logo</span>
</div>
<div className="input-side">
<div className="input-group">
<img src={logoUrl || currentLogo || '/logo'} alt="avatar" className="logo-preview" />
<Upload
name="logo"
listType="picture"
className="avatar-uploader"
showUploadList={false}
accept={ACCEPTED_FILE_TYPES.join(',')}
beforeUpload={beforeUpload}
customRequest={handleLogoUpdate}
disabled={loading}
>
{loading ? (
<LoadingOutlined style={{ color: 'white' }} />
) : (
<Button icon={<UploadOutlined />} />
)}
</Upload>
</div>
<FormStatusIndicator status={submitStatus} />
<p className="field-tip">{tip}</p>
</div>
</div>
);
}

View File

@ -38,13 +38,6 @@
}
.instance-details-container {
width: 100%;
.logo-preview {
display: inline-block;
margin: -1em 0 1em 11em;
height: 120px;
border: 1px solid var(--white-25);
}
}
.social-items-container {
background-color: var(--container-bg-color-alt);
@ -74,3 +67,19 @@
.other-field-container {
margin: 0.5em 0;
}
.logo-upload-container {
.input-group {
align-items: center;
}
img.logo-preview {
min-height: 120px;
min-width: 120px;
max-height: 256px;
max-width: 256px;
margin-right: 1em;
display: inline-block;
border: 1px solid var(--white-25);
}
}

View File

@ -79,7 +79,7 @@ export const TEXTFIELD_PROPS_LOGO = {
placeholder: '/img/mylogo.png',
label: 'Logo',
tip:
'Name of your logo in the data directory. We recommend that you use a square image that is at least 256x256.',
'Upload your logo if you have one. We recommend that you use a square image that is at least 256x256.',
};
export const TEXTFIELD_PROPS_STREAM_KEY = {
apiPath: API_STREAM_KEY,