Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Revamp image editor (#5462)

* new image editor

* Rm react-avatar-editor

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>

authored by

Mary
Dan Abramov
and committed by
GitHub
b9516202 ed512d6d

+318 -293
+1 -2
package.json
··· 167 167 "postinstall-postinstall": "^2.1.0", 168 168 "psl": "^1.9.0", 169 169 "react": "18.2.0", 170 - "react-avatar-editor": "^13.0.0", 171 170 "react-compiler-runtime": "file:./lib/react-compiler-runtime", 172 171 "react-dom": "^18.2.0", 172 + "react-image-crop": "^11.0.7", 173 173 "react-keyed-flatten-children": "^3.0.0", 174 174 "react-native": "0.74.1", 175 175 "react-native-compressor": "^1.8.24", ··· 236 236 "@types/lodash.set": "^4.3.7", 237 237 "@types/lodash.shuffle": "^4.2.7", 238 238 "@types/psl": "^1.1.1", 239 - "@types/react-avatar-editor": "^13.0.0", 240 239 "@types/react-dom": "^18.2.18", 241 240 "@types/react-responsive": "^8.0.5", 242 241 "@types/react-test-renderer": "^17.0.1",
+3 -1
src/lib/media/picker.web.tsx
··· 18 18 name: 'crop-image', 19 19 uri: opts.path, 20 20 dimensions: 21 - opts.height && opts.width 21 + opts.width && opts.height 22 22 ? {width: opts.width, height: opts.height} 23 23 : undefined, 24 + aspect: opts.webAspectRatio, 25 + circular: opts.webCircularCrop, 24 26 onSelect: (img?: RNImage) => { 25 27 if (img) { 26 28 resolve(img)
+4 -1
src/lib/media/types.ts
··· 18 18 cropperCircleOverlay?: boolean 19 19 } 20 20 21 - export type CropperOptions = Parameters<typeof openCropper>[0] 21 + export type CropperOptions = Parameters<typeof openCropper>[0] & { 22 + webAspectRatio?: number 23 + webCircularCrop?: boolean 24 + }
+2
src/state/modals/index.tsx
··· 39 39 name: 'crop-image' 40 40 uri: string 41 41 dimensions?: {width: number; height: number} 42 + aspect?: number 43 + circular?: boolean 42 44 onSelect: (img?: RNImage) => void 43 45 } 44 46
+14
src/view/com/composer/photos/EditImageDialog.tsx
··· 1 + import React from 'react' 2 + 3 + import {ComposerImage} from '#/state/gallery' 4 + import * as Dialog from '#/components/Dialog' 5 + 6 + export type EditImageDialogProps = { 7 + control: Dialog.DialogOuterProps['control'] 8 + image: ComposerImage 9 + onChange: (next: ComposerImage) => void 10 + } 11 + 12 + export const EditImageDialog = ({}: EditImageDialogProps): React.ReactNode => { 13 + return null 14 + }
+105
src/view/com/composer/photos/EditImageDialog.web.tsx
··· 1 + import 'react-image-crop/dist/ReactCrop.css' 2 + 3 + import React from 'react' 4 + import {View} from 'react-native' 5 + import {msg, Trans} from '@lingui/macro' 6 + import {useLingui} from '@lingui/react' 7 + import ReactCrop, {PercentCrop} from 'react-image-crop' 8 + 9 + import { 10 + ImageSource, 11 + ImageTransformation, 12 + manipulateImage, 13 + } from '#/state/gallery' 14 + import {atoms as a} from '#/alf' 15 + import {Button, ButtonText} from '#/components/Button' 16 + import * as Dialog from '#/components/Dialog' 17 + import {Text} from '#/components/Typography' 18 + import {EditImageDialogProps} from './EditImageDialog' 19 + 20 + export const EditImageDialog = (props: EditImageDialogProps) => { 21 + return ( 22 + <Dialog.Outer control={props.control}> 23 + <EditImageInner key={props.image.source.id} {...props} /> 24 + </Dialog.Outer> 25 + ) 26 + } 27 + 28 + const EditImageInner = ({control, image, onChange}: EditImageDialogProps) => { 29 + const {_} = useLingui() 30 + 31 + const source = image.source 32 + 33 + const initialCrop = getInitialCrop(source, image.manips) 34 + const [crop, setCrop] = React.useState(initialCrop) 35 + 36 + const isEmpty = !crop || (crop.width || crop.height) === 0 37 + const isNew = initialCrop ? true : !isEmpty 38 + 39 + const onPressSubmit = React.useCallback(async () => { 40 + const result = await manipulateImage(image, { 41 + crop: 42 + crop && (crop.width || crop.height) !== 0 43 + ? { 44 + originX: (crop.x * source.width) / 100, 45 + originY: (crop.y * source.height) / 100, 46 + width: (crop.width * source.width) / 100, 47 + height: (crop.height * source.height) / 100, 48 + } 49 + : undefined, 50 + }) 51 + 52 + onChange(result) 53 + control.close() 54 + }, [crop, image, source, control, onChange]) 55 + 56 + return ( 57 + <Dialog.Inner label={_(msg`Edit image`)}> 58 + <Dialog.Close /> 59 + 60 + <Text style={[a.text_2xl, a.font_bold, a.leading_tight, a.pb_sm]}> 61 + <Trans>Edit image</Trans> 62 + </Text> 63 + 64 + <View style={[a.align_center]}> 65 + <ReactCrop 66 + crop={crop} 67 + onChange={(_pixelCrop, percentCrop) => setCrop(percentCrop)} 68 + className="ReactCrop--no-animate"> 69 + <img src={source.path} style={{maxHeight: `50vh`}} /> 70 + </ReactCrop> 71 + </View> 72 + 73 + <View style={[a.mt_md, a.gap_md]}> 74 + <Button 75 + disabled={!isNew} 76 + label={_(msg`Save`)} 77 + size="large" 78 + color="primary" 79 + variant="solid" 80 + onPress={onPressSubmit}> 81 + <ButtonText> 82 + <Trans>Save</Trans> 83 + </ButtonText> 84 + </Button> 85 + </View> 86 + </Dialog.Inner> 87 + ) 88 + } 89 + 90 + const getInitialCrop = ( 91 + source: ImageSource, 92 + manips: ImageTransformation | undefined, 93 + ): PercentCrop | undefined => { 94 + const initialArea = manips?.crop 95 + 96 + if (initialArea) { 97 + return { 98 + unit: '%', 99 + x: (initialArea.originX / source.width) * 100, 100 + y: (initialArea.originY / source.height) * 100, 101 + width: (initialArea.width / source.width) * 100, 102 + height: (initialArea.height / source.height) * 100, 103 + } 104 + } 105 + }
+19 -15
src/view/com/composer/photos/Gallery.tsx
··· 21 21 import {Text} from '#/view/com/util/text/Text' 22 22 import {useTheme} from '#/alf' 23 23 import * as Dialog from '#/components/Dialog' 24 + import {EditImageDialog} from './EditImageDialog' 24 25 import {ImageAltTextDialog} from './ImageAltTextDialog' 25 26 26 27 const IMAGE_GAP = 8 ··· 144 145 const t = useTheme() 145 146 146 147 const altTextControl = Dialog.useDialogControl() 148 + const editControl = Dialog.useDialogControl() 147 149 148 150 const onImageEdit = () => { 149 151 if (isNative) { 150 152 cropImage(image).then(next => { 151 153 onChange(next) 152 154 }) 155 + } else { 156 + editControl.open() 153 157 } 154 158 } 155 159 ··· 185 189 </Text> 186 190 </TouchableOpacity> 187 191 <View style={imageControlsStyle}> 188 - {isNative && ( 189 - <TouchableOpacity 190 - testID="editPhotoButton" 191 - accessibilityRole="button" 192 - accessibilityLabel={_(msg`Edit image`)} 193 - accessibilityHint="" 194 - onPress={onImageEdit} 195 - style={styles.imageControl}> 196 - <FontAwesomeIcon 197 - icon="pen" 198 - size={12} 199 - style={{color: colors.white}} 200 - /> 201 - </TouchableOpacity> 202 - )} 192 + <TouchableOpacity 193 + testID="editPhotoButton" 194 + accessibilityRole="button" 195 + accessibilityLabel={_(msg`Edit image`)} 196 + accessibilityHint="" 197 + onPress={onImageEdit} 198 + style={styles.imageControl}> 199 + <FontAwesomeIcon icon="pen" size={12} style={{color: colors.white}} /> 200 + </TouchableOpacity> 203 201 <TouchableOpacity 204 202 testID="removePhotoButton" 205 203 accessibilityRole="button" ··· 234 232 235 233 <ImageAltTextDialog 236 234 control={altTextControl} 235 + image={image} 236 + onChange={onChange} 237 + /> 238 + 239 + <EditImageDialog 240 + control={editControl} 237 241 image={image} 238 242 onChange={onChange} 239 243 />
+145
src/view/com/modals/CropImage.web.tsx
··· 1 + import React from 'react' 2 + import {StyleSheet, TouchableOpacity, View} from 'react-native' 3 + import {Image as RNImage} from 'react-native-image-crop-picker' 4 + import {manipulateAsync, SaveFormat} from 'expo-image-manipulator' 5 + import {LinearGradient} from 'expo-linear-gradient' 6 + import {msg, Trans} from '@lingui/macro' 7 + import {useLingui} from '@lingui/react' 8 + import ReactCrop, {PercentCrop} from 'react-image-crop' 9 + 10 + import {usePalette} from '#/lib/hooks/usePalette' 11 + import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 12 + import {getDataUriSize} from '#/lib/media/util' 13 + import {gradients, s} from '#/lib/styles' 14 + import {useModalControls} from '#/state/modals' 15 + import {Text} from '#/view/com/util/text/Text' 16 + 17 + export const snapPoints = ['0%'] 18 + 19 + export function Component({ 20 + uri, 21 + aspect, 22 + circular, 23 + onSelect, 24 + }: { 25 + uri: string 26 + aspect?: number 27 + circular?: boolean 28 + onSelect: (img?: RNImage) => void 29 + }) { 30 + const pal = usePalette('default') 31 + const {_} = useLingui() 32 + 33 + const {closeModal} = useModalControls() 34 + const {isMobile} = useWebMediaQueries() 35 + 36 + const imageRef = React.useRef<HTMLImageElement>(null) 37 + const [crop, setCrop] = React.useState<PercentCrop>() 38 + 39 + const isEmpty = !crop || (crop.width || crop.height) === 0 40 + 41 + const onPressCancel = () => { 42 + onSelect(undefined) 43 + closeModal() 44 + } 45 + const onPressDone = async () => { 46 + const img = imageRef.current! 47 + 48 + const result = await manipulateAsync( 49 + uri, 50 + isEmpty 51 + ? [] 52 + : [ 53 + { 54 + crop: { 55 + originX: (crop.x * img.naturalWidth) / 100, 56 + originY: (crop.y * img.naturalHeight) / 100, 57 + width: (crop.width * img.naturalWidth) / 100, 58 + height: (crop.height * img.naturalHeight) / 100, 59 + }, 60 + }, 61 + ], 62 + { 63 + base64: true, 64 + format: SaveFormat.JPEG, 65 + }, 66 + ) 67 + 68 + onSelect({ 69 + path: result.uri, 70 + mime: 'image/jpeg', 71 + size: result.base64 !== undefined ? getDataUriSize(result.base64) : 0, 72 + width: result.width, 73 + height: result.height, 74 + }) 75 + 76 + closeModal() 77 + } 78 + 79 + return ( 80 + <View> 81 + <View style={[styles.cropper, pal.borderDark]}> 82 + <ReactCrop 83 + aspect={aspect} 84 + crop={crop} 85 + onChange={(_pixelCrop, percentCrop) => setCrop(percentCrop)} 86 + circularCrop={circular}> 87 + <img ref={imageRef} src={uri} style={{maxHeight: '75vh'}} /> 88 + </ReactCrop> 89 + </View> 90 + <View style={[styles.btns, isMobile && {paddingHorizontal: 16}]}> 91 + <TouchableOpacity 92 + onPress={onPressCancel} 93 + accessibilityRole="button" 94 + accessibilityLabel={_(msg`Cancel image crop`)} 95 + accessibilityHint={_(msg`Exits image cropping process`)}> 96 + <Text type="xl" style={pal.link}> 97 + <Trans>Cancel</Trans> 98 + </Text> 99 + </TouchableOpacity> 100 + <View style={s.flex1} /> 101 + <TouchableOpacity 102 + onPress={onPressDone} 103 + accessibilityRole="button" 104 + accessibilityLabel={_(msg`Save image crop`)} 105 + accessibilityHint={_(msg`Saves image crop settings`)}> 106 + <LinearGradient 107 + colors={[gradients.blueLight.start, gradients.blueLight.end]} 108 + start={{x: 0, y: 0}} 109 + end={{x: 1, y: 1}} 110 + style={[styles.btn]}> 111 + <Text type="xl-medium" style={s.white}> 112 + <Trans>Done</Trans> 113 + </Text> 114 + </LinearGradient> 115 + </TouchableOpacity> 116 + </View> 117 + </View> 118 + ) 119 + } 120 + 121 + const styles = StyleSheet.create({ 122 + cropper: { 123 + marginLeft: 'auto', 124 + marginRight: 'auto', 125 + borderWidth: 1, 126 + borderRadius: 4, 127 + overflow: 'hidden', 128 + alignItems: 'center', 129 + }, 130 + ctrls: { 131 + flexDirection: 'row', 132 + alignItems: 'center', 133 + marginTop: 10, 134 + }, 135 + btns: { 136 + flexDirection: 'row', 137 + alignItems: 'center', 138 + marginTop: 10, 139 + }, 140 + btn: { 141 + borderRadius: 4, 142 + paddingVertical: 8, 143 + paddingHorizontal: 24, 144 + }, 145 + })
+1 -1
src/view/com/modals/Modal.web.tsx
··· 12 12 import * as ChangeHandleModal from './ChangeHandle' 13 13 import * as ChangePasswordModal from './ChangePassword' 14 14 import * as CreateOrEditListModal from './CreateOrEditList' 15 - import * as CropImageModal from './crop-image/CropImage.web' 15 + import * as CropImageModal from './CropImage.web' 16 16 import * as DeleteAccountModal from './DeleteAccount' 17 17 import * as EditProfileModal from './EditProfile' 18 18 import * as InviteCodesModal from './InviteCodes'
-228
src/view/com/modals/crop-image/CropImage.web.tsx
··· 1 - import React from 'react' 2 - import {StyleSheet, TouchableOpacity, View} from 'react-native' 3 - import {Image as RNImage} from 'react-native-image-crop-picker' 4 - import {LinearGradient} from 'expo-linear-gradient' 5 - import {msg, Trans} from '@lingui/macro' 6 - import {useLingui} from '@lingui/react' 7 - import {Slider} from '@miblanchard/react-native-slider' 8 - import ImageEditor from 'react-avatar-editor' 9 - 10 - import {useModalControls} from '#/state/modals' 11 - import {usePalette} from 'lib/hooks/usePalette' 12 - import {RectTallIcon, RectWideIcon, SquareIcon} from 'lib/icons' 13 - import {Dimensions} from 'lib/media/types' 14 - import {getDataUriSize} from 'lib/media/util' 15 - import {gradients, s} from 'lib/styles' 16 - import {Text} from 'view/com/util/text/Text' 17 - import {calculateDimensions} from './cropImageUtil' 18 - 19 - enum AspectRatio { 20 - Square = 'square', 21 - Wide = 'wide', 22 - Tall = 'tall', 23 - Custom = 'custom', 24 - } 25 - 26 - const DIMS: Record<string, Dimensions> = { 27 - [AspectRatio.Square]: {width: 1000, height: 1000}, 28 - [AspectRatio.Wide]: {width: 1000, height: 750}, 29 - [AspectRatio.Tall]: {width: 750, height: 1000}, 30 - } 31 - 32 - export const snapPoints = ['0%'] 33 - 34 - export function Component({ 35 - uri, 36 - dimensions, 37 - onSelect, 38 - }: { 39 - uri: string 40 - dimensions?: Dimensions 41 - onSelect: (img?: RNImage) => void 42 - }) { 43 - const {closeModal} = useModalControls() 44 - const pal = usePalette('default') 45 - const {_} = useLingui() 46 - const defaultAspectStyle = dimensions 47 - ? AspectRatio.Custom 48 - : AspectRatio.Square 49 - const [as, setAs] = React.useState<AspectRatio>(defaultAspectStyle) 50 - const [scale, setScale] = React.useState<number>(1) 51 - const editorRef = React.useRef<ImageEditor>(null) 52 - const imageEditorWidth = dimensions ? dimensions.width : DIMS[as].width 53 - const imageEditorHeight = dimensions ? dimensions.height : DIMS[as].height 54 - 55 - const doSetAs = (v: AspectRatio) => () => setAs(v) 56 - 57 - const onPressCancel = () => { 58 - onSelect(undefined) 59 - closeModal() 60 - } 61 - const onPressDone = () => { 62 - const canvas = editorRef.current?.getImageScaledToCanvas() 63 - if (canvas) { 64 - const dataUri = canvas.toDataURL('image/jpeg') 65 - onSelect({ 66 - path: dataUri, 67 - mime: 'image/jpeg', 68 - size: getDataUriSize(dataUri), 69 - width: imageEditorWidth, 70 - height: imageEditorHeight, 71 - }) 72 - } else { 73 - onSelect(undefined) 74 - } 75 - closeModal() 76 - } 77 - 78 - let cropperStyle 79 - if (as === AspectRatio.Square) { 80 - cropperStyle = styles.cropperSquare 81 - } else if (as === AspectRatio.Wide) { 82 - cropperStyle = styles.cropperWide 83 - } else if (as === AspectRatio.Tall) { 84 - cropperStyle = styles.cropperTall 85 - } else if (as === AspectRatio.Custom) { 86 - const cropperDimensions = calculateDimensions( 87 - 550, 88 - imageEditorHeight, 89 - imageEditorWidth, 90 - ) 91 - cropperStyle = { 92 - width: cropperDimensions.width, 93 - height: cropperDimensions.height, 94 - } 95 - } 96 - 97 - return ( 98 - <View> 99 - <View style={[styles.cropper, pal.borderDark, cropperStyle]}> 100 - <ImageEditor 101 - ref={editorRef} 102 - style={styles.imageEditor} 103 - image={uri} 104 - width={imageEditorWidth} 105 - height={imageEditorHeight} 106 - scale={scale} 107 - border={0} 108 - /> 109 - </View> 110 - <View style={styles.ctrls}> 111 - <Slider 112 - value={scale} 113 - onValueChange={(v: number | number[]) => 114 - setScale(Array.isArray(v) ? v[0] : v) 115 - } 116 - minimumValue={1} 117 - maximumValue={3} 118 - containerStyle={styles.slider} 119 - /> 120 - {as === AspectRatio.Custom ? null : ( 121 - <> 122 - <TouchableOpacity 123 - onPress={doSetAs(AspectRatio.Wide)} 124 - accessibilityRole="button" 125 - accessibilityLabel={_(msg`Wide`)} 126 - accessibilityHint={_(msg`Sets image aspect ratio to wide`)}> 127 - <RectWideIcon 128 - size={24} 129 - style={as === AspectRatio.Wide ? s.blue3 : pal.text} 130 - /> 131 - </TouchableOpacity> 132 - <TouchableOpacity 133 - onPress={doSetAs(AspectRatio.Tall)} 134 - accessibilityRole="button" 135 - accessibilityLabel={_(msg`Tall`)} 136 - accessibilityHint={_(msg`Sets image aspect ratio to tall`)}> 137 - <RectTallIcon 138 - size={24} 139 - style={as === AspectRatio.Tall ? s.blue3 : pal.text} 140 - /> 141 - </TouchableOpacity> 142 - <TouchableOpacity 143 - onPress={doSetAs(AspectRatio.Square)} 144 - accessibilityRole="button" 145 - accessibilityLabel={_(msg`Square`)} 146 - accessibilityHint={_(msg`Sets image aspect ratio to square`)}> 147 - <SquareIcon 148 - size={24} 149 - style={as === AspectRatio.Square ? s.blue3 : pal.text} 150 - /> 151 - </TouchableOpacity> 152 - </> 153 - )} 154 - </View> 155 - <View style={styles.btns}> 156 - <TouchableOpacity 157 - onPress={onPressCancel} 158 - accessibilityRole="button" 159 - accessibilityLabel={_(msg`Cancel image crop`)} 160 - accessibilityHint={_(msg`Exits image cropping process`)}> 161 - <Text type="xl" style={pal.link}> 162 - <Trans>Cancel</Trans> 163 - </Text> 164 - </TouchableOpacity> 165 - <View style={s.flex1} /> 166 - <TouchableOpacity 167 - onPress={onPressDone} 168 - accessibilityRole="button" 169 - accessibilityLabel={_(msg`Save image crop`)} 170 - accessibilityHint={_(msg`Saves image crop settings`)}> 171 - <LinearGradient 172 - colors={[gradients.blueLight.start, gradients.blueLight.end]} 173 - start={{x: 0, y: 0}} 174 - end={{x: 1, y: 1}} 175 - style={[styles.btn]}> 176 - <Text type="xl-medium" style={s.white}> 177 - <Trans>Done</Trans> 178 - </Text> 179 - </LinearGradient> 180 - </TouchableOpacity> 181 - </View> 182 - </View> 183 - ) 184 - } 185 - 186 - const styles = StyleSheet.create({ 187 - cropper: { 188 - marginLeft: 'auto', 189 - marginRight: 'auto', 190 - borderWidth: 1, 191 - borderRadius: 4, 192 - overflow: 'hidden', 193 - }, 194 - cropperSquare: { 195 - width: 400, 196 - height: 400, 197 - }, 198 - cropperWide: { 199 - width: 400, 200 - height: 300, 201 - }, 202 - cropperTall: { 203 - width: 300, 204 - height: 400, 205 - }, 206 - imageEditor: { 207 - maxWidth: '100%', 208 - }, 209 - ctrls: { 210 - flexDirection: 'row', 211 - alignItems: 'center', 212 - marginTop: 10, 213 - }, 214 - slider: { 215 - flex: 1, 216 - marginRight: 10, 217 - }, 218 - btns: { 219 - flexDirection: 'row', 220 - alignItems: 'center', 221 - marginTop: 10, 222 - }, 223 - btn: { 224 - borderRadius: 4, 225 - paddingVertical: 8, 226 - paddingHorizontal: 24, 227 - }, 228 - })
-13
src/view/com/modals/crop-image/cropImageUtil.ts
··· 1 - export const calculateDimensions = ( 2 - maxWidth: number, 3 - originalHeight: number, 4 - originalWidth: number, 5 - ) => { 6 - const aspectRatio = originalWidth / originalHeight 7 - const newHeight = maxWidth / aspectRatio 8 - const newWidth = maxWidth 9 - return { 10 - width: newWidth, 11 - height: newHeight, 12 - } 13 - }
+10 -8
src/view/com/util/UserAvatar.tsx
··· 8 8 import {useLingui} from '@lingui/react' 9 9 import {useQueryClient} from '@tanstack/react-query' 10 10 11 - import {logger} from '#/logger' 12 - import {usePalette} from 'lib/hooks/usePalette' 11 + import {usePalette} from '#/lib/hooks/usePalette' 13 12 import { 14 13 useCameraPermission, 15 14 usePhotoLibraryPermission, 16 - } from 'lib/hooks/usePermissions' 17 - import {makeProfileLink} from 'lib/routes/links' 18 - import {colors} from 'lib/styles' 19 - import {isAndroid, isNative, isWeb} from 'platform/detection' 20 - import {precacheProfile} from 'state/queries/profile' 21 - import {HighPriorityImage} from 'view/com/util/images/Image' 15 + } from '#/lib/hooks/usePermissions' 16 + import {makeProfileLink} from '#/lib/routes/links' 17 + import {colors} from '#/lib/styles' 18 + import {logger} from '#/logger' 19 + import {isAndroid, isNative, isWeb} from '#/platform/detection' 20 + import {precacheProfile} from '#/state/queries/profile' 21 + import {HighPriorityImage} from '#/view/com/util/images/Image' 22 22 import {tokens, useTheme} from '#/alf' 23 23 import { 24 24 Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled, ··· 321 321 height: 1000, 322 322 width: 1000, 323 323 path: item.path, 324 + webAspectRatio: 1, 325 + webCircularCrop: true, 324 326 }) 325 327 326 328 onSelectNewAvatar(croppedImage)
+8 -7
src/view/com/util/UserBanner.tsx
··· 6 6 import {msg, Trans} from '@lingui/macro' 7 7 import {useLingui} from '@lingui/react' 8 8 9 - import {logger} from '#/logger' 10 - import {usePalette} from 'lib/hooks/usePalette' 9 + import {usePalette} from '#/lib/hooks/usePalette' 11 10 import { 12 11 useCameraPermission, 13 12 usePhotoLibraryPermission, 14 - } from 'lib/hooks/usePermissions' 15 - import {colors} from 'lib/styles' 16 - import {useTheme} from 'lib/ThemeContext' 17 - import {isAndroid, isNative} from 'platform/detection' 18 - import {EventStopper} from 'view/com/util/EventStopper' 13 + } from '#/lib/hooks/usePermissions' 14 + import {colors} from '#/lib/styles' 15 + import {useTheme} from '#/lib/ThemeContext' 16 + import {logger} from '#/logger' 17 + import {isAndroid, isNative} from '#/platform/detection' 18 + import {EventStopper} from '#/view/com/util/EventStopper' 19 19 import {tokens, useTheme as useAlfTheme} from '#/alf' 20 20 import { 21 21 Camera_Filled_Stroke2_Corner0_Rounded as CameraFilled, ··· 72 72 path: items[0].path, 73 73 width: 3000, 74 74 height: 1000, 75 + webAspectRatio: 3, 75 76 }), 76 77 ) 77 78 } catch (e: any) {
+6 -17
yarn.lock
··· 2570 2570 dependencies: 2571 2571 "@babel/helper-plugin-utils" "^7.22.5" 2572 2572 2573 - "@babel/plugin-transform-runtime@^7.0.0", "@babel/plugin-transform-runtime@^7.12.1", "@babel/plugin-transform-runtime@^7.16.4": 2573 + "@babel/plugin-transform-runtime@^7.0.0", "@babel/plugin-transform-runtime@^7.16.4": 2574 2574 version "7.22.10" 2575 2575 resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.10.tgz#89eda6daf1d3af6f36fb368766553054c8d7cd46" 2576 2576 integrity sha512-RchI7HePu1eu0CYNKHHHQdfenZcM4nz8rew5B1VWqeRKdcwW5aQ5HeG9eTUbWiAS1UrmHVLmoxTWHt3iLD/NhA== ··· 8261 8261 version "1.2.4" 8262 8262 resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" 8263 8263 integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== 8264 - 8265 - "@types/react-avatar-editor@^13.0.0": 8266 - version "13.0.0" 8267 - resolved "https://registry.yarnpkg.com/@types/react-avatar-editor/-/react-avatar-editor-13.0.0.tgz#5963e16c931746c47e478d669dd72d388b427393" 8268 - integrity sha512-5ymOayy6mfT35xTqzni7UjXvCNEg8/pH4pI5RenITp9PBc02KGTYjSV1WboXiQDYSh5KomLT0ngBLEAIhV1QoQ== 8269 - dependencies: 8270 - "@types/react" "*" 8271 8264 8272 8265 "@types/react-dom@^18.2.18": 8273 8266 version "18.2.18" ··· 18935 18928 regenerator-runtime "^0.13.9" 18936 18929 whatwg-fetch "^3.6.2" 18937 18930 18938 - react-avatar-editor@^13.0.0: 18939 - version "13.0.0" 18940 - resolved "https://registry.yarnpkg.com/react-avatar-editor/-/react-avatar-editor-13.0.0.tgz#55013625ee9ae715c1fe2dc553b8079994d8a5f2" 18941 - integrity sha512-0xw63MbRRQdDy7YI1IXU9+7tTFxYEFLV8CABvryYOGjZmXRTH2/UA0mafe57ns62uaEFX181kA4XlGlxCaeXKA== 18942 - dependencies: 18943 - "@babel/plugin-transform-runtime" "^7.12.1" 18944 - "@babel/runtime" "^7.12.5" 18945 - prop-types "^15.7.2" 18946 - 18947 18931 "react-compiler-runtime@file:./lib/react-compiler-runtime": 18948 18932 version "0.0.1" 18949 18933 ··· 19002 18986 version "1.0.3" 19003 18987 resolved "https://registry.yarnpkg.com/react-freeze/-/react-freeze-1.0.3.tgz#5e3ca90e682fed1d73a7cb50c2c7402b3e85618d" 19004 18988 integrity sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g== 18989 + 18990 + react-image-crop@^11.0.7: 18991 + version "11.0.7" 18992 + resolved "https://registry.yarnpkg.com/react-image-crop/-/react-image-crop-11.0.7.tgz#25f3d37ccbb65a05d19d23b4740a5912835c741e" 18993 + integrity sha512-ZciKWHDYzmm366JDL18CbrVyjnjH0ojufGDmScfS4ZUqLHg4nm6ATY+K62C75W4ZRNt4Ii+tX0bSjNk9LQ2xzQ== 19005 18994 19006 18995 "react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0: 19007 18996 version "18.2.0"