Skip to content

Commit a23115e

Browse files
feat: show who reacted to a message
1 parent b5005fa commit a23115e

File tree

5 files changed

+80
-21
lines changed

5 files changed

+80
-21
lines changed

src/chat/src/reaction.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use std::fmt::{Debug, Formatter};
2-
use std::sync::Arc;
31
use atomic_refcell::AtomicRefCell;
42
use gpui::{App, IntoElement, Rgba};
3+
use std::fmt::{Debug, Formatter};
4+
use std::sync::Arc;
55

66
pub type ReactionEvent<T> = (T, ReactionOperation);
77

@@ -14,7 +14,12 @@ pub enum MessageReactionType {
1414
#[derive(Clone, PartialEq)]
1515
pub enum ReactionEmoji {
1616
Simple(String),
17-
Custom { url: String, animated: bool, name: Option<String>, id: u64 },
17+
Custom {
18+
url: String,
19+
animated: bool,
20+
name: Option<String>,
21+
id: u64,
22+
},
1823
}
1924

2025
impl Debug for ReactionEmoji {
@@ -27,7 +32,6 @@ impl Debug for ReactionEmoji {
2732
}
2833

2934
pub trait MessageReaction: IntoElement {
30-
3135
fn get_count(&self, kind: Option<MessageReactionType>) -> u64;
3236
fn get_self_reaction(&self) -> Option<MessageReactionType>;
3337
fn get_emoji(&self) -> ReactionEmoji;
@@ -43,11 +47,12 @@ pub enum ReactionOperation {
4347
RemoveSelf(ReactionEmoji),
4448
RemoveAll,
4549
RemoveEmoji(ReactionEmoji),
50+
SetMembers(ReactionEmoji, Vec<String>),
4651
}
4752

4853
pub trait ReactionList {
4954
fn get_reactions(&self) -> &Arc<AtomicRefCell<Vec<impl MessageReaction>>>;
5055
fn increment(&mut self, emoji: &ReactionEmoji, kind: MessageReactionType, user_is_self: bool, by: isize);
51-
fn apply(&mut self, operation: ReactionOperation);
56+
fn apply(&mut self, operation: ReactionOperation, app: &mut App);
5257
fn get_content(&self, cx: &mut App) -> impl IntoElement;
5358
}

src/discord/src/client.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,28 @@ impl DiscordClient {
157157
}
158158
}
159159

160+
pub async fn load_users_reacting_to(&self, channel_id: ChannelId, message_id: MessageId, emoji: ReactionEmoji) {
161+
let reactions = channel_id.reaction_users(self.discord().http.clone(), message_id, Self::emoji_to_serenity(&emoji), Some(5), None).await;
162+
if reactions.is_err() {return;}
163+
let reactions = reactions.unwrap().iter().map(|user|user.name.clone()).collect();
164+
165+
self.send_reaction_operation(channel_id, message_id, ReactionOperation::SetMembers(emoji, reactions)).await;
166+
}
167+
160168
pub async fn add_reaction(&self, channel_id: ChannelId, message_id: MessageId, emoji: ReactionEmoji) {
161169
let reaction_type = Self::emoji_to_serenity(&emoji);
162170
channel_id.create_reaction(self.discord().http.clone(), message_id, reaction_type).await.unwrap();
171+
172+
// Refresh reactions in UI
173+
self.load_users_reacting_to(channel_id, message_id, emoji).await;
163174
}
164175

165176
pub async fn remove_reaction(&self, channel_id: ChannelId, message_id: MessageId, emoji: ReactionEmoji) {
166177
let reaction_type = Self::emoji_to_serenity(&emoji);
167178
channel_id.delete_reaction(self.discord().http.clone(), message_id, None, reaction_type).await.unwrap();
179+
180+
// Refresh reactions in UI
181+
self.load_users_reacting_to(channel_id, message_id, emoji).await;
168182
}
169183
}
170184

src/discord/src/message/reaction.rs

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
use crate::client::DiscordClient;
22
use components::theme::ActiveTheme;
3+
use components::tooltip::Tooltip;
34
use gpui::prelude::FluentBuilder;
4-
use gpui::{div, img, px, AnyElement, App, InteractiveElement, IntoElement, ParentElement, RenderOnce, Rgba, StatefulInteractiveElement, Styled};
5+
use gpui::{
6+
div, img, px, AnyElement, App, InteractiveElement, IntoElement, ParentElement, RenderOnce, Rgba,
7+
StatefulInteractiveElement, Styled,
8+
};
59
use scope_chat::reaction::MessageReactionType::Normal;
610
use scope_chat::reaction::{MessageReaction, MessageReactionType, ReactionEmoji};
711
use serenity::all::{ChannelId, MessageId, ReactionType};
812
use std::fmt::Debug;
9-
use std::sync::Arc;
13+
use std::sync::{Arc, Mutex};
1014
use MessageReactionType::Burst;
1115

1216
#[derive(Clone, Debug)]
@@ -43,6 +47,7 @@ pub struct DiscordMessageReaction {
4347
pub(crate) client: Arc<DiscordClient>,
4448
pub(crate) message_id: MessageId,
4549
pub(crate) channel_id: ChannelId,
50+
pub(crate) users: Arc<Mutex<Option<Vec<String>>>>,
4651
}
4752

4853
impl DiscordMessageReaction {
@@ -52,6 +57,7 @@ impl DiscordMessageReaction {
5257
client,
5358
message_id,
5459
channel_id,
60+
users: Arc::new(Mutex::new(None)),
5561
}
5662
}
5763

@@ -83,13 +89,15 @@ impl DiscordMessageReaction {
8389
fn handle_click(&self, app: &App) {
8490
let reaction = self.clone();
8591
let had_reaction = reaction.get_self_reaction().is_some();
86-
app.spawn(|_| async move {
87-
if had_reaction {
88-
reaction.client.remove_reaction(reaction.channel_id, reaction.message_id, reaction.get_emoji()).await;
89-
} else {
90-
reaction.client.add_reaction(reaction.channel_id, reaction.message_id, reaction.get_emoji()).await;
91-
}
92-
}).detach();
92+
app
93+
.spawn(|_| async move {
94+
if had_reaction {
95+
reaction.client.remove_reaction(reaction.channel_id, reaction.message_id, reaction.get_emoji()).await;
96+
} else {
97+
reaction.client.add_reaction(reaction.channel_id, reaction.message_id, reaction.get_emoji()).await;
98+
}
99+
})
100+
.detach();
93101
}
94102
}
95103

@@ -171,6 +179,12 @@ impl RenderOnce for DiscordMessageReaction {
171179
fn render(self, _: &mut gpui::Window, cx: &mut App) -> impl IntoElement {
172180
let emoji = self.get_emoji();
173181
let theme = cx.theme();
182+
183+
let channel = self.channel_id.clone();
184+
let message = self.message_id.clone();
185+
let closure_emoji = self.get_emoji();
186+
let client = self.client.clone();
187+
let users = self.users.clone();
174188
div()
175189
.px_1()
176190
.py_px()
@@ -189,6 +203,22 @@ impl RenderOnce for DiscordMessageReaction {
189203
.on_click(move |_, _, app| {
190204
self.handle_click(app);
191205
})
206+
.tooltip(move |win, cx| {
207+
let guard = users.lock().unwrap();
208+
let text = if guard.is_some() {
209+
guard.as_ref().clone().unwrap().join(", ")
210+
} else {
211+
"Loading...".to_string()
212+
};
213+
Tooltip::new(text, win, cx)
214+
})
215+
.on_hover(move |_, _, _| {
216+
let client = client.clone();
217+
let emoji = closure_emoji.clone();
218+
tokio::spawn(async move {
219+
client.load_users_reacting_to(channel, message, emoji).await;
220+
});
221+
})
192222
}
193223
}
194224

src/discord/src/message/reaction_list.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
11
use crate::client::DiscordClient;
22
use crate::message::reaction::{DiscordMessageReaction, ReactionData};
3+
use atomic_refcell::{AtomicRef, AtomicRefCell};
34
use gpui::{div, App, AppContext, Context, Entity, IntoElement, ParentElement, Render, Styled, Window};
45
use scope_chat::reaction::MessageReactionType::Normal;
56
use scope_chat::reaction::{MessageReaction, MessageReactionType, ReactionEmoji, ReactionList, ReactionOperation};
67
use serenity::all::{ChannelId, MessageId};
78
use std::fmt::Debug;
8-
use std::sync::{Arc, OnceLock};
9-
use atomic_refcell::{AtomicRef, AtomicRefCell};
9+
use std::sync::{Arc, Mutex, OnceLock};
1010

1111
#[derive(Clone)]
1212
pub struct DiscordReactionList {
1313
reactions: Arc<AtomicRefCell<Vec<DiscordMessageReaction>>>,
1414
message_id: MessageId,
1515
channel_id: ChannelId,
1616
client: Arc<DiscordClient>,
17-
entity: Arc<OnceLock<Entity<RenderableReactionList>>>
17+
entity: Arc<OnceLock<Entity<RenderableReactionList>>>,
1818
}
1919

2020
impl DiscordReactionList {
2121
pub fn new(reactions: Vec<serenity::all::MessageReaction>, channel_id: ChannelId, message_id: MessageId, client: Arc<DiscordClient>) -> Self {
2222
DiscordReactionList {
23-
reactions: Arc::new(AtomicRefCell::new(reactions.iter().map(|reaction| DiscordMessageReaction::new(reaction, client.clone(), message_id.clone(), channel_id.clone())).collect())),
23+
reactions: Arc::new(AtomicRefCell::new(
24+
reactions.iter().map(|reaction| DiscordMessageReaction::new(reaction, client.clone(), message_id.clone(), channel_id.clone())).collect(),
25+
)),
2426
message_id,
2527
channel_id,
2628
client,
27-
entity: Arc::new(OnceLock::new())
29+
entity: Arc::new(OnceLock::new()),
2830
}
2931
}
3032
}
@@ -52,14 +54,15 @@ impl ReactionList for DiscordReactionList {
5254
client: self.client.clone(),
5355
message_id: self.message_id.clone(),
5456
channel_id: self.channel_id.clone(),
57+
users: Arc::new(Mutex::new(None)),
5558
};
5659

5760
reaction.increment(kind, user_is_self, by);
5861
self.reactions.borrow_mut().push(reaction);
5962
}
6063
}
6164

