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