mirror of
				https://github.com/owncast/owncast.git
				synced 2025-10-31 18:18:06 +08:00 
			
		
		
		
	 383b80851b
			
		
	
	383b80851b
	
	
	
		
			
			* Initial plan * Add localization support to admin form status and error messages Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Format updated files with prettier Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * Replace t() with Translation component in admin page JSX Co-authored-by: gabek <414923+gabek@users.noreply.github.com> * update package-lock.json * Update web/i18n/en/translation.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: gabek <414923+gabek@users.noreply.github.com> Co-authored-by: Gabe Kangas <gabek@real-ity.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
		
			
				
	
	
		
			166 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			166 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { Button, Upload } from 'antd';
 | |
| import { RcFile } from 'antd/lib/upload/interface';
 | |
| import React, { useState, useContext, FC } from 'react';
 | |
| import dynamic from 'next/dynamic';
 | |
| import { useTranslation } from 'next-export-i18n';
 | |
| import { FormStatusIndicator } from './FormStatusIndicator';
 | |
| 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';
 | |
| import { NEXT_PUBLIC_API_HOST } from '../../utils/apis';
 | |
| import { Localization } from '../../types/localization';
 | |
| 
 | |
| import {
 | |
|   ACCEPTED_IMAGE_TYPES,
 | |
|   getBase64,
 | |
|   MAX_IMAGE_FILESIZE,
 | |
|   readableBytes,
 | |
| } from '../../utils/images';
 | |
| 
 | |
| // Lazy loaded components
 | |
| 
 | |
| const LoadingOutlined = dynamic(() => import('@ant-design/icons/LoadingOutlined'), {
 | |
|   ssr: false,
 | |
| });
 | |
| 
 | |
| const UploadOutlined = dynamic(() => import('@ant-design/icons/UploadOutlined'), {
 | |
|   ssr: false,
 | |
| });
 | |
| 
 | |
| // eslint-disable-next-line import/prefer-default-export
 | |
| export const EditLogo: FC = () => {
 | |
|   const { t } = useTranslation();
 | |
|   const [logoUrl, setlogoUrl] = useState(null);
 | |
|   const [loading, setLoading] = useState(false);
 | |
|   const [logoCachedbuster, setLogoCacheBuster] = useState(0);
 | |
| 
 | |
|   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 (file.size > MAX_IMAGE_FILESIZE) {
 | |
|         const msg = t(Localization.Admin.StatusMessages.fileSizeTooBig, {
 | |
|           size: readableBytes(file.size),
 | |
|         });
 | |
|         setSubmitStatus(
 | |
|           createInputStatus(
 | |
|             STATUS_ERROR,
 | |
|             t(Localization.Admin.StatusMessages.thereWasAnError, { message: msg }),
 | |
|           ),
 | |
|         );
 | |
|         resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
 | |
|         setLoading(false);
 | |
|         // eslint-disable-next-line no-promise-executor-return
 | |
|         return rej();
 | |
|       }
 | |
|       if (!ACCEPTED_IMAGE_TYPES.includes(file.type)) {
 | |
|         const msg = t(Localization.Admin.StatusMessages.fileTypeNotSupported, { type: file.type });
 | |
|         setSubmitStatus(
 | |
|           createInputStatus(
 | |
|             STATUS_ERROR,
 | |
|             t(Localization.Admin.StatusMessages.thereWasAnError, { message: msg }),
 | |
|           ),
 | |
|         );
 | |
|         resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
 | |
|         setLoading(false);
 | |
|         // eslint-disable-next-line no-promise-executor-return
 | |
|         return rej();
 | |
|       }
 | |
| 
 | |
|       getBase64(file, (url: string) => {
 | |
|         setlogoUrl(url);
 | |
|         setTimeout(() => res(), 100);
 | |
|       });
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   // 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);
 | |
|           setLogoCacheBuster(Math.floor(Math.random() * 100)); // Force logo to re-load
 | |
|         },
 | |
|         onError: (msg: string) => {
 | |
|           setSubmitStatus(
 | |
|             createInputStatus(
 | |
|               STATUS_ERROR,
 | |
|               t(Localization.Admin.StatusMessages.thereWasAnError, { message: msg }),
 | |
|             ),
 | |
|           );
 | |
|           setLoading(false);
 | |
|         },
 | |
|       });
 | |
|       resetTimer = setTimeout(resetStates, RESET_TIMEOUT);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   const logoDisplayUrl = `${NEXT_PUBLIC_API_HOST}logo?random=${logoCachedbuster}`;
 | |
| 
 | |
|   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={logoDisplayUrl} alt="avatar" className="logo-preview" />
 | |
|           <Upload
 | |
|             name="logo"
 | |
|             listType="picture"
 | |
|             className="avatar-uploader"
 | |
|             showUploadList={false}
 | |
|             accept={ACCEPTED_IMAGE_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>
 | |
|   );
 | |
| };
 |