wip: refactor
This commit is contained in:
@@ -250,18 +250,32 @@ async fn main() {
|
|||||||
let metadata = async_cx
|
let metadata = async_cx
|
||||||
.background_executor()
|
.background_executor()
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
(client.database().metadata(event.pubkey).await)
|
client
|
||||||
|
.database()
|
||||||
|
.metadata(event.pubkey)
|
||||||
|
.await
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
_ = async_cx.update_global::<ChatRegistry, _>(|state, _| {
|
_ = async_cx.update_global::<ChatRegistry, _>(|state, _cx| {
|
||||||
state.push(event, metadata);
|
state.push(event, metadata);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Signal::RecvMetadata(public_key) => {
|
Signal::RecvMetadata(public_key) => {
|
||||||
|
let metadata = async_cx
|
||||||
|
.background_executor()
|
||||||
|
.spawn(async move {
|
||||||
|
client
|
||||||
|
.database()
|
||||||
|
.metadata(public_key)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
_ = async_cx.update_global::<MetadataRegistry, _>(|state, _cx| {
|
_ = async_cx.update_global::<MetadataRegistry, _>(|state, _cx| {
|
||||||
state.seen(public_key);
|
state.seen(public_key, metadata);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use gpui::*;
|
use gpui::*;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use crate::utils::get_room_id;
|
use crate::utils::get_room_id;
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ pub struct Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ChatRegistry {
|
pub struct ChatRegistry {
|
||||||
pub new_messages: Vec<Message>,
|
pub new_messages: Arc<RwLock<Vec<Message>>>,
|
||||||
pub reload: bool,
|
pub reload: bool,
|
||||||
pub is_initialized: bool,
|
pub is_initialized: bool,
|
||||||
}
|
}
|
||||||
@@ -68,12 +69,15 @@ impl ChatRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, event: Event, metadata: Option<Metadata>) {
|
pub fn push(&mut self, event: Event, metadata: Option<Metadata>) {
|
||||||
self.new_messages.push(Message { event, metadata });
|
self.new_messages
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.push(Message { event, metadata });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
new_messages: Vec::new(),
|
new_messages: Arc::new(RwLock::new(Vec::new())),
|
||||||
reload: false,
|
reload: false,
|
||||||
is_initialized: false,
|
is_initialized: false,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
use gpui::*;
|
use gpui::*;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
sync::{Arc, Mutex, RwLock},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct MetadataRegistry {
|
pub struct MetadataRegistry {
|
||||||
seens: Vec<PublicKey>,
|
seens: Arc<Mutex<Vec<PublicKey>>>,
|
||||||
|
profiles: Arc<RwLock<HashMap<PublicKey, Metadata>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Global for MetadataRegistry {}
|
impl Global for MetadataRegistry {}
|
||||||
@@ -13,16 +18,31 @@ impl MetadataRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains(&self, public_key: PublicKey) -> bool {
|
pub fn contains(&self, public_key: PublicKey) -> bool {
|
||||||
self.seens.contains(&public_key)
|
self.seens.lock().unwrap().contains(&public_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn seen(&mut self, public_key: PublicKey) {
|
pub fn seen(&mut self, public_key: PublicKey, metadata: Option<Metadata>) {
|
||||||
if !self.seens.contains(&public_key) {
|
let mut seens = self.seens.lock().unwrap();
|
||||||
self.seens.push(public_key);
|
|
||||||
|
if !seens.contains(&public_key) {
|
||||||
|
seens.push(public_key);
|
||||||
|
|
||||||
|
drop(seens);
|
||||||
|
|
||||||
|
if let Some(metadata) = metadata {
|
||||||
|
self.profiles.write().unwrap().insert(public_key, metadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, public_key: PublicKey) -> Option<Metadata> {
|
||||||
|
self.profiles.read().unwrap().get(&public_key).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self { seens: Vec::new() }
|
let seens = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let profiles = Arc::new(RwLock::new(HashMap::new()));
|
||||||
|
|
||||||
|
Self { seens, profiles }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
104
crates/app/src/views/dock/chat/message.rs
Normal file
104
crates/app/src/views/dock/chat/message.rs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
use coop_ui::{theme::ActiveTheme, StyledExt};
|
||||||
|
use gpui::*;
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
use prelude::FluentBuilder;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
constants::IMAGE_SERVICE,
|
||||||
|
utils::{ago, show_npub},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, IntoElement)]
|
||||||
|
pub struct RoomMessage {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
author: PublicKey,
|
||||||
|
fallback: SharedString,
|
||||||
|
metadata: Option<Metadata>,
|
||||||
|
content: SharedString,
|
||||||
|
created_at: SharedString,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RoomMessage {
|
||||||
|
pub fn new(
|
||||||
|
author: PublicKey,
|
||||||
|
metadata: Option<Metadata>,
|
||||||
|
content: String,
|
||||||
|
created_at: Timestamp,
|
||||||
|
) -> Self {
|
||||||
|
let created_at = ago(created_at.as_u64()).into();
|
||||||
|
let fallback = show_npub(author, 16).into();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
author,
|
||||||
|
metadata,
|
||||||
|
fallback,
|
||||||
|
created_at,
|
||||||
|
content: content.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for RoomMessage {
|
||||||
|
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.gap_3()
|
||||||
|
.w_full()
|
||||||
|
.p_2()
|
||||||
|
.child(div().flex_shrink_0().map(|this| {
|
||||||
|
if let Some(metadata) = self.metadata.clone() {
|
||||||
|
if let Some(picture) = metadata.picture {
|
||||||
|
this.child(
|
||||||
|
img(format!(
|
||||||
|
"{}/?url={}&w=100&h=100&n=-1",
|
||||||
|
IMAGE_SERVICE, picture
|
||||||
|
))
|
||||||
|
.size_8()
|
||||||
|
.rounded_full()
|
||||||
|
.object_fit(ObjectFit::Cover),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.child(img("brand/avatar.png").size_8().rounded_full())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.child(img("brand/avatar.png").size_8().rounded_full())
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.flex_initial()
|
||||||
|
.overflow_hidden()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_baseline()
|
||||||
|
.gap_2()
|
||||||
|
.text_xs()
|
||||||
|
.child(div().font_semibold().map(|this| {
|
||||||
|
if let Some(metadata) = self.metadata {
|
||||||
|
if let Some(display_name) = metadata.display_name {
|
||||||
|
this.child(display_name)
|
||||||
|
} else {
|
||||||
|
this.child(self.fallback)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.child(self.fallback)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.child(self.created_at)
|
||||||
|
.text_color(cx.theme().muted_foreground),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(cx.theme().foreground)
|
||||||
|
.child(self.content),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use crate::states::chat::Room;
|
use crate::states::chat::Room;
|
||||||
|
|
||||||
|
mod message;
|
||||||
mod room;
|
mod room;
|
||||||
|
|
||||||
pub struct ChatPanel {
|
pub struct ChatPanel {
|
||||||
|
|||||||
@@ -2,109 +2,26 @@ use coop_ui::{
|
|||||||
button::{Button, ButtonVariants},
|
button::{Button, ButtonVariants},
|
||||||
input::{InputEvent, TextInput},
|
input::{InputEvent, TextInput},
|
||||||
theme::ActiveTheme,
|
theme::ActiveTheme,
|
||||||
v_flex, Icon, IconName, StyledExt,
|
v_flex, Icon, IconName,
|
||||||
};
|
};
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use prelude::FluentBuilder;
|
use std::sync::Arc;
|
||||||
use std::{collections::HashMap, sync::Arc};
|
|
||||||
|
|
||||||
|
use super::message::RoomMessage;
|
||||||
use crate::{
|
use crate::{
|
||||||
get_client,
|
get_client,
|
||||||
states::chat::{ChatRegistry, Room},
|
states::{
|
||||||
utils::{ago, show_npub},
|
chat::{ChatRegistry, Room},
|
||||||
|
metadata::MetadataRegistry,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, IntoElement)]
|
|
||||||
pub struct MessageItem {
|
|
||||||
author: PublicKey,
|
|
||||||
metadata: Option<Metadata>,
|
|
||||||
content: SharedString,
|
|
||||||
created_at: Timestamp,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MessageItem {
|
|
||||||
pub fn new(
|
|
||||||
author: PublicKey,
|
|
||||||
metadata: Option<Metadata>,
|
|
||||||
content: String,
|
|
||||||
created_at: Timestamp,
|
|
||||||
) -> Self {
|
|
||||||
MessageItem {
|
|
||||||
author,
|
|
||||||
metadata,
|
|
||||||
created_at,
|
|
||||||
content: content.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for MessageItem {
|
|
||||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
|
||||||
let ago = ago(self.created_at.as_u64());
|
|
||||||
let fallback_name = show_npub(self.author, 16);
|
|
||||||
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.gap_3()
|
|
||||||
.w_full()
|
|
||||||
.p_2()
|
|
||||||
.child(div().flex_shrink_0().map(|this| {
|
|
||||||
if let Some(metadata) = self.metadata.clone() {
|
|
||||||
if let Some(picture) = metadata.picture {
|
|
||||||
this.child(
|
|
||||||
img(picture)
|
|
||||||
.size_8()
|
|
||||||
.rounded_full()
|
|
||||||
.object_fit(ObjectFit::Cover),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.child(img("brand/avatar.png").size_8().rounded_full())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.child(img("brand/avatar.png").size_8().rounded_full())
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.flex_col()
|
|
||||||
.flex_initial()
|
|
||||||
.overflow_hidden()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_baseline()
|
|
||||||
.gap_2()
|
|
||||||
.text_xs()
|
|
||||||
.child(div().font_semibold().map(|this| {
|
|
||||||
if let Some(metadata) = self.metadata {
|
|
||||||
if let Some(display_name) = metadata.display_name {
|
|
||||||
this.child(display_name)
|
|
||||||
} else {
|
|
||||||
this.child(fallback_name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.child(fallback_name)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.child(div().child(ago).text_color(cx.theme().muted_foreground)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_sm()
|
|
||||||
.text_color(cx.theme().foreground)
|
|
||||||
.child(self.content),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Messages {
|
pub struct Messages {
|
||||||
count: usize,
|
count: usize,
|
||||||
items: Vec<MessageItem>,
|
items: Vec<RoomMessage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ChatRoom {
|
pub struct ChatRoom {
|
||||||
@@ -201,37 +118,19 @@ impl ChatRoom {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
if let Ok(events) = events {
|
if let Ok(events) = events {
|
||||||
let public_keys: Vec<PublicKey> = events
|
let items: Vec<RoomMessage> = events
|
||||||
.iter()
|
|
||||||
.unique_by(|ev| ev.pubkey)
|
|
||||||
.map(|ev| ev.pubkey)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut profiles = async_cx
|
|
||||||
.background_executor()
|
|
||||||
.spawn(async move {
|
|
||||||
let mut data: HashMap<PublicKey, Option<Metadata>> = HashMap::new();
|
|
||||||
|
|
||||||
for public_key in public_keys.into_iter() {
|
|
||||||
if let Ok(metadata) =
|
|
||||||
client.database().metadata(public_key).await
|
|
||||||
{
|
|
||||||
data.insert(public_key, metadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let items: Vec<MessageItem> = events
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.sorted_by_key(|ev| ev.created_at)
|
.sorted_by_key(|ev| ev.created_at)
|
||||||
.map(|ev| {
|
.map(|ev| {
|
||||||
// Get user's metadata
|
// Get user's metadata
|
||||||
let metadata = profiles.get_mut(&ev.pubkey).and_then(Option::take);
|
let metadata = async_cx
|
||||||
|
.read_global::<MetadataRegistry, _>(|state, _cx| {
|
||||||
|
state.get(ev.pubkey)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Return message item
|
// Return message item
|
||||||
MessageItem::new(ev.pubkey, metadata, ev.content, ev.created_at)
|
RoomMessage::new(ev.pubkey, metadata, ev.content, ev.created_at)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -253,14 +152,17 @@ impl ChatRoom {
|
|||||||
|
|
||||||
cx.observe_global::<ChatRegistry>(move |_, cx| {
|
cx.observe_global::<ChatRegistry>(move |_, cx| {
|
||||||
let state = cx.global::<ChatRegistry>();
|
let state = cx.global::<ChatRegistry>();
|
||||||
let events = state.new_messages.clone();
|
|
||||||
// let mut metadata = state.metadata.clone();
|
// let mut metadata = state.metadata.clone();
|
||||||
|
|
||||||
// TODO: filter messages
|
// TODO: filter messages
|
||||||
let items: Vec<MessageItem> = events
|
let items: Vec<RoomMessage> = state
|
||||||
|
.new_messages
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|m| {
|
.map(|m| {
|
||||||
MessageItem::new(
|
RoomMessage::new(
|
||||||
m.event.pubkey,
|
m.event.pubkey,
|
||||||
m.metadata,
|
m.metadata,
|
||||||
m.event.content,
|
m.event.content,
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ impl InboxItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn action(&self, cx: &mut WindowContext<'_>) {
|
pub fn action(&self, cx: &mut WindowContext<'_>) {
|
||||||
println!("Test");
|
|
||||||
let room = Arc::new(Room::new(&self.event));
|
let room = Arc::new(Room::new(&self.event));
|
||||||
|
|
||||||
cx.dispatch_action(Box::new(AddPanel {
|
cx.dispatch_action(Box::new(AddPanel {
|
||||||
|
|||||||
@@ -25,21 +25,23 @@ impl Inbox {
|
|||||||
|
|
||||||
cx.observe_global::<ChatRegistry>(|this, cx| {
|
cx.observe_global::<ChatRegistry>(|this, cx| {
|
||||||
let state = cx.global::<ChatRegistry>();
|
let state = cx.global::<ChatRegistry>();
|
||||||
|
let empty_messages = state.new_messages.read().unwrap().is_empty();
|
||||||
|
|
||||||
if state.reload || (state.is_initialized && state.new_messages.is_empty()) {
|
if state.reload || (state.is_initialized && empty_messages) {
|
||||||
this.load(cx);
|
this.load(cx);
|
||||||
} else {
|
} else {
|
||||||
#[allow(clippy::collapsible_if)]
|
#[allow(clippy::collapsible_if)]
|
||||||
if let Some(items) = this.items.read(cx).as_ref() {
|
if let Some(items) = this.items.read(cx).as_ref() {
|
||||||
// Get all new messages
|
|
||||||
let new_messages = state.new_messages.clone();
|
|
||||||
|
|
||||||
// Get all current chats
|
// Get all current chats
|
||||||
let current_rooms: Vec<String> =
|
let current_rooms: Vec<String> =
|
||||||
items.iter().map(|item| item.model.read(cx).id()).collect();
|
items.iter().map(|item| item.model.read(cx).id()).collect();
|
||||||
|
|
||||||
// Create view for new chats only
|
// Get all new messages
|
||||||
let new = new_messages
|
let messages = state
|
||||||
|
.new_messages
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|m| {
|
.filter(|m| {
|
||||||
let keys = m.event.tags.public_keys().copied().collect::<Vec<_>>();
|
let keys = m.event.tags.public_keys().copied().collect::<Vec<_>>();
|
||||||
@@ -49,6 +51,11 @@ impl Inbox {
|
|||||||
|
|
||||||
!current_rooms.iter().any(|id| id == &new_id)
|
!current_rooms.iter().any(|id| id == &new_id)
|
||||||
})
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Create view for new chats only
|
||||||
|
let new = messages
|
||||||
|
.into_iter()
|
||||||
.map(|m| cx.new_view(|cx| InboxItem::new(m.event, cx)))
|
.map(|m| cx.new_view(|cx| InboxItem::new(m.event, cx)))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user