diff options
| author | 2025-04-29 20:58:02 +0100 | |
|---|---|---|
| committer | 2025-04-29 20:58:02 +0100 | |
| commit | 22141aa6d7b0f97441a60111046d1ef7260b68dc (patch) | |
| tree | 68667672a2777aec2cfcf36575b973fb3e64e7f8 /src | |
| parent | 0e902e1f0a56e2f59cb91065a0ad8600631a1e49 (diff) | |
| download | macaw-web-22141aa6d7b0f97441a60111046d1ef7260b68dc.tar.gz macaw-web-22141aa6d7b0f97441a60111046d1ef7260b68dc.tar.bz2 macaw-web-22141aa6d7b0f97441a60111046d1ef7260b68dc.zip  | |
fix: use_context works
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib.rs | 363 | 
1 files changed, 226 insertions, 137 deletions
@@ -1,16 +1,36 @@ -use std::{ops::{Deref, DerefMut}, str::FromStr, sync::Arc, thread::sleep, time::{self, Duration}}; - -use filamento::{chat::{Chat, Message}, db::Db, error::{CommandError, ConnectionError, DatabaseError}, files::FilesMem, user::User, UpdateMessage}; +use std::{ +    borrow::Borrow, +    cell::RefCell, +    collections::HashMap, +    marker::PhantomData, +    ops::{Deref, DerefMut}, +    rc::Rc, +    str::FromStr, +    sync::{atomic::AtomicUsize, Arc}, +    thread::sleep, +    time::{self, Duration}, +}; + +use filamento::{ +    chat::{Chat, Message}, +    db::Db, +    error::{CommandError, ConnectionError, DatabaseError}, +    files::FilesMem, +    user::User, +    UpdateMessage, +}; +use futures::stream::StreamExt;  use indexmap::IndexMap;  use jid::JID; -use leptos::{prelude::*, task::spawn_local}; -use futures::stream::StreamExt; +use leptos::{ +    prelude::*, +    task::{spawn, spawn_local}, +};  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 reactive_stores::Store;  use stylance::import_style;  use thiserror::Error; -use tokio::{sync::mpsc::Receiver}; +use tokio::sync::{mpsc::Receiver, Mutex};  use tracing::debug;  use uuid::Uuid; @@ -43,7 +63,7 @@ impl DerefMut for Client {  #[component]  pub fn App() -> impl IntoView {      let (app, set_app) = signal(AppState::LoggedOut); -    let client: RwSignal<Option<(Client, Receiver<UpdateMessage>)>, LocalStorage> = RwSignal::new_local(None); +    let client: RwSignal<Option<(Client, Receiver<UpdateMessage>)>> = RwSignal::new(None);      view! {          {move || match &*app.read() { @@ -73,13 +93,13 @@ pub enum LoginError {  }  #[component] -fn LoginPage(set_app: WriteSignal<AppState>, set_client: RwSignal<Option<(Client, Receiver<UpdateMessage>)>, LocalStorage>) -> 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);      let connect_on_login = RwSignal::new(true); -    let (error, set_error) = signal_local(None::<LoginError>); +    let (error, set_error) = signal(None::<LoginError>);      let error_message = move || {          error.with(|error| {              if let Some(error) = error { @@ -128,7 +148,7 @@ fn LoginPage(set_app: WriteSignal<AppState>, set_client: RwSignal<Option<(Client                  jid: Arc::new(jid),                  file_store: files_mem,              }; -            // TODO: connect on login +              if *connect_on_login.read_untracked() {                  match client.connect().await {                      Ok(_) => {}, @@ -211,19 +231,26 @@ 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);      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 messages_store: StateStore<Uuid, Store<Message>> = StateStore::new(); +    provide_context(RwSignal::new_local(messages_store)); +    let chats_store: StateStore<JID, Store<Chat>> = StateStore::new(); +    provide_context(RwSignal::new_local(chats_store)); +    let users_store: StateStore<JID, Store<User>> = StateStore::new(); +    provide_context(RwSignal::new_local(users_store)); -    let updates_routine = OnceResource::new(async move { +    // // here we create a signal in the root that can be consumed +    // // anywhere in the app. +    // let (count, set_count) = signal(0); +    // // we'll pass the setter to specific components, +    // // but provide the count itself to the whole app via context +    // provide_context(count); + +    OnceResource::new(async move {          while let Some(update) = updates.recv().await {              match update {                  UpdateMessage::Online(online, items) => {}, @@ -243,85 +270,148 @@ fn Macaw(          }      }); -    view! { -        "logged in" -        <ChatsList /> +    view! { <ChatsList /> } +} + +// V has to be an arc signal +struct StateStore<K, V> { +    store: Rc<RefCell<HashMap<K, (V, usize)>>>, +} + +impl<K, V> Clone for StateStore<K, V> { +    fn clone(&self) -> Self { +        Self { +            store: self.store.clone(), +        }      }  } -fn chat_query() -> QueryScope<JID, Result<Chat, String>> { -    create_query(get_chat, QueryOptions::default()) +impl<K, V> StateStore<K, V> { +    pub fn new() -> Self { +        Self { +            store: Rc::new(RefCell::new(HashMap::new())), +        } +    } +} + +impl<K: Eq + std::hash::Hash + Clone, V: Clone> StateStore<K, V> { +    pub fn store(&self, key: K, value: V) -> StateListener<K, V> { +        let mut store = self.store.borrow_mut(); +        if let Some((v, count)) = store.get_mut(&key) { +            *v = value.clone(); +            *count += 1; +            StateListener { +                value, +                cleaner: StateCleaner { key, _ty: PhantomData  }, +            } +        } else { +            store.insert(key.clone(), (value.clone(), 1)); +            StateListener { +                value, +                cleaner: StateCleaner { +                    key, +                    _ty: PhantomData, +                } +            } +        } +    } + +    pub fn init() { + +    } +} + +impl<K: Eq + std::hash::Hash , V> StateStore<K, V> { +    pub fn update(&self, key: &K, value: V) { +        if let Some((v, _)) = self.store.borrow_mut().get_mut(key) { +            *v = value; +        } +    } + +    pub fn modify(&self, key: &K, modify: impl Fn(&mut V)) { +        if let Some((v, _)) = self.store.borrow_mut().get_mut(key) { +            modify(v); +        } +    } + +    fn remove(&self, key: &K) { +        let mut store = self.store.borrow_mut(); +        if let Some((_v, count)) = store.get_mut(key) { +            *count -= 1; +            if *count == 0 { +                store.remove(key); +            } +        } +    }  } -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()) +#[derive(Clone)] +struct StateListener<K, V> where K: Eq + std::hash::Hash + 'static, V: 'static { +    value: V, +    cleaner: StateCleaner<K, V>  } -fn user_query() -> QueryScope<JID, Result<User, String>> { -    create_query(get_user, QueryOptions::default()) +impl<K: std::cmp::Eq + std::hash::Hash, V> Deref for StateListener<K, V> { +    type Target = V; + +    fn deref(&self) -> &Self::Target { +        &self.value +    }  } -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()) +impl<K: std::cmp::Eq + std::hash::Hash, V> DerefMut for StateListener<K, V> { +    fn deref_mut(&mut self) -> &mut Self::Target { +        &mut self.value +    }  } -fn message_query() -> QueryScope<Uuid, Result<Message, String>> { -    create_query(get_message, QueryOptions::default()) +struct StateCleaner<K, V> where K: Eq + std::hash::Hash + 'static, V: 'static { +    key: K, +    _ty: PhantomData<V>, +    // state_store: StateStore<K, V>,  } -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()) +impl<K, V> Clone for StateCleaner<K, V> where K: Eq + std::hash::Hash + Clone { +    fn clone(&self) -> Self { +        let state_store  = use_context::<RwSignal<StateStore<K, V>, LocalStorage>>().unwrap(); +        if let Some((_v, count)) = state_store.read_untracked().store.borrow_mut().get_mut(&self.key) { +            *count += 1; +        } +        Self { +            key: self.key.clone(), +            _ty: PhantomData, +        } +    }  } -// fn got_chat(chat: Chat) -> QueryScope<JID, MacawChat> { -//     let fetcher = move |_| { -//         let chat = (&chat).clone(); -//         async { -//         MacawChat { -//             chat -//         } -//     }}; -//     create_query(fetcher, QueryOptions::default()) -// } +impl<K: Eq + std::hash::Hash + 'static, V: 'static> Drop for StateCleaner<K, V> { +    fn drop(&mut self) { +        let state_store = use_context::<RwSignal<StateStore<K, V>, LocalStorage>>().unwrap(); +        state_store.read_untracked().remove(&self.key) +    } +}  #[derive(Clone)]  struct MacawChat { -    chat: ReadSignal<Option<Option<Result<Chat, String>>>>, -    user: ReadSignal<Option<Option<Result<User, String>>>>, +    chat: StateListener<JID, Store<Chat>>, +    user: StateListener<JID, Store<User>>,  }  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()); +        let chat_state_store: RwSignal<StateStore<JID, Store<Chat>>, LocalStorage> = use_context().expect("no chat state store"); +        let user_state_store: RwSignal<StateStore<JID, Store<User>>, LocalStorage> = use_context().expect("no user state store"); +        let user = user_state_store.read_untracked().store(user.jid.clone(), Store::new(user)); +        let chat = chat_state_store.read_untracked().store(chat.correspondent.clone(), Store::new(chat));          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()), +            chat, +            user,          }      }  }  impl Deref for MacawChat { -    type Target = ReadSignal<Option<Option<Result<Chat, String>>>>; +    type Target = StateListener<JID, Store<Chat>>;      fn deref(&self) -> &Self::Target {          &self.chat @@ -336,30 +426,25 @@ impl DerefMut for MacawChat {  #[derive(Clone)]  struct MacawMessage { -    message: ReadSignal<Option<Option<Result<Message, String>>>>, -    user: ReadSignal<Option<Option<Result<User, String>>>>, +    message: StateListener<Uuid, Store<Message>>, +    user: StateListener<JID, Store<User>>,  }  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()); +        let message_state_store: RwSignal<StateStore<Uuid, Store<Message>>, LocalStorage> = use_context().expect("no message state store"); +        let user_state_store: RwSignal<StateStore<JID, Store<User>>, LocalStorage> = use_context().expect("no user state store"); +        let message = message_state_store.read_untracked().store(message.id, Store::new(message)); +        let user = user_state_store.read_untracked().store(user.jid.clone(), Store::new(user));          Self { -            message: ReadSignal::from_stream_unsync(message.data.to_stream()), -            user: ReadSignal::from_stream_unsync(user.data.to_stream()), +            message, +            user,          }      }  }  impl Deref for MacawMessage { -    type Target = ReadSignal<Option<Option<Result<Message, String>>>>; +    type Target = StateListener<Uuid, Store<Message>>;      fn deref(&self) -> &Self::Target {          &self.message @@ -372,73 +457,77 @@ impl DerefMut for MacawMessage {      }  } +struct MacawUser { +    user: StateListener<JID, Store<User>>, +} + +impl Deref for MacawUser { +    type Target = StateListener<JID, Store<User>>; + +    fn deref(&self) -> &Self::Target { +        &self.user +    } +} + +impl DerefMut for MacawUser { +    fn deref_mut(&mut self) -> &mut Self::Target { +        &mut self.user +    } +} + +#[component]  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)) -    }()); +    let client = use_context::<Client>().expect("client not in context"); +    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<JID, _>>(); +                set_chats.set(chats); +            }, +            Err(_) => { +                // TODO: show error message at top of chats list +            }, +        } +    });      // TODO: filter new messages signal      let new_messages_signal: ReadSignal<Option<(JID, MacawMessage)>> = use_context().unwrap(); -    OnceResource::new(async move { +    spawn_local(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"); -                        } -                    } +        load_chats.await; +        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_chats.write().shift_remove(&to) { +                    debug!("chat existed"); +                    set_chats.write().insert_before(0, to, (chat, new_message)); +                    debug!("done setting"); +                } else { +                    debug!("the chat didn't exist"); +                    let chat = client.get_chat(to.clone()).await.unwrap(); +                    let user = client.get_user(to.clone()).await.unwrap(); +                    let chat = MacawChat::got_chat_and_user(chat, user); +                    set_chats.write().insert_before(0, to, (chat, new_message)); +                    debug!("done setting");                  } -                debug!("set the new message");              } -            Err(_) => {},          } +        debug!("set the new message");      });      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() -                    } -                }} +                <For each=move || chats.get() key=|chat| chat.0.clone() let(chat)> +                    <p>{chat.0.to_string()}</p> +                </For>              </div>          </div>      }  | 
