From e44de946ac33c658ad1260b31ef55dd3db959869 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Fri, 4 Oct 2024 15:51:53 -0500 Subject: [PATCH 1/7] Add alt text limit to image dialog --- src/lib/constants.ts | 2 +- .../composer/char-progress/CharProgress.tsx | 26 +++---- .../composer/photos/ImageAltTextDialog.tsx | 72 ++++++++++++++----- 3 files changed, 67 insertions(+), 33 deletions(-) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 9bf1fb35ea..0edc9f2ad2 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -50,7 +50,7 @@ export const MAX_DM_GRAPHEME_LENGTH = 1000 // Recommended is 100 per: https://www.w3.org/WAI/GL/WCAG20/tests/test3.html // but increasing limit per user feedback -export const MAX_ALT_TEXT = 1000 +export const MAX_ALT_TEXT = 2000 export function IS_TEST_USER(handle?: string) { return handle && handle?.endsWith('.test') diff --git a/src/view/com/composer/char-progress/CharProgress.tsx b/src/view/com/composer/char-progress/CharProgress.tsx index a205fe0963..fdfe354e88 100644 --- a/src/view/com/composer/char-progress/CharProgress.tsx +++ b/src/view/com/composer/char-progress/CharProgress.tsx @@ -5,33 +5,29 @@ import ProgressCircle from 'react-native-progress/Circle' // @ts-ignore no type definition -prf import ProgressPie from 'react-native-progress/Pie' -import {MAX_GRAPHEME_LENGTH} from 'lib/constants' -import {usePalette} from 'lib/hooks/usePalette' -import {s} from 'lib/styles' +import {MAX_GRAPHEME_LENGTH} from '#/lib/constants' +import {usePalette} from '#/lib/hooks/usePalette' +import {s} from '#/lib/styles' import {Text} from '../../util/text/Text' -const DANGER_LENGTH = MAX_GRAPHEME_LENGTH - -export function CharProgress({count}: {count: number}) { +export function CharProgress({count, max}: {count: number; max?: number}) { + const maxLength = max || MAX_GRAPHEME_LENGTH const pal = usePalette('default') - const textColor = count > DANGER_LENGTH ? '#e60000' : pal.colors.text - const circleColor = count > DANGER_LENGTH ? '#e60000' : pal.colors.link + const textColor = count > maxLength ? '#e60000' : pal.colors.text + const circleColor = count > maxLength ? '#e60000' : pal.colors.link return ( <> - {MAX_GRAPHEME_LENGTH - count} + {maxLength - count} - {count > DANGER_LENGTH ? ( + {count > maxLength ? ( ) : ( )} diff --git a/src/view/com/composer/photos/ImageAltTextDialog.tsx b/src/view/com/composer/photos/ImageAltTextDialog.tsx index 16ce4351af..0f9c463b5d 100644 --- a/src/view/com/composer/photos/ImageAltTextDialog.tsx +++ b/src/view/com/composer/photos/ImageAltTextDialog.tsx @@ -5,12 +5,15 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {MAX_ALT_TEXT} from '#/lib/constants' +import {enforceLen} from '#/lib/strings/helpers' import {isAndroid, isWeb} from '#/platform/detection' import {ComposerImage} from '#/state/gallery' +import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import * as TextField from '#/components/forms/TextField' +import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' import {PortalComponent} from '#/components/Portal' import {Text} from '#/components/Typography' @@ -35,7 +38,7 @@ const ImageAltTextInner = ({ image, onChange, }: Props): React.ReactNode => { - const {_} = useLingui() + const {_, i18n} = useLingui() const t = useTheme() const windim = useWindowDimensions() @@ -44,7 +47,7 @@ const ImageAltTextInner = ({ const onPressSubmit = React.useCallback(() => { control.close() - onChange({...image, alt: altText.trim()}) + onChange({...image, alt: enforceLen(altText.trim(), MAX_ALT_TEXT, true)}) }, [control, image, altText, onChange]) const imageStyle = React.useMemo(() => { @@ -90,24 +93,59 @@ const ImageAltTextInner = ({ - - - Descriptive alt text - - - setAltText(text)} - value={altText} - multiline - numberOfLines={3} - autoFocus - /> - + + + + Descriptive alt text + + + setAltText(text)} + value={altText} + multiline + numberOfLines={3} + autoFocus + /> + + + + + + + + {altText.length > MAX_ALT_TEXT && ( + + + + + Alt text will be truncated. Limit: {i18n.number(MAX_ALT_TEXT)}{' '} + characters. + + + + )} + + + + {/* below the text input to force tab order */} diff --git a/src/view/com/composer/char-progress/CharProgress.tsx b/src/view/com/composer/char-progress/CharProgress.tsx index fdfe354e88..c61f753f26 100644 --- a/src/view/com/composer/char-progress/CharProgress.tsx +++ b/src/view/com/composer/char-progress/CharProgress.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {View} from 'react-native' +import {StyleProp, TextStyle, View, ViewStyle} from 'react-native' // @ts-ignore no type definition -prf import ProgressCircle from 'react-native-progress/Circle' // @ts-ignore no type definition -prf @@ -10,20 +10,32 @@ import {usePalette} from '#/lib/hooks/usePalette' import {s} from '#/lib/styles' import {Text} from '../../util/text/Text' -export function CharProgress({count, max}: {count: number; max?: number}) { +export function CharProgress({ + count, + max, + style, + textStyle, + size, +}: { + count: number + max?: number + style?: StyleProp + textStyle?: StyleProp + size?: number +}) { const maxLength = max || MAX_GRAPHEME_LENGTH const pal = usePalette('default') const textColor = count > maxLength ? '#e60000' : pal.colors.text const circleColor = count > maxLength ? '#e60000' : pal.colors.link return ( - <> - + + {maxLength - count} {count > maxLength ? ( ) : ( )} - + ) } diff --git a/src/view/com/composer/photos/ImageAltTextDialog.tsx b/src/view/com/composer/photos/ImageAltTextDialog.tsx index 0f9c463b5d..cec71ce02d 100644 --- a/src/view/com/composer/photos/ImageAltTextDialog.tsx +++ b/src/view/com/composer/photos/ImageAltTextDialog.tsx @@ -8,7 +8,7 @@ import {MAX_ALT_TEXT} from '#/lib/constants' import {enforceLen} from '#/lib/strings/helpers' import {isAndroid, isWeb} from '#/platform/detection' import {ComposerImage} from '#/state/gallery' -import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' +import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' @@ -24,31 +24,56 @@ type Props = { Portal: PortalComponent } -export const ImageAltTextDialog = (props: Props): React.ReactNode => { +export const ImageAltTextDialog = ({ + control, + image, + onChange, + Portal, +}: Props): React.ReactNode => { + const altTextRef = React.useRef(image.alt) + + const onSubmit = (text: string) => { + control.close() + onChange({ + ...image, + alt: enforceLen(text, MAX_ALT_TEXT, true), + }) + } + return ( - + { + onSubmit(altTextRef.current) + }} + Portal={Portal}> - + ) } const ImageAltTextInner = ({ - control, image, - onChange, -}: Props): React.ReactNode => { + altTextRef, + onSubmit, +}: { + image: Props['image'] + altTextRef: React.MutableRefObject + onSubmit: (text: string) => void +}): React.ReactNode => { const {_, i18n} = useLingui() const t = useTheme() - const windim = useWindowDimensions() - const [altText, setAltText] = React.useState(image.alt) - const onPressSubmit = React.useCallback(() => { - control.close() - onChange({...image, alt: enforceLen(altText.trim(), MAX_ALT_TEXT, true)}) - }, [control, image, altText, onChange]) + const onPressSubmit = () => { + onSubmit(altText) + } const imageStyle = React.useMemo(() => { const maxWidth = isWeb ? 450 : windim.width @@ -94,35 +119,23 @@ const ImageAltTextInner = ({ - + Descriptive alt text setAltText(text)} + onChangeText={text => { + altTextRef.current = text + setAltText(text) + }} value={altText} multiline numberOfLines={3} autoFocus /> - - - - {altText.length > MAX_ALT_TEXT && ( @@ -143,17 +156,20 @@ const ImageAltTextInner = ({ )} - + + + {/* Maybe fix this later -h */} {isAndroid ? : null} From 95c19c8ef403634a89ae46e492791ed9e0055fbc Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 4 Oct 2024 19:01:05 -0700 Subject: [PATCH 5/7] simplify close behavior --- src/view/com/composer/GifAltText.tsx | 33 +++++-------------- .../composer/photos/ImageAltTextDialog.tsx | 32 ++++++++---------- 2 files changed, 22 insertions(+), 43 deletions(-) diff --git a/src/view/com/composer/GifAltText.tsx b/src/view/com/composer/GifAltText.tsx index 2a12624054..052b3569f2 100644 --- a/src/view/com/composer/GifAltText.tsx +++ b/src/view/com/composer/GifAltText.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useState} from 'react' +import React, {useState} from 'react' import {TouchableOpacity, View} from 'react-native' import {AppBskyEmbedExternal} from '@atproto/api' import {msg, Trans} from '@lingui/macro' @@ -11,13 +11,13 @@ import { EmbedPlayerParams, parseEmbedPlayerFromUrl, } from '#/lib/strings/embed-player' -import {enforceLen} from '#/lib/strings/helpers' import {isAndroid} from '#/platform/detection' import {Gif} from '#/state/queries/tenor' import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper' import {atoms as a, native, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' +import {DialogControlProps} from '#/components/Dialog' import * as TextField from '#/components/forms/TextField' import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' @@ -54,15 +54,6 @@ export function GifAltText({ } }, [linkProp]) - const onPressSubmit = useCallback( - (alt: string) => { - control.close(() => { - onSubmit(alt) - }) - }, - [onSubmit, control], - ) - const parsedAlt = parseAltFromGIFDescription(link.description) const altTextRef = React.useRef('') @@ -112,10 +103,9 @@ export function GifAltText({ @@ -125,25 +115,18 @@ export function GifAltText({ function AltTextInner({ altTextRef, - onSubmit, + control, link, params, - initialValue: initalValue, }: { altTextRef: React.MutableRefObject - onSubmit: (text: string) => void + control: DialogControlProps link: AppBskyEmbedExternal.ViewExternal params: EmbedPlayerParams - initialValue: string }) { const t = useTheme() const {_, i18n} = useLingui() - const [altText, setAltText] = useState(initalValue) - const control = Dialog.useDialogContext() - - const onPressSubmit = useCallback(() => { - onSubmit(enforceLen(altText, MAX_ALT_TEXT, true)) - }, [onSubmit, altText]) + const [altText, setAltText] = useState(altTextRef.current) return ( @@ -199,7 +182,9 @@ function AltTextInner({ size="large" color="primary" variant="solid" - onPress={onPressSubmit} + onPress={() => { + control.close() + }} style={[a.flex_grow]}> Save diff --git a/src/view/com/composer/photos/ImageAltTextDialog.tsx b/src/view/com/composer/photos/ImageAltTextDialog.tsx index cec71ce02d..d33db4d22c 100644 --- a/src/view/com/composer/photos/ImageAltTextDialog.tsx +++ b/src/view/com/composer/photos/ImageAltTextDialog.tsx @@ -12,6 +12,7 @@ import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' +import {DialogControlProps} from '#/components/Dialog' import * as TextField from '#/components/forms/TextField' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' import {PortalComponent} from '#/components/Portal' @@ -32,49 +33,40 @@ export const ImageAltTextDialog = ({ }: Props): React.ReactNode => { const altTextRef = React.useRef(image.alt) - const onSubmit = (text: string) => { - control.close() - onChange({ - ...image, - alt: enforceLen(text, MAX_ALT_TEXT, true), - }) - } - return ( { - onSubmit(altTextRef.current) + onChange({ + ...image, + alt: enforceLen(altTextRef.current, MAX_ALT_TEXT, true), + }) }} Portal={Portal}> ) } const ImageAltTextInner = ({ - image, altTextRef, - onSubmit, + control, + image, }: { - image: Props['image'] altTextRef: React.MutableRefObject - onSubmit: (text: string) => void + control: DialogControlProps + image: Props['image'] }): React.ReactNode => { const {_, i18n} = useLingui() const t = useTheme() const windim = useWindowDimensions() const [altText, setAltText] = React.useState(image.alt) - const onPressSubmit = () => { - onSubmit(altText) - } - const imageStyle = React.useMemo(() => { const maxWidth = isWeb ? 450 : windim.width const source = image.transformed ?? image.source @@ -163,7 +155,9 @@ const ImageAltTextInner = ({ size="large" color="primary" variant="solid" - onPress={onPressSubmit} + onPress={() => { + control.close() + }} style={[a.flex_grow]}> Save From 13d195afc3c833dc6ee3fd6e6db66ff12974231b Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 4 Oct 2024 22:08:44 -0700 Subject: [PATCH 6/7] use state in gif alt --- src/view/com/composer/GifAltText.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/view/com/composer/GifAltText.tsx b/src/view/com/composer/GifAltText.tsx index 052b3569f2..a60956794a 100644 --- a/src/view/com/composer/GifAltText.tsx +++ b/src/view/com/composer/GifAltText.tsx @@ -55,7 +55,7 @@ export function GifAltText({ }, [linkProp]) const parsedAlt = parseAltFromGIFDescription(link.description) - const altTextRef = React.useRef('') + const [altText, setAltText] = useState(parsedAlt.alt) if (!gif || !params) return null @@ -97,12 +97,13 @@ export function GifAltText({ { - onSubmit(altTextRef.current) + onSubmit(altText) }} Portal={Portal}> + altText: string + setAltText: (text: string) => void control: DialogControlProps link: AppBskyEmbedExternal.ViewExternal params: EmbedPlayerParams }) { const t = useTheme() const {_, i18n} = useLingui() - const [altText, setAltText] = useState(altTextRef.current) return ( @@ -142,7 +144,6 @@ function AltTextInner({ label={_(msg`Alt text`)} placeholder={link.title} onChangeText={text => { - altTextRef.current = text setAltText(text) }} value={altText} From 8285f08195203632d465c4f292daab631c52de79 Mon Sep 17 00:00:00 2001 From: Hailey Date: Fri, 4 Oct 2024 22:23:38 -0700 Subject: [PATCH 7/7] state --- src/view/com/composer/GifAltText.tsx | 2 +- .../com/composer/photos/ImageAltTextDialog.tsx | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/view/com/composer/GifAltText.tsx b/src/view/com/composer/GifAltText.tsx index a60956794a..90d20d94f7 100644 --- a/src/view/com/composer/GifAltText.tsx +++ b/src/view/com/composer/GifAltText.tsx @@ -146,7 +146,7 @@ function AltTextInner({ onChangeText={text => { setAltText(text) }} - value={altText} + defaultValue={altText} multiline numberOfLines={3} autoFocus diff --git a/src/view/com/composer/photos/ImageAltTextDialog.tsx b/src/view/com/composer/photos/ImageAltTextDialog.tsx index d33db4d22c..e9e8d42224 100644 --- a/src/view/com/composer/photos/ImageAltTextDialog.tsx +++ b/src/view/com/composer/photos/ImageAltTextDialog.tsx @@ -31,7 +31,7 @@ export const ImageAltTextDialog = ({ onChange, Portal, }: Props): React.ReactNode => { - const altTextRef = React.useRef(image.alt) + const [altText, setAltText] = React.useState(image.alt) return ( { onChange({ ...image, - alt: enforceLen(altTextRef.current, MAX_ALT_TEXT, true), + alt: enforceLen(altText, MAX_ALT_TEXT, true), }) }} Portal={Portal}> @@ -47,25 +47,27 @@ export const ImageAltTextDialog = ({ ) } const ImageAltTextInner = ({ - altTextRef, + altText, + setAltText, control, image, }: { - altTextRef: React.MutableRefObject + altText: string + setAltText: (text: string) => void control: DialogControlProps image: Props['image'] }): React.ReactNode => { const {_, i18n} = useLingui() const t = useTheme() const windim = useWindowDimensions() - const [altText, setAltText] = React.useState(image.alt) const imageStyle = React.useMemo(() => { const maxWidth = isWeb ? 450 : windim.width @@ -119,10 +121,9 @@ const ImageAltTextInner = ({ { - altTextRef.current = text setAltText(text) }} - value={altText} + defaultValue={altText} multiline numberOfLines={3} autoFocus