123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- 'use client'
- import type { ChangeEvent, FC } from 'react'
- import { createRef, useEffect, useState } from 'react'
- import type { Area } from 'react-easy-crop'
- import Cropper from 'react-easy-crop'
- import classNames from 'classnames'
- import { ImagePlus } from '../icons/src/vender/line/images'
- import { useDraggableUploader } from './hooks'
- import { checkIsAnimatedImage } from './utils'
- import { ALLOW_FILE_EXTENSIONS } from '@/types/app'
- type UploaderProps = {
- className?: string
- onImageCropped?: (tempUrl: string, croppedAreaPixels: Area, fileName: string) => void
- onUpload?: (file?: File) => void
- }
- const Uploader: FC<UploaderProps> = ({
- className,
- onImageCropped,
- onUpload,
- }) => {
- const [inputImage, setInputImage] = useState<{ file: File; url: string }>()
- const [isAnimatedImage, setIsAnimatedImage] = useState<boolean>(false)
- 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
- onImageCropped?.(inputImage.url, croppedAreaPixels, inputImage.file.name)
- onUpload?.(undefined)
- }
- const handleLocalFileInput = (e: ChangeEvent<HTMLInputElement>) => {
- const file = e.target.files?.[0]
- if (file) {
- setInputImage({ file, url: URL.createObjectURL(file) })
- checkIsAnimatedImage(file).then((isAnimatedImage) => {
- setIsAnimatedImage(!!isAnimatedImage)
- if (isAnimatedImage)
- onUpload?.(file)
- })
- }
- }
- const {
- isDragActive,
- handleDragEnter,
- handleDragOver,
- handleDragLeave,
- handleDrop,
- } = useDraggableUploader((file: File) => setInputImage({ file, url: URL.createObjectURL(file) }))
- const inputRef = createRef<HTMLInputElement>()
- const handleShowImage = () => {
- if (isAnimatedImage) {
- return (
- <img src={inputImage?.url} alt='' />
- )
- }
- return (
- <Cropper
- image={inputImage?.url}
- crop={crop}
- zoom={zoom}
- aspect={1}
- onCropChange={setCrop}
- onCropComplete={onCropComplete}
- onZoomChange={setZoom}
- />
- )
- }
- 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
- ? <>
- <ImagePlus className="w-[30px] h-[30px] mb-3 pointer-events-none" />
- <div className="text-sm font-medium mb-[2px]">
- <span className="pointer-events-none">Drop your image here, or </span>
- <button className="text-components-button-primary-bg" onClick={() => inputRef.current?.click()}>browse</button>
- <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>
- <div className="text-xs pointer-events-none">Supports PNG, JPG, JPEG, WEBP and GIF</div>
- </>
- : handleShowImage()
- }
- </div>
- </div>
- )
- }
- export default Uploader
|