wip: refactor
This commit is contained in:
91
crates/app/src/views/dock/chat/form.rs
Normal file
91
crates/app/src/views/dock/chat/form.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
use coop_ui::{
|
||||
button::{Button, ButtonVariants},
|
||||
input::{InputEvent, TextInput},
|
||||
theme::ActiveTheme,
|
||||
Icon, IconName,
|
||||
};
|
||||
use gpui::*;
|
||||
use nostr_sdk::prelude::*;
|
||||
|
||||
use crate::get_client;
|
||||
|
||||
pub struct Form {
|
||||
to: PublicKey,
|
||||
input: View<TextInput>,
|
||||
}
|
||||
|
||||
impl Form {
|
||||
pub fn new(to: PublicKey, cx: &mut ViewContext<'_, Self>) -> Self {
|
||||
let input = cx.new_view(|cx| {
|
||||
TextInput::new(cx)
|
||||
.appearance(false)
|
||||
.text_size(coop_ui::Size::Small)
|
||||
.placeholder("Message...")
|
||||
.cleanable()
|
||||
});
|
||||
|
||||
cx.subscribe(&input, move |form, text_input, input_event, cx| {
|
||||
if let InputEvent::PressEnter = input_event {
|
||||
let content = text_input.read(cx).text().to_string();
|
||||
// TODO: clean up content
|
||||
|
||||
form.send_message(content, cx);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self { to, input }
|
||||
}
|
||||
|
||||
fn send_message(&mut self, content: String, cx: &mut ViewContext<Self>) {
|
||||
let send_to = self.to;
|
||||
let content_clone = content.clone();
|
||||
|
||||
cx.foreground_executor()
|
||||
.spawn(async move {
|
||||
let client = get_client();
|
||||
let signer = client.signer().await.unwrap();
|
||||
let public_key = signer.get_public_key().await.unwrap();
|
||||
|
||||
match client.send_private_msg(send_to, content, vec![]).await {
|
||||
Ok(_) => {
|
||||
// Send a copy to yourself
|
||||
if let Err(_e) = client
|
||||
.send_private_msg(public_key, content_clone, vec![])
|
||||
.await
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
Err(_) => todo!(),
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Form {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.h_12()
|
||||
.flex_shrink_0()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.px_2()
|
||||
.child(
|
||||
Button::new("upload")
|
||||
.icon(Icon::new(IconName::Upload))
|
||||
.ghost(),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.flex()
|
||||
.bg(cx.theme().muted)
|
||||
.rounded(px(cx.theme().radius))
|
||||
.px_2()
|
||||
.child(self.input.clone()),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,26 @@
|
||||
use gpui::*;
|
||||
use nostr_sdk::prelude::*;
|
||||
|
||||
use crate::get_client;
|
||||
use crate::{get_client, states::chat::ChatRegistry};
|
||||
|
||||
pub struct Messages {
|
||||
pub struct MessageList {
|
||||
member: PublicKey,
|
||||
messages: Model<Option<Events>>,
|
||||
}
|
||||
|
||||
impl Messages {
|
||||
impl MessageList {
|
||||
pub fn new(from: PublicKey, cx: &mut ViewContext<'_, Self>) -> Self {
|
||||
let messages = cx.new_model(|_| None);
|
||||
let async_messages = messages.clone();
|
||||
|
||||
Self {
|
||||
member: from,
|
||||
messages,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&self, cx: &mut ViewContext<Self>) {
|
||||
let messages = self.messages.clone();
|
||||
let member = self.member;
|
||||
|
||||
let mut async_cx = cx.to_async();
|
||||
|
||||
@@ -20,40 +30,45 @@ impl Messages {
|
||||
let signer = client.signer().await.unwrap();
|
||||
let public_key = signer.get_public_key().await.unwrap();
|
||||
|
||||
let recv_filter = Filter::new()
|
||||
let recv = Filter::new()
|
||||
.kind(Kind::PrivateDirectMessage)
|
||||
.author(from)
|
||||
.author(member)
|
||||
.pubkey(public_key);
|
||||
|
||||
let sender_filter = Filter::new()
|
||||
let send = Filter::new()
|
||||
.kind(Kind::PrivateDirectMessage)
|
||||
.author(public_key)
|
||||
.pubkey(from);
|
||||
.pubkey(member);
|
||||
|
||||
let events = async_cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
client
|
||||
.database()
|
||||
.query(vec![recv_filter, sender_filter])
|
||||
.await
|
||||
})
|
||||
.spawn(async move { client.database().query(vec![recv, send]).await })
|
||||
.await;
|
||||
|
||||
if let Ok(events) = events {
|
||||
_ = async_cx.update_model(&async_messages, |a, b| {
|
||||
_ = async_cx.update_model(&messages, |a, b| {
|
||||
*a = Some(events);
|
||||
b.notify();
|
||||
});
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
Self { messages }
|
||||
pub fn subscribe(&self, cx: &mut ViewContext<Self>) {
|
||||
let receiver = cx.global::<ChatRegistry>().receiver.clone();
|
||||
|
||||
cx.foreground_executor()
|
||||
.spawn(async move {
|
||||
while let Ok(event) = receiver.recv_async().await {
|
||||
println!("New message: {}", event.as_json())
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Messages {
|
||||
impl Render for MessageList {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let mut content = div().size_full().flex().flex_col().justify_end();
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
use coop_ui::{
|
||||
button::Button,
|
||||
button_group::ButtonGroup,
|
||||
dock::{DockItemState, Panel, PanelEvent, TitleStyle},
|
||||
input::TextInput,
|
||||
popup_menu::PopupMenu,
|
||||
Sizable,
|
||||
};
|
||||
use form::Form;
|
||||
use gpui::*;
|
||||
use messages::Messages;
|
||||
use list::MessageList;
|
||||
use nostr_sdk::*;
|
||||
|
||||
pub mod messages;
|
||||
pub mod form;
|
||||
pub mod list;
|
||||
|
||||
pub struct ChatPanel {
|
||||
// Panel
|
||||
@@ -19,17 +18,21 @@ pub struct ChatPanel {
|
||||
zoomable: bool,
|
||||
focus_handle: FocusHandle,
|
||||
// Chat Room
|
||||
messages: View<Messages>,
|
||||
input: View<TextInput>,
|
||||
list: View<MessageList>,
|
||||
form: View<Form>,
|
||||
}
|
||||
|
||||
impl ChatPanel {
|
||||
pub fn new(from: PublicKey, cx: &mut WindowContext) -> View<Self> {
|
||||
let input = cx.new_view(TextInput::new);
|
||||
let messages = cx.new_view(|cx| Messages::new(from, cx));
|
||||
let form = cx.new_view(|cx| Form::new(from, cx));
|
||||
let list = cx.new_view(|cx| {
|
||||
let list = MessageList::new(from, cx);
|
||||
// Load messages from database
|
||||
list.init(cx);
|
||||
// Subscribe for new message
|
||||
list.subscribe(cx);
|
||||
|
||||
input.update(cx, |input, _cx| {
|
||||
input.set_placeholder("Message");
|
||||
list
|
||||
});
|
||||
|
||||
cx.new_view(|cx| Self {
|
||||
@@ -37,8 +40,8 @@ impl ChatPanel {
|
||||
closeable: true,
|
||||
zoomable: true,
|
||||
focus_handle: cx.focus_handle(),
|
||||
messages,
|
||||
input,
|
||||
list,
|
||||
form,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -91,22 +94,7 @@ impl Render for ChatPanel {
|
||||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(self.messages.clone())
|
||||
.child(
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.px_2()
|
||||
.h_11()
|
||||
.child(self.input.clone())
|
||||
.child(
|
||||
ButtonGroup::new("actions")
|
||||
.large()
|
||||
.child(Button::new("upload").label("Upload"))
|
||||
.child(Button::new("send").label("Send")),
|
||||
),
|
||||
)
|
||||
.child(self.list.clone())
|
||||
.child(self.form.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use prelude::FluentBuilder;
|
||||
|
||||
use crate::{
|
||||
get_client,
|
||||
states::signal::SignalRegistry,
|
||||
states::metadata::{MetadataRegistry, Signal},
|
||||
utils::{ago, show_npub},
|
||||
views::app::AddPanel,
|
||||
};
|
||||
@@ -153,6 +153,55 @@ impl Chat {
|
||||
|
||||
let mut async_cx = cx.to_async();
|
||||
|
||||
let client = get_client();
|
||||
let signal = cx.global::<MetadataRegistry>();
|
||||
|
||||
if !signal.contains(public_key) {
|
||||
cx.foreground_executor()
|
||||
.spawn(async move {
|
||||
let query = async_cx
|
||||
.background_executor()
|
||||
.spawn(async move { client.database().metadata(public_key).await })
|
||||
.await;
|
||||
|
||||
if let Ok(metadata) = query {
|
||||
_ = async_cx.update_model(&async_metadata, |a, b| {
|
||||
*a = metadata;
|
||||
b.notify();
|
||||
});
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
} else {
|
||||
let reqs = signal.reqs.clone();
|
||||
|
||||
cx.foreground_executor()
|
||||
.spawn(async move {
|
||||
if let Err(e) = reqs.send(Signal::REQ(public_key)).await {
|
||||
println!("Error: {}", e)
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.observe_global::<MetadataRegistry>(|view, cx| {
|
||||
view.profile(cx);
|
||||
})
|
||||
.detach();
|
||||
};
|
||||
|
||||
Self {
|
||||
public_key,
|
||||
last_seen,
|
||||
metadata,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn profile(&self, cx: &mut ViewContext<Self>) {
|
||||
let public_key = self.public_key;
|
||||
let async_metadata = self.metadata.clone();
|
||||
let mut async_cx = cx.to_async();
|
||||
|
||||
cx.foreground_executor()
|
||||
.spawn(async move {
|
||||
let client = get_client();
|
||||
@@ -169,47 +218,6 @@ impl Chat {
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.update_global::<SignalRegistry, _>(|state, _cx| {
|
||||
state.add_to_queue(public_key);
|
||||
});
|
||||
|
||||
cx.observe_global::<SignalRegistry>(|chat, cx| {
|
||||
chat.load_profile(cx);
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
public_key,
|
||||
last_seen,
|
||||
metadata,
|
||||
title: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn load_profile(&self, cx: &mut ViewContext<Self>) {
|
||||
let public_key = self.public_key;
|
||||
let async_metadata = self.metadata.clone();
|
||||
let mut async_cx = cx.to_async();
|
||||
|
||||
if cx.global::<SignalRegistry>().contains(self.public_key) {
|
||||
cx.foreground_executor()
|
||||
.spawn(async move {
|
||||
let client = get_client();
|
||||
let query = async_cx
|
||||
.background_executor()
|
||||
.spawn(async move { client.database().metadata(public_key).await })
|
||||
.await;
|
||||
|
||||
if let Ok(metadata) = query {
|
||||
_ = async_cx.update_model(&async_metadata, |a, b| {
|
||||
*a = metadata;
|
||||
b.notify();
|
||||
});
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use chat::Chat;
|
||||
use coop_ui::{theme::ActiveTheme, v_flex, Collapsible, Icon, IconName, StyledExt};
|
||||
use gpui::*;
|
||||
|
||||
use itertools::Itertools;
|
||||
use prelude::FluentBuilder;
|
||||
use std::cmp::Reverse;
|
||||
|
||||
use crate::states::chat::ChatRegistry;
|
||||
use crate::states::{account::AccountRegistry, chat::ChatRegistry};
|
||||
|
||||
pub mod chat;
|
||||
|
||||
@@ -19,7 +20,6 @@ impl Inbox {
|
||||
pub fn new(cx: &mut ViewContext<'_, Self>) -> Self {
|
||||
let chats = cx.new_model(|_| None);
|
||||
|
||||
// Reload UI if global state changes
|
||||
cx.observe_global::<ChatRegistry>(|inbox, cx| {
|
||||
inbox.load(cx);
|
||||
})
|
||||
@@ -39,17 +39,22 @@ impl Inbox {
|
||||
|
||||
// Read global chat registry
|
||||
let events = cx.global::<ChatRegistry>().get(cx);
|
||||
let current_user = cx.global::<AccountRegistry>().get();
|
||||
|
||||
if let Some(events) = events {
|
||||
let chats: Vec<View<Chat>> = events
|
||||
.into_iter()
|
||||
.map(|event| cx.new_view(|cx| Chat::new(event, cx)))
|
||||
.collect();
|
||||
if let Some(public_key) = current_user {
|
||||
if let Some(events) = events {
|
||||
let chats: Vec<View<Chat>> = events
|
||||
.into_iter()
|
||||
.filter(|ev| ev.pubkey != public_key)
|
||||
.sorted_by_key(|ev| Reverse(ev.created_at))
|
||||
.map(|ev| cx.new_view(|cx| Chat::new(ev, cx)))
|
||||
.collect();
|
||||
|
||||
cx.update_model(&self.chats, |a, b| {
|
||||
*a = Some(chats);
|
||||
b.notify();
|
||||
});
|
||||
cx.update_model(&self.chats, |a, b| {
|
||||
*a = Some(chats);
|
||||
b.notify();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user