Image editor mobile layout update (#613)
* Image editor mobile layout update * Minor viewport fixzio/stable
parent
aa786068cf
commit
e2055dfb78
|
@ -52,16 +52,14 @@ export class GalleryModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
async edit(image: ImageModel) {
|
async edit(image: ImageModel) {
|
||||||
if (!isNative) {
|
if (isNative) {
|
||||||
|
this.crop(image)
|
||||||
|
} else {
|
||||||
this.rootStore.shell.openModal({
|
this.rootStore.shell.openModal({
|
||||||
name: 'edit-image',
|
name: 'edit-image',
|
||||||
image,
|
image,
|
||||||
gallery: this,
|
gallery: this,
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
this.crop(image)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,12 +13,12 @@ import {compressAndResizeImageForPost} from 'lib/media/manip'
|
||||||
// Cases to consider: ExternalEmbed
|
// Cases to consider: ExternalEmbed
|
||||||
|
|
||||||
export interface ImageManipulationAttributes {
|
export interface ImageManipulationAttributes {
|
||||||
|
aspectRatio?: '4:3' | '1:1' | '3:4' | 'None'
|
||||||
rotate?: number
|
rotate?: number
|
||||||
scale?: number
|
scale?: number
|
||||||
position?: Position
|
position?: Position
|
||||||
flipHorizontal?: boolean
|
flipHorizontal?: boolean
|
||||||
flipVertical?: boolean
|
flipVertical?: boolean
|
||||||
aspectRatio?: '4:3' | '1:1' | '3:4' | 'None'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ImageModel implements RNImage {
|
export class ImageModel implements RNImage {
|
||||||
|
@ -34,14 +34,14 @@ export class ImageModel implements RNImage {
|
||||||
scaledHeight: number = POST_IMG_MAX.height
|
scaledHeight: number = POST_IMG_MAX.height
|
||||||
|
|
||||||
// Web manipulation
|
// Web manipulation
|
||||||
aspectRatio?: ImageManipulationAttributes['aspectRatio']
|
prev?: RNImage
|
||||||
position?: Position = undefined
|
attributes: ImageManipulationAttributes = {
|
||||||
prev?: RNImage = undefined
|
aspectRatio: '1:1',
|
||||||
rotation?: number = 0
|
scale: 1,
|
||||||
scale?: number = 1
|
flipHorizontal: false,
|
||||||
flipHorizontal?: boolean = false
|
flipVertical: false,
|
||||||
flipVertical?: boolean = false
|
rotate: 0,
|
||||||
|
}
|
||||||
prevAttributes: ImageManipulationAttributes = {}
|
prevAttributes: ImageManipulationAttributes = {}
|
||||||
|
|
||||||
constructor(public rootStore: RootStoreModel, image: RNImage) {
|
constructor(public rootStore: RootStoreModel, image: RNImage) {
|
||||||
|
@ -65,6 +65,25 @@ export class ImageModel implements RNImage {
|
||||||
// : MAX_IMAGE_SIZE_IN_BYTES / this.size
|
// : MAX_IMAGE_SIZE_IN_BYTES / this.size
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
setRatio(aspectRatio: ImageManipulationAttributes['aspectRatio']) {
|
||||||
|
this.attributes.aspectRatio = aspectRatio
|
||||||
|
}
|
||||||
|
|
||||||
|
setRotate(degrees: number) {
|
||||||
|
this.attributes.rotate = degrees
|
||||||
|
this.manipulate({})
|
||||||
|
}
|
||||||
|
|
||||||
|
flipVertical() {
|
||||||
|
this.attributes.flipVertical = !this.attributes.flipVertical
|
||||||
|
this.manipulate({})
|
||||||
|
}
|
||||||
|
|
||||||
|
flipHorizontal() {
|
||||||
|
this.attributes.flipHorizontal = !this.attributes.flipHorizontal
|
||||||
|
this.manipulate({})
|
||||||
|
}
|
||||||
|
|
||||||
get ratioMultipliers() {
|
get ratioMultipliers() {
|
||||||
return {
|
return {
|
||||||
'4:3': 4 / 3,
|
'4:3': 4 / 3,
|
||||||
|
@ -162,33 +181,19 @@ export class ImageModel implements RNImage {
|
||||||
crop?: ActionCrop['crop']
|
crop?: ActionCrop['crop']
|
||||||
} & ImageManipulationAttributes,
|
} & ImageManipulationAttributes,
|
||||||
) {
|
) {
|
||||||
const {aspectRatio, crop, flipHorizontal, flipVertical, rotate, scale} =
|
const {aspectRatio, crop, position, scale} = attributes
|
||||||
attributes
|
|
||||||
const modifiers = []
|
const modifiers = []
|
||||||
|
|
||||||
if (flipHorizontal !== undefined) {
|
if (this.attributes.flipHorizontal) {
|
||||||
this.flipHorizontal = flipHorizontal
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flipVertical !== undefined) {
|
|
||||||
this.flipVertical = flipVertical
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.flipHorizontal) {
|
|
||||||
modifiers.push({flip: FlipType.Horizontal})
|
modifiers.push({flip: FlipType.Horizontal})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.flipVertical) {
|
if (this.attributes.flipVertical) {
|
||||||
modifiers.push({flip: FlipType.Vertical})
|
modifiers.push({flip: FlipType.Vertical})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Fix rotation -- currently not functional
|
if (this.attributes.rotate !== undefined) {
|
||||||
if (rotate !== undefined) {
|
modifiers.push({rotate: this.attributes.rotate})
|
||||||
this.rotation = rotate
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.rotation !== undefined) {
|
|
||||||
modifiers.push({rotate: this.rotation})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (crop !== undefined) {
|
if (crop !== undefined) {
|
||||||
|
@ -203,18 +208,21 @@ export class ImageModel implements RNImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scale !== undefined) {
|
if (scale !== undefined) {
|
||||||
this.scale = scale
|
this.attributes.scale = scale
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position !== undefined) {
|
||||||
|
this.attributes.position = position
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aspectRatio !== undefined) {
|
if (aspectRatio !== undefined) {
|
||||||
this.aspectRatio = aspectRatio
|
this.attributes.aspectRatio = aspectRatio
|
||||||
}
|
}
|
||||||
|
|
||||||
const ratioMultiplier = this.ratioMultipliers[this.aspectRatio ?? '1:1']
|
const ratioMultiplier =
|
||||||
|
this.ratioMultipliers[this.attributes.aspectRatio ?? '1:1']
|
||||||
|
|
||||||
// TODO: Ollie - should support up to 2000 but smaller images that scale
|
const MAX_SIDE = 2000
|
||||||
// up need an updated compression factor calculation. Use 1000 for now.
|
|
||||||
const MAX_SIDE = 1000
|
|
||||||
|
|
||||||
const result = await ImageManipulator.manipulateAsync(
|
const result = await ImageManipulator.manipulateAsync(
|
||||||
this.path,
|
this.path,
|
||||||
|
@ -223,7 +231,7 @@ export class ImageModel implements RNImage {
|
||||||
{resize: ratioMultiplier > 1 ? {width: MAX_SIDE} : {height: MAX_SIDE}},
|
{resize: ratioMultiplier > 1 ? {width: MAX_SIDE} : {height: MAX_SIDE}},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
compress: 0.7, // TODO: revisit compression calculation
|
compress: 0.9,
|
||||||
format: SaveFormat.JPEG,
|
format: SaveFormat.JPEG,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -238,16 +246,12 @@ export class ImageModel implements RNImage {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetCompressed() {
|
||||||
|
this.manipulate({})
|
||||||
|
}
|
||||||
|
|
||||||
previous() {
|
previous() {
|
||||||
this.compressed = this.prev
|
this.compressed = this.prev
|
||||||
|
this.attributes = this.prevAttributes
|
||||||
const {flipHorizontal, flipVertical, rotate, position, scale} =
|
|
||||||
this.prevAttributes
|
|
||||||
|
|
||||||
this.scale = scale
|
|
||||||
this.rotation = rotate
|
|
||||||
this.flipHorizontal = flipHorizontal
|
|
||||||
this.flipVertical = flipVertical
|
|
||||||
this.position = position
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,148 +18,114 @@ import {Slider} from '@miblanchard/react-native-slider'
|
||||||
import {MaterialIcons} from '@expo/vector-icons'
|
import {MaterialIcons} from '@expo/vector-icons'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {getKeys} from 'lib/type-assertions'
|
import {getKeys} from 'lib/type-assertions'
|
||||||
|
import {isDesktopWeb} from 'platform/detection'
|
||||||
|
|
||||||
export const snapPoints = ['80%']
|
export const snapPoints = ['80%']
|
||||||
|
|
||||||
|
const RATIOS = {
|
||||||
|
'4:3': {
|
||||||
|
Icon: RectWideIcon,
|
||||||
|
},
|
||||||
|
'1:1': {
|
||||||
|
Icon: SquareIcon,
|
||||||
|
},
|
||||||
|
'3:4': {
|
||||||
|
Icon: RectTallIcon,
|
||||||
|
},
|
||||||
|
None: {
|
||||||
|
label: 'None',
|
||||||
|
Icon: MaterialIcons,
|
||||||
|
name: 'do-not-disturb-alt',
|
||||||
|
},
|
||||||
|
} as const
|
||||||
|
|
||||||
|
type AspectRatio = keyof typeof RATIOS
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
image: ImageModel
|
image: ImageModel
|
||||||
gallery: GalleryModel
|
gallery: GalleryModel
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is only used for desktop web
|
|
||||||
export const Component = observer(function ({image, gallery}: Props) {
|
export const Component = observer(function ({image, gallery}: Props) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const store = useStores()
|
|
||||||
const {shell} = store
|
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const winDim = useWindowDimensions()
|
const store = useStores()
|
||||||
|
const windowDimensions = useWindowDimensions()
|
||||||
|
|
||||||
const [altText, setAltText] = useState(image.altText)
|
const {
|
||||||
const [aspectRatio, setAspectRatio] = useState<AspectRatio>(
|
aspectRatio,
|
||||||
image.aspectRatio ?? 'None',
|
// rotate = 0
|
||||||
)
|
} = image.attributes
|
||||||
const [flipHorizontal, setFlipHorizontal] = useState<boolean>(
|
|
||||||
image.flipHorizontal ?? false,
|
|
||||||
)
|
|
||||||
const [flipVertical, setFlipVertical] = useState<boolean>(
|
|
||||||
image.flipVertical ?? false,
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: doesn't seem to be working correctly with crop
|
|
||||||
// const [rotation, setRotation] = useState(image.rotation ?? 0)
|
|
||||||
const [scale, setScale] = useState<number>(image.scale ?? 1)
|
|
||||||
const [position, setPosition] = useState<Position>()
|
|
||||||
const [isEditing, setIsEditing] = useState(false)
|
|
||||||
const editorRef = useRef<ImageEditor>(null)
|
const editorRef = useRef<ImageEditor>(null)
|
||||||
|
const [scale, setScale] = useState<number>(image.attributes.scale ?? 1)
|
||||||
const imgEditorStyles = useMemo(() => {
|
const [position, setPosition] = useState<Position | undefined>(
|
||||||
const dim = Math.min(425, winDim.width - 24)
|
image.attributes.position,
|
||||||
return {width: dim, height: dim}
|
|
||||||
}, [winDim.width])
|
|
||||||
|
|
||||||
const manipulationAttributes = useMemo(
|
|
||||||
() => ({
|
|
||||||
// TODO: doesn't seem to be working correctly with crop
|
|
||||||
// ...(rotation !== undefined ? {rotate: rotation} : {}),
|
|
||||||
...(flipHorizontal !== undefined ? {flipHorizontal} : {}),
|
|
||||||
...(flipVertical !== undefined ? {flipVertical} : {}),
|
|
||||||
}),
|
|
||||||
[flipHorizontal, flipVertical],
|
|
||||||
)
|
)
|
||||||
|
const [altText, setAltText] = useState('')
|
||||||
useEffect(() => {
|
|
||||||
const manipulateImage = async () => {
|
|
||||||
await image.manipulate(manipulationAttributes)
|
|
||||||
}
|
|
||||||
|
|
||||||
manipulateImage()
|
|
||||||
}, [image, manipulationAttributes])
|
|
||||||
|
|
||||||
const ratios = useMemo(
|
|
||||||
() =>
|
|
||||||
({
|
|
||||||
'4:3': {
|
|
||||||
hint: 'Sets image aspect ratio to wide',
|
|
||||||
Icon: RectWideIcon,
|
|
||||||
},
|
|
||||||
'1:1': {
|
|
||||||
hint: 'Sets image aspect ratio to square',
|
|
||||||
Icon: SquareIcon,
|
|
||||||
},
|
|
||||||
'3:4': {
|
|
||||||
hint: 'Sets image aspect ratio to tall',
|
|
||||||
Icon: RectTallIcon,
|
|
||||||
},
|
|
||||||
None: {
|
|
||||||
label: 'None',
|
|
||||||
hint: 'Sets image aspect ratio to tall',
|
|
||||||
Icon: MaterialIcons,
|
|
||||||
name: 'do-not-disturb-alt',
|
|
||||||
},
|
|
||||||
} as const),
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
|
|
||||||
type AspectRatio = keyof typeof ratios
|
|
||||||
|
|
||||||
const onFlipHorizontal = useCallback(() => {
|
const onFlipHorizontal = useCallback(() => {
|
||||||
setFlipHorizontal(!flipHorizontal)
|
image.flipHorizontal()
|
||||||
image.manipulate({flipHorizontal})
|
}, [image])
|
||||||
}, [flipHorizontal, image])
|
|
||||||
|
|
||||||
const onFlipVertical = useCallback(() => {
|
const onFlipVertical = useCallback(() => {
|
||||||
setFlipVertical(!flipVertical)
|
image.flipVertical()
|
||||||
image.manipulate({flipVertical})
|
}, [image])
|
||||||
}, [flipVertical, image])
|
|
||||||
|
// const onSetRotate = useCallback(
|
||||||
|
// (direction: 'left' | 'right') => {
|
||||||
|
// const rotation = (rotate + 90 * (direction === 'left' ? -1 : 1)) % 360
|
||||||
|
// image.setRotate(rotation)
|
||||||
|
// },
|
||||||
|
// [rotate, image],
|
||||||
|
// )
|
||||||
|
|
||||||
|
const onSetRatio = useCallback(
|
||||||
|
(ratio: AspectRatio) => {
|
||||||
|
image.setRatio(ratio)
|
||||||
|
},
|
||||||
|
[image],
|
||||||
|
)
|
||||||
|
|
||||||
const adjustments = useMemo(
|
const adjustments = useMemo(
|
||||||
() =>
|
() => [
|
||||||
[
|
|
||||||
// {
|
// {
|
||||||
// name: 'rotate-left',
|
// name: 'rotate-left' as const,
|
||||||
// label: 'Rotate left',
|
// label: 'Rotate left',
|
||||||
// hint: 'Rotate image left',
|
|
||||||
// onPress: () => {
|
// onPress: () => {
|
||||||
// const rotate = (rotation - 90) % 360
|
// onSetRotate('left')
|
||||||
// setRotation(rotate)
|
|
||||||
// image.manipulate({rotate})
|
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
// {
|
// {
|
||||||
// name: 'rotate-right',
|
// name: 'rotate-right' as const,
|
||||||
// label: 'Rotate right',
|
// label: 'Rotate right',
|
||||||
// hint: 'Rotate image right',
|
|
||||||
// onPress: () => {
|
// onPress: () => {
|
||||||
// const rotate = (rotation + 90) % 360
|
// onSetRotate('right')
|
||||||
// setRotation(rotate)
|
|
||||||
// image.manipulate({rotate})
|
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
name: 'flip',
|
name: 'flip' as const,
|
||||||
label: 'Flip horizontal',
|
label: 'Flip horizontal',
|
||||||
hint: 'Flip image horizontally',
|
|
||||||
onPress: onFlipHorizontal,
|
onPress: onFlipHorizontal,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'flip',
|
name: 'flip' as const,
|
||||||
label: 'Flip vertically',
|
label: 'Flip vertically',
|
||||||
hint: 'Flip image vertically',
|
|
||||||
onPress: onFlipVertical,
|
onPress: onFlipVertical,
|
||||||
},
|
},
|
||||||
] as const,
|
],
|
||||||
[onFlipHorizontal, onFlipVertical],
|
[onFlipHorizontal, onFlipVertical],
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
image.prev = image.compressed
|
image.prev = image.compressed
|
||||||
setIsEditing(true)
|
image.prevAttributes = image.attributes
|
||||||
|
image.resetCompressed()
|
||||||
}, [image])
|
}, [image])
|
||||||
|
|
||||||
const onCloseModal = useCallback(() => {
|
const onCloseModal = useCallback(() => {
|
||||||
shell.closeModal()
|
store.shell.closeModal()
|
||||||
setIsEditing(false)
|
}, [store.shell])
|
||||||
}, [shell])
|
|
||||||
|
|
||||||
const onPressCancel = useCallback(async () => {
|
const onPressCancel = useCallback(async () => {
|
||||||
await gallery.previous(image)
|
await gallery.previous(image)
|
||||||
|
@ -184,25 +150,12 @@ export const Component = observer(function ({image, gallery}: Props) {
|
||||||
...(position !== undefined ? {position} : {}),
|
...(position !== undefined ? {position} : {}),
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...manipulationAttributes,
|
|
||||||
aspectRatio,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
image.prevAttributes = manipulationAttributes
|
image.prev = image.compressed
|
||||||
|
image.prevAttributes = image.attributes
|
||||||
onCloseModal()
|
onCloseModal()
|
||||||
}, [
|
}, [altText, image, position, scale, onCloseModal])
|
||||||
altText,
|
|
||||||
aspectRatio,
|
|
||||||
image,
|
|
||||||
manipulationAttributes,
|
|
||||||
position,
|
|
||||||
scale,
|
|
||||||
onCloseModal,
|
|
||||||
])
|
|
||||||
|
|
||||||
const onPressRatio = useCallback((as: AspectRatio) => {
|
|
||||||
setAspectRatio(as)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const getLabelIconSize = useCallback((as: AspectRatio) => {
|
const getLabelIconSize = useCallback((as: AspectRatio) => {
|
||||||
switch (as) {
|
switch (as) {
|
||||||
|
@ -220,26 +173,35 @@ export const Component = observer(function ({image, gallery}: Props) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const {width, height} = image.getDisplayDimensions(
|
const computedWidth =
|
||||||
aspectRatio,
|
windowDimensions.width > 500 ? 410 : windowDimensions.width - 80
|
||||||
imgEditorStyles.width,
|
const sideLength = isDesktopWeb ? 300 : computedWidth
|
||||||
)
|
|
||||||
|
const dimensions = image.getDisplayDimensions(aspectRatio, sideLength)
|
||||||
|
const imgContainerStyles = {width: sideLength, height: sideLength}
|
||||||
|
|
||||||
|
const imgControlStyles = {
|
||||||
|
alignItems: 'center' as const,
|
||||||
|
flexDirection: isDesktopWeb ? ('row' as const) : ('column' as const),
|
||||||
|
gap: isDesktopWeb ? 5 : 0,
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View testID="editImageModal" style={[pal.view, styles.container, s.flex1]}>
|
<View testID="editImageModal" style={[pal.view, styles.container, s.flex1]}>
|
||||||
<Text style={[styles.title, pal.text]}>Edit image</Text>
|
<Text style={[styles.title, pal.text]}>Edit image</Text>
|
||||||
|
<View style={[styles.gap18, s.flexRow]}>
|
||||||
<View>
|
<View>
|
||||||
<View style={[styles.imgContainer, imgEditorStyles, pal.borderDark]}>
|
<View
|
||||||
|
style={[styles.imgContainer, pal.borderDark, imgContainerStyles]}>
|
||||||
<ImageEditor
|
<ImageEditor
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
style={styles.imgEditor}
|
style={styles.imgEditor}
|
||||||
image={isEditing ? image.compressed.path : image.path}
|
image={image.compressed.path}
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
scale={scale}
|
scale={scale}
|
||||||
border={0}
|
border={0}
|
||||||
position={position}
|
position={position}
|
||||||
onPositionChange={setPosition}
|
onPositionChange={setPosition}
|
||||||
|
{...dimensions}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Slider
|
<Slider
|
||||||
|
@ -250,10 +212,16 @@ export const Component = observer(function ({image, gallery}: Props) {
|
||||||
minimumValue={1}
|
minimumValue={1}
|
||||||
maximumValue={3}
|
maximumValue={3}
|
||||||
/>
|
/>
|
||||||
<View style={[s.flexRow, styles.gap18]}>
|
</View>
|
||||||
<View style={styles.imgControls}>
|
<View>
|
||||||
{getKeys(ratios).map(ratio => {
|
{isDesktopWeb ? (
|
||||||
const {hint, Icon, ...props} = ratios[ratio]
|
<Text type="sm-bold" style={pal.text}>
|
||||||
|
Ratios
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
<View style={imgControlStyles}>
|
||||||
|
{getKeys(RATIOS).map(ratio => {
|
||||||
|
const {Icon, ...props} = RATIOS[ratio]
|
||||||
const labelIconSize = getLabelIconSize(ratio)
|
const labelIconSize = getLabelIconSize(ratio)
|
||||||
const isSelected = aspectRatio === ratio
|
const isSelected = aspectRatio === ratio
|
||||||
|
|
||||||
|
@ -261,10 +229,10 @@ export const Component = observer(function ({image, gallery}: Props) {
|
||||||
<Pressable
|
<Pressable
|
||||||
key={ratio}
|
key={ratio}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
onPressRatio(ratio)
|
onSetRatio(ratio)
|
||||||
}}
|
}}
|
||||||
accessibilityLabel={ratio}
|
accessibilityLabel={ratio}
|
||||||
accessibilityHint={hint}>
|
accessibilityHint="">
|
||||||
<Icon
|
<Icon
|
||||||
size={labelIconSize}
|
size={labelIconSize}
|
||||||
style={[styles.imgControl, isSelected ? s.blue3 : pal.text]}
|
style={[styles.imgControl, isSelected ? s.blue3 : pal.text]}
|
||||||
|
@ -281,18 +249,22 @@ export const Component = observer(function ({image, gallery}: Props) {
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</View>
|
</View>
|
||||||
<View style={[styles.verticalSep, pal.border]} />
|
{isDesktopWeb ? (
|
||||||
<View style={styles.imgControls}>
|
<Text type="sm-bold" style={[pal.text, styles.subsection]}>
|
||||||
{adjustments.map(({label, hint, name, onPress}) => (
|
Transformations
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
<View style={imgControlStyles}>
|
||||||
|
{adjustments.map(({label, name, onPress}) => (
|
||||||
<Pressable
|
<Pressable
|
||||||
key={label}
|
key={label}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
accessibilityLabel={label}
|
accessibilityLabel={label}
|
||||||
accessibilityHint={hint}
|
accessibilityHint=""
|
||||||
style={styles.flipBtn}>
|
style={styles.flipBtn}>
|
||||||
<MaterialIcons
|
<MaterialIcons
|
||||||
name={name}
|
name={name}
|
||||||
size={label.startsWith('Flip') ? 22 : 24}
|
size={label?.startsWith('Flip') ? 22 : 24}
|
||||||
style={[
|
style={[
|
||||||
pal.text,
|
pal.text,
|
||||||
label === 'Flip vertically'
|
label === 'Flip vertically'
|
||||||
|
@ -305,7 +277,10 @@ export const Component = observer(function ({image, gallery}: Props) {
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View style={[styles.gap18]}>
|
<View style={[styles.gap18, styles.bottomSection, pal.border]}>
|
||||||
|
<Text type="sm-bold" style={pal.text} nativeID="alt-text">
|
||||||
|
Accessibility
|
||||||
|
</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
testID="altTextImageInput"
|
testID="altTextImageInput"
|
||||||
style={[styles.textArea, pal.border, pal.text]}
|
style={[styles.textArea, pal.border, pal.text]}
|
||||||
|
@ -313,11 +288,9 @@ export const Component = observer(function ({image, gallery}: Props) {
|
||||||
multiline
|
multiline
|
||||||
value={altText}
|
value={altText}
|
||||||
onChangeText={text => setAltText(enforceLen(text, MAX_ALT_TEXT))}
|
onChangeText={text => setAltText(enforceLen(text, MAX_ALT_TEXT))}
|
||||||
placeholder="Image description"
|
accessibilityLabel="Alt text"
|
||||||
placeholderTextColor={pal.colors.textLight}
|
accessibilityHint=""
|
||||||
accessibilityLabel="Image alt text"
|
accessibilityLabelledBy="alt-text"
|
||||||
accessibilityHint="Sets image alt text for screenreaders"
|
|
||||||
accessibilityLabelledBy="imageAltText"
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.btns}>
|
<View style={styles.btns}>
|
||||||
|
@ -345,30 +318,16 @@ export const Component = observer(function ({image, gallery}: Props) {
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
gap: 18,
|
gap: 18,
|
||||||
paddingVertical: 18,
|
paddingHorizontal: isDesktopWeb ? undefined : 16,
|
||||||
paddingHorizontal: 12,
|
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
gap18: {
|
subsection: {marginTop: 12},
|
||||||
gap: 18,
|
gap18: {gap: 18},
|
||||||
},
|
|
||||||
|
|
||||||
title: {
|
title: {
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
},
|
},
|
||||||
|
|
||||||
textArea: {
|
|
||||||
borderWidth: 1,
|
|
||||||
borderRadius: 6,
|
|
||||||
paddingTop: 10,
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
fontSize: 16,
|
|
||||||
height: 100,
|
|
||||||
textAlignVertical: 'top',
|
|
||||||
},
|
|
||||||
|
|
||||||
btns: {
|
btns: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
@ -379,28 +338,12 @@ const styles = StyleSheet.create({
|
||||||
paddingVertical: 8,
|
paddingVertical: 8,
|
||||||
paddingHorizontal: 24,
|
paddingHorizontal: 24,
|
||||||
},
|
},
|
||||||
|
|
||||||
verticalSep: {
|
|
||||||
borderLeftWidth: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
imgControls: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
gap: 5,
|
|
||||||
},
|
|
||||||
imgControl: {
|
imgControl: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
height: 40,
|
height: 40,
|
||||||
},
|
},
|
||||||
flipVertical: {
|
|
||||||
transform: [{rotate: '90deg'}],
|
|
||||||
},
|
|
||||||
flipBtn: {
|
|
||||||
paddingHorizontal: 4,
|
|
||||||
paddingVertical: 8,
|
|
||||||
},
|
|
||||||
imgEditor: {
|
imgEditor: {
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
},
|
},
|
||||||
|
@ -408,11 +351,29 @@ const styles = StyleSheet.create({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
height: 425,
|
|
||||||
width: 425,
|
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderRadius: 8,
|
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
overflow: 'hidden',
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
flipVertical: {
|
||||||
|
transform: [{rotate: '90deg'}],
|
||||||
|
},
|
||||||
|
flipBtn: {
|
||||||
|
paddingHorizontal: 4,
|
||||||
|
paddingVertical: 8,
|
||||||
|
},
|
||||||
|
textArea: {
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 6,
|
||||||
|
paddingTop: 10,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
fontSize: 16,
|
||||||
|
height: 100,
|
||||||
|
textAlignVertical: 'top',
|
||||||
|
maxHeight: isDesktopWeb ? undefined : 50,
|
||||||
|
},
|
||||||
|
bottomSection: {
|
||||||
|
borderTopWidth: 1,
|
||||||
|
paddingTop: 18,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue