diff --git a/client/web/src/components/AvatarPicker.tsx b/client/web/src/components/AvatarPicker.tsx index 3fb75183..804d2e03 100644 --- a/client/web/src/components/AvatarPicker.tsx +++ b/client/web/src/components/AvatarPicker.tsx @@ -3,7 +3,7 @@ import { closeModal, openModal } from './Modal'; import { showToasts, t } from 'tailchat-shared'; import { Avatar } from 'antd'; import { Icon } from 'tailchat-design'; -import { ModalAvatarCropper } from './modals/AvatarCropper'; +import { ImageCropperModal } from './modals/ImageCropper'; import { isGIF } from '@/utils/file-helper'; interface AvatarPickerProps extends PropsWithChildren { @@ -41,7 +41,7 @@ export const AvatarPicker: React.FC = React.memo((props) => { reader.addEventListener('load', () => { if (reader.result) { const key = openModal( - { closeModal(key); diff --git a/client/web/src/components/modals/AvatarCropper.tsx b/client/web/src/components/modals/ImageCropper.tsx similarity index 92% rename from client/web/src/components/modals/AvatarCropper.tsx rename to client/web/src/components/modals/ImageCropper.tsx index ed4a712d..c2d5becf 100644 --- a/client/web/src/components/modals/AvatarCropper.tsx +++ b/client/web/src/components/modals/ImageCropper.tsx @@ -2,18 +2,65 @@ import Cropper from 'react-easy-crop'; import type { Area } from 'react-easy-crop/types'; import _isNil from 'lodash/isNil'; import { showToasts, t } from 'tailchat-shared'; -import React, { useRef, useState } from 'react'; +import React, { useState } from 'react'; import { Button } from 'antd'; import { ModalWrapper } from '../Modal'; -const createImage = (url: string): Promise => - new Promise((resolve, reject) => { +/** + * 头像裁剪模态框 + */ +export const ImageCropperModal: React.FC<{ + imageUrl: string; + aspect?: number; + onConfirm: (croppedImageBlobUrl: string) => void; +}> = React.memo((props) => { + const aspect = props.aspect ?? 1; + const [crop, setCrop] = useState({ x: 0, y: 0 }); + const [zoom, setZoom] = useState(1); + const [area, setArea] = useState({ width: 0, height: 0, x: 0, y: 0 }); + + const handleConfirm = async () => { + const blobUrl = await getCroppedImg( + await createImage(props.imageUrl), + area + ); + props.onConfirm(blobUrl); + }; + + return ( + +
+ setArea(area)} + /> +
+ + +
+ ); +}); +ImageCropperModal.displayName = 'ImageCropperModal'; + +function createImage(url: string): Promise { + return new Promise((resolve, reject) => { const image = new Image(); image.addEventListener('load', () => resolve(image)); image.addEventListener('error', (error) => reject(error)); image.setAttribute('crossOrigin', 'anonymous'); // needed to avoid cross-origin issues on CodeSandbox image.src = url; }); +} function getRadianAngle(degreeValue: number) { return (degreeValue * Math.PI) / 180; @@ -93,47 +140,3 @@ function getCroppedImg( } }); } - -/** - * 头像裁剪模态框 - */ -export const ModalAvatarCropper: React.FC<{ - imageUrl: string; - onConfirm: (croppedImageBlobUrl: string) => void; -}> = React.memo((props) => { - const [crop, setCrop] = useState({ x: 0, y: 0 }); - const [zoom, setZoom] = useState(1); - const [area, setArea] = useState({ width: 0, height: 0, x: 0, y: 0 }); - - const handleConfirm = async () => { - const blobUrl = await getCroppedImg( - await createImage(props.imageUrl), - area - ); - props.onConfirm(blobUrl); - }; - - return ( - -
- setArea(area)} - /> -
- - -
- ); -}); -ModalAvatarCropper.displayName = 'ModalAvatarCropper';