mirror of
				https://github.com/langgenius/dify.git
				synced 2025-10-31 02:42:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			150 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			150 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import type { FC } from 'react'
 | |
| import { useCallback, useState } from 'react'
 | |
| import { useTranslation } from 'react-i18next'
 | |
| import type { Area } from 'react-easy-crop'
 | |
| import Modal from '../modal'
 | |
| import Divider from '../divider'
 | |
| 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 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
 | |
|   background: string
 | |
| }
 | |
| 
 | |
| export type AppIconImageSelection = {
 | |
|   type: 'image'
 | |
|   fileId: string
 | |
|   url: string
 | |
| }
 | |
| 
 | |
| export type AppIconSelection = AppIconEmojiSelection | AppIconImageSelection
 | |
| 
 | |
| type AppIconPickerProps = {
 | |
|   onSelect?: (payload: AppIconSelection) => void
 | |
|   onClose?: () => void
 | |
|   className?: string
 | |
| }
 | |
| 
 | |
| const AppIconPicker: FC<AppIconPickerProps> = ({
 | |
|   onSelect,
 | |
|   onClose,
 | |
|   className,
 | |
| }) => {
 | |
|   const { t } = useTranslation()
 | |
| 
 | |
|   const tabs = [
 | |
|     { key: 'emoji', label: t('app.iconPicker.emoji'), icon: <span className="text-lg">🤖</span> },
 | |
|     { key: 'image', label: t('app.iconPicker.image'), icon: <ImagePlus /> },
 | |
|   ]
 | |
|   const [activeTab, setActiveTab] = useState<AppIconType>('emoji')
 | |
| 
 | |
|   const [emoji, setEmoji] = useState<{ emoji: string; background: string }>()
 | |
|   const handleSelectEmoji = useCallback((emoji: string, background: string) => {
 | |
|     setEmoji({ emoji, background })
 | |
|   }, [setEmoji])
 | |
| 
 | |
|   const [uploading, setUploading] = useState<boolean>()
 | |
| 
 | |
|   const { handleLocalFileUpload } = useLocalFileUploader({
 | |
|     limit: 3,
 | |
|     disabled: false,
 | |
|     onUpload: (imageFile: ImageFile) => {
 | |
|       if (imageFile.fileId) {
 | |
|         setUploading(false)
 | |
|         onSelect?.({
 | |
|           type: 'image',
 | |
|           fileId: imageFile.fileId,
 | |
|           url: imageFile.url,
 | |
|         })
 | |
|       }
 | |
|     },
 | |
|   })
 | |
| 
 | |
|   type InputImageInfo = { file: File } | { tempUrl: string; croppedAreaPixels: Area; fileName: string }
 | |
|   const [inputImageInfo, setInputImageInfo] = useState<InputImageInfo>()
 | |
| 
 | |
|   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 () => {
 | |
|     if (activeTab === 'emoji') {
 | |
|       if (emoji) {
 | |
|         onSelect?.({
 | |
|           type: 'emoji',
 | |
|           icon: emoji.emoji,
 | |
|           background: emoji.background,
 | |
|         })
 | |
|       }
 | |
|     }
 | |
|     else {
 | |
|       if (!inputImageInfo)
 | |
|         return
 | |
|       setUploading(true)
 | |
|       if ('file' in inputImageInfo) {
 | |
|         handleLocalFileUpload(inputImageInfo.file)
 | |
|         return
 | |
|       }
 | |
|       const blob = await getCroppedImg(inputImageInfo.tempUrl, inputImageInfo.croppedAreaPixels, inputImageInfo.fileName)
 | |
|       const file = new File([blob], inputImageInfo.fileName, { type: blob.type })
 | |
|       handleLocalFileUpload(file)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return <Modal
 | |
|     onClose={() => { }}
 | |
|     isShow
 | |
|     closable={false}
 | |
|     wrapperClassName={className}
 | |
|     className={cn(s.container, '!w-[362px] !p-0')}
 | |
|   >
 | |
|     {!DISABLE_UPLOAD_IMAGE_AS_ICON && <div className="p-2 pb-0 w-full">
 | |
|       <div className='p-1 flex items-center justify-center gap-2 bg-background-body rounded-xl'>
 | |
|         {tabs.map(tab => (
 | |
|           <button
 | |
|             key={tab.key}
 | |
|             className={`
 | |
|                         p-2 flex-1 flex justify-center items-center h-8 rounded-xl text-sm shrink-0 font-medium
 | |
|                         ${activeTab === tab.key && 'bg-components-main-nav-nav-button-bg-active shadow-md'}
 | |
|                       `}
 | |
|             onClick={() => setActiveTab(tab.key as AppIconType)}
 | |
|           >
 | |
|             {tab.icon}   {tab.label}
 | |
|           </button>
 | |
|         ))}
 | |
|       </div>
 | |
|     </div>}
 | |
| 
 | |
|     <EmojiPickerInner className={cn(activeTab === 'emoji' ? 'block' : 'hidden', 'pt-2')} onSelect={handleSelectEmoji} />
 | |
|     <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'>
 | |
|       <Button className='w-full' onClick={() => onClose?.()}>
 | |
|         {t('app.iconPicker.cancel')}
 | |
|       </Button>
 | |
| 
 | |
|       <Button variant="primary" className='w-full' disabled={uploading} loading={uploading} onClick={handleSelect}>
 | |
|         {t('app.iconPicker.ok')}
 | |
|       </Button>
 | |
|     </div>
 | |
|   </Modal>
 | |
| }
 | |
| 
 | |
| export default AppIconPicker
 | 
