| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  | 'use client' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import type { ChangeEvent, FC } from 'react' | 
					
						
							|  |  |  | import { createRef, useEffect, useState } from 'react' | 
					
						
							| 
									
										
										
										
											2025-01-22 11:11:31 +09:00
										 |  |  | import Cropper, { type Area, type CropperProps } from 'react-easy-crop' | 
					
						
							| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  | import classNames from 'classnames' | 
					
						
							| 
									
										
										
										
											2025-04-02 22:35:51 +08:00
										 |  |  | import { useTranslation } from 'react-i18next' | 
					
						
							| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import { ImagePlus } from '../icons/src/vender/line/images' | 
					
						
							|  |  |  | import { useDraggableUploader } from './hooks' | 
					
						
							| 
									
										
										
										
											2024-11-06 08:05:05 +07:00
										 |  |  | import { checkIsAnimatedImage } from './utils' | 
					
						
							| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  | import { ALLOW_FILE_EXTENSIONS } from '@/types/app' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-08 08:37:21 +08:00
										 |  |  | export type OnImageInput = { | 
					
						
							|  |  |  |   (isCropped: true, tempUrl: string, croppedAreaPixels: Area, fileName: string): void | 
					
						
							|  |  |  |   (isCropped: false, file: File): void | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  | type UploaderProps = { | 
					
						
							|  |  |  |   className?: string | 
					
						
							| 
									
										
										
										
											2025-01-22 11:11:31 +09:00
										 |  |  |   cropShape?: CropperProps['cropShape'] | 
					
						
							| 
									
										
										
										
											2024-12-08 08:37:21 +08:00
										 |  |  |   onImageInput?: OnImageInput | 
					
						
							| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-08 08:37:21 +08:00
										 |  |  | const ImageInput: FC<UploaderProps> = ({ | 
					
						
							| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  |   className, | 
					
						
							| 
									
										
										
										
											2025-01-22 11:11:31 +09:00
										 |  |  |   cropShape, | 
					
						
							| 
									
										
										
										
											2024-12-08 08:37:21 +08:00
										 |  |  |   onImageInput, | 
					
						
							| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  | }) => { | 
					
						
							| 
									
										
										
										
											2025-04-02 22:35:51 +08:00
										 |  |  |   const { t } = useTranslation() | 
					
						
							| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  |   const [inputImage, setInputImage] = useState<{ file: File; url: string }>() | 
					
						
							| 
									
										
										
										
											2024-11-06 08:05:05 +07:00
										 |  |  |   const [isAnimatedImage, setIsAnimatedImage] = useState<boolean>(false) | 
					
						
							| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  |   useEffect(() => { | 
					
						
							|  |  |  |     return () => { | 
					
						
							|  |  |  |       if (inputImage) | 
					
						
							|  |  |  |         URL.revokeObjectURL(inputImage.url) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }, [inputImage]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const [crop, setCrop] = useState({ x: 0, y: 0 }) | 
					
						
							|  |  |  |   const [zoom, setZoom] = useState(1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const onCropComplete = async (_: Area, croppedAreaPixels: Area) => { | 
					
						
							|  |  |  |     if (!inputImage) | 
					
						
							|  |  |  |       return | 
					
						
							| 
									
										
										
										
											2024-12-08 08:37:21 +08:00
										 |  |  |     onImageInput?.(true, inputImage.url, croppedAreaPixels, inputImage.file.name) | 
					
						
							| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const handleLocalFileInput = (e: ChangeEvent<HTMLInputElement>) => { | 
					
						
							|  |  |  |     const file = e.target.files?.[0] | 
					
						
							| 
									
										
										
										
											2024-11-06 08:05:05 +07:00
										 |  |  |     if (file) { | 
					
						
							| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  |       setInputImage({ file, url: URL.createObjectURL(file) }) | 
					
						
							| 
									
										
										
										
											2024-11-06 08:05:05 +07:00
										 |  |  |       checkIsAnimatedImage(file).then((isAnimatedImage) => { | 
					
						
							|  |  |  |         setIsAnimatedImage(!!isAnimatedImage) | 
					
						
							|  |  |  |         if (isAnimatedImage) | 
					
						
							| 
									
										
										
										
											2024-12-08 08:37:21 +08:00
										 |  |  |           onImageInput?.(false, file) | 
					
						
							| 
									
										
										
										
											2024-11-06 08:05:05 +07:00
										 |  |  |       }) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const { | 
					
						
							|  |  |  |     isDragActive, | 
					
						
							|  |  |  |     handleDragEnter, | 
					
						
							|  |  |  |     handleDragOver, | 
					
						
							|  |  |  |     handleDragLeave, | 
					
						
							|  |  |  |     handleDrop, | 
					
						
							|  |  |  |   } = useDraggableUploader((file: File) => setInputImage({ file, url: URL.createObjectURL(file) })) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const inputRef = createRef<HTMLInputElement>() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-06 08:05:05 +07:00
										 |  |  |   const handleShowImage = () => { | 
					
						
							|  |  |  |     if (isAnimatedImage) { | 
					
						
							|  |  |  |       return ( | 
					
						
							|  |  |  |         <img src={inputImage?.url} alt='' /> | 
					
						
							|  |  |  |       ) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       <Cropper | 
					
						
							|  |  |  |         image={inputImage?.url} | 
					
						
							|  |  |  |         crop={crop} | 
					
						
							|  |  |  |         zoom={zoom} | 
					
						
							|  |  |  |         aspect={1} | 
					
						
							| 
									
										
										
										
											2025-01-22 11:11:31 +09:00
										 |  |  |         cropShape={cropShape} | 
					
						
							| 
									
										
										
										
											2024-11-06 08:05:05 +07:00
										 |  |  |         onCropChange={setCrop} | 
					
						
							|  |  |  |         onCropComplete={onCropComplete} | 
					
						
							|  |  |  |         onZoomChange={setZoom} | 
					
						
							|  |  |  |       /> | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  |   return ( | 
					
						
							|  |  |  |     <div className={classNames(className, 'w-full px-3 py-1.5')}> | 
					
						
							|  |  |  |       <div | 
					
						
							|  |  |  |         className={classNames( | 
					
						
							|  |  |  |           isDragActive && 'border-primary-600', | 
					
						
							|  |  |  |           'relative aspect-square bg-gray-50 border-[1.5px] border-gray-200 border-dashed rounded-lg flex flex-col justify-center items-center text-gray-500')} | 
					
						
							|  |  |  |         onDragEnter={handleDragEnter} | 
					
						
							|  |  |  |         onDragOver={handleDragOver} | 
					
						
							|  |  |  |         onDragLeave={handleDragLeave} | 
					
						
							|  |  |  |         onDrop={handleDrop} | 
					
						
							|  |  |  |       > | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           !inputImage | 
					
						
							|  |  |  |             ? <> | 
					
						
							| 
									
										
										
										
											2025-03-21 17:41:03 +08:00
										 |  |  |               <ImagePlus className="pointer-events-none mb-3 h-[30px] w-[30px]" /> | 
					
						
							|  |  |  |               <div className="mb-[2px] text-sm font-medium"> | 
					
						
							| 
									
										
										
										
											2025-04-02 22:35:51 +08:00
										 |  |  |                 <span className="pointer-events-none">{t('common.imageInput.dropImageHere')} </span> | 
					
						
							|  |  |  |                 <button className="text-components-button-primary-bg" onClick={() => inputRef.current?.click()}>{t('common.imageInput.browse')}</button> | 
					
						
							| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  |                 <input | 
					
						
							|  |  |  |                   ref={inputRef} type="file" className="hidden" | 
					
						
							|  |  |  |                   onClick={e => ((e.target as HTMLInputElement).value = '')} | 
					
						
							|  |  |  |                   accept={ALLOW_FILE_EXTENSIONS.map(ext => `.${ext}`).join(',')} | 
					
						
							|  |  |  |                   onChange={handleLocalFileInput} | 
					
						
							|  |  |  |                 /> | 
					
						
							|  |  |  |               </div> | 
					
						
							| 
									
										
										
										
											2025-04-02 22:35:51 +08:00
										 |  |  |               <div className="pointer-events-none">{t('common.imageInput.supportedFormats')}</div> | 
					
						
							| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  |             </> | 
					
						
							| 
									
										
										
										
											2024-11-06 08:05:05 +07:00
										 |  |  |             : handleShowImage() | 
					
						
							| 
									
										
										
										
											2024-08-19 09:16:33 +08:00
										 |  |  |         } | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |   ) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-08 08:37:21 +08:00
										 |  |  | export default ImageInput |