Revamp image editor (#5462)

* new image editor

* Rm react-avatar-editor

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
This commit is contained in:
Mary
2024-09-24 23:27:40 +07:00
committed by GitHub
parent ed512d6dc5
commit b9516202fa
14 changed files with 318 additions and 293 deletions
@@ -0,0 +1,14 @@
import React from 'react'
import {ComposerImage} from '#/state/gallery'
import * as Dialog from '#/components/Dialog'
export type EditImageDialogProps = {
control: Dialog.DialogOuterProps['control']
image: ComposerImage
onChange: (next: ComposerImage) => void
}
export const EditImageDialog = ({}: EditImageDialogProps): React.ReactNode => {
return null
}
@@ -0,0 +1,105 @@
import 'react-image-crop/dist/ReactCrop.css'
import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import ReactCrop, {PercentCrop} from 'react-image-crop'
import {
ImageSource,
ImageTransformation,
manipulateImage,
} from '#/state/gallery'
import {atoms as a} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {Text} from '#/components/Typography'
import {EditImageDialogProps} from './EditImageDialog'
export const EditImageDialog = (props: EditImageDialogProps) => {
return (
<Dialog.Outer control={props.control}>
<EditImageInner key={props.image.source.id} {...props} />
</Dialog.Outer>
)
}
const EditImageInner = ({control, image, onChange}: EditImageDialogProps) => {
const {_} = useLingui()
const source = image.source
const initialCrop = getInitialCrop(source, image.manips)
const [crop, setCrop] = React.useState(initialCrop)
const isEmpty = !crop || (crop.width || crop.height) === 0
const isNew = initialCrop ? true : !isEmpty
const onPressSubmit = React.useCallback(async () => {
const result = await manipulateImage(image, {
crop:
crop && (crop.width || crop.height) !== 0
? {
originX: (crop.x * source.width) / 100,
originY: (crop.y * source.height) / 100,
width: (crop.width * source.width) / 100,
height: (crop.height * source.height) / 100,
}
: undefined,
})
onChange(result)
control.close()
}, [crop, image, source, control, onChange])
return (
<Dialog.Inner label={_(msg`Edit image`)}>
<Dialog.Close />
<Text style={[a.text_2xl, a.font_bold, a.leading_tight, a.pb_sm]}>
<Trans>Edit image</Trans>
</Text>
<View style={[a.align_center]}>
<ReactCrop
crop={crop}
onChange={(_pixelCrop, percentCrop) => setCrop(percentCrop)}
className="ReactCrop--no-animate">
<img src={source.path} style={{maxHeight: `50vh`}} />
</ReactCrop>
</View>
<View style={[a.mt_md, a.gap_md]}>
<Button
disabled={!isNew}
label={_(msg`Save`)}
size="large"
color="primary"
variant="solid"
onPress={onPressSubmit}>
<ButtonText>
<Trans>Save</Trans>
</ButtonText>
</Button>
</View>
</Dialog.Inner>
)
}
const getInitialCrop = (
source: ImageSource,
manips: ImageTransformation | undefined,
): PercentCrop | undefined => {
const initialArea = manips?.crop
if (initialArea) {
return {
unit: '%',
x: (initialArea.originX / source.width) * 100,
y: (initialArea.originY / source.height) * 100,
width: (initialArea.width / source.width) * 100,
height: (initialArea.height / source.height) * 100,
}
}
}
+19 -15
View File
@@ -21,6 +21,7 @@ import {ComposerImage, cropImage} from '#/state/gallery'
import {Text} from '#/view/com/util/text/Text'
import {useTheme} from '#/alf'
import * as Dialog from '#/components/Dialog'
import {EditImageDialog} from './EditImageDialog'
import {ImageAltTextDialog} from './ImageAltTextDialog'
const IMAGE_GAP = 8
@@ -144,12 +145,15 @@ const GalleryItem = ({
const t = useTheme()
const altTextControl = Dialog.useDialogControl()
const editControl = Dialog.useDialogControl()
const onImageEdit = () => {
if (isNative) {
cropImage(image).then(next => {
onChange(next)
})
} else {
editControl.open()
}
}
@@ -185,21 +189,15 @@ const GalleryItem = ({
</Text>
</TouchableOpacity>
<View style={imageControlsStyle}>
{isNative && (
<TouchableOpacity
testID="editPhotoButton"
accessibilityRole="button"
accessibilityLabel={_(msg`Edit image`)}
accessibilityHint=""
onPress={onImageEdit}
style={styles.imageControl}>
<FontAwesomeIcon
icon="pen"
size={12}
style={{color: colors.white}}
/>
</TouchableOpacity>
)}
<TouchableOpacity
testID="editPhotoButton"
accessibilityRole="button"
accessibilityLabel={_(msg`Edit image`)}
accessibilityHint=""
onPress={onImageEdit}
style={styles.imageControl}>
<FontAwesomeIcon icon="pen" size={12} style={{color: colors.white}} />
</TouchableOpacity>
<TouchableOpacity
testID="removePhotoButton"
accessibilityRole="button"
@@ -237,6 +235,12 @@ const GalleryItem = ({
image={image}
onChange={onChange}
/>
<EditImageDialog
control={editControl}
image={image}
onChange={onChange}
/>
</View>
)
}