summaryrefslogtreecommitdiffstats
path: root/src/lib.rs
diff options
context:
space:
mode:
authorLibravatar cel 🌸 <cel@bunny.garden>2025-04-29 20:58:02 +0100
committerLibravatar cel 🌸 <cel@bunny.garden>2025-04-29 20:58:02 +0100
commit22141aa6d7b0f97441a60111046d1ef7260b68dc (patch)
tree68667672a2777aec2cfcf36575b973fb3e64e7f8 /src/lib.rs
parent0e902e1f0a56e2f59cb91065a0ad8600631a1e49 (diff)
downloadmacaw-web-22141aa6d7b0f97441a60111046d1ef7260b68dc.tar.gz
macaw-web-22141aa6d7b0f97441a60111046d1ef7260b68dc.tar.bz2
macaw-web-22141aa6d7b0f97441a60111046d1ef7260b68dc.zip
fix: use_context works
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs363
1 files changed, 226 insertions, 137 deletions
diff --git a/src/lib.rs b/src/lib.rs
index b6e7cea..11ebc54 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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>
}