From 59720e716e37a6b59806b8dd94d726443882aa47 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Mon, 10 Jul 2023 00:16:11 +0800 Subject: [PATCH] refactor: AvatarPicker -> ImagePicker --- client/web/src/components/AvatarPicker.tsx | 95 ----------------- client/web/src/components/AvatarUploader.tsx | 37 +++---- client/web/src/components/ImagePicker.tsx | 102 +++++++++++++++++++ 3 files changed, 115 insertions(+), 119 deletions(-) delete mode 100644 client/web/src/components/AvatarPicker.tsx create mode 100644 client/web/src/components/ImagePicker.tsx diff --git a/client/web/src/components/AvatarPicker.tsx b/client/web/src/components/AvatarPicker.tsx deleted file mode 100644 index 804d2e03..00000000 --- a/client/web/src/components/AvatarPicker.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { PropsWithChildren, useRef, useState } from 'react'; -import { closeModal, openModal } from './Modal'; -import { showToasts, t } from 'tailchat-shared'; -import { Avatar } from 'antd'; -import { Icon } from 'tailchat-design'; -import { ImageCropperModal } from './modals/ImageCropper'; -import { isGIF } from '@/utils/file-helper'; - -interface AvatarPickerProps extends PropsWithChildren { - className?: string; - imageUrl?: string; // 初始image url, 仅children为空时生效 - onChange?: (blobUrl: string) => void; - disabled?: boolean; // 禁用选择 -} -/** - * 头像选择器 - */ -export const AvatarPicker: React.FC = React.memo((props) => { - const fileRef = useRef(null); - const [avatarUrl, setAvatarUrl] = useState(props.imageUrl || ''); // 裁剪后并使用的url/或者未经裁剪的 gif url - - const updateAvatar = (imageBlobUrl: string) => { - setAvatarUrl(imageBlobUrl); - - if (typeof props.onChange === 'function') { - props.onChange(imageBlobUrl); - } - }; - - const handleSelectFile = (e: React.ChangeEvent) => { - if (e.target.files && e.target.files.length > 0) { - const pickedFile = e.target.files[0]; - if (!pickedFile) { - return; - } - - if (isGIF(pickedFile)) { - updateAvatar(URL.createObjectURL(pickedFile)); - } else { - const reader = new FileReader(); - reader.addEventListener('load', () => { - if (reader.result) { - const key = openModal( - { - closeModal(key); - updateAvatar(croppedImageBlobUrl); - }} - />, - { - maskClosable: false, - closable: true, - } - ); - } else { - showToasts(t('文件读取失败'), 'error'); - } - }); - reader.readAsDataURL(pickedFile); - } - - // 清理选中状态 - e.target.files = null; - e.target.value = ''; - } - }; - - return ( -
-
!props.disabled && fileRef.current?.click()} - > - - {props.children ? ( - props.children - ) : ( - } - src={avatarUrl} - /> - )} -
-
- ); -}); -AvatarPicker.displayName = 'AvatarPicker'; diff --git a/client/web/src/components/AvatarUploader.tsx b/client/web/src/components/AvatarUploader.tsx index aa857ec4..2d53cbdd 100644 --- a/client/web/src/components/AvatarUploader.tsx +++ b/client/web/src/components/AvatarUploader.tsx @@ -1,9 +1,8 @@ import { blobUrlToFile } from '@/utils/file-helper'; -import { Icon } from 'tailchat-design'; import clsx from 'clsx'; import React, { PropsWithChildren, useState } from 'react'; import { uploadFile, UploadFileResult, useAsyncRequest } from 'tailchat-shared'; -import { AvatarPicker } from './AvatarPicker'; +import { ImagePicker } from './ImagePicker'; export const AvatarUploader: React.FC< PropsWithChildren<{ @@ -32,32 +31,22 @@ export const AvatarUploader: React.FC< ); return ( - -
- {props.children} - {loading && ( -
- )} - + {loading && (
- -
-
- + className="absolute bottom-0 left-0 h-1" + style={{ width: `${uploadProgress}%` }} + /> + )} + + {props.children} + ); }); AvatarUploader.displayName = 'AvatarUploader'; diff --git a/client/web/src/components/ImagePicker.tsx b/client/web/src/components/ImagePicker.tsx new file mode 100644 index 00000000..8a7f0296 --- /dev/null +++ b/client/web/src/components/ImagePicker.tsx @@ -0,0 +1,102 @@ +import React, { PropsWithChildren, useRef } from 'react'; +import { closeModal, openModal } from './Modal'; +import { showToasts, t, useEvent } from 'tailchat-shared'; +import { Icon } from 'tailchat-design'; +import { ImageCropperModal } from './modals/ImageCropper'; +import { isGIF } from '@/utils/file-helper'; +import clsx from 'clsx'; + +interface ImagePickerProps extends PropsWithChildren { + className?: string; + imageUrl?: string; // 初始image url, 仅children为空时生效 + aspect?: number; + onChange?: (blobUrl: string) => void; + disabled?: boolean; // 禁用选择 +} +/** + * 头像选择器 + */ +export const ImagePicker: React.FC = React.memo((props) => { + const fileRef = useRef(null); + + const updateAvatar = (imageBlobUrl: string) => { + if (typeof props.onChange === 'function') { + props.onChange(imageBlobUrl); + } + }; + + const handleSelectFile = useEvent( + (e: React.ChangeEvent) => { + if (e.target.files && e.target.files.length > 0) { + const pickedFile = e.target.files[0]; + if (!pickedFile) { + return; + } + + if (isGIF(pickedFile)) { + updateAvatar(URL.createObjectURL(pickedFile)); + } else { + const reader = new FileReader(); + reader.addEventListener('load', () => { + if (reader.result) { + const key = openModal( + { + closeModal(key); + updateAvatar(croppedImageBlobUrl); + }} + />, + { + maskClosable: false, + closable: true, + } + ); + } else { + showToasts(t('文件读取失败'), 'error'); + } + }); + reader.readAsDataURL(pickedFile); + } + + // 清理选中状态 + e.target.files = null; + e.target.value = ''; + } + } + ); + + return ( +
+
!props.disabled && fileRef.current?.click()} + > + + +
+ {props.children} + +
+ +
+
+
+
+ ); +}); +ImagePicker.displayName = 'ImagePicker';