summaryrefslogtreecommitdiffstats
path: root/src/components/chats_list
diff options
context:
space:
mode:
authorLibravatar cel 🌸 <cel@bunny.garden>2025-06-01 16:10:26 +0100
committerLibravatar cel 🌸 <cel@bunny.garden>2025-06-01 17:27:40 +0100
commit6ee4190a26f32bfa953302ee363ad3bb6c384ebb (patch)
tree2c3182c29d5780a0ad9c9770b5e546312bea49b4 /src/components/chats_list
parentf76c80c1d23177ab00c81240ee3a75d3bcda0e3b (diff)
downloadmacaw-web-6ee4190a26f32bfa953302ee363ad3bb6c384ebb.tar.gz
macaw-web-6ee4190a26f32bfa953302ee363ad3bb6c384ebb.tar.bz2
macaw-web-6ee4190a26f32bfa953302ee363ad3bb6c384ebb.zip
refactor: reorganise code
Diffstat (limited to '')
-rw-r--r--src/components/chats_list.rs109
-rw-r--r--src/components/chats_list/chats_list_item.rs66
2 files changed, 175 insertions, 0 deletions
diff --git a/src/components/chats_list.rs b/src/components/chats_list.rs
new file mode 100644
index 0000000..b8cf34c
--- /dev/null
+++ b/src/components/chats_list.rs
@@ -0,0 +1,109 @@
+use chats_list_item::ChatsListItem;
+use indexmap::IndexMap;
+use jid::BareJID;
+use leptos::prelude::*;
+use tracing::debug;
+
+use crate::{chat::MacawChat, client::Client, components::{icon::IconComponent, new_chat::NewChatWidget, overlay::Overlay}, icon::Icon, message::MacawMessage, message_subscriptions::MessageSubscriptions};
+
+mod chats_list_item;
+
+#[component]
+pub fn ChatsList() -> impl IntoView {
+ let (chats, set_chats) = signal(IndexMap::new());
+
+ let load_chats = LocalResource::new(move || async move {
+ let client = use_context::<Client>().expect("client not in context");
+ let chats = client
+ .get_chats_ordered_with_latest_messages_and_users()
+ .await
+ .map_err(|e| e.to_string());
+ match chats {
+ Ok(c) => {
+ let chats = c
+ .into_iter()
+ .map(|((chat, chat_user), (message, message_user))| {
+ (
+ chat.correspondent.clone(),
+ (
+ MacawChat::got_chat_and_user(chat, chat_user),
+ MacawMessage::got_message_and_user(message, message_user),
+ ),
+ )
+ })
+ .collect::<IndexMap<BareJID, _>>();
+ set_chats.set(chats);
+ }
+ Err(_) => {
+ // TODO: show error message at top of chats list
+ }
+ }
+ });
+
+ let (open_new_chat, set_open_new_chat) = signal(false);
+
+ // TODO: filter new messages signal
+ let new_messages_signal: RwSignal<MessageSubscriptions> = use_context().unwrap();
+ let (sub_id, set_sub_id) = signal(None);
+ let _load_new_messages = LocalResource::new(move || async move {
+ load_chats.await;
+ let (sub_id, mut new_messages) = new_messages_signal.write().subscribe_all();
+ set_sub_id.set(Some(sub_id));
+ while let Some((to, new_message)) = new_messages.recv().await {
+ debug!("got new message in let");
+ let mut chats = set_chats.write();
+ if let Some((chat, _latest_message)) = chats.shift_remove(&to) {
+ // TODO: check if new message is actually latest message
+ debug!("chat existed");
+ debug!("new message: {}", new_message.message.read().body.body);
+ chats.insert_before(0, to, (chat.clone(), new_message));
+ debug!("done setting");
+ } else {
+ debug!("the chat didn't exist");
+ let client = use_context::<Client>().expect("client not in context");
+ let chat = client.get_chat(to.clone()).await.unwrap();
+ let user = client.get_user(to.clone()).await.unwrap();
+ debug!("before got chat");
+ let chat = MacawChat::got_chat_and_user(chat, user);
+ debug!("after got chat");
+ chats.insert_before(0, to, (chat, new_message));
+ debug!("done setting");
+ }
+ }
+ debug!("set the new message");
+ });
+ on_cleanup(move || {
+ if let Some(sub_id) = sub_id.get() {
+ new_messages_signal.write().unsubscribe_all(sub_id);
+ }
+ });
+
+ view! {
+ <div class="chats-list panel">
+ // TODO: update icon, tooltip on hover.
+ <div class="header">
+ <h2>Chats</h2>
+ <div class="new-chat header-icon" class:open=open_new_chat >
+ <IconComponent icon=Icon::NewBubble24 on:click=move |_| set_open_new_chat.update(|state| *state = !*state)/>
+ {move || {
+ if *open_new_chat.read() {
+ view! {
+ <Overlay set_open=set_open_new_chat>
+ <NewChatWidget set_open_new_chat />
+ </Overlay>
+ }.into_any()
+ } else {
+ view! {}.into_any()
+ }
+ }}
+ </div>
+ </div>
+ <div class="chats-list-chats">
+ <For each=move || chats.get() key=|chat| chat.1.1.message.read().id let(chat)>
+ <ChatsListItem chat=chat.1.0 message=chat.1.1 />
+ </For>
+ </div>
+ </div>
+ }
+}
+
diff --git a/src/components/chats_list/chats_list_item.rs b/src/components/chats_list/chats_list_item.rs
new file mode 100644
index 0000000..191f163
--- /dev/null
+++ b/src/components/chats_list/chats_list_item.rs
@@ -0,0 +1,66 @@
+use std::ops::Deref;
+
+use chrono::Local;
+use filamento::{chat::{Chat, ChatStoreFields, Message, MessageStoreFields}, user::User};
+use leptos::prelude::*;
+use reactive_stores::{ArcStore, Store};
+use tracing::debug;
+
+use crate::{chat::MacawChat, components::{avatar::AvatarWithPresence, sidebar::Open}, message::MacawMessage, open_chats::{OpenChatsPanel, OpenChatsPanelStoreFields}, user::get_name};
+
+#[component]
+pub fn ChatsListItem(chat: MacawChat, message: MacawMessage) -> impl IntoView {
+ let chat_chat: Store<Chat> = <ArcStore<Chat> as Clone>::clone(&chat.chat).into();
+ let chat_user: Store<User> =
+ <ArcStore<filamento::user::User> as Clone>::clone(&chat.user).into();
+ let message_message: Store<Message> = <ArcStore<Message> as Clone>::clone(&message.message).into();
+ let name = move || get_name(chat_user, true);
+
+ // TODO: store fine-grained reactivity
+ let latest_message_body = move || message_message.body().get().body;
+ let open_chats: Store<OpenChatsPanel> =
+ use_context().expect("no open chats panel store in context");
+
+ let open_chat = move |_| {
+ debug!("opening chat");
+ open_chats.update(|open_chats| open_chats.open(chat.clone()));
+ };
+
+ let open = move || {
+ if let Some(open_chat) = &*open_chats.chat_view().read() {
+ debug!("got open chat: {:?}", open_chat);
+ if *open_chat == *chat_chat.correspondent().read() {
+ return Open::Focused;
+ }
+ }
+ if let Some(_backgrounded_chat) = open_chats
+ .chats()
+ .read()
+ .get(chat_chat.correspondent().read().deref())
+ {
+ return Open::Open;
+ }
+ Open::Closed
+ };
+ let focused = move || open().is_focused();
+ let open = move || open().is_open();
+
+ let date = move || message_message.timestamp().read().naive_local();
+ let now = move || Local::now().naive_local();
+ let timeinfo = move || if date().date() == now().date() {
+ // TODO: localisation/config
+ date().time().format("%H:%M").to_string()
+ } else {
+ date().date().format("%d/%m").to_string()
+ };
+
+ view! {
+ <div class="chats-list-item" class:open=move || open() class:focused=move || focused() on:click=open_chat>
+ <AvatarWithPresence user=chat_user />
+ <div class="item-info">
+ <div class="main-info"><p class="name">{name}</p><p class="timestamp">{timeinfo}</p></div>
+ <div class="sub-info"><p class="message-preview">{latest_message_body}</p><p><!-- "TODO: delivery or unread state" --></p></div>
+ </div>
+ </div>
+ }
+}