mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-20 19:09:12 +08:00
fix: cannot upload animated webp image as app icon (#11453)
This commit is contained in:
parent
7e1184c071
commit
266d32bd77
@ -11,16 +11,19 @@ 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
|
||||
export type OnImageInput = {
|
||||
(isCropped: true, tempUrl: string, croppedAreaPixels: Area, fileName: string): void
|
||||
(isCropped: false, file: File): void
|
||||
}
|
||||
|
||||
const Uploader: FC<UploaderProps> = ({
|
||||
type UploaderProps = {
|
||||
className?: string
|
||||
onImageInput?: OnImageInput
|
||||
}
|
||||
|
||||
const ImageInput: FC<UploaderProps> = ({
|
||||
className,
|
||||
onImageCropped,
|
||||
onUpload,
|
||||
onImageInput,
|
||||
}) => {
|
||||
const [inputImage, setInputImage] = useState<{ file: File; url: string }>()
|
||||
const [isAnimatedImage, setIsAnimatedImage] = useState<boolean>(false)
|
||||
@ -37,8 +40,7 @@ const Uploader: FC<UploaderProps> = ({
|
||||
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<HTMLInputElement>) => {
|
||||
@ -48,7 +50,7 @@ const Uploader: FC<UploaderProps> = ({
|
||||
checkIsAnimatedImage(file).then((isAnimatedImage) => {
|
||||
setIsAnimatedImage(!!isAnimatedImage)
|
||||
if (isAnimatedImage)
|
||||
onUpload?.(file)
|
||||
onImageInput?.(false, file)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -117,4 +119,4 @@ const Uploader: FC<UploaderProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default Uploader
|
||||
export default ImageInput
|
@ -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<AppIconPickerProps> = ({
|
||||
},
|
||||
})
|
||||
|
||||
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<InputImageInfo>()
|
||||
|
||||
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<AppIconPickerProps> = ({
|
||||
}
|
||||
}
|
||||
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<AppIconPickerProps> = ({
|
||||
</div>}
|
||||
|
||||
<EmojiPickerInner className={cn(activeTab === 'emoji' ? 'block' : 'hidden', 'pt-2')} onSelect={handleSelectEmoji} />
|
||||
<Uploader className={activeTab === 'image' ? 'block' : 'hidden'} onImageCropped={handleImageCropped} onUpload={handleUpload}/>
|
||||
<ImageInput className={activeTab === 'image' ? 'block' : 'hidden'} onImageInput={handleImageInput} />
|
||||
|
||||
<Divider className='m-0' />
|
||||
<div className='w-full flex items-center justify-center p-3 gap-2'>
|
||||
|
@ -116,12 +116,12 @@ export default async function getCroppedImg(
|
||||
})
|
||||
}
|
||||
|
||||
export function checkIsAnimatedImage(file) {
|
||||
export function checkIsAnimatedImage(file: File): Promise<boolean> {
|
||||
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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user