Skip to content

Commit 64a9ff9

Browse files
feat: add / remove reactions
1 parent e8b3cea commit 64a9ff9

File tree

13 files changed

+141
-92
lines changed

13 files changed

+141
-92
lines changed

Cargo.lock

Lines changed: 13 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ gpui = { git = "https://github.com/scopeclient/zed.git", branch = "feature/expor
1010
] }
1111
components = { package = "ui", git = "https://github.com/scopeclient/components", version = "0.1.0" }
1212
reqwest_client = { git = "https://github.com/scopeclient/zed.git", branch = "feature/export-platform-window", version = "0.1.0" }
13+
atomic_refcell = "0.1.13"

src/chat/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ edition = "2021"
77
tokio = "1.41.1"
88
chrono.workspace = true
99
gpui.workspace = true
10+
atomic_refcell.workspace = true

src/chat/src/reaction.rs

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

46
pub type ReactionEvent<T> = (T, ReactionOperation);
57

@@ -43,9 +45,9 @@ pub enum ReactionOperation {
4345
RemoveEmoji(ReactionEmoji),
4446
}
4547

46-
pub trait ReactionList: IntoElement {
47-
fn get_reactions(&self) -> &Vec<impl MessageReaction>;
48-
fn get_reaction(&self, emoji: &ReactionEmoji) -> Option<&impl MessageReaction>;
48+
pub trait ReactionList {
49+
fn get_reactions(&self) -> &Arc<AtomicRefCell<Vec<impl MessageReaction>>>;
4950
fn increment(&mut self, emoji: &ReactionEmoji, kind: MessageReactionType, user_is_self: bool, by: isize);
5051
fn apply(&mut self, operation: ReactionOperation);
52+
fn get_content(&self, cx: &mut App) -> impl IntoElement;
5153
}

src/discord/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ scope-backend-cache = { version = "0.1.0", path = "../cache" }
1414
url = "2.5.3"
1515
querystring = "1.1.0"
1616
catty = "0.1.5"
17-
atomic_refcell = "0.1.13"
17+
atomic_refcell.workspace = true
1818
rand = "0.8.5"
1919
dashmap = "6.1.0"
2020
log = "0.4.22"

src/discord/src/channel/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl Channel for DiscordChannel {
7676
sent_time: Utc::now(),
7777
list_item_id: Snowflake::random(),
7878
},
79-
content: OnceLock::new(),
79+
content: Arc::new(OnceLock::new()),
8080
}
8181
}
8282

src/discord/src/client.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ impl DiscordClient {
5656
let client = Arc::new_cyclic(|weak| DiscordClient {
5757
ready_notifier: AtomicRefCell::new(Some(sender)),
5858
weak: weak.clone(),
59-
6059
..Default::default()
6160
});
6261

@@ -147,18 +146,26 @@ impl DiscordClient {
147146
}
148147
}
149148

150-
pub async fn add_reaction(&self, channel_id: ChannelId, message_id: MessageId, emoji: ReactionEmoji) {
151-
let reaction_type = match emoji {
149+
fn emoji_to_serenity(emoji: &ReactionEmoji) -> ReactionType {
150+
match emoji.clone() {
152151
ReactionEmoji::Simple(c) => ReactionType::Unicode(c),
153152
ReactionEmoji::Custom { name, animated, id, .. } => ReactionType::Custom {
154153
id: EmojiId::new(id),
155154
animated,
156155
name,
157156
},
158-
};
157+
}
158+
}
159159

160+
pub async fn add_reaction(&self, channel_id: ChannelId, message_id: MessageId, emoji: ReactionEmoji) {
161+
let reaction_type = Self::emoji_to_serenity(&emoji);
160162
channel_id.create_reaction(self.discord().http.clone(), message_id, reaction_type).await.unwrap();
161163
}
164+
165+
pub async fn remove_reaction(&self, channel_id: ChannelId, message_id: MessageId, emoji: ReactionEmoji) {
166+
let reaction_type = Self::emoji_to_serenity(&emoji);
167+
channel_id.delete_reaction(self.discord().http.clone(), message_id, None, reaction_type).await.unwrap();
168+
}
162169
}
163170

