|
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 Uploader from './Uploader' |
|
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, |
|
}) |
|
} |
|
}, |
|
}) |
|
|
|
const [imageCropInfo, setImageCropInfo] = useState<{ tempUrl: string; croppedAreaPixels: Area; fileName: string }>() |
|
const handleImageCropped = async (tempUrl: string, croppedAreaPixels: Area, fileName: string) => { |
|
setImageCropInfo({ tempUrl, croppedAreaPixels, fileName }) |
|
} |
|
|
|
const [uploadImageInfo, setUploadImageInfo] = useState<{ file?: File }>() |
|
const handleUpload = async (file?: File) => { |
|
setUploadImageInfo({ file }) |
|
} |
|
|
|
const handleSelect = async () => { |
|
if (activeTab === 'emoji') { |
|
if (emoji) { |
|
onSelect?.({ |
|
type: 'emoji', |
|
icon: emoji.emoji, |
|
background: emoji.background, |
|
}) |
|
} |
|
} |
|
else { |
|
if (!imageCropInfo && !uploadImageInfo) |
|
return |
|
setUploading(true) |
|
if (imageCropInfo.file) { |
|
handleLocalFileUpload(imageCropInfo.file) |
|
return |
|
} |
|
const blob = await getCroppedImg(imageCropInfo.tempUrl, imageCropInfo.croppedAreaPixels, imageCropInfo.fileName) |
|
const file = new File([blob], imageCropInfo.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>} |
|
|
|
<Divider className='m-0' /> |
|
|
|
<EmojiPickerInner className={activeTab === 'emoji' ? 'block' : 'hidden'} onSelect={handleSelectEmoji} /> |
|
<Uploader className={activeTab === 'image' ? 'block' : 'hidden'} onImageCropped={handleImageCropped} onUpload={handleUpload}/> |
|
|
|
<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 |
|
|