62-
fn apply(&mut self, operation: ReactionOperation) {
65+
fn apply(&mut self, operation: ReactionOperation, cx: &mut App) {
6366
match operation {
6467
ReactionOperation::Add(emoji, ty) => {
6568
self.increment(&emoji, ty, false, 1);
@@ -79,6 +82,13 @@ impl ReactionList for DiscordReactionList {
7982
ReactionOperation::RemoveEmoji(emoji) => {
8083
self.reactions.borrow_mut().retain(|reaction| reaction.get_emoji() != emoji);
8184
}
85+
ReactionOperation::SetMembers(emoji, members) => {
86+
if let Some(reaction) = self.reactions.borrow_mut().iter_mut().find(|reaction| reaction.get_emoji() == emoji) {
87+
let mut reactions = reaction.users.lock().unwrap();
88+
reactions.replace(members);
89+
self.entity.get().as_ref().map(|entity| cx.notify(entity.entity_id()));
90+
}
91+
}
8292
}
8393
}
8494

src/ui/src/channel/message_list.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ where
122122
if let Element::Resolved(Some(haystack)) = item {
123123
if haystack.get_identifier() == Some(reaction.0) {
124124
if let Some(reactions) = haystack.get_reactions() {
125-
reactions.apply(reaction.1);
125+
reactions.apply(reaction.1, cx);
126126
}
127127

128128
cx.notify();

0 commit comments

Comments
 (0)