164171
#[async_trait]

src/discord/src/message/content.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::message::reaction_list::DiscordReactionList;
22
use gpui::prelude::FluentBuilder;
33
use gpui::{div, Context, IntoElement, ParentElement, Render, Styled, Window};
44
use serenity::all::Message;
5+
use scope_chat::reaction::ReactionList;
56

67
#[derive(Clone, Debug)]
78
pub struct DiscordMessageContent {
@@ -29,10 +30,10 @@ impl DiscordMessageContent {
2930
}
3031

3132
impl Render for DiscordMessageContent {
32-
fn render(&mut self, _: &mut Window, _: &mut Context<DiscordMessageContent>) -> impl IntoElement {
33+
fn render(&mut self, _: &mut Window, cx: &mut Context<DiscordMessageContent>) -> impl IntoElement {
3334
div()
3435
.opacity(if self.is_pending { 0.25 } else { 1.0 })
3536
.child(self.content.clone())
36-
.when_some(self.reactions.clone(), |d, reactions| d.child(reactions))
37+
.when_some(self.reactions.clone(), |d, reactions| d.child(reactions.get_content(cx)))
3738
}
3839
}

src/discord/src/message/mod.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,19 @@ pub enum DiscordMessageData {
2424
sent_time: DateTime<Utc>,
2525
list_item_id: Snowflake,
2626
},
27-
Received(Arc<serenity::model::channel::Message>, Option<Arc<serenity::model::guild::Member>>, DiscordReactionList),
27+
Received(
28+
Arc<serenity::model::channel::Message>,
29+
Option<Arc<serenity::model::guild::Member>>,
30+
DiscordReactionList,
31+
),
2832
}
2933

3034
#[derive(Clone)]
3135
pub struct DiscordMessage {
3236
pub client: Arc<DiscordClient>,
3337
pub channel: Arc<serenity::model::channel::Channel>,
3438
pub data: DiscordMessageData,
35-
pub content: OnceLock<Entity<DiscordMessageContent>>,
39+
pub content: Arc<OnceLock<Entity<DiscordMessageContent>>>,
3640
}
3741

3842
impl DiscordMessage {
@@ -45,13 +49,13 @@ impl DiscordMessage {
4549
}
4650
.unwrap();
4751

48-
let reactions = DiscordReactionList::new_serenity(msg.reactions.clone(), channel.id(), msg.id.clone(), client.clone());
52+
let reactions = DiscordReactionList::new(msg.reactions.clone(), channel.id(), msg.id.clone(), client.clone());
4953

5054
Self {
5155
client,
5256
channel,
5357
data: DiscordMessageData::Received(msg, member, reactions),
54-
content: OnceLock::new(),
58+
content: Arc::new(OnceLock::new()),
5559
}
5660
}
5761

@@ -61,12 +65,12 @@ impl DiscordMessage {
6165
channel: Arc<serenity::model::channel::Channel>,
6266
member: Option<Arc<serenity::model::guild::Member>>,
6367
) -> Self {
64-
let reactions = DiscordReactionList::new_serenity(msg.reactions.clone(), channel.id(), msg.id.clone(), client.clone());
68+
let reactions = DiscordReactionList::new(msg.reactions.clone(), channel.id(), msg.id.clone(), client.clone());
6569
Self {
6670
client,
6771
channel,
6872
data: DiscordMessageData::Received(msg, member, reactions),
69-
content: OnceLock::new(),
73+
content: Arc::new(OnceLock::new()),
7074
}
7175
}
7276
}
@@ -129,18 +133,18 @@ impl Message for DiscordMessage {
129133
}
130134

