chore: use same composer and message actions for nip29
This commit is contained in:
+15
-19
@@ -2047,18 +2047,6 @@ impl Render for ChatPanel {
|
||||
this.upload(window, cx);
|
||||
})),
|
||||
)
|
||||
.when(cfg!(not(target_arch = "wasm32")), |this| {
|
||||
this.child(
|
||||
Button::new("nutzap")
|
||||
.icon(IconName::Wallet)
|
||||
.tooltip("Send eCash")
|
||||
.disabled(dictating)
|
||||
.ghost()
|
||||
.on_click(cx.listener(move |this, _ev, window, cx| {
|
||||
this.open_nutzap(window, cx);
|
||||
})),
|
||||
)
|
||||
})
|
||||
.when(dictating, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
@@ -2082,17 +2070,25 @@ impl Render for ChatPanel {
|
||||
.flex_1(),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.items_end()
|
||||
.gap_1()
|
||||
.pl_1()
|
||||
.child(SpeechButton::new(&self.speech)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.pl_1()
|
||||
.gap_1()
|
||||
.child(SpeechButton::new(&self.speech))
|
||||
.when(cfg!(not(target_arch = "wasm32")), |this| {
|
||||
this.child(
|
||||
Button::new("nutzap")
|
||||
.icon(IconName::Wallet)
|
||||
.tooltip("Send eCash")
|
||||
.disabled(dictating)
|
||||
.ghost()
|
||||
.on_click(cx.listener(
|
||||
move |this, _ev, window, cx| {
|
||||
this.open_nutzap(window, cx);
|
||||
},
|
||||
)),
|
||||
)
|
||||
})
|
||||
.child(self.render_emoji_menu(dictating, window, cx))
|
||||
.child(self.render_config_menu(dictating, window, cx))
|
||||
.child(
|
||||
|
||||
+78
-21
@@ -2,13 +2,13 @@ use std::collections::BTreeMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use chat_ui::ToggleDictation;
|
||||
use chat_ui::{Command, ToggleDictation};
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
Action, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
FollowMode, InteractiveElement, IntoElement, ListAlignment, ListScrollEvent, ListState,
|
||||
ParentElement, Render, SharedString, Styled, Subscription, Task, WeakEntity, Window, div, list,
|
||||
px,
|
||||
Action, Anchor, AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, FollowMode, InteractiveElement, IntoElement, ListAlignment,
|
||||
ListScrollEvent, ListState, ParentElement, Render, SharedString, Styled, Subscription, Task,
|
||||
WeakEntity, Window, div, list, px,
|
||||
};
|
||||
use gpui_kit::assets::IconName;
|
||||
use gpui_kit::base::StyledExt as _;
|
||||
@@ -16,6 +16,7 @@ use gpui_kit::component::button::{Button, ButtonVariant, ButtonVariants};
|
||||
use gpui_kit::component::dialog::DialogButtonProps;
|
||||
use gpui_kit::component::dock::{Panel, PanelEvent};
|
||||
use gpui_kit::component::input::{InputEvent, Textarea, TextareaState};
|
||||
use gpui_kit::component::menu::DropdownMenu;
|
||||
use gpui_kit::component::notification::Notification;
|
||||
use gpui_kit::component::scroll::Scrollbar;
|
||||
use gpui_kit::component::speech::{SpeechButton, SpeechEvent, SpeechState, SpeechWaveform};
|
||||
@@ -28,7 +29,7 @@ use settings::AppSettings;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use speech::speaker::Speaker;
|
||||
use state::{NostrRegistry, pick_file, upload_file};
|
||||
use ui::{Avatar, RenderedText, RevealText, WelcomeMessage, rendered_text};
|
||||
use ui::{Avatar, REACTION_EMOJIS, RenderedText, RevealText, WelcomeMessage, rendered_text};
|
||||
use util::{attachment_name, display_name, opens_run, pin_label, report};
|
||||
|
||||
mod details;
|
||||
@@ -1104,7 +1105,23 @@ impl GroupPanel {
|
||||
});
|
||||
}
|
||||
|
||||
fn render_composer(&self, cx: &mut Context<Self>) -> AnyElement {
|
||||
fn on_command(&mut self, command: &Command, window: &mut Window, cx: &mut Context<Self>) {
|
||||
match command {
|
||||
Command::Insert(content) => {
|
||||
self.input.update(cx, |input, cx| {
|
||||
let new_value = format!("{} {}", input.value(), content);
|
||||
input.set_value(new_value, window, cx);
|
||||
});
|
||||
}
|
||||
Command::Copy(public_key) => match public_key.to_bech32() {
|
||||
Ok(content) => cx.write_to_clipboard(ClipboardItem::new_string(content)),
|
||||
Err(error) => log::warn!("nip29: encoding the author key failed: {error}"),
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_composer(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
||||
let notice = self.group.upgrade().and_then(|group| {
|
||||
let group = group.read(cx);
|
||||
|
||||
@@ -1135,14 +1152,13 @@ impl GroupPanel {
|
||||
.flex_shrink_0()
|
||||
.w_full()
|
||||
.p_2()
|
||||
.gap_1()
|
||||
.gap_1p5()
|
||||
.when_some(self.reply_to, |this, target| {
|
||||
this.child(self.render_reply_target(target, cx))
|
||||
})
|
||||
.children(self.render_attachments(cx))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.items_end()
|
||||
.child(
|
||||
Button::new("upload")
|
||||
@@ -1161,6 +1177,7 @@ impl GroupPanel {
|
||||
.h_8()
|
||||
.flex_1()
|
||||
.gap_2()
|
||||
.pl_1()
|
||||
.child(SpeechWaveform::new(&self.speech).small().flex_1())
|
||||
.when_some(self.speech_started, |this, started| {
|
||||
this.child(speech::elapsed(started, cx))
|
||||
@@ -1171,28 +1188,67 @@ impl GroupPanel {
|
||||
this.child(
|
||||
Textarea::new(&self.input)
|
||||
.appearance(false)
|
||||
.bg(cx.theme().input)
|
||||
.small()
|
||||
.min_h_8()
|
||||
.py_1()
|
||||
.flex_1(),
|
||||
)
|
||||
})
|
||||
.child(SpeechButton::new(&self.speech))
|
||||
.child(
|
||||
Button::new("send")
|
||||
.icon(ui::IconName::PaperPlaneFill)
|
||||
.tooltip("Send")
|
||||
.ghost()
|
||||
.disabled(dictating || self.uploading)
|
||||
.on_click(cx.listener(|this, _event, window, cx| {
|
||||
this.send(window, cx);
|
||||
})),
|
||||
h_flex()
|
||||
.pl_1()
|
||||
.gap_1()
|
||||
.child(SpeechButton::new(&self.speech))
|
||||
.child(self.render_emoji_menu(dictating, window, cx))
|
||||
.child(
|
||||
Button::new("send")
|
||||
.icon(ui::IconName::PaperPlaneFill)
|
||||
.ghost()
|
||||
.disabled(dictating || self.uploading)
|
||||
.on_click(cx.listener(|this, _event, window, cx| {
|
||||
this.send(window, cx);
|
||||
})),
|
||||
),
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_emoji_menu(
|
||||
&self,
|
||||
disabled: bool,
|
||||
_window: &Window,
|
||||
cx: &Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let current_user = nostr.read(cx).current_user();
|
||||
|
||||
let emojis = current_user
|
||||
.map(|current_user| {
|
||||
self.directory
|
||||
.read(cx)
|
||||
.person(¤t_user, cx)
|
||||
.emojis()
|
||||
.to_vec()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
Button::new("emoji")
|
||||
.icon(ui::IconName::Emoji)
|
||||
.ghost()
|
||||
.disabled(disabled)
|
||||
.dropdown_menu_with_anchor(Anchor::BottomLeft, move |this, _window, _cx| {
|
||||
let menu = REACTION_EMOJIS.iter().fold(this, |this, emoji| {
|
||||
this.menu(*emoji, Box::new(Command::Insert(emoji.to_string())))
|
||||
});
|
||||
|
||||
emojis.iter().fold(menu, |this, (shortcode, _)| {
|
||||
let label = format!(":{shortcode}:");
|
||||
this.menu(label.clone(), Box::new(Command::Insert(label)))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn render_reply_target(&self, target: EventId, cx: &mut Context<Self>) -> AnyElement {
|
||||
let context = self.group.upgrade().and_then(|entity| {
|
||||
entity.read_with(cx, |group, cx| {
|
||||
@@ -1375,13 +1431,14 @@ impl Focusable for GroupPanel {
|
||||
}
|
||||
|
||||
impl Render for GroupPanel {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let notice = self.notice(cx);
|
||||
let empty = self.item_count(cx) == 2;
|
||||
|
||||
v_flex()
|
||||
.size_full()
|
||||
.min_w_0()
|
||||
.on_action(cx.listener(Self::on_command))
|
||||
.on_action(cx.listener(Self::toggle_dictation))
|
||||
.child(self.render_encryption_notice(cx))
|
||||
.child(self.render_banner(cx))
|
||||
@@ -1422,6 +1479,6 @@ impl Render for GroupPanel {
|
||||
})
|
||||
.child(Scrollbar::vertical(&self.list_state)),
|
||||
)
|
||||
.child(self.render_composer(cx))
|
||||
.child(self.render_composer(window, cx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use chat::Message;
|
||||
use chat_ui::Command;
|
||||
use common::TimestampExt;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
@@ -17,8 +18,8 @@ use settings::AppSettings;
|
||||
use speech::speaker::END_GRACE;
|
||||
use state::NostrRegistry;
|
||||
use ui::{
|
||||
Avatar, MessageActions, MessageMedia, MessageRow, REACTION_EMOJIS, ReactionBar, ReplyPreview,
|
||||
group_reactions, react_menu,
|
||||
Avatar, COMPACT_REACTION_EMOJIS, MessageActions, MessageMedia, MessageRow, REACTION_EMOJIS,
|
||||
ReactionBar, ReplyPreview, group_reactions, react_menu,
|
||||
};
|
||||
|
||||
use crate::GroupPanel;
|
||||
@@ -91,15 +92,45 @@ pub(crate) fn render(
|
||||
let admin = actions.admin;
|
||||
let pinned = actions.pinned;
|
||||
let group = actions.group;
|
||||
let speech_panel = actions.panel;
|
||||
let panel = speech_panel.clone();
|
||||
let panel = actions.panel;
|
||||
let speech_panel = panel.clone();
|
||||
let speech_text = message.preview();
|
||||
let speech_id = message.id;
|
||||
let speaking = actions.speaking;
|
||||
let author = message.author;
|
||||
|
||||
let row = row.overlay(
|
||||
MessageActions::new().speech(speaking, move |_event, _window, cx| {
|
||||
_ = speech_panel.update_in(cx, |panel, window, cx| {
|
||||
let mut bar = MessageActions::new();
|
||||
|
||||
for emoji in COMPACT_REACTION_EMOJIS {
|
||||
let emoji = *emoji;
|
||||
let group = group.clone();
|
||||
let panel = panel.clone();
|
||||
|
||||
bar = bar.reaction(emoji, move |_event, _window, cx| {
|
||||
if let Err(error) = panel.update_in(cx, |panel, window, cx| {
|
||||
panel.react(group.clone(), id, emoji.to_owned(), window, cx);
|
||||
}) {
|
||||
log::warn!("nip29: sending a reaction failed: {error}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let reply_panel = panel.clone();
|
||||
let bar = bar.reply(move |_event, _window, cx| {
|
||||
if let Err(error) = reply_panel.update_in(cx, |panel, window, cx| {
|
||||
panel.start_reply(id, window, cx);
|
||||
}) {
|
||||
log::warn!("nip29: starting a reply failed: {error}");
|
||||
}
|
||||
});
|
||||
|
||||
let bar = bar
|
||||
.copy(
|
||||
SharedString::from(format!("message-copy-{id}")),
|
||||
message.preview(),
|
||||
)
|
||||
.speech(speaking, move |_event, _window, cx| {
|
||||
if let Err(error) = speech_panel.update_in(cx, |panel, window, cx| {
|
||||
match panel.speaker.speak(&speech_text) {
|
||||
Ok(duration) => {
|
||||
panel.speech_reveal = Some((speech_id, duration));
|
||||
@@ -125,9 +156,14 @@ pub(crate) fn render(
|
||||
window.push_notification(Notification::error(error.to_string()), cx)
|
||||
}
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
}) {
|
||||
log::warn!("nip29: reading the message aloud failed: {error}");
|
||||
}
|
||||
});
|
||||
|
||||
let row = row.overlay(bar.dropdown(move |menu, _window, _cx| {
|
||||
menu.menu("Copy author", Box::new(Command::Copy(author)))
|
||||
}));
|
||||
|
||||
let custom = nostr
|
||||
.read(cx)
|
||||
|
||||
Reference in New Issue
Block a user