wip: refactor
This commit is contained in:
@@ -250,18 +250,32 @@ async fn main() {
|
||||
let metadata = async_cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
(client.database().metadata(event.pubkey).await)
|
||||
client
|
||||
.database()
|
||||
.metadata(event.pubkey)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.await;
|
||||
|
||||
_ = async_cx.update_global::<ChatRegistry, _>(|state, _| {
|
||||
_ = async_cx.update_global::<ChatRegistry, _>(|state, _cx| {
|
||||
state.push(event, metadata);
|
||||
});
|
||||
}
|
||||
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| {
|
||||
state.seen(public_key);
|
||||
state.seen(public_key, metadata);
|
||||
})
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use gpui::*;
|
||||
use nostr_sdk::prelude::*;
|
||||
use serde::Deserialize;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use crate::utils::get_room_id;
|
||||
|
||||
@@ -47,7 +48,7 @@ pub struct Message {
|
||||
}
|
||||
|
||||
pub struct ChatRegistry {
|
||||
pub new_messages: Vec<Message>,
|
||||
pub new_messages: Arc<RwLock<Vec<Message>>>,
|
||||
pub reload: bool,
|
||||
pub is_initialized: bool,
|
||||
}
|
||||
@@ -68,12 +69,15 @@ impl ChatRegistry {
|
||||
}
|
||||
|
||||
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 {
|
||||
Self {
|
||||
new_messages: Vec::new(),
|
||||
new_messages: Arc::new(RwLock::new(Vec::new())),
|
||||
reload: false,
|
||||
is_initialized: false,
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
use gpui::*;
|
||||
use nostr_sdk::prelude::*;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
};
|
||||
|
||||
pub struct MetadataRegistry {
|
||||
seens: Vec<PublicKey>,
|
||||
seens: Arc<Mutex<Vec<PublicKey>>>,
|
||||
profiles: Arc<RwLock<HashMap<PublicKey, Metadata>>>,
|
||||
}
|
||||
|
||||
impl Global for MetadataRegistry {}
|
||||
@@ -13,16 +18,31 @@ impl MetadataRegistry {
|
||||
}
|
||||
|
||||
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) {
|
||||
if !self.seens.contains(&public_key) {
|
||||
self.seens.push(public_key);
|
||||
pub fn seen(&mut self, public_key: PublicKey, metadata: Option<Metadata>) {
|
||||
let mut seens = self.seens.lock().unwrap();
|
||||
|
||||
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 {
|
||||
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;
|
||||
|
||||
mod message;
|
||||
mod room;
|
||||
|
||||
pub struct ChatPanel {
|
||||
|
||||
@@ -2,109 +2,26 @@ use coop_ui::{
|
||||
button::{Button, ButtonVariants},
|
||||
input::{InputEvent, TextInput},
|
||||
theme::ActiveTheme,
|
||||
v_flex, Icon, IconName, StyledExt,
|
||||
v_flex, Icon, IconName,
|
||||
};
|
||||
use gpui::*;
|
||||
use itertools::Itertools;
|
||||
use nostr_sdk::prelude::*;
|
||||
use prelude::FluentBuilder;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::message::RoomMessage;
|
||||
use crate::{
|
||||
get_client,
|
||||
states::chat::{ChatRegistry, Room},
|
||||
utils::{ago, show_npub},
|
||||
states::{
|
||||
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)]
|
||||
pub struct Messages {
|
||||
count: usize,
|
||||
items: Vec<MessageItem>,
|
||||
items: Vec<RoomMessage>,
|
||||
}
|
||||
|
||||
pub struct ChatRoom {
|
||||
@@ -201,37 +118,19 @@ impl ChatRoom {
|
||||
.await;
|
||||
|
||||
if let Ok(events) = events {
|
||||
let public_keys: Vec<PublicKey> = 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
|
||||
let items: Vec<RoomMessage> = events
|
||||
.into_iter()
|
||||
.sorted_by_key(|ev| ev.created_at)
|
||||
.map(|ev| {
|
||||
// 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
|
||||
MessageItem::new(ev.pubkey, metadata, ev.content, ev.created_at)
|
||||
RoomMessage::new(ev.pubkey, metadata, ev.content, ev.created_at)
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -253,14 +152,17 @@ impl ChatRoom {
|
||||
|
||||
cx.observe_global::<ChatRegistry>(move |_, cx| {
|
||||
let state = cx.global::<ChatRegistry>();
|
||||
let events = state.new_messages.clone();
|
||||
// let mut metadata = state.metadata.clone();
|
||||
|
||||
// TODO: filter messages
|
||||
let items: Vec<MessageItem> = events
|
||||
let items: Vec<RoomMessage> = state
|
||||
.new_messages
|
||||
.read()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|m| {
|
||||
MessageItem::new(
|
||||
RoomMessage::new(
|
||||
m.event.pubkey,
|
||||
m.metadata,
|
||||
m.event.content,
|
||||
|
||||
@@ -70,7 +70,6 @@ impl InboxItem {
|
||||
}
|
||||
|
||||
pub fn action(&self, cx: &mut WindowContext<'_>) {
|
||||
println!("Test");
|
||||
let room = Arc::new(Room::new(&self.event));
|
||||
|
||||
cx.dispatch_action(Box::new(AddPanel {
|
||||
|
||||
@@ -25,21 +25,23 @@ impl Inbox {
|
||||
|
||||
cx.observe_global::<ChatRegistry>(|this, cx| {
|
||||
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);
|
||||
} else {
|
||||
#[allow(clippy::collapsible_if)]
|
||||
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
|
||||
let current_rooms: Vec<String> =
|
||||
items.iter().map(|item| item.model.read(cx).id()).collect();
|
||||
|
||||
// Create view for new chats only
|
||||
let new = new_messages
|
||||
// Get all new messages
|
||||
let messages = state
|
||||
.new_messages
|
||||
.read()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|m| {
|
||||
let keys = m.event.tags.public_keys().copied().collect::<Vec<_>>();
|
||||
@@ -49,6 +51,11 @@ impl Inbox {
|
||||
|
||||
!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)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user