Last active
August 15, 2024 22:19
-
-
Save proteye/b9fc465caeea474fef3873b7297d58ad to your computer and use it in GitHub Desktop.
Image uploading in KeystoneJS document editor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import { NotEditable, component, fields, FormField } from '@keystone-6/fields-document/component-blocks' | |
import { TAny } from '../types' | |
import { TImageFieldData, TImageFieldOptions, TImageFieldValue } from './types' | |
import { ImageUploader } from './components/ImageUploader' | |
const customFields = { | |
image({ listKey }: TImageFieldOptions): FormField<TImageFieldValue, TImageFieldOptions> { | |
return { | |
kind: 'form', | |
Input({ value, onChange }) { | |
return <ImageUploader listKey={listKey} defaultValue={value} mode="edit" onChange={onChange} /> | |
}, | |
options: { listKey }, | |
defaultValue: null, | |
validate(value) { | |
return typeof value === 'object' | |
}, | |
} | |
}, | |
} | |
export const componentBlocks: TAny = { | |
image: component({ | |
preview: ({ fields }) => ( | |
<NotEditable> | |
<ImageUploader | |
listKey={fields.image.options.listKey} | |
defaultValue={fields.imageRel.value?.data as TImageFieldData} | |
imageAlt={fields.imageAlt.value} | |
onChange={fields.image.onChange} | |
onImageAltChange={fields.imageAlt.onChange} | |
onRelationChange={fields.imageRel.onChange} | |
/> | |
</NotEditable> | |
), | |
label: 'Image', | |
schema: { | |
imageAlt: fields.text({ | |
label: 'Image Alt', | |
defaultValue: '', | |
}), | |
image: customFields.image({ | |
listKey: 'Image', | |
}), | |
imageRel: fields.relationship({ | |
listKey: 'Image', | |
label: 'Image Relation', | |
selection: 'id, image { url }', | |
}), | |
}, | |
chromeless: true, | |
}), | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** @jsxRuntime classic */ | |
/** @jsx jsx */ | |
import { jsx } from '@keystone-ui/core' | |
import { FC } from 'react' | |
import styles from './styles' | |
import { IImageUploaderProps } from './types' | |
import useBase from './useBase' | |
export const ImageUploader: FC<IImageUploaderProps> = (props) => { | |
const { altText, imageSrc, loading, isShowLabel, isShowImage, handleAltTextChange, handleUploadChange } = | |
useBase(props) | |
const { mode } = props | |
return ( | |
<div css={styles.container(mode)}> | |
<label id="file" css={styles.imageUploader(isShowImage)}> | |
{isShowLabel && <span>🖱 Click to select a file...</span>} | |
{loading && <span>Loading...</span>} | |
<input | |
autoComplete="off" | |
type="file" | |
accept={'image/*'} | |
style={{ display: 'none' }} | |
onChange={handleUploadChange} | |
/> | |
<img | |
src={imageSrc} | |
alt={altText} | |
css={styles.imagePreview} | |
style={{ display: isShowImage ? 'block' : 'none' }} | |
/> | |
</label> | |
{mode === 'preview' && ( | |
<div css={styles.inputWrapper}> | |
<label>Image Alt:</label> | |
<input type="text" placeholder="" css={styles.textInput} value={altText} onChange={handleAltTextChange} /> | |
</div> | |
)} | |
</div> | |
) | |
} | |
ImageUploader.defaultProps = { | |
defaultValue: null, | |
imageAlt: '', | |
mode: 'preview', | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** @jsxRuntime classic */ | |
/** @jsx jsx */ | |
import { jsx } from '@keystone-ui/core' | |
import { FC } from 'react' | |
import styles from './styles' | |
import { IImageUploaderProps } from './types' | |
import useBase from './useBase' | |
export const ImageUploader: FC<IImageUploaderProps> = (props) => { | |
const { altText, imageSrc, loading, isShowLabel, isShowImage, handleAltTextChange, handleUploadChange } = | |
useBase(props) | |
const { mode } = props | |
return ( | |
<div css={styles.container(mode)}> | |
<label id="file" css={styles.imageUploader(isShowImage)}> | |
{isShowLabel && <span>🖱 Click to select a file...</span>} | |
{loading && <span>Loading...</span>} | |
<input | |
autoComplete="off" | |
type="file" | |
accept={'image/*'} | |
style={{ display: 'none' }} | |
onChange={handleUploadChange} | |
/> | |
<img | |
src={imageSrc} | |
alt={altText} | |
css={styles.imagePreview} | |
style={{ display: isShowImage ? 'block' : 'none' }} | |
/> | |
</label> | |
{mode === 'preview' && ( | |
<div css={styles.inputWrapper}> | |
<label>Image Alt:</label> | |
<input type="text" placeholder="" css={styles.textInput} value={altText} onChange={handleAltTextChange} /> | |
</div> | |
)} | |
</div> | |
) | |
} | |
ImageUploader.defaultProps = { | |
defaultValue: null, | |
imageAlt: '', | |
mode: 'preview', | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { HydratedRelationshipData } from '@keystone-6/fields-document/dist/declarations/src/DocumentEditor/component-blocks/api' | |
import { TImageFieldValue } from '../../types' | |
export interface IImageUploaderProps { | |
listKey: string | |
defaultValue?: TImageFieldValue | |
imageAlt?: string | |
mode?: 'edit' | 'preview' | |
onChange?(value: TImageFieldValue): void | |
onImageAltChange?(value: string): void | |
onRelationChange?(value: HydratedRelationshipData): void | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ChangeEvent, useCallback, useState } from 'react' | |
import { useMutation, gql } from '@keystone-6/core/admin-ui/apollo' | |
import { useList } from '@keystone-6/core/admin-ui/context' | |
import { useToasts } from '@keystone-ui/toast' | |
import { IImageUploaderProps } from './types' | |
const useBase = ({ | |
listKey, | |
defaultValue, | |
imageAlt, | |
onChange, | |
onImageAltChange, | |
onRelationChange, | |
}: IImageUploaderProps) => { | |
const [altText, setAltText] = useState(imageAlt ?? '') | |
const [imageSrc, setImageSrc] = useState(defaultValue?.image?.url ?? '') | |
const list = useList(listKey) | |
const toasts = useToasts() | |
const UPLOAD_IMAGE = gql` | |
mutation ${list.gqlNames.createMutationName}($file: Upload!) { | |
${list.gqlNames.createMutationName}(data: { image: { upload: $file } }) { | |
id, name, type, image { id, extension, filesize, height, width, url } | |
} | |
} | |
` | |
const [uploadImage, { loading }] = useMutation(UPLOAD_IMAGE) | |
const uploadFile = useCallback( | |
async (file: File) => { | |
try { | |
return await uploadImage({ | |
variables: { file }, | |
}) | |
} catch (err: any) { | |
toasts.addToast({ | |
title: `Failed to upload file: ${file.name}`, | |
tone: 'negative', | |
message: err.message, | |
}) | |
} | |
return null | |
}, | |
[toasts, uploadImage], | |
) | |
const handleAltTextChange = useCallback( | |
async (e: ChangeEvent<HTMLInputElement>) => { | |
const { value } = e.currentTarget | |
setAltText(value) | |
onImageAltChange?.(value) | |
}, | |
[onImageAltChange], | |
) | |
const handleUploadChange = useCallback( | |
async (e: ChangeEvent<HTMLInputElement>) => { | |
const selectedFile = e.currentTarget.files?.[0] | |
const src = selectedFile ? URL.createObjectURL(selectedFile) : '' | |
setImageSrc(src) | |
if (selectedFile) { | |
const result = await uploadFile(selectedFile) | |
const uploadedImage = result?.data?.createImage | |
onChange?.({ id: uploadedImage.id }) | |
if (onRelationChange) { | |
setTimeout( | |
() => onRelationChange({ id: uploadedImage.id, label: uploadedImage.name, data: uploadedImage }), | |
0, | |
) | |
} | |
} | |
}, | |
[onChange, onRelationChange], | |
) | |
return { | |
altText, | |
imageSrc, | |
loading, | |
isShowLabel: !loading && !imageSrc, | |
isShowImage: !loading && !!imageSrc, | |
handleAltTextChange, | |
handleUploadChange, | |
} | |
} | |
export default useBase |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you so much for your help! I implemented the image upload field on my website, and it’s working perfectly. I really appreciate your guidance! 😊