mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at main 142 lines 4.7 kB view raw
1import {useCallback, useRef, useState} from 'react' 2import {Pressable, View} from 'react-native' 3import {type ChatBskyConvoDefs} from '@atproto/api' 4import {msg} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6 7import {useConvoActive} from '#/state/messages/convo' 8import {useSession} from '#/state/session' 9import * as Toast from '#/view/com/util/Toast' 10import {atoms as a, useTheme} from '#/alf' 11import {MessageContextMenu} from '#/components/dms/MessageContextMenu' 12import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontalIcon} from '#/components/icons/DotGrid' 13import {EmojiSmile_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components/icons/Emoji' 14import {EmojiReactionPicker} from './EmojiReactionPicker' 15import {hasReachedReactionLimit} from './util' 16 17export function ActionsWrapper({ 18 message, 19 isFromSelf, 20 children, 21}: { 22 message: ChatBskyConvoDefs.MessageView 23 isFromSelf: boolean 24 children: React.ReactNode 25}) { 26 const viewRef = useRef(null) 27 const t = useTheme() 28 const {_} = useLingui() 29 const convo = useConvoActive() 30 const {currentAccount} = useSession() 31 32 const [showActions, setShowActions] = useState(false) 33 34 const onMouseEnter = useCallback(() => { 35 setShowActions(true) 36 }, []) 37 38 const onMouseLeave = useCallback(() => { 39 setShowActions(false) 40 }, []) 41 42 // We need to handle the `onFocus` separately because we want to know if there is a related target (the element 43 // that is losing focus). If there isn't that means the focus is coming from a dropdown that is now closed. 44 const onFocus = useCallback<React.FocusEventHandler>(e => { 45 if (e.nativeEvent.relatedTarget == null) return 46 setShowActions(true) 47 }, []) 48 49 const onEmojiSelect = useCallback( 50 (emoji: string) => { 51 if ( 52 message.reactions?.find( 53 reaction => 54 reaction.value === emoji && 55 reaction.sender.did === currentAccount?.did, 56 ) 57 ) { 58 convo 59 .removeReaction(message.id, emoji) 60 .catch(() => Toast.show(_(msg`Failed to remove emoji reaction`))) 61 } else { 62 if (hasReachedReactionLimit(message, currentAccount?.did)) return 63 convo 64 .addReaction(message.id, emoji) 65 .catch(() => 66 Toast.show(_(msg`Failed to add emoji reaction`), 'xmark'), 67 ) 68 } 69 }, 70 [_, convo, message, currentAccount?.did], 71 ) 72 73 return ( 74 <View 75 onMouseEnter={onMouseEnter} 76 onMouseLeave={onMouseLeave} 77 // @ts-expect-error web only 78 onFocus={onFocus} 79 onBlur={onMouseLeave} 80 style={[a.flex_1, isFromSelf ? a.flex_row : a.flex_row_reverse]} 81 ref={viewRef}> 82 <View 83 style={[ 84 a.justify_center, 85 a.flex_row, 86 a.align_center, 87 isFromSelf 88 ? [a.mr_xs, {marginLeft: 'auto'}, a.flex_row_reverse] 89 : [a.ml_xs, {marginRight: 'auto'}], 90 ]}> 91 <EmojiReactionPicker message={message} onEmojiSelect={onEmojiSelect}> 92 {({props, state, isNative, control}) => { 93 // always false, file is platform split 94 if (isNative) return null 95 const showMenuTrigger = showActions || control.isOpen ? 1 : 0 96 return ( 97 <Pressable 98 {...props} 99 style={[ 100 {opacity: showMenuTrigger}, 101 a.p_xs, 102 a.rounded_full, 103 (state.hovered || state.pressed) && t.atoms.bg_contrast_25, 104 ]}> 105 <EmojiSmileIcon 106 size="md" 107 style={t.atoms.text_contrast_medium} 108 /> 109 </Pressable> 110 ) 111 }} 112 </EmojiReactionPicker> 113 <MessageContextMenu message={message}> 114 {({props, state, isNative, control}) => { 115 // always false, file is platform split 116 if (isNative) return null 117 const showMenuTrigger = showActions || control.isOpen ? 1 : 0 118 return ( 119 <Pressable 120 {...props} 121 style={[ 122 {opacity: showMenuTrigger}, 123 a.p_xs, 124 a.rounded_full, 125 (state.hovered || state.pressed) && t.atoms.bg_contrast_25, 126 ]}> 127 <DotsHorizontalIcon 128 size="md" 129 style={t.atoms.text_contrast_medium} 130 /> 131 </Pressable> 132 ) 133 }} 134 </MessageContextMenu> 135 </View> 136 <View 137 style={[{maxWidth: '80%'}, isFromSelf ? a.align_end : a.align_start]}> 138 {children} 139 </View> 140 </View> 141 ) 142}