131135
// TODO: want reviewer discussion. I'm really stretching the abilities of gpui here and im not sure if this is the right way to do this.
136+
// Additional Context to this discussion: the OnceLock in context CANNOT be cloned because it messes with the internal gpui entityids
137+
// and recreates the entity on every interaction making it impossible to interact with the message content in any way. Right now, I'm
138+
// using an arc for this, but this feels like a band-aid solution. I do agree that this should be refactored out at some point.
132139
fn get_content(&self, cx: &mut App) -> Entity<Self::Content> {
133140
self
134141
.content
135142
.get_or_init(|| {
136-
let content = match &self.data {
143+
cx.new(|_| match &self.data {
137144
DiscordMessageData::Pending { content, .. } => DiscordMessageContent::pending(content.clone()),
138145
DiscordMessageData::Received(message, _, reactions) => DiscordMessageContent::received(message, reactions),
139-
};
140-
141-
cx.new(|_| content)
142-
})
143-
.clone()
146+
})
147+
}).clone()
144148
}
145149

146150
fn get_identifier(&self) -> Option<Snowflake> {

src/discord/src/message/reaction.rs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
use crate::client::DiscordClient;
12
use components::theme::ActiveTheme;
23
use gpui::prelude::FluentBuilder;
3-
use gpui::{div, img, px, AnyElement, App, IntoElement, ParentElement, RenderOnce, Rgba, Styled};
4+
use gpui::{div, img, px, AnyElement, App, InteractiveElement, IntoElement, ParentElement, RenderOnce, Rgba, StatefulInteractiveElement, Styled};
45
use scope_chat::reaction::MessageReactionType::Normal;
56
use scope_chat::reaction::{MessageReaction, MessageReactionType, ReactionEmoji};
6-
use serenity::all::ReactionType;
7+
use serenity::all::{ChannelId, MessageId, ReactionType};
78
use std::fmt::Debug;
9+
use std::sync::Arc;
810
use MessageReactionType::Burst;
911

1012
#[derive(Clone, Debug)]
@@ -35,15 +37,21 @@ impl ReactionData {
3537
}
3638
}
3739

38-
#[derive(Clone, Debug, IntoElement)]
40+
#[derive(Clone, IntoElement)]
3941
pub struct DiscordMessageReaction {
4042
pub data: ReactionData,
43+
pub(crate) client: Arc<DiscordClient>,
44+
pub(crate) message_id: MessageId,
45+
pub(crate) channel_id: ChannelId,
4146
}
4247

4348
impl DiscordMessageReaction {
44-
pub fn from_message(reaction: &serenity::all::MessageReaction) -> Self {
49+
pub fn new(reaction: &serenity::all::MessageReaction, client: Arc<DiscordClient>, message_id: MessageId, channel_id: ChannelId) -> Self {
4550
DiscordMessageReaction {
4651
data: ReactionData::Message(reaction.clone()),
52+
client,
53+
message_id,
54+
channel_id,
4755
}
4856
}
4957

@@ -71,6 +79,18 @@ impl DiscordMessageReaction {
7179
ReactionEmoji::Custom { url, .. } => img(url.clone()).w(px(16f32)).h(px(16f32)).into_any_element(),
7280
}
7381
}
82+
83+
fn handle_click(&self, app: &App) {
84+
let reaction = self.clone();
85+
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();
93+
}
7494
}
7595

7696
impl MessageReaction for DiscordMessageReaction {
@@ -165,6 +185,10 @@ impl RenderOnce for DiscordMessageReaction {
165185
.gap_1()
166186
.child(Self::render_emoji(&emoji))
167187
.child(self.get_count(None).to_string())
188+
.id("reaction")
189+
.on_click(move |_, _, app| {
190+
self.handle_click(app);
191+
})
168192
}
169193
}
170194

@@ -183,3 +207,9 @@ pub fn discord_reaction_to_emoji(reaction: &ReactionType) -> ReactionEmoji {
183207
}
184208
}
185209
}
210+
211+
impl Debug for DiscordMessageReaction {
212+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213+
f.debug_struct("DiscordMessageReaction").field("data", &self.data).finish()
214+
}
215+
}

0 commit comments

Comments
 (0)