mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 20:03:12 +03:00
Wired image upload for Twitter setting
refs https://github.com/TryGhost/Team/issues/3318 - adds new file service that allows managing image and file uploads - wires file upload and save to twitter setting - fixes issue with dynamic SVG icon loading not working randomly
This commit is contained in:
parent
eaa16c2f75
commit
0f7fc2855e
@ -1,5 +1,4 @@
|
||||
import React, {useCallback, useEffect} from 'react';
|
||||
import {ChangeEvent, useState} from 'react';
|
||||
import React, {ChangeEvent} from 'react';
|
||||
|
||||
export interface FileUploadProps {
|
||||
id: string;
|
||||
@ -14,23 +13,12 @@ export interface FileUploadProps {
|
||||
}
|
||||
|
||||
const FileUpload: React.FC<FileUploadProps> = ({id, onUpload, children, style, ...props}) => {
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||
|
||||
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files && event.target.files.length > 0) {
|
||||
setSelectedFile(event.target.files[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileUpload = useCallback(() => {
|
||||
const selectedFile = event.target.files?.[0];
|
||||
if (selectedFile) {
|
||||
onUpload?.(selectedFile);
|
||||
}
|
||||
}, [onUpload, selectedFile]);
|
||||
|
||||
useEffect(() => {
|
||||
handleFileUpload();
|
||||
}, [handleFileUpload]);
|
||||
};
|
||||
|
||||
return (
|
||||
<label htmlFor={id} style={style} {...props}>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
|
||||
interface UseDynamicSVGImportOptions {
|
||||
onCompleted?: (
|
||||
@ -12,8 +12,8 @@ function useDynamicSVGImport(
|
||||
name: string,
|
||||
options: UseDynamicSVGImportOptions = {}
|
||||
) {
|
||||
const ImportedIconRef = useRef<React.FC<React.SVGProps<SVGSVGElement>>>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [SvgComponent, setSvgComponent] = useState<React.FC<React.SVGProps<SVGSVGElement>> | null | undefined>(null);
|
||||
const [error, setError] = useState<Error>();
|
||||
|
||||
const {onCompleted, onError} = options;
|
||||
@ -21,21 +21,22 @@ function useDynamicSVGImport(
|
||||
setLoading(true);
|
||||
const importIcon = async (): Promise<void> => {
|
||||
try {
|
||||
ImportedIconRef.current = (
|
||||
const SvgIcon: React.FC<React.SVGProps<SVGSVGElement>> = (
|
||||
await import(`../assets/icons/${name}.svg`)
|
||||
).ReactComponent;
|
||||
onCompleted?.(name, ImportedIconRef.current);
|
||||
setSvgComponent(() => SvgIcon);
|
||||
onCompleted?.(name, SvgIcon);
|
||||
} catch (err: any) {
|
||||
onError?.(err);
|
||||
setError(err);
|
||||
setError(() => err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setLoading(() => false);
|
||||
}
|
||||
};
|
||||
importIcon();
|
||||
}, [name, onCompleted, onError]);
|
||||
|
||||
return {error, loading, SvgIcon: ImportedIconRef.current};
|
||||
return {error, loading, SvgComponent};
|
||||
}
|
||||
|
||||
export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | number;
|
||||
@ -62,8 +63,8 @@ interface IconProps {
|
||||
* - all icons must have all it's children color value set `currentColor`
|
||||
* - all strokes must be paths and _NOT_ outlined objects. Stroke width should be set to 1.5px
|
||||
*/
|
||||
const Icon: React.FC<IconProps> = ({name, size = 'md', color = 'black', className}) => {
|
||||
const {SvgIcon} = useDynamicSVGImport(name);
|
||||
const Icon: React.FC<IconProps> = ({name, size = 'md', color = 'black', className = ''}) => {
|
||||
const {SvgComponent} = useDynamicSVGImport(name);
|
||||
|
||||
let styles = '';
|
||||
|
||||
@ -92,9 +93,9 @@ const Icon: React.FC<IconProps> = ({name, size = 'md', color = 'black', classNam
|
||||
styles += ` text-${color}`;
|
||||
}
|
||||
|
||||
if (SvgIcon) {
|
||||
if (SvgComponent) {
|
||||
return (
|
||||
<SvgIcon className={`${styles} ${className}`} />
|
||||
<SvgComponent className={`${styles} ${className}`} />
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
@ -2,8 +2,12 @@ import React from 'react';
|
||||
import setupGhostApi from '../../utils/api';
|
||||
import {createContext} from 'react';
|
||||
|
||||
export interface FileService {
|
||||
uploadImage: (file: File) => Promise<string>;
|
||||
}
|
||||
interface ServicesContextProps {
|
||||
api: ReturnType<typeof setupGhostApi>;
|
||||
fileService: FileService|null
|
||||
}
|
||||
|
||||
interface ServicesProviderProps {
|
||||
@ -12,14 +16,22 @@ interface ServicesProviderProps {
|
||||
}
|
||||
|
||||
const ServicesContext = createContext<ServicesContextProps>({
|
||||
api: setupGhostApi({ghostVersion: ''})
|
||||
api: setupGhostApi({ghostVersion: ''}),
|
||||
fileService: null
|
||||
});
|
||||
|
||||
const ServicesProvider: React.FC<ServicesProviderProps> = ({children, ghostVersion}) => {
|
||||
const apiService = setupGhostApi({ghostVersion});
|
||||
const fileService = {
|
||||
uploadImage: async (file: File): Promise<string> => {
|
||||
const response = await apiService.images.upload({file});
|
||||
return response.images[0].url;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<ServicesContext.Provider value={{
|
||||
api: apiService
|
||||
api: apiService,
|
||||
fileService
|
||||
}}>
|
||||
{children}
|
||||
</ServicesContext.Provider>
|
||||
|
@ -1,9 +1,10 @@
|
||||
import ImageUpload from '../../../admin-x-ds/global/ImageUpload';
|
||||
import React from 'react';
|
||||
import React, {useContext} from 'react';
|
||||
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
||||
import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupContent';
|
||||
import TextField from '../../../admin-x-ds/global/TextField';
|
||||
import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||
import {FileService, ServicesContext} from '../../providers/ServiceProvider';
|
||||
|
||||
const Twitter: React.FC = () => {
|
||||
const {
|
||||
@ -17,7 +18,11 @@ const Twitter: React.FC = () => {
|
||||
handleStateChange
|
||||
} = useSettingGroup();
|
||||
|
||||
const [twitterTitle, twitterDescription, siteTitle, siteDescription] = getSettingValues(['twitter_title', 'twitter_description', 'title', 'description']) as string[];
|
||||
const {fileService} = useContext(ServicesContext) as {fileService: FileService};
|
||||
|
||||
const [
|
||||
twitterTitle, twitterDescription, twitterImage, siteTitle, siteDescription
|
||||
] = getSettingValues(['twitter_title', 'twitter_description', 'twitter_image', 'title', 'description']) as string[];
|
||||
|
||||
const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
updateSetting('twitter_title', e.target.value);
|
||||
@ -27,12 +32,17 @@ const Twitter: React.FC = () => {
|
||||
updateSetting('twitter_description', e.target.value);
|
||||
};
|
||||
|
||||
const handleImageUpload = (file: File) => {
|
||||
alert(file.name);
|
||||
const handleImageUpload = async (file: File) => {
|
||||
try {
|
||||
const imageUrl = await fileService.uploadImage(file);
|
||||
updateSetting('twitter_image', imageUrl);
|
||||
} catch (err: any) {
|
||||
// handle error
|
||||
}
|
||||
};
|
||||
|
||||
const handleImageDelete = () => {
|
||||
alert('Delete twitter image');
|
||||
updateSetting('twitter_image', '');
|
||||
};
|
||||
|
||||
const values = (
|
||||
@ -44,6 +54,7 @@ const Twitter: React.FC = () => {
|
||||
<ImageUpload
|
||||
height='200px'
|
||||
id='twitter-image'
|
||||
imageURL={twitterImage}
|
||||
label='Upload twitter image'
|
||||
onDelete={handleImageDelete}
|
||||
onUpload={handleImageUpload}
|
||||
|
Loading…
Reference in New Issue
Block a user