Simplify the web edit-image-dialog (drop aspect ratio controls)
This commit is contained in:
@@ -20,7 +20,7 @@ import {getDataUriSize} from '#/lib/media/util'
|
|||||||
import {isIOS, isNative} from '#/platform/detection'
|
import {isIOS, isNative} from '#/platform/detection'
|
||||||
|
|
||||||
export type ImageTransformation = {
|
export type ImageTransformation = {
|
||||||
crop?: ActionCrop['crop'] & {aspect: number}
|
crop?: ActionCrop['crop']
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ImageMeta = {
|
export type ImageMeta = {
|
||||||
|
|||||||
@@ -2,13 +2,12 @@ import 'react-image-crop/dist/ReactCrop.css'
|
|||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {Path, Svg} from 'react-native-svg'
|
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import ReactCrop, {PercentCrop} from 'react-image-crop'
|
import ReactCrop, {PercentCrop} from 'react-image-crop'
|
||||||
|
|
||||||
import {manipulateImage} from '#/state/gallery'
|
import {manipulateImage} from '#/state/gallery'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
@@ -24,17 +23,14 @@ export const EditImageDialog = (props: EditImageDialogProps) => {
|
|||||||
|
|
||||||
const EditImageInner = ({control, image, onChange}: EditImageDialogProps) => {
|
const EditImageInner = ({control, image, onChange}: EditImageDialogProps) => {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
|
||||||
|
|
||||||
const source = image.source
|
const source = image.source
|
||||||
const manips = image.manips
|
const manips = image.manips
|
||||||
|
|
||||||
const {initialCrop, initialAspect, sourceAspect} = React.useMemo(() => {
|
const {initialCrop} = React.useMemo(() => {
|
||||||
const initialArea = manips?.crop
|
const initialArea = manips?.crop
|
||||||
const originalAspect = source.width / source.height
|
|
||||||
|
|
||||||
let crop: PercentCrop | undefined
|
let crop: PercentCrop | undefined
|
||||||
let aspect = originalAspect
|
|
||||||
|
|
||||||
if (initialArea) {
|
if (initialArea) {
|
||||||
crop = {
|
crop = {
|
||||||
@@ -44,19 +40,14 @@ const EditImageInner = ({control, image, onChange}: EditImageDialogProps) => {
|
|||||||
width: (initialArea.width / source.width) * 100,
|
width: (initialArea.width / source.width) * 100,
|
||||||
height: (initialArea.height / source.height) * 100,
|
height: (initialArea.height / source.height) * 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
aspect = initialArea.aspect
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
initialCrop: crop,
|
initialCrop: crop,
|
||||||
initialAspect: aspect,
|
|
||||||
sourceAspect: originalAspect,
|
|
||||||
}
|
}
|
||||||
}, [source, manips])
|
}, [source, manips])
|
||||||
|
|
||||||
const [crop, setCrop] = React.useState(initialCrop)
|
const [crop, setCrop] = React.useState(initialCrop)
|
||||||
const [aspect, setAspect] = React.useState(initialAspect)
|
|
||||||
|
|
||||||
const isEmpty = !crop || (crop.width || crop.height) === 0
|
const isEmpty = !crop || (crop.width || crop.height) === 0
|
||||||
const isNew = initialCrop ? true : !isEmpty
|
const isNew = initialCrop ? true : !isEmpty
|
||||||
@@ -65,7 +56,6 @@ const EditImageInner = ({control, image, onChange}: EditImageDialogProps) => {
|
|||||||
const result = await manipulateImage(image, {
|
const result = await manipulateImage(image, {
|
||||||
crop: !isEmpty
|
crop: !isEmpty
|
||||||
? {
|
? {
|
||||||
aspect: aspect,
|
|
||||||
originX: (crop.x * source.width) / 100,
|
originX: (crop.x * source.width) / 100,
|
||||||
originY: (crop.y * source.height) / 100,
|
originY: (crop.y * source.height) / 100,
|
||||||
width: (crop.width * source.width) / 100,
|
width: (crop.width * source.width) / 100,
|
||||||
@@ -76,14 +66,7 @@ const EditImageInner = ({control, image, onChange}: EditImageDialogProps) => {
|
|||||||
|
|
||||||
onChange(result)
|
onChange(result)
|
||||||
control.close()
|
control.close()
|
||||||
}, [crop, isEmpty, aspect, image, source, control, onChange])
|
}, [crop, isEmpty, image, source, control, onChange])
|
||||||
|
|
||||||
const changeAspect = (next: number) => {
|
|
||||||
if (next !== aspect) {
|
|
||||||
setAspect(next)
|
|
||||||
setCrop(undefined)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog.Inner label={_(msg`Edit image`)}>
|
<Dialog.Inner label={_(msg`Edit image`)}>
|
||||||
@@ -95,7 +78,6 @@ const EditImageInner = ({control, image, onChange}: EditImageDialogProps) => {
|
|||||||
|
|
||||||
<View style={[a.align_center]}>
|
<View style={[a.align_center]}>
|
||||||
<ReactCrop
|
<ReactCrop
|
||||||
aspect={aspect}
|
|
||||||
crop={crop}
|
crop={crop}
|
||||||
onChange={(_, next) => setCrop(next)}
|
onChange={(_, next) => setCrop(next)}
|
||||||
className="ReactCrop--no-animate">
|
className="ReactCrop--no-animate">
|
||||||
@@ -104,55 +86,6 @@ const EditImageInner = ({control, image, onChange}: EditImageDialogProps) => {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={[a.mt_md, a.gap_md]}>
|
<View style={[a.mt_md, a.gap_md]}>
|
||||||
<View style={[a.flex_row]}>
|
|
||||||
<ToolbarButton
|
|
||||||
label={_(msg`Original`)}
|
|
||||||
onPress={() => changeAspect(sourceAspect)}>
|
|
||||||
<Svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
|
||||||
<Path
|
|
||||||
d="M3 5H21V19H3V5Z"
|
|
||||||
stroke={
|
|
||||||
aspect === sourceAspect
|
|
||||||
? t.palette.primary_600
|
|
||||||
: t.palette.white
|
|
||||||
}
|
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
</Svg>
|
|
||||||
</ToolbarButton>
|
|
||||||
|
|
||||||
<ToolbarButton
|
|
||||||
label={_(msg`Wide`)}
|
|
||||||
onPress={() => changeAspect(16 / 9)}>
|
|
||||||
<Svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
|
||||||
<Path
|
|
||||||
d="M1 7H23V17H1V7Z"
|
|
||||||
stroke={
|
|
||||||
aspect === 16 / 9 ? t.palette.primary_600 : t.palette.white
|
|
||||||
}
|
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
</Svg>
|
|
||||||
</ToolbarButton>
|
|
||||||
|
|
||||||
<ToolbarButton
|
|
||||||
label={_(msg`Square`)}
|
|
||||||
onPress={() => changeAspect(1 / 1)}>
|
|
||||||
<Svg width="24" height="24" viewBox="0 0 24 24" fill="none">
|
|
||||||
<Path
|
|
||||||
d="M4 4H20V20H4V4Z"
|
|
||||||
stroke={
|
|
||||||
aspect === 1 / 1 ? t.palette.primary_600 : t.palette.white
|
|
||||||
}
|
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
</Svg>
|
|
||||||
</ToolbarButton>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
disabled={!isNew}
|
disabled={!isNew}
|
||||||
label={_(msg`Save`)}
|
label={_(msg`Save`)}
|
||||||
@@ -168,25 +101,3 @@ const EditImageInner = ({control, image, onChange}: EditImageDialogProps) => {
|
|||||||
</Dialog.Inner>
|
</Dialog.Inner>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ToolbarButton = ({
|
|
||||||
children,
|
|
||||||
label,
|
|
||||||
onPress,
|
|
||||||
}: {
|
|
||||||
children: React.ReactElement
|
|
||||||
label: string
|
|
||||||
onPress?: () => void
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
onPress={onPress}
|
|
||||||
label={label}
|
|
||||||
style={a.p_sm}
|
|
||||||
variant="ghost"
|
|
||||||
shape="round"
|
|
||||||
color="primary">
|
|
||||||
{children}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user