Bluesky app fork with some witchin' additions 馃挮
at readme-update 230 lines 6.7 kB view raw
1import {useState} from 'react' 2import {TouchableOpacity, View} from 'react-native' 3import {msg, Plural, Trans} from '@lingui/macro' 4import {useLingui} from '@lingui/react' 5 6import {HITSLOP_10, MAX_ALT_TEXT} from '#/lib/constants' 7import {parseAltFromGIFDescription} from '#/lib/gif-alt-text' 8import { 9 type EmbedPlayerParams, 10 parseEmbedPlayerFromUrl, 11} from '#/lib/strings/embed-player' 12import {useResolveGifQuery} from '#/state/queries/resolve-link' 13import {type Gif} from '#/state/queries/tenor' 14import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper' 15import {atoms as a, useTheme} from '#/alf' 16import {Button, ButtonText} from '#/components/Button' 17import * as Dialog from '#/components/Dialog' 18import {type DialogControlProps} from '#/components/Dialog' 19import * as TextField from '#/components/forms/TextField' 20import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' 21import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 22import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' 23import {GifEmbed} from '#/components/Post/Embed/ExternalEmbed/Gif' 24import {Text} from '#/components/Typography' 25import {IS_ANDROID} from '#/env' 26import {AltTextReminder} from './photos/Gallery' 27 28export function GifAltTextDialog({ 29 gif, 30 altText, 31 onSubmit, 32}: { 33 gif: Gif 34 altText: string 35 onSubmit: (alt: string) => void 36}) { 37 const {data} = useResolveGifQuery(gif) 38 const vendorAltText = parseAltFromGIFDescription(data?.description ?? '').alt 39 const params = data ? parseEmbedPlayerFromUrl(data.uri) : undefined 40 if (!data || !params) { 41 return null 42 } 43 return ( 44 <GifAltTextDialogLoaded 45 altText={altText} 46 vendorAltText={vendorAltText} 47 thumb={data.thumb?.source.path} 48 params={params} 49 onSubmit={onSubmit} 50 /> 51 ) 52} 53 54export function GifAltTextDialogLoaded({ 55 vendorAltText, 56 altText, 57 onSubmit, 58 params, 59 thumb, 60}: { 61 vendorAltText: string 62 altText: string 63 onSubmit: (alt: string) => void 64 params: EmbedPlayerParams 65 thumb: string | undefined 66}) { 67 const control = Dialog.useDialogControl() 68 const {_} = useLingui() 69 const t = useTheme() 70 const [altTextDraft, setAltTextDraft] = useState(altText || vendorAltText) 71 return ( 72 <> 73 <TouchableOpacity 74 testID="altTextButton" 75 accessibilityRole="button" 76 accessibilityLabel={_(msg`Add alt text`)} 77 accessibilityHint="" 78 hitSlop={HITSLOP_10} 79 onPress={control.open} 80 style={[ 81 a.absolute, 82 {top: 8, left: 8}, 83 {borderRadius: 6}, 84 a.pl_xs, 85 a.pr_sm, 86 a.py_2xs, 87 a.flex_row, 88 a.gap_xs, 89 a.align_center, 90 {backgroundColor: 'rgba(0, 0, 0, 0.75)'}, 91 ]}> 92 {altText ? ( 93 <Check size="xs" fill={t.palette.white} style={a.ml_xs} /> 94 ) : ( 95 <Plus size="sm" fill={t.palette.white} /> 96 )} 97 <Text 98 style={[a.font_semi_bold, {color: t.palette.white}]} 99 accessible={false}> 100 <Trans>ALT</Trans> 101 </Text> 102 </TouchableOpacity> 103 104 <AltTextReminder /> 105 106 <Dialog.Outer 107 control={control} 108 onClose={() => { 109 onSubmit(altTextDraft) 110 }}> 111 <Dialog.Handle /> 112 <AltTextInner 113 vendorAltText={vendorAltText} 114 altText={altTextDraft} 115 onChange={setAltTextDraft} 116 thumb={thumb} 117 control={control} 118 params={params} 119 /> 120 </Dialog.Outer> 121 </> 122 ) 123} 124 125function AltTextInner({ 126 vendorAltText, 127 altText, 128 onChange, 129 control, 130 params, 131 thumb, 132}: { 133 vendorAltText: string 134 altText: string 135 onChange: (text: string) => void 136 control: DialogControlProps 137 params: EmbedPlayerParams 138 thumb: string | undefined 139}) { 140 const t = useTheme() 141 const {_, i18n} = useLingui() 142 143 return ( 144 <Dialog.ScrollableInner label={_(msg`Add alt text`)}> 145 <View style={a.flex_col_reverse}> 146 <View style={[a.mt_md, a.gap_md]}> 147 <View style={[a.gap_sm]}> 148 <View style={[a.relative]}> 149 <TextField.LabelText> 150 <Trans>Descriptive alt text</Trans> 151 </TextField.LabelText> 152 <TextField.Root> 153 <Dialog.Input 154 label={_(msg`Alt text`)} 155 placeholder={vendorAltText} 156 onChangeText={onChange} 157 defaultValue={altText} 158 multiline 159 numberOfLines={3} 160 autoFocus 161 onKeyPress={({nativeEvent}) => { 162 if (nativeEvent.key === 'Escape') { 163 control.close() 164 } 165 }} 166 /> 167 </TextField.Root> 168 </View> 169 170 {altText.length > MAX_ALT_TEXT && ( 171 <View style={[a.pb_sm, a.flex_row, a.gap_xs]}> 172 <CircleInfo fill={t.palette.negative_500} /> 173 <Text 174 style={[ 175 a.italic, 176 a.leading_snug, 177 t.atoms.text_contrast_medium, 178 ]}> 179 <Trans> 180 Alt text will be truncated.{' '} 181 <Plural 182 value={MAX_ALT_TEXT} 183 other={`Limit: ${i18n.number(MAX_ALT_TEXT)} characters.`} 184 /> 185 </Trans> 186 </Text> 187 </View> 188 )} 189 </View> 190 191 <AltTextCounterWrapper altText={altText}> 192 <Button 193 label={_(msg`Save`)} 194 size="large" 195 color="primary" 196 variant="solid" 197 onPress={() => { 198 control.close() 199 }} 200 style={[a.flex_grow]}> 201 <ButtonText> 202 <Trans>Save</Trans> 203 </ButtonText> 204 </Button> 205 </AltTextCounterWrapper> 206 </View> 207 {/* below the text input to force tab order */} 208 <View> 209 <Text 210 style={[a.text_2xl, a.font_semi_bold, a.leading_tight, a.pb_sm]}> 211 <Trans>Add alt text</Trans> 212 </Text> 213 <View style={[a.align_center]}> 214 <GifEmbed 215 thumb={thumb} 216 altText={altText} 217 isPreferredAltText={true} 218 params={params} 219 hideAlt 220 style={[{height: 225}]} 221 /> 222 </View> 223 </View> 224 </View> 225 <Dialog.Close /> 226 {/* Maybe fix this later -h */} 227 {IS_ANDROID ? <View style={{height: 300}} /> : null} 228 </Dialog.ScrollableInner> 229 ) 230}