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);
|
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| {
|
.when(dictating, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
@@ -2082,17 +2070,25 @@ impl Render for ChatPanel {
|
|||||||
.flex_1(),
|
.flex_1(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.items_end()
|
|
||||||
.gap_1()
|
|
||||||
.pl_1()
|
|
||||||
.child(SpeechButton::new(&self.speech)),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.pl_1()
|
.pl_1()
|
||||||
.gap_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_emoji_menu(dictating, window, cx))
|
||||||
.child(self.render_config_menu(dictating, window, cx))
|
.child(self.render_config_menu(dictating, window, cx))
|
||||||
.child(
|
.child(
|
||||||
|
|||||||
+78
-21
@@ -2,13 +2,13 @@ use std::collections::BTreeMap;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use chat_ui::ToggleDictation;
|
use chat_ui::{Command, ToggleDictation};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
Action, Anchor, AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter,
|
||||||
FollowMode, InteractiveElement, IntoElement, ListAlignment, ListScrollEvent, ListState,
|
FocusHandle, Focusable, FollowMode, InteractiveElement, IntoElement, ListAlignment,
|
||||||
ParentElement, Render, SharedString, Styled, Subscription, Task, WeakEntity, Window, div, list,
|
ListScrollEvent, ListState, ParentElement, Render, SharedString, Styled, Subscription, Task,
|
||||||
px,
|
WeakEntity, Window, div, list, px,
|
||||||
};
|
};
|
||||||
use gpui_kit::assets::IconName;
|
use gpui_kit::assets::IconName;
|
||||||
use gpui_kit::base::StyledExt as _;
|
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::dialog::DialogButtonProps;
|
||||||
use gpui_kit::component::dock::{Panel, PanelEvent};
|
use gpui_kit::component::dock::{Panel, PanelEvent};
|
||||||
use gpui_kit::component::input::{InputEvent, Textarea, TextareaState};
|
use gpui_kit::component::input::{InputEvent, Textarea, TextareaState};
|
||||||
|
use gpui_kit::component::menu::DropdownMenu;
|
||||||
use gpui_kit::component::notification::Notification;
|
use gpui_kit::component::notification::Notification;
|
||||||
use gpui_kit::component::scroll::Scrollbar;
|
use gpui_kit::component::scroll::Scrollbar;
|
||||||
use gpui_kit::component::speech::{SpeechButton, SpeechEvent, SpeechState, SpeechWaveform};
|
use gpui_kit::component::speech::{SpeechButton, SpeechEvent, SpeechState, SpeechWaveform};
|
||||||
@@ -28,7 +29,7 @@ use settings::AppSettings;
|
|||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
use speech::speaker::Speaker;
|
use speech::speaker::Speaker;
|
||||||
use state::{NostrRegistry, pick_file, upload_file};
|
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};
|
use util::{attachment_name, display_name, opens_run, pin_label, report};
|
||||||
|
|
||||||
mod details;
|
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 notice = self.group.upgrade().and_then(|group| {
|
||||||
let group = group.read(cx);
|
let group = group.read(cx);
|
||||||
|
|
||||||
@@ -1135,14 +1152,13 @@ impl GroupPanel {
|
|||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.w_full()
|
.w_full()
|
||||||
.p_2()
|
.p_2()
|
||||||
.gap_1()
|
.gap_1p5()
|
||||||
.when_some(self.reply_to, |this, target| {
|
.when_some(self.reply_to, |this, target| {
|
||||||
this.child(self.render_reply_target(target, cx))
|
this.child(self.render_reply_target(target, cx))
|
||||||
})
|
})
|
||||||
.children(self.render_attachments(cx))
|
.children(self.render_attachments(cx))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
|
||||||
.items_end()
|
.items_end()
|
||||||
.child(
|
.child(
|
||||||
Button::new("upload")
|
Button::new("upload")
|
||||||
@@ -1161,6 +1177,7 @@ impl GroupPanel {
|
|||||||
.h_8()
|
.h_8()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
|
.pl_1()
|
||||||
.child(SpeechWaveform::new(&self.speech).small().flex_1())
|
.child(SpeechWaveform::new(&self.speech).small().flex_1())
|
||||||
.when_some(self.speech_started, |this, started| {
|
.when_some(self.speech_started, |this, started| {
|
||||||
this.child(speech::elapsed(started, cx))
|
this.child(speech::elapsed(started, cx))
|
||||||
@@ -1171,28 +1188,67 @@ impl GroupPanel {
|
|||||||
this.child(
|
this.child(
|
||||||
Textarea::new(&self.input)
|
Textarea::new(&self.input)
|
||||||
.appearance(false)
|
.appearance(false)
|
||||||
.bg(cx.theme().input)
|
|
||||||
.small()
|
.small()
|
||||||
.min_h_8()
|
.min_h_8()
|
||||||
.py_1()
|
.py_1()
|
||||||
.flex_1(),
|
.flex_1(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.child(SpeechButton::new(&self.speech))
|
|
||||||
.child(
|
.child(
|
||||||
Button::new("send")
|
h_flex()
|
||||||
.icon(ui::IconName::PaperPlaneFill)
|
.pl_1()
|
||||||
.tooltip("Send")
|
.gap_1()
|
||||||
.ghost()
|
.child(SpeechButton::new(&self.speech))
|
||||||
.disabled(dictating || self.uploading)
|
.child(self.render_emoji_menu(dictating, window, cx))
|
||||||
.on_click(cx.listener(|this, _event, window, cx| {
|
.child(
|
||||||
this.send(window, cx);
|
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()
|
.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 {
|
fn render_reply_target(&self, target: EventId, cx: &mut Context<Self>) -> AnyElement {
|
||||||
let context = self.group.upgrade().and_then(|entity| {
|
let context = self.group.upgrade().and_then(|entity| {
|
||||||
entity.read_with(cx, |group, cx| {
|
entity.read_with(cx, |group, cx| {
|
||||||
@@ -1375,13 +1431,14 @@ impl Focusable for GroupPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Render 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 notice = self.notice(cx);
|
||||||
let empty = self.item_count(cx) == 2;
|
let empty = self.item_count(cx) == 2;
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
.min_w_0()
|
.min_w_0()
|
||||||
|
.on_action(cx.listener(Self::on_command))
|
||||||
.on_action(cx.listener(Self::toggle_dictation))
|
.on_action(cx.listener(Self::toggle_dictation))
|
||||||
.child(self.render_encryption_notice(cx))
|
.child(self.render_encryption_notice(cx))
|
||||||
.child(self.render_banner(cx))
|
.child(self.render_banner(cx))
|
||||||
@@ -1422,6 +1479,6 @@ impl Render for GroupPanel {
|
|||||||
})
|
})
|
||||||
.child(Scrollbar::vertical(&self.list_state)),
|
.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::Message;
|
||||||
|
use chat_ui::Command;
|
||||||
use common::TimestampExt;
|
use common::TimestampExt;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@@ -17,8 +18,8 @@ use settings::AppSettings;
|
|||||||
use speech::speaker::END_GRACE;
|
use speech::speaker::END_GRACE;
|
||||||
use state::NostrRegistry;
|
use state::NostrRegistry;
|
||||||
use ui::{
|
use ui::{
|
||||||
Avatar, MessageActions, MessageMedia, MessageRow, REACTION_EMOJIS, ReactionBar, ReplyPreview,
|
Avatar, COMPACT_REACTION_EMOJIS, MessageActions, MessageMedia, MessageRow, REACTION_EMOJIS,
|
||||||
group_reactions, react_menu,
|
ReactionBar, ReplyPreview, group_reactions, react_menu,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::GroupPanel;
|
use crate::GroupPanel;
|
||||||
@@ -91,15 +92,45 @@ pub(crate) fn render(
|
|||||||
let admin = actions.admin;
|
let admin = actions.admin;
|
||||||
let pinned = actions.pinned;
|
let pinned = actions.pinned;
|
||||||
let group = actions.group;
|
let group = actions.group;
|
||||||
let speech_panel = actions.panel;
|
let panel = actions.panel;
|
||||||
let panel = speech_panel.clone();
|
let speech_panel = panel.clone();
|
||||||
let speech_text = message.preview();
|
let speech_text = message.preview();
|
||||||
let speech_id = message.id;
|
let speech_id = message.id;
|
||||||
let speaking = actions.speaking;
|
let speaking = actions.speaking;
|
||||||
|
let author = message.author;
|
||||||
|
|
||||||
let row = row.overlay(
|
let mut bar = MessageActions::new();
|
||||||
MessageActions::new().speech(speaking, move |_event, _window, cx| {
|
|
||||||
_ = speech_panel.update_in(cx, |panel, window, cx| {
|
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) {
|
match panel.speaker.speak(&speech_text) {
|
||||||
Ok(duration) => {
|
Ok(duration) => {
|
||||||
panel.speech_reveal = Some((speech_id, 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)
|
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
|
let custom = nostr
|
||||||
.read(cx)
|
.read(cx)
|
||||||
|
|||||||
Reference in New Issue
Block a user