diff --git a/web/app/components/base/app-icon-picker/Uploader.tsx b/web/app/components/base/app-icon-picker/ImageInput.tsx similarity index 89% rename from web/app/components/base/app-icon-picker/Uploader.tsx rename to web/app/components/base/app-icon-picker/ImageInput.tsx index ba0ef6b2b2178a..f26f5a1fcb91db 100644 --- a/web/app/components/base/app-icon-picker/Uploader.tsx +++ b/web/app/components/base/app-icon-picker/ImageInput.tsx @@ -11,16 +11,19 @@ import { useDraggableUploader } from './hooks' import { checkIsAnimatedImage } from './utils' import { ALLOW_FILE_EXTENSIONS } from '@/types/app' +export type OnImageInput = { + (isCropped: true, tempUrl: string, croppedAreaPixels: Area, fileName: string): void + (isCropped: false, file: File): void +} + type UploaderProps = { className?: string - onImageCropped?: (tempUrl: string, croppedAreaPixels: Area, fileName: string) => void - onUpload?: (file?: File) => void + onImageInput?: OnImageInput } -const Uploader: FC = ({ +const ImageInput: FC = ({ className, - onImageCropped, - onUpload, + onImageInput, }) => { const [inputImage, setInputImage] = useState<{ file: File; url: string }>() const [isAnimatedImage, setIsAnimatedImage] = useState(false) @@ -37,8 +40,7 @@ const Uploader: FC = ({ const onCropComplete = async (_: Area, croppedAreaPixels: Area) => { if (!inputImage) return - onImageCropped?.(inputImage.url, croppedAreaPixels, inputImage.file.name) - onUpload?.(undefined) + onImageInput?.(true, inputImage.url, croppedAreaPixels, inputImage.file.name) } const handleLocalFileInput = (e: ChangeEvent) => { @@ -48,7 +50,7 @@ const Uploader: FC = ({ checkIsAnimatedImage(file).then((isAnimatedImage) => { setIsAnimatedImage(!!isAnimatedImage) if (isAnimatedImage) - onUpload?.(file) + onImageInput?.(false, file) }) } } @@ -117,4 +119,4 @@ const Uploader: FC = ({ ) } -export default Uploader +export default ImageInput diff --git a/web/app/components/base/app-icon-picker/index.tsx b/web/app/components/base/app-icon-picker/index.tsx index 14c9802c770ea1..277e2fa1d0862f 100644 --- a/web/app/components/base/app-icon-picker/index.tsx +++ b/web/app/components/base/app-icon-picker/index.tsx @@ -8,12 +8,14 @@ import Button from '../button' import { ImagePlus } from '../icons/src/vender/line/images' import { useLocalFileUploader } from '../image-uploader/hooks' import EmojiPickerInner from '../emoji-picker/Inner' -import Uploader from './Uploader' +import type { OnImageInput } from './ImageInput' +import ImageInput from './ImageInput' import s from './style.module.css' import getCroppedImg from './utils' import type { AppIconType, ImageFile } from '@/types/app' import cn from '@/utils/classnames' import { DISABLE_UPLOAD_IMAGE_AS_ICON } from '@/config' + export type AppIconEmojiSelection = { type: 'emoji' icon: string @@ -69,14 +71,15 @@ const AppIconPicker: FC = ({ }, }) - const [imageCropInfo, setImageCropInfo] = useState<{ tempUrl: string; croppedAreaPixels: Area; fileName: string }>() - const handleImageCropped = async (tempUrl: string, croppedAreaPixels: Area, fileName: string) => { - setImageCropInfo({ tempUrl, croppedAreaPixels, fileName }) - } + type InputImageInfo = { file: File } | { tempUrl: string; croppedAreaPixels: Area; fileName: string } + const [inputImageInfo, setInputImageInfo] = useState() - const [uploadImageInfo, setUploadImageInfo] = useState<{ file?: File }>() - const handleUpload = async (file?: File) => { - setUploadImageInfo({ file }) + const handleImageInput: OnImageInput = async (isCropped: boolean, fileOrTempUrl: string | File, croppedAreaPixels?: Area, fileName?: string) => { + setInputImageInfo( + isCropped + ? { tempUrl: fileOrTempUrl as string, croppedAreaPixels: croppedAreaPixels!, fileName: fileName! } + : { file: fileOrTempUrl as File }, + ) } const handleSelect = async () => { @@ -90,15 +93,15 @@ const AppIconPicker: FC = ({ } } else { - if (!imageCropInfo && !uploadImageInfo) + if (!inputImageInfo) return setUploading(true) - if (imageCropInfo.file) { - handleLocalFileUpload(imageCropInfo.file) + if ('file' in inputImageInfo) { + handleLocalFileUpload(inputImageInfo.file) return } - const blob = await getCroppedImg(imageCropInfo.tempUrl, imageCropInfo.croppedAreaPixels, imageCropInfo.fileName) - const file = new File([blob], imageCropInfo.fileName, { type: blob.type }) + const blob = await getCroppedImg(inputImageInfo.tempUrl, inputImageInfo.croppedAreaPixels, inputImageInfo.fileName) + const file = new File([blob], inputImageInfo.fileName, { type: blob.type }) handleLocalFileUpload(file) } } @@ -128,7 +131,7 @@ const AppIconPicker: FC = ({ } - +
diff --git a/web/app/components/base/app-icon-picker/utils.ts b/web/app/components/base/app-icon-picker/utils.ts index 99154d56da3097..f63b75eaa10572 100644 --- a/web/app/components/base/app-icon-picker/utils.ts +++ b/web/app/components/base/app-icon-picker/utils.ts @@ -116,12 +116,12 @@ export default async function getCroppedImg( }) } -export function checkIsAnimatedImage(file) { +export function checkIsAnimatedImage(file: File): Promise { return new Promise((resolve, reject) => { const fileReader = new FileReader() fileReader.onload = function (e) { - const arr = new Uint8Array(e.target.result) + const arr = new Uint8Array(e.target?.result as ArrayBuffer) // Check file extension const fileName = file.name.toLowerCase() @@ -148,7 +148,7 @@ export function checkIsAnimatedImage(file) { } // Function to check for WebP signature -function isWebP(arr) { +function isWebP(arr: Uint8Array) { return ( arr[0] === 0x52 && arr[1] === 0x49 && arr[2] === 0x46 && arr[3] === 0x46 && arr[8] === 0x57 && arr[9] === 0x45 && arr[10] === 0x42 && arr[11] === 0x50 @@ -156,7 +156,7 @@ function isWebP(arr) { } // Function to check if the WebP is animated (contains ANIM chunk) -function checkWebPAnimation(arr) { +function checkWebPAnimation(arr: Uint8Array) { // Search for the ANIM chunk in WebP to determine if it's animated for (let i = 12; i < arr.length - 4; i++) { if (arr[i] === 0x41 && arr[i + 1] === 0x4E && arr[i + 2] === 0x49 && arr[i + 3] === 0x4D)