From 3031f8144e806ac3a78e32702db3167c80a66b93 Mon Sep 17 00:00:00 2001 From: nebunez <76886915+nebunez@users.noreply.github.com> Date: Sun, 21 Mar 2021 19:07:12 -0400 Subject: [PATCH] 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 --- .../config/edit-instance-details.tsx | 16 +-- web/components/config/edit-logo.tsx | 122 ++++++++++++++++++ web/styles/config-public-details.scss | 23 +++- web/utils/config-constants.tsx | 2 +- 4 files changed, 144 insertions(+), 19 deletions(-) create mode 100644 web/components/config/edit-logo.tsx diff --git a/web/components/config/edit-instance-details.tsx b/web/components/config/edit-instance-details.tsx index 4f7311467..70e1dbac1 100644 --- a/web/components/config/edit-instance-details.tsx +++ b/web/components/config/edit-instance-details.tsx @@ -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} /> - - {instanceDetails.logo && uploaded logo} -
+ {/* Logo section */} + + +

Increase your audience by appearing in the{' '} diff --git a/web/components/config/edit-logo.tsx b/web/components/config/edit-logo.tsx new file mode 100644 index 000000000..89ff83812 --- /dev/null +++ b/web/components/config/edit-logo.tsx @@ -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(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((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 ( +

+
+ Logo +
+ +
+
+ avatar + + {loading ? ( + + ) : ( +
+ +

{tip}

+
+
+ ); +} diff --git a/web/styles/config-public-details.scss b/web/styles/config-public-details.scss index c03d9a13b..69835a009 100644 --- a/web/styles/config-public-details.scss +++ b/web/styles/config-public-details.scss @@ -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); + } +} diff --git a/web/utils/config-constants.tsx b/web/utils/config-constants.tsx index ac5b68891..af04be30f 100644 --- a/web/utils/config-constants.tsx +++ b/web/utils/config-constants.tsx @@ -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,