summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs633
1 files changed, 426 insertions, 207 deletions
diff --git a/src/lib.rs b/src/lib.rs
index f907933..92cbe1a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,28 +6,44 @@ use std::{
ops::{Deref, DerefMut},
rc::Rc,
str::FromStr,
- sync::{atomic::AtomicUsize, Arc, RwLock},
+ sync::{Arc, RwLock, atomic::AtomicUsize},
thread::sleep,
time::{self, Duration},
};
-use base64::{prelude::BASE64_STANDARD, Engine};
+use base64::{Engine, prelude::BASE64_STANDARD};
use chrono::{NaiveDateTime, TimeDelta};
use filamento::{
- chat::{Body, Chat, ChatStoreFields, Delivery, Message, MessageStoreFields}, db::Db, error::{CommandError, ConnectionError, DatabaseError}, files::FileStore, files::opfs::OPFSError, files::FilesMem, files::FilesOPFS, roster::{Contact, ContactStoreFields}, user::{User, UserStoreFields}, UpdateMessage
+ UpdateMessage,
+ chat::{Body, Chat, ChatStoreFields, Delivery, Message, MessageStoreFields},
+ db::Db,
+ error::{CommandError, ConnectionError, DatabaseError},
+ files::FileStore,
+ files::FilesMem,
+ files::FilesOPFS,
+ files::opfs::OPFSError,
+ roster::{Contact, ContactStoreFields},
+ user::{User, UserStoreFields},
};
use futures::stream::StreamExt;
use indexmap::IndexMap;
use jid::JID;
use leptos::{
- ev::{Event, KeyboardEvent, SubmitEvent}, html::{self, Div, Input, Pre, Textarea}, prelude::*, tachys::{dom::document, reactive_graph::bind::GetValue}, task::{spawn, spawn_local}
+ ev::{Event, KeyboardEvent, SubmitEvent},
+ html::{self, Div, Input, Pre, Textarea},
+ prelude::*,
+ tachys::{dom::document, reactive_graph::bind::GetValue},
+ task::{spawn, spawn_local},
};
use leptos_meta::Stylesheet;
-use leptos_use::{use_textarea_autosize, UseTextareaAutosizeReturn};
+use leptos_use::{UseTextareaAutosizeReturn, use_textarea_autosize};
use reactive_stores::{ArcStore, Store, StoreField};
use stylance::import_style;
use thiserror::Error;
-use tokio::sync::{mpsc::{self, Receiver}, Mutex};
+use tokio::sync::{
+ Mutex,
+ mpsc::{self, Receiver},
+};
use tracing::{debug, error};
use uuid::Uuid;
@@ -61,11 +77,7 @@ impl FileStore for Files {
}
}
- async fn store(
- &self,
- name: &str,
- data: &[u8],
- ) -> Result<(), Self::Err> {
+ async fn store(&self, name: &str, data: &[u8]) -> Result<(), Self::Err> {
match self {
Files::Mem(files_mem) => Ok(files_mem.store(name, data).await.unwrap()),
Files::Opfs(files_opfs) => Ok(files_opfs.store(name, data).await?),
@@ -90,7 +102,7 @@ impl Files {
} else {
None
}
- },
+ }
Files::Opfs(files_opfs) => files_opfs.get_src(file_name).await.ok(),
}
}
@@ -145,7 +157,10 @@ pub enum LoginError {
}
#[component]
-fn LoginPage(set_app: WriteSignal<AppState>, set_client: RwSignal<Option<(Client, Receiver<UpdateMessage>)>>) -> impl IntoView {
+fn LoginPage(
+ set_app: WriteSignal<AppState>,
+ set_client: RwSignal<Option<(Client, Receiver<UpdateMessage>)>>,
+) -> impl IntoView {
let jid = RwSignal::new("".to_string());
let password = RwSignal::new("".to_string());
let remember_me = RwSignal::new(false);
@@ -171,13 +186,13 @@ fn LoginPage(set_app: WriteSignal<AppState>, set_client: RwSignal<Option<(Client
if jid.read_untracked().is_empty() {
set_error.set(Some(LoginError::MissingJID));
set_login_pending.set(false);
- return
+ return;
}
if password.read_untracked().is_empty() {
set_error.set(Some(LoginError::MissingPassword));
set_login_pending.set(false);
- return
+ return;
}
let jid = match JID::from_str(&jid.read_untracked()) {
@@ -185,14 +200,16 @@ fn LoginPage(set_app: WriteSignal<AppState>, set_client: RwSignal<Option<(Client
Err(e) => {
set_error.set(Some(e.into()));
set_login_pending.set(false);
- return
- },
+ return;
+ }
};
// initialise the client
let db = if remember_me.get_untracked() {
debug!("creating db in opfs");
- Db::create_connect_and_migrate(jid.as_bare().to_string()).await.unwrap()
+ Db::create_connect_and_migrate(jid.as_bare().to_string())
+ .await
+ .unwrap()
} else {
debug!("creating db in memory");
Db::create_connect_and_migrate_memory().await.unwrap()
@@ -204,14 +221,18 @@ fn LoginPage(set_app: WriteSignal<AppState>, set_client: RwSignal<Option<(Client
Err(e) => {
set_error.set(Some(e.into()));
set_login_pending.set(false);
- return
- },
+ return;
+ }
}
} else {
Files::Mem(FilesMem::new())
};
- let (client, updates) =
- filamento::Client::new(jid.clone(), password.read_untracked().clone(), db, files.clone());
+ let (client, updates) = filamento::Client::new(
+ jid.clone(),
+ password.read_untracked().clone(),
+ db,
+ files.clone(),
+ );
// TODO: remember_me
let client = Client {
client,
@@ -221,12 +242,12 @@ fn LoginPage(set_app: WriteSignal<AppState>, set_client: RwSignal<Option<(Client
if *connect_on_login.read_untracked() {
match client.connect().await {
- Ok(_) => {},
+ Ok(_) => {}
Err(e) => {
set_error.set(Some(e.into()));
set_login_pending.set(false);
- return
- },
+ return;
+ }
}
}
@@ -307,17 +328,12 @@ impl MessageSubscriptions {
}
}
- pub async fn broadcast(
- &mut self,
- to: JID,
- message: MacawMessage,
- ) {
+ pub async fn broadcast(&mut self, to: JID, message: MacawMessage) {
// subscriptions to all
let mut removals = Vec::new();
for (id, sender) in &self.all {
match sender.send((to.clone(), message.clone())).await {
- Ok(_) => {
- }
+ Ok(_) => {}
Err(_) => {
removals.push(*id);
}
@@ -332,8 +348,7 @@ impl MessageSubscriptions {
let mut removals = Vec::new();
for (id, sender) in &*subscribers {
match sender.send(message.clone()).await {
- Ok(_) => {
- }
+ Ok(_) => {}
Err(_) => {
removals.push(*id);
}
@@ -348,19 +363,14 @@ impl MessageSubscriptions {
}
}
- pub fn subscribe_all(
- &mut self,
- ) -> (Uuid, Receiver<(JID, MacawMessage)>) {
+ pub fn subscribe_all(&mut self) -> (Uuid, Receiver<(JID, MacawMessage)>) {
let (send, recv) = mpsc::channel(10);
let id = Uuid::new_v4();
self.all.insert(id, send);
(id, recv)
}
- pub fn subscribe_chat(
- &mut self,
- chat: JID,
- ) -> (Uuid, Receiver<MacawMessage>) {
+ pub fn subscribe_chat(&mut self, chat: JID) -> (Uuid, Receiver<MacawMessage>) {
let (send, recv) = mpsc::channel(10);
let id = Uuid::new_v4();
if let Some(chat_subscribers) = self.subset.get_mut(&chat) {
@@ -399,7 +409,7 @@ impl Roster {
// TODO: multiple panels
// pub struct OpenChats {
-// panels:
+// panels:
// }
#[derive(Store, Default)]
@@ -413,16 +423,28 @@ pub struct OpenChatsPanel {
pub fn open_chat(open_chats: Store<OpenChatsPanel>, chat: MacawChat) {
if let Some(jid) = &*open_chats.chat_view().read() {
if let Some((index, _jid, entry)) = open_chats.chats().write().shift_remove_full(jid) {
- let new_jid = <ArcStore<filamento::chat::Chat> as Clone>::clone(&chat.chat).correspondent().read().clone();
- open_chats.chats().write().insert_before(index, new_jid.clone(), chat);
+ let new_jid = <ArcStore<filamento::chat::Chat> as Clone>::clone(&chat.chat)
+ .correspondent()
+ .read()
+ .clone();
+ open_chats
+ .chats()
+ .write()
+ .insert_before(index, new_jid.clone(), chat);
*open_chats.chat_view().write() = Some(new_jid);
} else {
- let new_jid = <ArcStore<filamento::chat::Chat> as Clone>::clone(&chat.chat).correspondent().read().clone();
+ let new_jid = <ArcStore<filamento::chat::Chat> as Clone>::clone(&chat.chat)
+ .correspondent()
+ .read()
+ .clone();
open_chats.chats().write().insert(new_jid.clone(), chat);
*open_chats.chat_view().write() = Some(new_jid);
}
} else {
- let new_jid = <ArcStore<filamento::chat::Chat> as Clone>::clone(&chat.chat).correspondent().read().clone();
+ let new_jid = <ArcStore<filamento::chat::Chat> as Clone>::clone(&chat.chat)
+ .correspondent()
+ .read()
+ .clone();
open_chats.chats().write().insert(new_jid.clone(), chat);
*open_chats.chat_view().write() = Some(new_jid);
}
@@ -433,16 +455,25 @@ impl OpenChatsPanel {
if let Some(jid) = &mut self.chat_view {
debug!("a chat was already open");
if let Some((index, _jid, entry)) = self.chats.shift_remove_full(jid) {
- let new_jid = <ArcStore<filamento::chat::Chat> as Clone>::clone(&chat.chat).correspondent().read().clone();
+ let new_jid = <ArcStore<filamento::chat::Chat> as Clone>::clone(&chat.chat)
+ .correspondent()
+ .read()
+ .clone();
self.chats.insert_before(index, new_jid.clone(), chat);
*&mut self.chat_view = Some(new_jid);
} else {
- let new_jid = <ArcStore<filamento::chat::Chat> as Clone>::clone(&chat.chat).correspondent().read().clone();
+ let new_jid = <ArcStore<filamento::chat::Chat> as Clone>::clone(&chat.chat)
+ .correspondent()
+ .read()
+ .clone();
self.chats.insert(new_jid.clone(), chat);
*&mut self.chat_view = Some(new_jid);
}
} else {
- let new_jid = <ArcStore<filamento::chat::Chat> as Clone>::clone(&chat.chat).correspondent().read().clone();
+ let new_jid = <ArcStore<filamento::chat::Chat> as Clone>::clone(&chat.chat)
+ .correspondent()
+ .read()
+ .clone();
self.chats.insert(new_jid.clone(), chat);
*&mut self.chat_view = Some(new_jid);
}
@@ -451,11 +482,11 @@ impl OpenChatsPanel {
// TODO:
// pub fn open_in_new_tab_unfocused(&mut self) {
-
+
// }
// pub fn open_in_new_tab_focus(&mut self) {
-
+
// }
}
@@ -490,12 +521,18 @@ fn Macaw(
while let Some(update) = updates.recv().await {
match update {
UpdateMessage::Online(online, items) => {
- let contacts = items.into_iter().map(|(contact, user)| {
- (contact.user_jid.clone(), MacawContact::got_contact_and_user(contact, user))
- }).collect();
+ let contacts = items
+ .into_iter()
+ .map(|(contact, user)| {
+ (
+ contact.user_jid.clone(),
+ MacawContact::got_contact_and_user(contact, user),
+ )
+ })
+ .collect();
roster.contacts().set(contacts);
- },
- UpdateMessage::Offline(offline) => {},
+ }
+ UpdateMessage::Offline(offline) => {}
UpdateMessage::RosterUpdate(contact, user) => {
roster.contacts().update(|roster| {
if let Some(macaw_contact) = roster.get_mut(&contact.user_jid) {
@@ -506,30 +543,41 @@ fn Macaw(
roster.insert(jid, contact);
}
});
- },
+ }
UpdateMessage::RosterDelete(jid) => {
roster.contacts().update(|roster| {
roster.remove(&jid);
});
- },
- UpdateMessage::Presence { from, presence } => {},
+ }
+ UpdateMessage::Presence { from, presence } => {}
UpdateMessage::Message { to, from, message } => {
debug!("before got message");
let new_message = MacawMessage::got_message_and_user(message, from);
debug!("after got message");
- spawn_local(async move { message_subscriptions.write_untracked().broadcast(to, new_message).await });
+ spawn_local(async move {
+ message_subscriptions
+ .write_untracked()
+ .broadcast(to, new_message)
+ .await
+ });
debug!("after set message");
- },
+ }
UpdateMessage::MessageDelivery { id, chat, delivery } => {
- messages_store.modify(&id, |message| <ArcStore<filamento::chat::Message> as Clone>::clone(&message).delivery().set(Some(delivery)));
- },
- UpdateMessage::SubscriptionRequest(jid) => {},
+ messages_store.modify(&id, |message| {
+ <ArcStore<filamento::chat::Message> as Clone>::clone(&message)
+ .delivery()
+ .set(Some(delivery))
+ });
+ }
+ UpdateMessage::SubscriptionRequest(jid) => {}
UpdateMessage::NickChanged { jid, nick } => {
- users_store.modify(&jid, |user| user.update(|user| *&mut user.nick = nick.clone()));
- },
+ users_store.modify(&jid, |user| {
+ user.update(|user| *&mut user.nick = nick.clone())
+ });
+ }
UpdateMessage::AvatarChanged { jid, id } => {
users_store.modify(&jid, |user| *&mut user.write().avatar = id.clone());
- },
+ }
}
}
});
@@ -549,11 +597,13 @@ pub enum SidebarOpen {
pub fn toggle_open(state: &mut Option<SidebarOpen>, open: SidebarOpen) {
match state {
- Some(opened) => if *opened == open {
- *state = None
- } else {
- *state = Some(open)
- },
+ Some(opened) => {
+ if *opened == open {
+ *state = None
+ } else {
+ *state = Some(open)
+ }
+ }
None => *state = Some(open),
}
}
@@ -595,7 +645,7 @@ pub fn Sidebar() -> impl IntoView {
SidebarOpen::Chats => view! {
<ChatsList />
}.into_any(),
- }
+ }
} else {
view! {}.into_any()
}}
@@ -644,7 +694,8 @@ pub fn OpenChatsPanelView() -> impl IntoView {
#[component]
pub fn OpenChatView(chat: MacawChat) -> impl IntoView {
- let chat_chat: Store<Chat> = <ArcStore<filamento::chat::Chat> as Clone>::clone(&chat.chat).into();
+ let chat_chat: Store<Chat> =
+ <ArcStore<filamento::chat::Chat> as Clone>::clone(&chat.chat).into();
let chat_jid = move || chat_chat.correspondent().get();
view! {
@@ -678,88 +729,153 @@ pub fn ChatViewHeader(chat: MacawChat) -> impl IntoView {
#[component]
pub fn MessageHistoryBuffer(chat: MacawChat) -> impl IntoView {
- let (messages, set_messages) = signal(IndexMap::new());
- let chat_chat: Store<Chat> = <ArcStore<filamento::chat::Chat> as Clone>::clone(&chat.chat).into();
- let chat_user: Store<User> = <ArcStore<filamento::user::User> as Clone>::clone(&chat.user).into();
-
- let load_messages = LocalResource::new(move || async move {
- let client = use_context::<Client>().expect("client not in context");
- let messages = client.get_messages_with_users(chat_chat.correspondent().get_untracked()).await.map_err(|e| e.to_string());
- match messages {
- Ok(m) => {
- let messages = m.into_iter().map(|(message, message_user)| {
- (message.id, MacawMessage::got_message_and_user(message, message_user))
- }).collect::<IndexMap<Uuid, _>>();
- set_messages.set(messages);
- },
- Err(_) => {
- // TODO: show error message at top of chats list
- },
+ let (messages, set_messages) = arc_signal(IndexMap::new());
+ let chat_chat: Store<Chat> =
+ <ArcStore<filamento::chat::Chat> as Clone>::clone(&chat.chat).into();
+ let chat_user: Store<User> =
+ <ArcStore<filamento::user::User> as Clone>::clone(&chat.user).into();
+
+ let load_set_messages = set_messages.clone();
+ let load_messages = LocalResource::new(move || {
+ let load_set_messages = load_set_messages.clone();
+ async move {
+ let client = use_context::<Client>().expect("client not in context");
+ let messages = client
+ .get_messages_with_users(chat_chat.correspondent().get_untracked())
+ .await
+ .map_err(|e| e.to_string());
+ match messages {
+ Ok(m) => {
+ let messages = m
+ .into_iter()
+ .map(|(message, message_user)| {
+ (
+ message.id,
+ MacawMessage::got_message_and_user(message, message_user),
+ )
+ })
+ .collect::<IndexMap<Uuid, _>>();
+ load_set_messages.set(messages);
+ }
+ Err(err) => {
+ error!("{err}")
+ // TODO: show error message at top of chats list
+ }
+ }
}
});
// 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_messages.await;
- let (sub_id, mut new_messages) = new_messages_signal.write().subscribe_chat(chat_chat.correspondent().get_untracked());
- set_sub_id.set(Some(sub_id));
- while let Some(new_message) = new_messages.recv().await {
- debug!("got new message in let message buffer");
- let mut messages= set_messages.write();
- if let Some((_, last)) = messages.last() {
- if *<ArcStore<filamento::chat::Message> as Clone>::clone(&last.message).timestamp().read_untracked() < *<ArcStore<filamento::chat::Message> as Clone>::clone(&new_message).timestamp().read_untracked() {
- messages
- .insert(<ArcStore<filamento::chat::Message> as Clone>::clone(&new_message.message).id().get_untracked(), new_message);
- debug!("set the new message in message buffer");
+ let load_new_messages_set = set_messages.clone();
+ let _load_new_messages = LocalResource::new(move || {
+ let load_new_messages_set = load_new_messages_set.clone();
+ async move {
+ load_messages.await;
+ let (sub_id, mut new_messages) = new_messages_signal
+ .write()
+ .subscribe_chat(chat_chat.correspondent().get_untracked());
+ set_sub_id.set(Some(sub_id));
+ while let Some(new_message) = new_messages.recv().await {
+ debug!("got new message in let message buffer");
+ let mut messages = load_new_messages_set.write();
+ if let Some((_, last)) = messages.last() {
+ if *<ArcStore<filamento::chat::Message> as Clone>::clone(&last.message)
+ .timestamp()
+ .read_untracked()
+ < *<ArcStore<filamento::chat::Message> as Clone>::clone(&new_message)
+ .timestamp()
+ .read_untracked()
+ {
+ messages.insert(
+ <ArcStore<filamento::chat::Message> as Clone>::clone(
+ &new_message.message,
+ )
+ .id()
+ .get_untracked(),
+ new_message,
+ );
+ debug!("set the new message in message buffer");
+ } else {
+ let index = match messages.binary_search_by(|_, value| {
+ <ArcStore<filamento::chat::Message> as Clone>::clone(&value.message)
+ .timestamp()
+ .read_untracked()
+ .cmp(
+ &<ArcStore<filamento::chat::Message> as Clone>::clone(
+ &new_message.message,
+ )
+ .timestamp()
+ .read_untracked(),
+ )
+ }) {
+ Ok(i) => i,
+ Err(i) => i,
+ };
+ messages.insert_before(
+ // TODO: check if this logic is correct
+ index,
+ <ArcStore<filamento::chat::Message> as Clone>::clone(
+ &new_message.message,
+ )
+ .id()
+ .get_untracked(),
+ new_message,
+ );
+ debug!("set the new message in message buffer");
+ }
} else {
- let index = match messages.binary_search_by(|_, value| {
- <ArcStore<filamento::chat::Message> as Clone>::clone(&value.message).timestamp().read_untracked().cmp(&<ArcStore<filamento::chat::Message> as Clone>::clone(&new_message.message).timestamp().read_untracked())
- }) {
- Ok(i) => i,
- Err(i) => i,
- };
- messages.insert_before(
- // TODO: check if this logic is correct
- index,
- <ArcStore<filamento::chat::Message> as Clone>::clone(&new_message.message).id().get_untracked(),
+ messages.insert(
+ <ArcStore<filamento::chat::Message> as Clone>::clone(&new_message.message)
+ .id()
+ .get_untracked(),
new_message,
);
debug!("set the new message in message buffer");
}
- } else {
- messages
- .insert(<ArcStore<filamento::chat::Message> as Clone>::clone(&new_message.message).id().get_untracked(), new_message);
- debug!("set the new message in message buffer");
}
}
});
on_cleanup(move || {
if let Some(sub_id) = sub_id.get() {
- new_messages_signal.write().unsubscribe_chat(sub_id, chat_chat.correspondent().get());
+ new_messages_signal
+ .write()
+ .unsubscribe_chat(sub_id, chat_chat.correspondent().get());
}
});
let each = move || {
let mut last_timestamp = NaiveDateTime::MIN;
let mut last_user: Option<JID> = None;
- let mut messages = messages.get().into_iter().map(|(id, message)| {
- let message_timestamp = <ArcStore<filamento::chat::Message> as Clone>::clone(&message.message).timestamp().read().naive_local();
- // if message_timestamp.date() > last_timestamp.date() {
- // messages_view = messages_view.push(date(message_timestamp.date()));
- // }
- let major = if last_user.as_ref() != Some(&message.message.read().from)
- || message_timestamp - last_timestamp > TimeDelta::minutes(3)
- {
- true
- } else {
- false
- };
- last_user = Some(<ArcStore<filamento::chat::Message> as Clone>::clone(&message.message).from().get());
- last_timestamp = message_timestamp;
- (id, (message, major, false))
- }).collect::<Vec<_>>();
+ let mut messages = messages
+ .get()
+ .into_iter()
+ .map(|(id, message)| {
+ let message_timestamp =
+ <ArcStore<filamento::chat::Message> as Clone>::clone(&message.message)
+ .timestamp()
+ .read()
+ .naive_local();
+ // if message_timestamp.date() > last_timestamp.date() {
+ // messages_view = messages_view.push(date(message_timestamp.date()));
+ // }
+ let major = if last_user.as_ref() != Some(&message.message.read().from)
+ || message_timestamp - last_timestamp > TimeDelta::minutes(3)
+ {
+ true
+ } else {
+ false
+ };
+ last_user = Some(
+ <ArcStore<filamento::chat::Message> as Clone>::clone(&message.message)
+ .from()
+ .get(),
+ );
+ last_timestamp = message_timestamp;
+ (id, (message, major, false))
+ })
+ .collect::<Vec<_>>();
if let Some((_id, (_, _, last))) = messages.last_mut() {
*last = true
}
@@ -858,23 +974,40 @@ pub fn IconComponent(icon: Icon) -> impl IntoView {
pub fn Delivery(delivery: Delivery) -> impl IntoView {
match delivery {
// TODO: proper icon coloring/theming
- Delivery::Sending => view! { <IconComponent class:visible=true class:light=true icon=Icon::Sending16 /> }.into_any(),
- Delivery::Written => view! { <IconComponent class:light=true icon=Icon::Sent16 /> }.into_any(),
+ Delivery::Sending => {
+ view! { <IconComponent class:visible=true class:light=true icon=Icon::Sending16 /> }
+ .into_any()
+ }
+ Delivery::Written => {
+ view! { <IconComponent class:light=true icon=Icon::Sent16 /> }.into_any()
+ }
// TODO: message receipts
// Delivery::Written => view! {}.into_any(),
Delivery::Sent => view! { <IconComponent class:light=true icon=Icon::Sent16 /> }.into_any(),
- Delivery::Delivered => view! { <IconComponent class:light=true icon=Icon::Delivered16 /> }.into_any(),
+ Delivery::Delivered => {
+ view! { <IconComponent class:light=true icon=Icon::Delivered16 /> }.into_any()
+ }
// TODO: check if there is also the icon class
- Delivery::Read => view! { <IconComponent class:light=true class:read=true icon=Icon::Delivered16 /> }.into_any(),
- Delivery::Failed => view! { <IconComponent class:visible=true class:light=true icon=Icon::Error16Color /> }.into_any(),
+ Delivery::Read => {
+ view! { <IconComponent class:light=true class:read=true icon=Icon::Delivered16 /> }
+ .into_any()
+ }
+ Delivery::Failed => {
+ view! { <IconComponent class:visible=true class:light=true icon=Icon::Error16Color /> }
+ .into_any()
+ }
// TODO: queued icon
- Delivery::Queued => view! { <IconComponent class:visible=true class:light=true icon=Icon::Sending16 /> }.into_any(),
- }
+ Delivery::Queued => {
+ view! { <IconComponent class:visible=true class:light=true icon=Icon::Sending16 /> }
+ .into_any()
+ }
+ }
}
#[component]
pub fn Message(message: MacawMessage, major: bool, r#final: bool) -> impl IntoView {
- let message_message: Store<Message> = <ArcStore<filamento::chat::Message> as Clone>::clone(&message.message).into();
+ let message_message: Store<Message> =
+ <ArcStore<filamento::chat::Message> as Clone>::clone(&message.message).into();
let message_user = <ArcStore<filamento::user::User> as Clone>::clone(&message.user).into();
let avatar = LocalResource::new(move || get_avatar(message_user));
let name = move || get_name(message_user);
@@ -927,13 +1060,28 @@ pub fn ChatViewMessageComposer(chat: JID) -> impl IntoView {
let send_message = move || {
let value = chat.clone();
- spawn_local(async move { match client.read_untracked().send_message(value, Body { body: new_message.get_untracked() }).await {
- Ok(_) => {
- new_message.set("".to_string());
- message_input.write_untracked().as_ref().expect("message input div not mounted").set_text_content(Some(""));
- },
- Err(e) => tracing::error!("message send error: {}", e),
- }})
+ spawn_local(async move {
+ match client
+ .read_untracked()
+ .send_message(
+ value,
+ Body {
+ body: new_message.get_untracked(),
+ },
+ )
+ .await
+ {
+ Ok(_) => {
+ new_message.set("".to_string());
+ message_input
+ .write_untracked()
+ .as_ref()
+ .expect("message input div not mounted")
+ .set_text_content(Some(""));
+ }
+ Err(e) => tracing::error!("message send error: {}", e),
+ }
+ })
};
let _focus = Effect::new(move |_| {
@@ -1005,7 +1153,7 @@ impl<K, V> PartialEq for ArcStateStore<K, V> {
impl<K, V> Clone for ArcStateStore<K, V> {
fn clone(&self) -> Self {
Self {
- store: Arc::clone(&self.store)
+ store: Arc::clone(&self.store),
}
}
}
@@ -1022,7 +1170,7 @@ impl<K, V> ArcStateStore<K, V> {
#[derive(Debug)]
struct StateStore<K, V, S = SyncStorage> {
- inner: ArenaItem<ArcStateStore<K, V>, S>
+ inner: ArenaItem<ArcStateStore<K, V>, S>,
}
impl<K, V, S> Dispose for StateStore<K, V, S> {
@@ -1031,13 +1179,22 @@ impl<K, V, S> Dispose for StateStore<K, V, S> {
}
}
-impl<K, V> StateStore<K, V> where K: Send + Sync + 'static, V: Send + Sync + 'static {
+impl<K, V> StateStore<K, V>
+where
+ K: Send + Sync + 'static,
+ V: Send + Sync + 'static,
+{
pub fn new() -> Self {
Self::new_with_storage()
}
}
-impl<K, V, S> StateStore<K, V, S> where K: 'static, V: 'static, S: Storage<ArcStateStore<K, V>> {
+impl<K, V, S> StateStore<K, V, S>
+where
+ K: 'static,
+ V: 'static,
+ S: Storage<ArcStateStore<K, V>>,
+{
pub fn new_with_storage() -> Self {
Self {
inner: ArenaItem::new_with_storage(ArcStateStore::new()),
@@ -1045,13 +1202,21 @@ impl<K, V, S> StateStore<K, V, S> where K: 'static, V: 'static, S: Storage<ArcSt
}
}
-impl<K, V> StateStore<K, V, LocalStorage> where K: 'static, V: 'static {
+impl<K, V> StateStore<K, V, LocalStorage>
+where
+ K: 'static,
+ V: 'static,
+{
pub fn new_local() -> Self {
Self::new_with_storage()
}
}
-impl<K: std::marker::Send + std::marker::Sync + 'static, V: std::marker::Send + std::marker::Sync + 'static> From<ArcStateStore<K, V>> for StateStore<K, V> {
+impl<
+ K: std::marker::Send + std::marker::Sync + 'static,
+ V: std::marker::Send + std::marker::Sync + 'static,
+> From<ArcStateStore<K, V>> for StateStore<K, V>
+{
fn from(value: ArcStateStore<K, V>) -> Self {
Self {
inner: ArenaItem::new_with_storage(value),
@@ -1075,7 +1240,11 @@ impl<K, V, S> Clone for StateStore<K, V, S> {
}
}
-impl<K: Eq + std::hash::Hash + Clone, V: Clone> StateStore<K, V> where K: Send + Sync + 'static, V: Send + Sync + 'static {
+impl<K: Eq + std::hash::Hash + Clone, V: Clone> StateStore<K, V>
+where
+ K: Send + Sync + 'static,
+ V: Send + Sync + 'static,
+{
pub fn store(&self, key: K, value: V) -> StateListener<K, V> {
{
let store = self.inner.try_get_value().unwrap();
@@ -1091,12 +1260,19 @@ impl<K: Eq + std::hash::Hash + Clone, V: Clone> StateStore<K, V> where K: Send +
};
StateListener {
value,
- cleaner: StateCleaner { key, state_store: self.clone() },
+ cleaner: StateCleaner {
+ key,
+ state_store: self.clone(),
+ },
}
}
}
-impl<K, V> StateStore<K, V> where K: Eq + std::hash::Hash + Send + Sync + 'static, V: Send + Sync + 'static {
+impl<K, V> StateStore<K, V>
+where
+ K: Eq + std::hash::Hash + Send + Sync + 'static,
+ V: Send + Sync + 'static,
+{
pub fn update(&self, key: &K, value: V) {
let store = self.inner.try_get_value().unwrap();
let mut store = store.store.write().unwrap();
@@ -1127,12 +1303,20 @@ impl<K, V> StateStore<K, V> where K: Eq + std::hash::Hash + Send + Sync + 'stati
}
#[derive(Clone)]
-struct StateListener<K, V> where K: Eq + std::hash::Hash + 'static + std::marker::Send + std::marker::Sync, V: 'static + std::marker::Send + std::marker::Sync {
+struct StateListener<K, V>
+where
+ K: Eq + std::hash::Hash + 'static + std::marker::Send + std::marker::Sync,
+ V: 'static + std::marker::Send + std::marker::Sync,
+{
value: V,
- cleaner: StateCleaner<K, V>
+ cleaner: StateCleaner<K, V>,
}
-impl<K: std::cmp::Eq + std::hash::Hash + std::marker::Send + std::marker::Sync, V: std::marker::Send + std::marker::Sync> Deref for StateListener<K, V> {
+impl<
+ K: std::cmp::Eq + std::hash::Hash + std::marker::Send + std::marker::Sync,
+ V: std::marker::Send + std::marker::Sync,
+> Deref for StateListener<K, V>
+{
type Target = V;
fn deref(&self) -> &Self::Target {
@@ -1140,7 +1324,9 @@ impl<K: std::cmp::Eq + std::hash::Hash + std::marker::Send + std::marker::Sync,
}
}
-impl<K: std::cmp::Eq + std::hash::Hash + Send + Sync, V: Send + Sync> DerefMut for StateListener<K, V> {
+impl<K: std::cmp::Eq + std::hash::Hash + Send + Sync, V: Send + Sync> DerefMut
+ for StateListener<K, V>
+{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.value
}
@@ -1151,12 +1337,20 @@ struct ArcStateCleaner<K, V> {
state_store: ArcStateStore<K, V>,
}
-struct StateCleaner<K, V> where K: Eq + std::hash::Hash + Send + Sync + 'static, V: Send + Sync + 'static {
+struct StateCleaner<K, V>
+where
+ K: Eq + std::hash::Hash + Send + Sync + 'static,
+ V: Send + Sync + 'static,
+{
key: K,
state_store: StateStore<K, V>,
}
-impl<K, V> Clone for StateCleaner<K, V> where K: Eq + std::hash::Hash + Clone + Send + Sync, V: Send + Sync {
+impl<K, V> Clone for StateCleaner<K, V>
+where
+ K: Eq + std::hash::Hash + Clone + Send + Sync,
+ V: Send + Sync,
+{
fn clone(&self) -> Self {
{
let store = self.state_store.inner.try_get_value().unwrap();
@@ -1172,7 +1366,9 @@ impl<K, V> Clone for StateCleaner<K, V> where K: Eq + std::hash::Hash + Clone +
}
}
-impl<K: Eq + std::hash::Hash + Send + Sync + 'static, V: Send + Sync + 'static> Drop for StateCleaner<K, V> {
+impl<K: Eq + std::hash::Hash + Send + Sync + 'static, V: Send + Sync + 'static> Drop
+ for StateCleaner<K, V>
+{
fn drop(&mut self) {
self.state_store.remove(&self.key);
}
@@ -1186,14 +1382,13 @@ struct MacawChat {
impl MacawChat {
fn got_chat_and_user(chat: Chat, user: User) -> Self {
- let chat_state_store: StateStore<JID, ArcStore<Chat>> = use_context().expect("no chat state store");
- let user_state_store: StateStore<JID, ArcStore<User>> = use_context().expect("no user state store");
+ let chat_state_store: StateStore<JID, ArcStore<Chat>> =
+ use_context().expect("no chat state store");
+ let user_state_store: StateStore<JID, ArcStore<User>> =
+ use_context().expect("no user state store");
let user = user_state_store.store(user.jid.clone(), ArcStore::new(user));
let chat = chat_state_store.store(chat.correspondent.clone(), ArcStore::new(chat));
- Self {
- chat,
- user,
- }
+ Self { chat, user }
}
}
@@ -1219,14 +1414,13 @@ struct MacawMessage {
impl MacawMessage {
fn got_message_and_user(message: Message, user: User) -> Self {
- let message_state_store: StateStore<Uuid, ArcStore<Message>> = use_context().expect("no message state store");
- let user_state_store: StateStore<JID, ArcStore<User>> = use_context().expect("no user state store");
+ let message_state_store: StateStore<Uuid, ArcStore<Message>> =
+ use_context().expect("no message state store");
+ let user_state_store: StateStore<JID, ArcStore<User>> =
+ use_context().expect("no user state store");
let message = message_state_store.store(message.id, ArcStore::new(message));
let user = user_state_store.store(user.jid.clone(), ArcStore::new(user));
- Self {
- message,
- user,
- }
+ Self { message, user }
}
}
@@ -1271,12 +1465,10 @@ struct MacawContact {
impl MacawContact {
fn got_contact_and_user(contact: Contact, user: User) -> Self {
let contact = Store::new(contact);
- let user_state_store: StateStore<JID, ArcStore<User>> = use_context().expect("no user state store");
+ let user_state_store: StateStore<JID, ArcStore<User>> =
+ use_context().expect("no user state store");
let user = user_state_store.store(user.jid.clone(), ArcStore::new(user));
- Self {
- contact,
- user,
- }
+ Self { contact, user }
}
}
@@ -1300,17 +1492,29 @@ fn ChatsList() -> impl IntoView {
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());
+ 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<JID, _>>();
+ 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<JID, _>>();
set_chats.set(chats);
- },
+ }
Err(_) => {
// TODO: show error message at top of chats list
- },
+ }
}
});
@@ -1382,15 +1586,20 @@ fn RosterList() -> impl IntoView {
#[component]
fn RosterListItem(contact: MacawContact) -> impl IntoView {
let contact_contact: Store<Contact> = contact.contact;
- let contact_user: Store<User> = <ArcStore<filamento::user::User> as Clone>::clone(&contact.user).into();
+ let contact_user: Store<User> =
+ <ArcStore<filamento::user::User> as Clone>::clone(&contact.user).into();
let avatar = LocalResource::new(move || get_avatar(contact_user));
let name = move || get_name(contact_user);
- let open_chats: Store<OpenChatsPanel> = use_context().expect("no open chats panel store in context");
+ let open_chats: Store<OpenChatsPanel> =
+ use_context().expect("no open chats panel store in context");
// TODO: why can this not be in the closure?????
// TODO: not good, as overwrites preexisting chat state with possibly incorrect one...
- let chat = Chat { correspondent: contact_user.jid().get(), have_chatted: false };
+ let chat = Chat {
+ correspondent: contact_user.jid().get(),
+ have_chatted: false,
+ };
let chat = MacawChat::got_chat_and_user(chat, contact_user.get());
let open_chat = move |_| {
@@ -1398,20 +1607,24 @@ fn RosterListItem(contact: MacawContact) -> impl IntoView {
open_chats.update(|open_chats| open_chats.open(chat.clone()));
};
- let open =move || {
+ let open = move || {
if let Some(open_chat) = &*open_chats.chat_view().read() {
debug!("got open chat: {:?}", open_chat);
if *open_chat == *contact_user.jid().read() {
- return Open::Focused
+ return Open::Focused;
}
}
- if let Some(_backgrounded_chat) = open_chats.chats().read().get(contact_user.jid().read().deref()) {
- return Open::Open
- }
+ if let Some(_backgrounded_chat) = open_chats
+ .chats()
+ .read()
+ .get(contact_user.jid().read().deref())
+ {
+ return Open::Open;
+ }
Open::Closed
};
- let focused =move || open().is_focused();
- let open=move || open().is_open();
+ let focused = move || open().is_focused();
+ let open = move || open().is_open();
view! {
<div class="roster-list-item" class:open=move || open() class:focused=move || focused() on:click=open_chat>
@@ -1487,33 +1700,39 @@ impl Open {
#[component]
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 chat_user: Store<User> =
+ <ArcStore<filamento::user::User> as Clone>::clone(&chat.user).into();
let avatar = LocalResource::new(move || get_avatar(chat_user));
let name = move || get_name(chat_user);
// TODO: store fine-grained reactivity
let latest_message_body = move || message.get().body.body;
- let open_chats: Store<OpenChatsPanel> = use_context().expect("no open chats panel store in context");
+ 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 || {
+ 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
+ return Open::Focused;
}
}
- if let Some(_backgrounded_chat) = open_chats.chats().read().get(chat_chat.correspondent().read().deref()) {
- return Open::Open
- }
+ 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 focused = move || open().is_focused();
+ let open = move || open().is_open();
view! {
<div class="chats-list-item" class:open=move || open() class:focused=move || focused() on:click=open_chat>