diff options
author | 2025-04-26 14:57:41 +0100 | |
---|---|---|
committer | 2025-04-26 14:57:41 +0100 | |
commit | 0e902e1f0a56e2f59cb91065a0ad8600631a1e49 (patch) | |
tree | 278db82117155bcded77e746e192bf523ef890fd /src/lib.rs | |
parent | b1fc29669b54d544e02d2d73b8dc841d43a5c92a (diff) | |
download | macaw-web-0e902e1f0a56e2f59cb91065a0ad8600631a1e49.tar.gz macaw-web-0e902e1f0a56e2f59cb91065a0ad8600631a1e49.tar.bz2 macaw-web-0e902e1f0a56e2f59cb91065a0ad8600631a1e49.zip |
before removing leptos-query
Diffstat (limited to 'src/lib.rs')
-rw-r--r-- | src/lib.rs | 265 |
1 files changed, 244 insertions, 21 deletions
@@ -1,12 +1,18 @@ use std::{ops::{Deref, DerefMut}, str::FromStr, sync::Arc, thread::sleep, time::{self, Duration}}; -use filamento::{db::Db, files::FilesMem, UpdateMessage}; +use filamento::{chat::{Chat, Message}, db::Db, error::{CommandError, ConnectionError, DatabaseError}, files::FilesMem, user::User, UpdateMessage}; +use indexmap::IndexMap; use jid::JID; -use leptos::{logging::debug_warn, prelude::*, task::spawn_local}; +use leptos::{prelude::*, task::spawn_local}; +use futures::stream::StreamExt; use leptos_meta::Stylesheet; +use leptos_query::{create_query, provide_query_client, provide_query_client_with_options, DefaultQueryOptions, QueryOptions, QueryResult, QueryScope}; +use leptos_reactive::{SignalGetUntracked, SignalStream}; use stylance::import_style; use thiserror::Error; use tokio::{sync::mpsc::Receiver}; +use tracing::debug; +use uuid::Uuid; pub enum AppState { LoggedOut, @@ -44,7 +50,6 @@ pub fn App() -> impl IntoView { AppState::LoggedOut => view! { <LoginPage set_app set_client=client /> }.into_any(), AppState::LoggedIn => { if let Some((client, updates)) = client.write_untracked().take() { - debug_warn!("opening app"); view! { <Macaw client updates /> }.into_any() } else { set_app.set(AppState::LoggedOut); @@ -63,6 +68,8 @@ pub enum LoginError { MissingJID, #[error("Invalid JID: {0}")] InvalidJID(#[from] jid::ParseError), + #[error("Connection Error: {0}")] + ConnectionError(#[from] CommandError<ConnectionError>), } #[component] @@ -71,8 +78,8 @@ fn LoginPage(set_app: WriteSignal<AppState>, set_client: RwSignal<Option<(Client let password = RwSignal::new("".to_string()); let remember_me = RwSignal::new(false); let connect_on_login = RwSignal::new(true); - let (error, set_error) = signal(None::<LoginError>); + let (error, set_error) = signal_local(None::<LoginError>); let error_message = move || { error.with(|error| { if let Some(error) = error { @@ -85,7 +92,7 @@ fn LoginPage(set_app: WriteSignal<AppState>, set_client: RwSignal<Option<(Client let (login_pending, set_login_pending) = signal(false); - let login = Action::new(move |_| { + let login = Action::new_local(move |_| { async move { set_login_pending.set(true); @@ -116,18 +123,26 @@ fn LoginPage(set_app: WriteSignal<AppState>, set_client: RwSignal<Option<(Client let (client, updates) = filamento::Client::new(jid.clone(), password.read_untracked().clone(), db, files_mem.clone()); // TODO: remember_me - // TODO: connect on login let client = Client { client, jid: Arc::new(jid), file_store: files_mem, }; + // TODO: connect on login + if *connect_on_login.read_untracked() { + match client.connect().await { + Ok(_) => {}, + Err(e) => { + set_error.set(Some(e.into())); + set_login_pending.set(false); + return + }, + } + } // debug!("before setting app state"); set_client.set(Some((client, updates))); - debug_warn!("going"); set_app.set(AppState::LoggedIn); - debug_warn!("gone"); } }); @@ -196,27 +211,235 @@ fn Macaw( client: Client, mut updates: Receiver<UpdateMessage>, ) -> impl IntoView { + // TODO: is there some kind of context_local? let client = RwSignal::new_local(client); provide_context(client); - debug_warn!("ajsdlkfjalsf"); + + let (new_messages, set_new_messages) = signal(None::<(JID, MacawMessage)>); + provide_context(new_messages); + + provide_query_client_with_options(DefaultQueryOptions { + resource_option: leptos_query::ResourceOption::Local, + ..Default::default() + }); let updates_routine = OnceResource::new(async move { - debug_warn!("started"); while let Some(update) = updates.recv().await { match update { - UpdateMessage::Online(online, items) => todo!(), - UpdateMessage::Offline(offline) => todo!(), - UpdateMessage::RosterUpdate(contact, user) => todo!(), - UpdateMessage::RosterDelete(jid) => todo!(), - UpdateMessage::Presence { from, presence } => todo!(), - UpdateMessage::Message { to, from, message } => todo!(), - UpdateMessage::MessageDelivery { id, chat, delivery } => todo!(), - UpdateMessage::SubscriptionRequest(jid) => todo!(), - UpdateMessage::NickChanged { jid, nick } => todo!(), - UpdateMessage::AvatarChanged { jid, id } => todo!(), + UpdateMessage::Online(online, items) => {}, + UpdateMessage::Offline(offline) => {}, + UpdateMessage::RosterUpdate(contact, user) => {}, + UpdateMessage::RosterDelete(jid) => {}, + UpdateMessage::Presence { from, presence } => {}, + UpdateMessage::Message { to, from, message } => { + let new_message = MacawMessage::got_message_and_user(message, from); + set_new_messages.set(Some((to, new_message))); + }, + UpdateMessage::MessageDelivery { id, chat, delivery } => {}, + UpdateMessage::SubscriptionRequest(jid) => {}, + UpdateMessage::NickChanged { jid, nick } => {}, + UpdateMessage::AvatarChanged { jid, id } => {}, + } + } + }); + + view! { + "logged in" + <ChatsList /> + } +} + +fn chat_query() -> QueryScope<JID, Result<Chat, String>> { + create_query(get_chat, QueryOptions::default()) +} + +async fn get_chat(jid: JID) -> Result<Chat, String> { + let client: Client = use_context::<RwSignal<Client, LocalStorage>>().unwrap().read_untracked().clone(); + client.get_chat(jid).await.map_err(|e| e.to_string()) +} + +fn user_query() -> QueryScope<JID, Result<User, String>> { + create_query(get_user, QueryOptions::default()) +} + +async fn get_user(jid: JID) -> Result<User, String> { + let client: Client = use_context::<RwSignal<Client, LocalStorage>>().unwrap().read_untracked().clone(); + client.get_user(jid).await.map_err(|e| e.to_string()) +} + +fn message_query() -> QueryScope<Uuid, Result<Message, String>> { + create_query(get_message, QueryOptions::default()) +} + +async fn get_message(id: Uuid) -> Result<Message, String> { + let client: Client = use_context::<RwSignal<Client, LocalStorage>>().unwrap().read_untracked().clone(); + client.get_message(id).await.map_err(|e| e.to_string()) +} + +// fn got_chat(chat: Chat) -> QueryScope<JID, MacawChat> { +// let fetcher = move |_| { +// let chat = (&chat).clone(); +// async { +// MacawChat { +// chat +// } +// }}; +// create_query(fetcher, QueryOptions::default()) +// } + +#[derive(Clone)] +struct MacawChat { + chat: ReadSignal<Option<Option<Result<Chat, String>>>>, + user: ReadSignal<Option<Option<Result<User, String>>>>, +} + +impl MacawChat { + fn got_chat_and_user(chat: Chat, user: User) -> Self { + let correspondent = chat.correspondent.clone(); + let chat_query = chat_query(); + chat_query.set_query_data(correspondent.clone(), Ok(chat)); + let chat = chat_query.use_query(move || correspondent.clone()); + let jid = user.jid.clone(); + let user_query = user_query(); + user_query.set_query_data(jid.clone(), Ok(user)); + let user = user_query.use_query(move || jid.clone()); + Self { + chat: ReadSignal::from_stream_unsync(chat.data.to_stream()), + user: ReadSignal::from_stream_unsync(user.data.to_stream()), + } + } + + fn get(jid: JID) -> Self { + let jid1 = jid.clone(); + let chat = chat_query().use_query(move || (&jid1).clone()); + let user = user_query().use_query(move || (&jid).clone()); + Self { + chat: ReadSignal::from_stream_unsync(chat.data.to_stream()), + user: ReadSignal::from_stream_unsync(user.data.to_stream()), + } + } +} + +impl Deref for MacawChat { + type Target = ReadSignal<Option<Option<Result<Chat, String>>>>; + + fn deref(&self) -> &Self::Target { + &self.chat + } +} + +impl DerefMut for MacawChat { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.chat + } +} + +#[derive(Clone)] +struct MacawMessage { + message: ReadSignal<Option<Option<Result<Message, String>>>>, + user: ReadSignal<Option<Option<Result<User, String>>>>, +} + +impl MacawMessage { + fn got_message_and_user(message: Message, user: User) -> Self { + debug!("executing the got message"); + let message_id = message.id; + let message_query = message_query(); + message_query.set_query_data(message.id, Ok(message)); + let message = message_query.use_query(move || message_id); + let jid = user.jid.clone(); + let user_query = user_query(); + user_query.set_query_data(jid.clone(), Ok(user)); + let user = user_query.use_query(move || jid.clone()); + Self { + message: ReadSignal::from_stream_unsync(message.data.to_stream()), + user: ReadSignal::from_stream_unsync(user.data.to_stream()), + } + } +} + +impl Deref for MacawMessage { + type Target = ReadSignal<Option<Option<Result<Message, String>>>>; + + fn deref(&self) -> &Self::Target { + &self.message + } +} + +impl DerefMut for MacawMessage { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.message + } +} + +fn ChatsList() -> impl IntoView { + let chats: LocalResource<std::result::Result<(ReadSignal<IndexMap<JID, (MacawChat, MacawMessage)>, LocalStorage>, WriteSignal<IndexMap<JID, (MacawChat, MacawMessage)>, LocalStorage>), String>> = LocalResource::new(move || async move || -> Result<_, _> { + let client: Client = use_context::<RwSignal<Client, LocalStorage>>().unwrap().read().clone(); + let chats = client.get_chats_ordered_with_latest_messages_and_users().await.map_err(|e| e.to_string())?; + let chats = chats.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, set_chats) = signal_local(chats); + Ok((chats, set_chats)) + }()); + + // TODO: filter new messages signal + let new_messages_signal: ReadSignal<Option<(JID, MacawMessage)>> = use_context().unwrap(); + OnceResource::new(async move { + let mut new_message = new_messages_signal.to_stream(); + match chats.await { + Ok((c, set_c)) => { + while let Some(new_message) = new_message.next().await { + debug!("got new message in let"); + if let Some((to, new_message)) = new_message { + if let Some((chat, _latest_message)) = set_c.write().shift_remove(&to) { + debug!("chat existed"); + set_c.write().insert_before(0, to, (chat, new_message)); + debug!("done setting"); + } else { + debug!("the chat didn't exist"); + let chat = MacawChat::get(to.clone()); + set_c.write().insert_before(0, to, (chat, new_message)); + debug!("done setting"); + } + } + } + debug!("set the new message"); } + Err(_) => {}, } }); - view! { "logged in" } + view! { + <div class="chats-list panel"> + <h2>Chats</h2> + <div> + {move || { + if let Some(chats) = &*chats.read() { + match &**chats { + Ok((chats, _)) => { + let chats = chats.clone(); + view! { + <For + each=move || chats.get() + key=|chat| chat.0.clone() + let(chat) + > + <p>{chat.0.to_string()}</p> + </For> + } + .into_any() + } + Err(e) => { + view! { <div class="error">{format!("{}", e)}</div> }.into_any() + } + } + } else { + None::<String>.into_any() + } + }} + </div> + </div> + } } |