.
This commit is contained in:
@@ -62,11 +62,35 @@ impl SendReport {
|
|||||||
|
|
||||||
/// Returns true if the send was successful.
|
/// Returns true if the send was successful.
|
||||||
pub fn success(&self) -> bool {
|
pub fn success(&self) -> bool {
|
||||||
if let Some(output) = self.output.as_ref() {
|
self.error.is_none() && self.output.as_ref().is_some_and(|o| !o.success.is_empty())
|
||||||
!output.failed.is_empty()
|
}
|
||||||
} else {
|
|
||||||
false
|
/// Returns true if the send failed.
|
||||||
}
|
pub fn failed(&self) -> bool {
|
||||||
|
self.error.is_some() && self.output.as_ref().is_some_and(|o| !o.failed.is_empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SendStatus {
|
||||||
|
Ok {
|
||||||
|
id: EventId,
|
||||||
|
relay: RelayUrl,
|
||||||
|
},
|
||||||
|
Failed {
|
||||||
|
id: EventId,
|
||||||
|
relay: RelayUrl,
|
||||||
|
message: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendStatus {
|
||||||
|
pub fn ok(id: EventId, relay: RelayUrl) -> Self {
|
||||||
|
Self::Ok { id, relay }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn failed(id: EventId, relay: RelayUrl, message: String) -> Self {
|
||||||
|
Self::Failed { id, relay, message }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
pub use actions::*;
|
pub use actions::*;
|
||||||
use anyhow::{Context as AnyhowContext, Error};
|
use anyhow::{Context as AnyhowContext, Error};
|
||||||
use chat::{Message, RenderedMessage, Room, RoomEvent, SendReport};
|
use chat::{Message, RenderedMessage, Room, RoomEvent, SendReport, SendStatus};
|
||||||
use common::RenderedTimestamp;
|
use common::RenderedTimestamp;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@@ -24,7 +24,6 @@ use theme::ActiveTheme;
|
|||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::dock::{Panel, PanelEvent};
|
use ui::dock::{Panel, PanelEvent};
|
||||||
use ui::indicator::Indicator;
|
|
||||||
use ui::input::{InputEvent, InputState, TextInput};
|
use ui::input::{InputEvent, InputState, TextInput};
|
||||||
use ui::menu::DropdownMenu;
|
use ui::menu::DropdownMenu;
|
||||||
use ui::notification::Notification;
|
use ui::notification::Notification;
|
||||||
@@ -185,22 +184,33 @@ impl ChatPanel {
|
|||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let sent_ids = self.sent_ids.clone();
|
let sent_ids = self.sent_ids.clone();
|
||||||
|
let reports = self.reports_by_id.downgrade();
|
||||||
|
|
||||||
let (tx, rx) = flume::bounded::<(EventId, RelayUrl)>(256);
|
let (tx, rx) = flume::bounded::<Arc<SendStatus>>(256);
|
||||||
|
|
||||||
self.tasks.push(cx.background_spawn(async move {
|
self.tasks.push(cx.background_spawn(async move {
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
|
|
||||||
while let Some(notification) = notifications.next().await {
|
while let Some(notification) = notifications.next().await {
|
||||||
if let ClientNotification::Message {
|
if let ClientNotification::Message {
|
||||||
message: RelayMessage::Ok { event_id, .. },
|
message:
|
||||||
|
RelayMessage::Ok {
|
||||||
|
event_id,
|
||||||
|
status,
|
||||||
|
message,
|
||||||
|
},
|
||||||
relay_url,
|
relay_url,
|
||||||
} = notification
|
} = notification
|
||||||
{
|
{
|
||||||
let sent_ids = sent_ids.read().await;
|
let sent_ids = sent_ids.read().await;
|
||||||
|
|
||||||
if sent_ids.contains(&event_id) {
|
if sent_ids.contains(&event_id) {
|
||||||
tx.send_async((event_id, relay_url)).await.ok();
|
let status = if status {
|
||||||
|
SendStatus::ok(event_id, relay_url)
|
||||||
|
} else {
|
||||||
|
SendStatus::failed(event_id, relay_url, message.into())
|
||||||
|
};
|
||||||
|
tx.send_async(Arc::new(status)).await.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,24 +218,31 @@ impl ChatPanel {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
|
|
||||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
self.tasks.push(cx.spawn(async move |_this, cx| {
|
||||||
while let Ok((event_id, relay_url)) = rx.recv_async().await {
|
while let Ok(status) = rx.recv_async().await {
|
||||||
this.update(cx, |this, cx| {
|
reports.update(cx, |this, cx| {
|
||||||
this.reports_by_id.update(cx, |this, cx| {
|
for reports in this.values_mut() {
|
||||||
for reports in this.values_mut() {
|
for report in reports.iter_mut() {
|
||||||
for report in reports.iter_mut() {
|
let Some(output) = report.output.as_mut() else {
|
||||||
if let Some(output) = report.output.as_mut() {
|
continue;
|
||||||
if output.id() == &event_id {
|
};
|
||||||
output.success.insert(relay_url.clone());
|
match &*status {
|
||||||
cx.notify();
|
SendStatus::Ok { id, relay } => {
|
||||||
|
if output.id() == id {
|
||||||
|
output.success.insert(relay.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SendStatus::Failed { id, relay, message } => {
|
||||||
|
if output.id() == id {
|
||||||
|
output.failed.insert(relay.clone(), message.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -442,23 +459,12 @@ impl ChatPanel {
|
|||||||
self.reports_by_id
|
self.reports_by_id
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.get(id)
|
.get(id)
|
||||||
.is_some_and(|reports| reports.iter().any(|r| r.pending()))
|
.is_some_and(|reports| reports.iter().all(|r| r.pending()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a message was sent successfully by its ID
|
/// Check if a message has any reports
|
||||||
fn sent_success(&self, id: &EventId, cx: &App) -> bool {
|
fn has_reports(&self, id: &EventId, cx: &App) -> bool {
|
||||||
self.reports_by_id
|
self.reports_by_id.read(cx).get(id).is_some()
|
||||||
.read(cx)
|
|
||||||
.get(id)
|
|
||||||
.is_some_and(|reports| reports.iter().any(|r| r.success()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if a message failed to send by its ID
|
|
||||||
fn sent_failed(&self, id: &EventId, cx: &App) -> Option<bool> {
|
|
||||||
self.reports_by_id
|
|
||||||
.read(cx)
|
|
||||||
.get(id)
|
|
||||||
.map(|reports| reports.iter().all(|r| !r.success()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all sent reports for a message by its ID
|
/// Get all sent reports for a message by its ID
|
||||||
@@ -825,19 +831,13 @@ impl ChatPanel {
|
|||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let id = message.id;
|
let id = message.id;
|
||||||
let author = self.profile(&message.author, cx);
|
let author = self.profile(&message.author, cx);
|
||||||
let public_key = author.public_key();
|
let pk = author.public_key();
|
||||||
|
|
||||||
let replies = message.replies_to.as_slice();
|
let replies = message.replies_to.as_slice();
|
||||||
let has_replies = !replies.is_empty();
|
let has_replies = !replies.is_empty();
|
||||||
|
|
||||||
// Check if message is sent failed
|
|
||||||
let sent_pending = self.sent_pending(&id, cx);
|
let sent_pending = self.sent_pending(&id, cx);
|
||||||
|
let has_reports = self.has_reports(&id, cx);
|
||||||
// Check if message is sent successfully
|
|
||||||
let sent_success = self.sent_success(&id, cx);
|
|
||||||
|
|
||||||
// Check if message is sent failed
|
|
||||||
let sent_failed = self.sent_failed(&id, cx);
|
|
||||||
|
|
||||||
// Hide avatar setting
|
// Hide avatar setting
|
||||||
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
||||||
@@ -854,14 +854,17 @@ impl ChatPanel {
|
|||||||
.flex()
|
.flex()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.when(!hide_avatar, |this| {
|
.when(!hide_avatar, |this| {
|
||||||
this.child(Avatar::new(author.avatar()).dropdown_menu(
|
this.child(
|
||||||
move |this, _window, _cx| {
|
Avatar::new(author.avatar())
|
||||||
this.menu("Copy Public Key", Box::new(Command::Copy(public_key)))
|
.flex_shrink_0()
|
||||||
.menu("View Relays", Box::new(Command::Relays(public_key)))
|
.relative()
|
||||||
.separator()
|
.dropdown_menu(move |this, _window, _cx| {
|
||||||
.menu("View on njump.me", Box::new(Command::Njump(public_key)))
|
this.menu("Public Key", Box::new(Command::Copy(pk)))
|
||||||
},
|
.menu("View Relays", Box::new(Command::Relays(pk)))
|
||||||
))
|
.separator()
|
||||||
|
.menu("View on njump.me", Box::new(Command::Njump(pk)))
|
||||||
|
}),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
@@ -882,21 +885,16 @@ impl ChatPanel {
|
|||||||
)
|
)
|
||||||
.child(message.created_at.to_human_time())
|
.child(message.created_at.to_human_time())
|
||||||
.when(sent_pending, |this| {
|
.when(sent_pending, |this| {
|
||||||
this.child(deferred(Indicator::new().small()))
|
this.child(SharedString::from("• Sending..."))
|
||||||
})
|
})
|
||||||
.when(sent_success, |this| {
|
.when(has_reports, |this| {
|
||||||
this.child(deferred(self.render_sent_indicator(&id, cx)))
|
this.child(deferred(self.render_sent_reports(&id, cx)))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.when(has_replies, |this| {
|
.when(has_replies, |this| {
|
||||||
this.children(self.render_message_replies(replies, cx))
|
this.children(self.render_message_replies(replies, cx))
|
||||||
})
|
})
|
||||||
.child(rendered_text)
|
.child(rendered_text),
|
||||||
.when_some(sent_failed, |this, failed| {
|
|
||||||
this.when(failed, |this| {
|
|
||||||
this.child(deferred(self.render_message_reports(&id, cx)))
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
@@ -909,7 +907,7 @@ impl ChatPanel {
|
|||||||
.h_full()
|
.h_full()
|
||||||
.bg(cx.theme().border_transparent),
|
.bg(cx.theme().border_transparent),
|
||||||
)
|
)
|
||||||
.child(self.render_actions(&id, &public_key, cx))
|
.child(self.render_actions(&id, &pk, cx))
|
||||||
.on_mouse_down(
|
.on_mouse_down(
|
||||||
MouseButton::Middle,
|
MouseButton::Middle,
|
||||||
cx.listener(move |this, _, _window, cx| {
|
cx.listener(move |this, _, _window, cx| {
|
||||||
@@ -969,50 +967,37 @@ impl ChatPanel {
|
|||||||
items
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_sent_indicator(&self, id: &EventId, cx: &Context<Self>) -> impl IntoElement {
|
fn render_sent_reports(&self, id: &EventId, cx: &App) -> impl IntoElement {
|
||||||
|
let reports = self.sent_reports(id, cx);
|
||||||
|
|
||||||
|
let success = reports
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|reports| reports.iter().any(|r| r.success()));
|
||||||
|
|
||||||
|
let failed = reports
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|reports| reports.iter().all(|r| r.failed()));
|
||||||
|
|
||||||
|
let label = if success {
|
||||||
|
SharedString::from("• Sent")
|
||||||
|
} else if failed {
|
||||||
|
SharedString::from("• Failed")
|
||||||
|
} else {
|
||||||
|
SharedString::from("• Sending...")
|
||||||
|
};
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.id(SharedString::from(id.to_hex()))
|
.id(SharedString::from(id.to_hex()))
|
||||||
.child(SharedString::from("• Sent"))
|
.child(label)
|
||||||
.when_some(self.sent_reports(id, cx), |this, reports| {
|
.when(failed, |this| this.text_color(cx.theme().text_danger))
|
||||||
|
.when_some(reports, |this, reports| {
|
||||||
this.on_click(move |_e, window, cx| {
|
this.on_click(move |_e, window, cx| {
|
||||||
let reports = reports.clone();
|
let reports = reports.clone();
|
||||||
|
|
||||||
window.open_modal(cx, move |this, _window, cx| {
|
window.open_modal(cx, move |this, _window, cx| {
|
||||||
this.show_close(true)
|
this.title(SharedString::from("Sent Reports"))
|
||||||
.title(SharedString::from("Sent Reports"))
|
.show_close(true)
|
||||||
.child(v_flex().pb_2().gap_4().children({
|
.child(v_flex().gap_4().children({
|
||||||
let mut items = Vec::with_capacity(reports.len());
|
|
||||||
|
|
||||||
for report in reports.iter() {
|
|
||||||
items.push(Self::render_report(report, cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
items
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_message_reports(&self, id: &EventId, cx: &Context<Self>) -> impl IntoElement {
|
|
||||||
h_flex()
|
|
||||||
.id(SharedString::from(id.to_hex()))
|
|
||||||
.gap_1()
|
|
||||||
.text_color(cx.theme().text_danger)
|
|
||||||
.text_xs()
|
|
||||||
.italic()
|
|
||||||
.child(Icon::new(IconName::Info).small())
|
|
||||||
.child(SharedString::from(
|
|
||||||
"Failed to send message. Click to see details.",
|
|
||||||
))
|
|
||||||
.when_some(self.sent_reports(id, cx), |this, reports| {
|
|
||||||
this.on_click(move |_e, window, cx| {
|
|
||||||
let reports = reports.clone();
|
|
||||||
|
|
||||||
window.open_modal(cx, move |this, _window, cx| {
|
|
||||||
this.show_close(true)
|
|
||||||
.title(SharedString::from("Sent Reports"))
|
|
||||||
.child(v_flex().gap_4().w_full().children({
|
|
||||||
let mut items = Vec::with_capacity(reports.len());
|
let mut items = Vec::with_capacity(reports.len());
|
||||||
|
|
||||||
for report in reports.iter() {
|
for report in reports.iter() {
|
||||||
|
|||||||
Reference in New Issue
Block a user