summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar cel 🌸 <cel@bunny.garden>2025-04-26 14:57:41 +0100
committerLibravatar cel 🌸 <cel@bunny.garden>2025-04-26 14:57:41 +0100
commit0e902e1f0a56e2f59cb91065a0ad8600631a1e49 (patch)
tree278db82117155bcded77e746e192bf523ef890fd
parentb1fc29669b54d544e02d2d73b8dc841d43a5c92a (diff)
downloadmacaw-web-0e902e1f0a56e2f59cb91065a0ad8600631a1e49.tar.gz
macaw-web-0e902e1f0a56e2f59cb91065a0ad8600631a1e49.tar.bz2
macaw-web-0e902e1f0a56e2f59cb91065a0ad8600631a1e49.zip
before removing leptos-query
-rw-r--r--Cargo.lock137
-rw-r--r--Cargo.toml10
-rw-r--r--index.html2
-rw-r--r--src/lib.rs265
-rw-r--r--src/main.rs3
5 files changed, 389 insertions, 28 deletions
diff --git a/Cargo.lock b/Cargo.lock
index cf9b487..471b27e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
]
diff --git a/Cargo.toml b/Cargo.toml
index c42775a..5dc98c9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"] }
diff --git a/index.html b/index.html
index 6579e70..a3ab66f 100644
--- a/index.html
+++ b/index.html
@@ -15,6 +15,8 @@
<link data-trunk rel="copy-dir" href="assets" />
+ <title>Macaw IM</title>
+
</head>
<body></body>
diff --git a/src/lib.rs b/src/lib.rs
index 8e4f407..b6e7cea 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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)
}