diff options
| author | 2025-04-26 14:57:41 +0100 | |
|---|---|---|
| committer | 2025-04-26 14:57:41 +0100 | |
| commit | 0e902e1f0a56e2f59cb91065a0ad8600631a1e49 (patch) | |
| tree | 278db82117155bcded77e746e192bf523ef890fd | |
| parent | b1fc29669b54d544e02d2d73b8dc841d43a5c92a (diff) | |
| download | macaw-web-0e902e1f0a56e2f59cb91065a0ad8600631a1e49.tar.gz macaw-web-0e902e1f0a56e2f59cb91065a0ad8600631a1e49.tar.bz2 macaw-web-0e902e1f0a56e2f59cb91065a0ad8600631a1e49.zip  | |
before removing leptos-query
| -rw-r--r-- | Cargo.lock | 137 | ||||
| -rw-r--r-- | Cargo.toml | 10 | ||||
| -rw-r--r-- | index.html | 2 | ||||
| -rw-r--r-- | src/lib.rs | 265 | ||||
| -rw-r--r-- | src/main.rs | 3 | 
5 files changed, 389 insertions, 28 deletions
@@ -318,6 +318,7 @@ dependencies = [   "iana-time-zone",   "js-sys",   "num-traits", + "serde",   "wasm-bindgen",   "windows-link",  ] @@ -376,6 +377,16 @@ dependencies = [  ]  [[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]]  name = "const_format"  version = "0.2.34"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -734,6 +745,7 @@ dependencies = [   "jid",   "lampada",   "rusqlite", + "serde",   "sha1",   "sha2",   "sha3", @@ -1424,6 +1436,7 @@ name = "jid"  version = "0.1.0"  dependencies = [   "rusqlite", + "serde",  ]  [[package]] @@ -1469,6 +1482,7 @@ dependencies = [   "jid",   "luz",   "peanuts", + "serde",   "stanza",   "thiserror 2.0.12",   "tokio", @@ -1504,11 +1518,11 @@ dependencies = [   "leptos_hot_reload",   "leptos_macro",   "leptos_server", - "oco_ref", + "oco_ref 0.2.0",   "or_poisoned",   "paste",   "reactive_graph", - "rustc-hash", + "rustc-hash 2.1.1",   "send_wrapper",   "serde",   "serde_qs", @@ -1608,6 +1622,33 @@ dependencies = [  ]  [[package]] +name = "leptos_reactive" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4161acbf80f59219d8d14182371f57302bc7ff81ee41aba8ba1ff7295727f23" +dependencies = [ + "base64", + "cfg-if", + "futures", + "indexmap", + "js-sys", + "oco_ref 0.1.1", + "paste", + "pin-project", + "rustc-hash 1.1.0", + "self_cell", + "serde", + "serde-wasm-bindgen", + "serde_json", + "slotmap", + "thiserror 1.0.69", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]]  name = "leptos_server"  version = "0.7.8"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1741,13 +1782,21 @@ dependencies = [  name = "macaw-web"  version = "0.1.0"  dependencies = [ + "console_error_panic_hook",   "filamento", + "futures", + "indexmap",   "jid",   "leptos",   "leptos_meta", + "leptos_reactive", + "serde",   "stylance",   "thiserror 2.0.12",   "tokio", + "tracing", + "tracing-wasm", + "uuid",  ]  [[package]] @@ -1986,6 +2035,16 @@ dependencies = [  [[package]]  name = "oco_ref" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51ebcefb2f0b9a5e0bea115532c8ae4215d1b01eff176d0f4ba4192895c2708" +dependencies = [ + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "oco_ref"  version = "0.2.0"  source = "registry+https://github.com/rust-lang/crates.io-index"  checksum = "64b94982fe39a861561cf67ff17a7849f2cedadbbad960a797634032b7abb998" @@ -2434,7 +2493,7 @@ dependencies = [   "hydration_context",   "or_poisoned",   "pin-project-lite", - "rustc-hash", + "rustc-hash 2.1.1",   "send_wrapper",   "serde",   "slotmap", @@ -2454,7 +2513,7 @@ dependencies = [   "paste",   "reactive_graph",   "reactive_stores_macro", - "rustc-hash", + "rustc-hash 2.1.1",  ]  [[package]] @@ -2580,6 +2639,12 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"  [[package]]  name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash"  version = "2.1.1"  source = "registry+https://github.com/rust-lang/crates.io-index"  checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" @@ -2668,6 +2733,12 @@ dependencies = [  ]  [[package]] +name = "self_cell" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" + +[[package]]  name = "send_wrapper"  version = "0.6.0"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2686,6 +2757,17 @@ dependencies = [  ]  [[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]]  name = "serde_derive"  version = "1.0.219"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2815,6 +2897,15 @@ dependencies = [  ]  [[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]]  name = "shlex"  version = "1.3.0"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2856,6 +2947,7 @@ version = "1.0.7"  source = "registry+https://github.com/rust-lang/crates.io-index"  checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"  dependencies = [ + "serde",   "version_check",  ] @@ -3038,14 +3130,14 @@ dependencies = [   "js-sys",   "linear-map",   "next_tuple", - "oco_ref", + "oco_ref 0.2.0",   "once_cell",   "or_poisoned",   "parking_lot",   "paste",   "reactive_graph",   "reactive_stores", - "rustc-hash", + "rustc-hash 2.1.1",   "send_wrapper",   "slotmap",   "throw_error", @@ -3119,6 +3211,16 @@ dependencies = [  ]  [[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]]  name = "throw_error"  version = "0.2.0"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3290,6 +3392,28 @@ dependencies = [  ]  [[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "tracing-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" +dependencies = [ + "tracing", + "tracing-subscriber", + "wasm-bindgen", +] + +[[package]]  name = "trust-dns-proto"  version = "0.22.0"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3442,6 +3566,7 @@ checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"  dependencies = [   "getrandom 0.3.2",   "js-sys", + "serde",   "wasm-bindgen",  ] @@ -4,10 +4,18 @@ version = "0.1.0"  edition = "2024"  [dependencies] -filamento = { path = "../luz/filamento" } +console_error_panic_hook = "0.1.7" +filamento = { path = "../luz/filamento", features = ["serde"] } +futures = "0.3.31" +indexmap = "2.9.0"  jid = { path = "../luz/jid" }  leptos = { version = "0.7.8", features = ["csr"] }  leptos_meta = "0.7.8" +leptos_reactive = { version = "0.6.15", features = ["csr"] } +serde = "1.0.219"  stylance = "0.6.0"  thiserror = "2.0.12"  tokio = { version = "1.44.2", features = ["sync"] } +tracing = "0.1.41" +tracing-wasm = "0.2.1" +uuid = { version = "1.16.0", features = ["v4"] } @@ -15,6 +15,8 @@    <link data-trunk rel="copy-dir" href="assets" /> +  <title>Macaw IM</title> +  </head>  <body></body> @@ -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> +    }  } diff --git a/src/main.rs b/src/main.rs index 8b89d4d..fd4a1de 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,5 +2,8 @@ use leptos::prelude::*;  use macaw_web::App;  fn main() { +    tracing_wasm::set_as_global_default(); +    console_error_panic_hook::set_once(); +      leptos::mount::mount_to_body(App)  }  | 
