diff options
35 files changed, 991 insertions, 586 deletions
diff --git a/src/chat.rs b/src/chat.rs index f4202f6..1c92425 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2,10 +2,13 @@ use std::ops::{Deref, DerefMut}; use filamento::{chat::Chat, user::User}; use jid::BareJID; -use reactive_stores::ArcStore; use leptos::prelude::*; +use reactive_stores::ArcStore; -use crate::{state_store::{StateListener, StateStore}, user::{ArcMacawUser, MacawUser}}; +use crate::{ + state_store::{StateListener, StateStore}, + user::{ArcMacawUser, MacawUser}, +}; #[derive(Clone, Copy)] pub struct MacawChat { @@ -81,4 +84,3 @@ impl DerefMut for ArcMacawChat { &mut self.chat } } - diff --git a/src/client.rs b/src/client.rs index b3ff6bb..423beaf 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,4 +1,7 @@ -use std::{ops::{Deref, DerefMut}, sync::Arc}; +use std::{ + ops::{Deref, DerefMut}, + sync::Arc, +}; use jid::BareJID; use leptos::prelude::*; @@ -27,4 +30,3 @@ impl DerefMut for Client { &mut self.client } } - diff --git a/src/components/avatar.rs b/src/components/avatar.rs index 292173e..7ab7544 100644 --- a/src/components/avatar.rs +++ b/src/components/avatar.rs @@ -2,34 +2,48 @@ use filamento::{presence::PresenceType, user::User}; use leptos::prelude::*; use reactive_stores::Store; -use crate::{components::icon::{show_to_icon, IconComponent}, icon::Icon, user::{get_avatar, MacawUser}, user_presences::UserPresences}; +use crate::{ + components::icon::{IconComponent, show_to_icon}, + icon::Icon, + user::{MacawUser, get_avatar}, + user_presences::UserPresences, +}; #[component] pub fn AvatarWithPresence(user: MacawUser) -> impl IntoView { let user_presences: Store<UserPresences> = use_context().expect("no user presences in context"); - let presence = move || user_presences.write().get_user_presences(&user.get().read().jid).read().presence(); - let show_icon = move || presence().map(|(_, presence)| { - match presence.presence { - PresenceType::Online(online) => if let Some(show) = online.show { - Some(show_to_icon(show)) - } else { - Some(Icon::Available16Color) - }, - PresenceType::Offline(offline) => None, - } - }).unwrap_or_default(); + let presence = move || { + user_presences + .write() + .get_user_presences(&user.get().read().jid) + .read() + .presence() + }; + let show_icon = move || { + presence() + .map(|(_, presence)| match presence.presence { + PresenceType::Online(online) => { + if let Some(show) = online.show { + Some(show_to_icon(show)) + } else { + Some(Icon::Available16Color) + } + } + PresenceType::Offline(offline) => None, + }) + .unwrap_or_default() + }; view! { <div class="avatar-with-presence"> - <img class="avatar" src=move || user.avatar().get() /> - {move || if let Some(icon) = show_icon() { - view!{ - <IconComponent icon=icon class:presence-show-icon=true /> - }.into_any() - } else { - view! {}.into_any() - }} + <img class="avatar" src=move || user.avatar().get() /> + {move || { + if let Some(icon) = show_icon() { + view! { <IconComponent icon=icon class:presence-show-icon=true /> }.into_any() + } else { + view! {}.into_any() + } + }} </div> } } - diff --git a/src/components/chat_header.rs b/src/components/chat_header.rs index fe4e8d9..47367dc 100644 --- a/src/components/chat_header.rs +++ b/src/components/chat_header.rs @@ -12,15 +12,11 @@ pub fn ChatViewHeader(chat: MacawChat) -> impl IntoView { view! { <div class="chat-view-header panel"> {move || { - view! { - <AvatarWithPresence user=chat.user /> - } - }} - <div class="user-info"> + view! { <AvatarWithPresence user=chat.user /> } + }} <div class="user-info"> <h2 class="name">{name}</h2> <h3>{jid}</h3> </div> </div> } } - diff --git a/src/components/chats_list.rs b/src/components/chats_list.rs index f958ebe..43ee53e 100644 --- a/src/components/chats_list.rs +++ b/src/components/chats_list.rs @@ -4,7 +4,14 @@ use jid::BareJID; use leptos::prelude::*; use tracing::debug; -use crate::{chat::{ArcMacawChat, MacawChat}, client::Client, components::{icon::IconComponent, new_chat::NewChatWidget, overlay::Overlay}, icon::Icon, message::{ArcMacawMessage, MacawMessage}, message_subscriptions::MessageSubscriptions}; +use crate::{ + chat::{ArcMacawChat, MacawChat}, + client::Client, + components::{icon::IconComponent, new_chat::NewChatWidget, overlay::Overlay}, + icon::Icon, + message::{ArcMacawMessage, MacawMessage}, + message_subscriptions::MessageSubscriptions, +}; mod chats_list_item; @@ -22,11 +29,13 @@ pub fn ChatsList() -> impl IntoView { Ok(c) => { let mut chats = IndexMap::new(); for ((chat, chat_user), (message, message_user)) in c { - chats.insert(chat.correspondent.clone(), ( + chats.insert( + chat.correspondent.clone(), + ( ArcMacawChat::got_chat_and_user(chat, chat_user).await, ArcMacawMessage::got_message_and_user(message, message_user).await, - - )); + ), + ); } set_chats.set(chats); } @@ -51,7 +60,10 @@ pub fn ChatsList() -> impl IntoView { if let Some((chat, _latest_message)) = chats.shift_remove(&to) { // TODO: check if new message is actually latest message debug!("chat existed"); - debug!("new message: {}", new_message.message.get().read().body.body); + debug!( + "new message: {}", + new_message.message.get().read().body.body + ); chats.insert_before(0, to, (chat.clone(), new_message)); debug!("done setting"); } else { @@ -79,15 +91,19 @@ pub fn ChatsList() -> impl IntoView { // TODO: update icon, tooltip on hover. <div class="header"> <h2>Chats</h2> - <div class="new-chat header-icon" class:open=open_new_chat > - <IconComponent icon=Icon::NewBubble24 on:click=move |_| set_open_new_chat.update(|state| *state = !*state)/> + <div class="new-chat header-icon" class:open=open_new_chat> + <IconComponent + icon=Icon::NewBubble24 + on:click=move |_| set_open_new_chat.update(|state| *state = !*state) + /> {move || { if *open_new_chat.read() { view! { <Overlay set_open=set_open_new_chat> <NewChatWidget set_open_new_chat /> </Overlay> - }.into_any() + } + .into_any() } else { view! {}.into_any() } @@ -102,4 +118,3 @@ pub fn ChatsList() -> impl IntoView { </div> } } - diff --git a/src/components/chats_list/chats_list_item.rs b/src/components/chats_list/chats_list_item.rs index ae01288..e61bf45 100644 --- a/src/components/chats_list/chats_list_item.rs +++ b/src/components/chats_list/chats_list_item.rs @@ -1,12 +1,21 @@ use std::ops::Deref; use chrono::Local; -use filamento::{chat::{Chat, ChatStoreFields, Message, MessageStoreFields}, user::User}; +use filamento::{ + chat::{Chat, ChatStoreFields, Message, MessageStoreFields}, + user::User, +}; use leptos::prelude::*; use reactive_stores::{ArcStore, Store}; use tracing::debug; -use crate::{chat::MacawChat, components::{avatar::AvatarWithPresence, sidebar::Open}, message::MacawMessage, open_chats::{OpenChatsPanel, OpenChatsPanelStoreFields}, user::get_name}; +use crate::{ + chat::MacawChat, + components::{avatar::AvatarWithPresence, sidebar::Open}, + message::MacawMessage, + open_chats::{OpenChatsPanel, OpenChatsPanelStoreFields}, + user::get_name, +}; #[component] pub fn ChatsListItem(chat: MacawChat, message: MacawMessage) -> impl IntoView { @@ -44,23 +53,36 @@ pub fn ChatsListItem(chat: MacawChat, message: MacawMessage) -> impl IntoView { let date = move || message.get().timestamp().read().naive_local(); let now = move || Local::now().naive_local(); - let timeinfo = move || if date().date() == now().date() { - // TODO: localisation/config - date().time().format("%H:%M").to_string() - } else { - date().date().format("%d/%m").to_string() + let timeinfo = move || { + if date().date() == now().date() { + // TODO: localisation/config + date().time().format("%H:%M").to_string() + } else { + date().date().format("%d/%m").to_string() + } }; view! { - <div class="chats-list-item" class:open=move || open() class:focused=move || focused() on:click=open_chat> + <div + class="chats-list-item" + class:open=move || open() + class:focused=move || focused() + on:click=open_chat + > {move || { - view! { - <AvatarWithPresence user=chat.user /> - } + view! { <AvatarWithPresence user=chat.user /> } }} <div class="item-info"> - <div class="main-info"><p class="name">{name}</p><p class="timestamp">{timeinfo}</p></div> - <div class="sub-info"><p class="message-preview">{latest_message_body}</p><p><!-- "TODO: delivery or unread state" --></p></div> + <div class="main-info"> + <p class="name">{name}</p> + <p class="timestamp">{timeinfo}</p> + </div> + <div class="sub-info"> + <p class="message-preview">{latest_message_body}</p> + <p> + <!-- "TODO: delivery or unread state" --> + </p> + </div> </div> </div> } diff --git a/src/components/icon.rs b/src/components/icon.rs index 7eaa52f..307d367 100644 --- a/src/components/icon.rs +++ b/src/components/icon.rs @@ -1,5 +1,5 @@ -use leptos::prelude::*; use filamento::{chat::Delivery, presence::Show}; +use leptos::prelude::*; use crate::icon::Icon; @@ -7,7 +7,12 @@ use crate::icon::Icon; #[component] pub fn IconComponent(icon: Icon) -> impl IntoView { view! { - <img class:light=icon.light() class:icon=true style=move || format!("height: {}px; width: {}px", icon.size(), icon.size()) src=move || icon.src() /> + <img + class:light=icon.light() + class:icon=true + style=move || format!("height: {}px; width: {}px", icon.size(), icon.size()) + src=move || icon.src() + /> } } @@ -53,5 +58,3 @@ pub fn Delivery(delivery: Delivery) -> impl IntoView { } } } - - diff --git a/src/components/message.rs b/src/components/message.rs index 78ebeb6..9eb0b09 100644 --- a/src/components/message.rs +++ b/src/components/message.rs @@ -2,7 +2,10 @@ use filamento::chat::MessageStoreFields; use leptos::prelude::*; use reactive_stores::{ArcStore, Store}; -use crate::{message::MacawMessage, user::{get_avatar, get_name, NO_AVATAR}}; +use crate::{ + message::MacawMessage, + user::{NO_AVATAR, get_avatar, get_name}, +}; use super::icon::Delivery; @@ -16,33 +19,54 @@ pub fn Message(message: MacawMessage, major: bool, r#final: bool) -> impl IntoVi if major { view! { <div class:final=r#final class="chat-message major"> - <div class="left"> - <Transition fallback=|| view! { <img class="avatar" src=NO_AVATAR /> } > + <div class="left"> + <Transition fallback=|| view! { <img class="avatar" src=NO_AVATAR /> }> <img class="avatar" src=move || message.user.avatar().get() /> </Transition> - </div> + </div> <div class="middle"> <div class="message-info"> <div class="message-user-name">{name}</div> - <div class="message-timestamp">{move || message.get().timestamp().read().format("%H:%M").to_string()}</div> + <div class="message-timestamp"> + {move || message.get().timestamp().read().format("%H:%M").to_string()} + </div> </div> <div class="message-text"> {move || message.get().body().read().body.clone()} </div> </div> - <div class="right message-delivery">{move || message.get().delivery().get().map(|delivery| view! { <Delivery class:light=true delivery /> } ) }</div> + <div class="right message-delivery"> + {move || { + message + .get() + .delivery() + .get() + .map(|delivery| view! { <Delivery class:light=true delivery /> }) + }} + </div> </div> - }.into_any() + } + .into_any() } else { view! { <div class:final=r#final class="chat-message minor"> <div class="left message-timestamp"> {move || message.get().timestamp().read().format("%H:%M").to_string()} </div> - <div class="middle message-text">{move || message.get().body().read().body.clone()}</div> - <div class="right message-delivery">{move || message.get().delivery().get().map(|delivery| view! { <Delivery delivery /> } ) }</div> + <div class="middle message-text"> + {move || message.get().body().read().body.clone()} + </div> + <div class="right message-delivery"> + {move || { + message + .get() + .delivery() + .get() + .map(|delivery| view! { <Delivery delivery /> }) + }} + </div> </div> - }.into_any() + } + .into_any() } } - diff --git a/src/components/message_composer.rs b/src/components/message_composer.rs index 3876a5a..09e5f80 100644 --- a/src/components/message_composer.rs +++ b/src/components/message_composer.rs @@ -62,36 +62,34 @@ pub fn ChatViewMessageComposer(chat: BareJID) -> impl IntoView { // TODO: placeholder view! { - <form - class="new-message-composer panel" - > + <form class="new-message-composer panel"> <div class="text-box" - on:input:target=move |ev| new_message.set(ev.target().text_content().unwrap_or_default()) + on:input:target=move |ev| { + new_message.set(ev.target().text_content().unwrap_or_default()) + } node_ref=message_input contenteditable on:keydown=move |ev| { match ev.key_code() { 16 => set_shift_pressed.set(true), - 13 => if !shift_pressed.get() { - ev.prevent_default(); - send_message(); + 13 => { + if !shift_pressed.get() { + ev.prevent_default(); + send_message(); + } } _ => {} - // debug!("shift pressed down"); } } on:keyup=move |ev| { - match ev.key_code() { + match ev.key_code() { 16 => set_shift_pressed.set(false), _ => {} - // debug!("shift released"); } } ></div> - // <input hidden type="submit" /> + // <input hidden type="submit" /> </form> } } - - diff --git a/src/components/message_history_buffer.rs b/src/components/message_history_buffer.rs index dc93054..cf4c328 100644 --- a/src/components/message_history_buffer.rs +++ b/src/components/message_history_buffer.rs @@ -1,5 +1,8 @@ use chrono::{NaiveDateTime, TimeDelta}; -use filamento::{chat::{Chat, ChatStoreFields, MessageStoreFields}, user::User}; +use filamento::{ + chat::{Chat, ChatStoreFields, MessageStoreFields}, + user::User, +}; use indexmap::IndexMap; use jid::BareJID; use leptos::prelude::*; @@ -7,7 +10,13 @@ use reactive_stores::{ArcStore, Store}; use tracing::{debug, error}; use uuid::Uuid; -use crate::{chat::MacawChat, client::Client, components::message::Message, message::{ArcMacawMessage, MacawMessage}, message_subscriptions::MessageSubscriptions}; +use crate::{ + chat::MacawChat, + client::Client, + components::message::Message, + message::{ArcMacawMessage, MacawMessage}, + message_subscriptions::MessageSubscriptions, +}; #[component] pub fn MessageHistoryBuffer(chat: MacawChat) -> impl IntoView { @@ -26,7 +35,10 @@ pub fn MessageHistoryBuffer(chat: MacawChat) -> impl IntoView { Ok(m) => { let mut messages = IndexMap::new(); for (message, message_user) in m { - messages.insert(message.id, ArcMacawMessage::got_message_and_user(message, message_user).await); + messages.insert( + message.id, + ArcMacawMessage::got_message_and_user(message, message_user).await, + ); } load_set_messages.set(messages); } @@ -54,30 +66,16 @@ pub fn MessageHistoryBuffer(chat: MacawChat) -> impl IntoView { debug!("got new message in let message buffer"); let mut messages = load_new_messages_set.write(); if let Some((_, last)) = messages.last() { - if *last.get() - .timestamp() - .read() - < *new_message.get() - .timestamp() - .read() - { - messages.insert( - new_message.get() - .id() - .get(), - new_message, - ); + if *last.get().timestamp().read() < *new_message.get().timestamp().read() { + messages.insert(new_message.get().id().get(), new_message); debug!("set the new message in message buffer"); } else { let index = match messages.binary_search_by(|_, value| { - value.get() + value + .get() .timestamp() .read() - .cmp( - &new_message.get() - .timestamp() - .read(), - ) + .cmp(&new_message.get().timestamp().read()) }) { Ok(i) => i, Err(i) => i, @@ -85,21 +83,13 @@ pub fn MessageHistoryBuffer(chat: MacawChat) -> impl IntoView { messages.insert_before( // TODO: check if this logic is correct index, - - new_message.get() - .id() - .get(), + new_message.get().id().get(), new_message, ); debug!("set the new message in message buffer"); } } else { - messages.insert( - new_message.get() - .id() - .get(), - new_message, - ); + messages.insert(new_message.get().id().get(), new_message); debug!("set the new message in message buffer"); } } @@ -120,11 +110,7 @@ pub fn MessageHistoryBuffer(chat: MacawChat) -> impl IntoView { .get() .into_iter() .map(|(id, message)| { - let message_timestamp = - message.message.get() - .timestamp() - .read() - .naive_local(); + let message_timestamp = message.message.get().timestamp().read().naive_local(); // TODO: mark new day // if message_timestamp.date() > last_timestamp.date() { // messages_view = messages_view.push(date(message_timestamp.date())); @@ -136,11 +122,7 @@ pub fn MessageHistoryBuffer(chat: MacawChat) -> impl IntoView { } else { false }; - last_user = Some( - message.get() - .from() - .get(), - ); + last_user = Some(message.get().from().get()); last_timestamp = message_timestamp; (id, (message, major, false)) }) @@ -159,4 +141,3 @@ pub fn MessageHistoryBuffer(chat: MacawChat) -> impl IntoView { </div> } } - diff --git a/src/components/mod.rs b/src/components/mod.rs index 879f99e..0e2ffce 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,13 +1,13 @@ -pub mod sidebar; -mod chats_list; -mod new_chat; -mod roster_list; mod avatar; +pub mod chat_header; +mod chats_list; +pub mod icon; mod message; -pub mod message_history_buffer; pub mod message_composer; -pub mod chat_header; -mod overlay; +pub mod message_history_buffer; pub mod modal; -pub mod icon; +mod new_chat; +mod overlay; mod personal_status; +mod roster_list; +pub mod sidebar; diff --git a/src/components/modal.rs b/src/components/modal.rs index 62e1fac..f0fd68a 100644 --- a/src/components/modal.rs +++ b/src/components/modal.rs @@ -1,16 +1,21 @@ -use leptos::prelude::*; use leptos::ev::MouseEvent; +use leptos::prelude::*; #[component] -pub fn Modal(on_background_click: impl Fn(MouseEvent) + 'static, children: Children) -> impl IntoView { +pub fn Modal( + on_background_click: impl Fn(MouseEvent) + 'static, + children: Children, +) -> impl IntoView { view! { - <div class="modal" on:click=move |e| { - if e.current_target() == e.target() { - on_background_click(e) + <div + class="modal" + on:click=move |e| { + if e.current_target() == e.target() { + on_background_click(e) + } } - }> + > {children()} </div> } } - diff --git a/src/components/new_chat.rs b/src/components/new_chat.rs index 2f9b943..3e7a261 100644 --- a/src/components/new_chat.rs +++ b/src/components/new_chat.rs @@ -1,12 +1,22 @@ use std::str::FromStr; -use filamento::{chat::Chat, error::{CommandError, DatabaseError}, user::User}; +use filamento::{ + chat::Chat, + error::{CommandError, DatabaseError}, + user::User, +}; use jid::{BareJID, JID}; use leptos::{html::Input, prelude::*}; use reactive_stores::{ArcStore, Store}; use thiserror::Error; -use crate::{chat::{ArcMacawChat, MacawChat}, client::Client, open_chats::OpenChatsPanel, state_store::StateStore, user::{fetch_avatar, ArcMacawUser, MacawUser}}; +use crate::{ + chat::{ArcMacawChat, MacawChat}, + client::Client, + open_chats::OpenChatsPanel, + state_store::StateStore, + user::{ArcMacawUser, MacawUser, fetch_avatar}, +}; #[derive(Clone, Debug, Error)] pub enum NewChatError { @@ -34,7 +44,7 @@ pub fn NewChatWidget(set_open_new_chat: WriteSignal<bool>) -> impl IntoView { }) }; let (new_chat_pending, set_new_chat_pending) = signal(false); - + let open_chats: Store<OpenChatsPanel> = use_context().expect("no open chats panel store in context"); let client = use_context::<Client>().expect("client not in context"); @@ -72,7 +82,7 @@ pub fn NewChatWidget(set_open_new_chat: WriteSignal<bool>) -> impl IntoView { set_error.set(Some(e.into())); set_new_chat_pending.set(false); return; - }, + } }; let chat = { @@ -80,7 +90,9 @@ pub fn NewChatWidget(set_open_new_chat: WriteSignal<bool>) -> impl IntoView { // let user = user_state_store.store(user.jid.clone(), ArcStore::new(user)); let old_user = user_state_store.get_listener(user.jid.clone()); let user = if let Some(old_user) = old_user { - old_user.update(|(old_user, _avatar)| { old_user.set(user); }); + old_user.update(|(old_user, _avatar)| { + old_user.set(user); + }); old_user } else { let avatar = fetch_avatar(user.avatar.as_deref()).await; @@ -129,4 +141,3 @@ pub fn NewChatWidget(set_open_new_chat: WriteSignal<bool>) -> impl IntoView { </div> } } - diff --git a/src/components/overlay.rs b/src/components/overlay.rs index d4ff1bf..396e885 100644 --- a/src/components/overlay.rs +++ b/src/components/overlay.rs @@ -5,12 +5,14 @@ use tracing::debug; pub fn Overlay(set_open: WriteSignal<bool>, children: Children) -> impl IntoView { view! { <div class="overlay"> - <div class="overlay-background" on:click=move |_| { - debug!("set open to false"); - set_open.update(|state| *state = false) - }></div> + <div + class="overlay-background" + on:click=move |_| { + debug!("set open to false"); + set_open.update(|state| *state = false) + } + ></div> <div class="overlay-content">{children()}</div> </div> } } - diff --git a/src/components/personal_status.rs b/src/components/personal_status.rs index eb7f6e1..59ffa47 100644 --- a/src/components/personal_status.rs +++ b/src/components/personal_status.rs @@ -1,39 +1,57 @@ -use filamento::{presence::{Offline, Online, PresenceType, Show}, user::{User, UserStoreFields}}; +use filamento::{ + presence::{Offline, Online, PresenceType, Show}, + user::{User, UserStoreFields}, +}; use leptos::{html, prelude::*}; use reactive_stores::{ArcStore, Store}; use tracing::{debug, error}; -use crate::{client::Client, components::{avatar::AvatarWithPresence, overlay::Overlay}, user::{get_name, MacawUser}, user_presences::UserPresences, views::{macaw::settings::SettingsPage, AppState}}; +use crate::{ + client::Client, + components::{avatar::AvatarWithPresence, overlay::Overlay}, + user::{MacawUser, get_name}, + user_presences::UserPresences, + views::{AppState, macaw::settings::SettingsPage}, +}; #[component] pub fn PersonalStatus() -> impl IntoView { let user: LocalResource<MacawUser> = use_context().expect("no local user in context"); let (open, set_open) = signal(false); - move || if let Some(user) = user.get() { - view! { - <div class="dock-item" class:focused=move || *open.read() on:click=move |_| { - debug!("set open to true"); - set_open.update(|state| *state = !*state) - }> - <AvatarWithPresence user /> - <div class="dock-pill"></div> - </div> - {move || { - let open = open.get(); - debug!("open = {:?}", open); - if open { - view! { - <Overlay set_open> - <PersonalStatusMenu user set_open/> - </Overlay> - }.into_any() - } else { - view! {}.into_any() - }}} - }.into_any() - } else { - view! {}.into_any() + move || { + if let Some(user) = user.get() { + view! { + <div + class="dock-item" + class:focused=move || *open.read() + on:click=move |_| { + debug!("set open to true"); + set_open.update(|state| *state = !*state) + } + > + <AvatarWithPresence user /> + <div class="dock-pill"></div> + </div> + {move || { + let open = open.get(); + debug!("open = {:?}", open); + if open { + view! { + <Overlay set_open> + <PersonalStatusMenu user set_open /> + </Overlay> + } + .into_any() + } else { + view! {}.into_any() + } + }} + } + .into_any() + } else { + view! {}.into_any() + } } } @@ -42,24 +60,30 @@ pub fn PersonalStatusMenu(user: MacawUser, set_open: WriteSignal<bool>) -> impl let set_app: WriteSignal<AppState> = use_context().unwrap(); let show_settings: RwSignal<Option<SettingsPage>> = use_context().unwrap(); let user_presences: Store<UserPresences> = use_context().expect("no user presence store"); - + let client = use_context::<Client>().expect("client not in context"); let client1 = client.clone(); let (show_value, set_show_value) = signal({ - let show = match user_presences.write().get_user_presences(&user.get().jid().read()).write().resource_presence(client.resource.read().clone().unwrap_or_default()).presence { - PresenceType::Online(online) => match online.show { - Some(s) => match s { - Show::Away => 3, - Show::Chat => 0, - Show::DoNotDisturb => 2, - Show::ExtendedAway => 4, + let show = match user_presences + .write() + .get_user_presences(&user.get().jid().read()) + .write() + .resource_presence(client.resource.read().clone().unwrap_or_default()) + .presence + { + PresenceType::Online(online) => match online.show { + Some(s) => match s { + Show::Away => 3, + Show::Chat => 0, + Show::DoNotDisturb => 2, + Show::ExtendedAway => 4, + }, + None => 1, }, - None => 1, - }, - PresenceType::Offline(_offline) => 5, - }; - debug!("initial show = {show}"); - show + PresenceType::Offline(_offline) => 5, + }; + debug!("initial show = {show}"); + show }); let show_select: NodeRef<html::Select> = NodeRef::new(); @@ -71,7 +95,7 @@ pub fn PersonalStatusMenu(user: MacawUser, set_open: WriteSignal<bool>) -> impl } }); let set_status = Action::new_local(move |show_value: &i32| { - let show_value = show_value.to_owned(); + let show_value = show_value.to_owned(); let client = client1.clone(); async move { if let Err(e) = match show_value { @@ -79,46 +103,71 @@ pub fn PersonalStatusMenu(user: MacawUser, set_open: WriteSignal<bool>) -> impl if let Ok(r) = client.connect().await { client.resource.set(Some(r)) }; - client.set_status(Online { show: Some(Show::Chat), ..Default::default() }).await - }, + client + .set_status(Online { + show: Some(Show::Chat), + ..Default::default() + }) + .await + } 1 => { if let Ok(r) = client.connect().await { client.resource.set(Some(r)) }; - client.set_status(Online { show: None, ..Default::default() }).await - }, + client + .set_status(Online { + show: None, + ..Default::default() + }) + .await + } 2 => { if let Ok(r) = client.connect().await { client.resource.set(Some(r)) }; - client.set_status(Online { show: Some(Show::DoNotDisturb), ..Default::default() }).await - }, + client + .set_status(Online { + show: Some(Show::DoNotDisturb), + ..Default::default() + }) + .await + } 3 => { if let Ok(r) = client.connect().await { client.resource.set(Some(r)) }; - client.set_status(Online { show: Some(Show::Away), ..Default::default() }).await - }, + client + .set_status(Online { + show: Some(Show::Away), + ..Default::default() + }) + .await + } 4 => { if let Ok(r) = client.connect().await { client.resource.set(Some(r)) }; - client.set_status(Online { show: Some(Show::ExtendedAway), ..Default::default() }).await - }, + client + .set_status(Online { + show: Some(Show::ExtendedAway), + ..Default::default() + }) + .await + } 5 => { if let Ok(_) = client.disconnect(Offline::default()).await { client.resource.set(None) } set_show_value.set(5); - return + return; } _ => { error!("invalid availability select"); - return + return; } } { error!("show set error: {e}"); - return + return; } set_show_value.set(show_value); } @@ -142,36 +191,55 @@ pub fn PersonalStatusMenu(user: MacawUser, set_open: WriteSignal<bool>) -> impl } prop:show_value=move || show_value.get().to_string() > - <option value="0" selected=move || show_value.get_untracked() == 0>Available to Chat</option> - <option value="1" selected=move || show_value.get_untracked() == 1>Online</option> - <option value="2" selected=move || show_value.get_untracked() == 2>Do not disturb</option> - <option value="3" selected=move || show_value.get_untracked() == 3>Away</option> - <option value="4" selected=move || show_value.get_untracked() == 4>Extended Away</option> - <option value="5" selected=move || show_value.get_untracked() == 5>Offline</option> + <option value="0" selected=move || show_value.get_untracked() == 0> + Available to Chat + </option> + <option value="1" selected=move || show_value.get_untracked() == 1> + Online + </option> + <option value="2" selected=move || show_value.get_untracked() == 2> + Do not disturb + </option> + <option value="3" selected=move || show_value.get_untracked() == 3> + Away + </option> + <option value="4" selected=move || show_value.get_untracked() == 4> + Extended Away + </option> + <option value="5" selected=move || show_value.get_untracked() == 5> + Offline + </option> </select> </div> <hr /> - <div class="menu-item" on:click=move |_| { - show_settings.set(Some(SettingsPage::Profile)); - set_open.set(false); - }> + <div + class="menu-item" + on:click=move |_| { + show_settings.set(Some(SettingsPage::Profile)); + set_open.set(false); + } + > Profile </div> - <div class="menu-item" on:click=move |_| { - show_settings.set(Some(SettingsPage::Account)); - set_open.set(false); - }> + <div + class="menu-item" + on:click=move |_| { + show_settings.set(Some(SettingsPage::Account)); + set_open.set(false); + } + > Settings </div> <hr /> - <div class="menu-item" on:click=move |_| { - // TODO: check if client is actually dropped/shutdown eventually - disconnect.dispatch(()); - set_app.set(AppState::LoggedOut) - }> + <div + class="menu-item" + on:click=move |_| { + disconnect.dispatch(()); + set_app.set(AppState::LoggedOut) + } + > Log out </div> </div> } } - diff --git a/src/components/roster_list.rs b/src/components/roster_list.rs index 310b703..50c7989 100644 --- a/src/components/roster_list.rs +++ b/src/components/roster_list.rs @@ -6,14 +6,20 @@ use leptos::prelude::*; use reactive_stores::Store; use roster_list_item::RosterListItem; -use crate::{components::icon::IconComponent, icon::Icon, open_chats::{OpenChatsPanel, OpenChatsPanelStoreFields}, roster::{Roster, RosterStoreFields}}; +use crate::{ + components::icon::IconComponent, + icon::Icon, + open_chats::{OpenChatsPanel, OpenChatsPanelStoreFields}, + roster::{Roster, RosterStoreFields}, +}; mod contact_request_manager; mod roster_list_item; #[component] pub fn RosterList() -> impl IntoView { - let requests: ReadSignal<HashSet<BareJID>> = use_context().expect("no pending subscriptions in context"); + let requests: ReadSignal<HashSet<BareJID>> = + use_context().expect("no pending subscriptions in context"); let open_chats: Store<OpenChatsPanel> = use_context().expect("no open chats panel store in context"); @@ -29,12 +35,13 @@ pub fn RosterList() -> impl IntoView { <div class="header"> <h2>Roster</h2> <div class="add-contact header-icon" class:open=open_add_contact> - <IconComponent icon=Icon::AddContact24 on:click=move |_| set_open_add_contact.update(|state| *state = !*state)/> + <IconComponent + icon=Icon::AddContact24 + on:click=move |_| set_open_add_contact.update(|state| *state = !*state) + /> {move || { if !requests.read().is_empty() { - view! { - <div class="badge"></div> - }.into_any() + view! { <div class="badge"></div> }.into_any() } else { view! {}.into_any() } @@ -47,17 +54,21 @@ pub fn RosterList() -> impl IntoView { <div class="roster-add-contact"> <AddContact /> </div> - }.into_any() + } + .into_any() } else { view! {}.into_any() } }} <div class="roster-list-roster"> - <For each=move || roster.contacts().get() key=|contact| contact.0.clone() let(contact)> + <For + each=move || roster.contacts().get() + key=|contact| contact.0.clone() + let(contact) + > <RosterListItem contact=contact.1 /> </For> </div> </div> } } - diff --git a/src/components/roster_list/contact_request_manager.rs b/src/components/roster_list/contact_request_manager.rs index 174e677..cfb5f28 100644 --- a/src/components/roster_list/contact_request_manager.rs +++ b/src/components/roster_list/contact_request_manager.rs @@ -1,12 +1,18 @@ use std::{collections::HashSet, str::FromStr}; -use filamento::{error::{CommandError, SubscribeError}, roster::ContactStoreFields}; +use filamento::{ + error::{CommandError, SubscribeError}, + roster::ContactStoreFields, +}; use jid::{BareJID, JID}; use leptos::{html::Input, prelude::*}; use reactive_stores::Store; use thiserror::Error; -use crate::{client::Client, roster::{Roster, RosterStoreFields}}; +use crate::{ + client::Client, + roster::{Roster, RosterStoreFields}, +}; #[derive(Clone, Debug, Error)] pub enum AddContactError { @@ -21,9 +27,11 @@ pub enum AddContactError { #[component] // TODO: rename pub fn AddContact() -> impl IntoView { - let requests: ReadSignal<HashSet<BareJID>> = use_context().expect("no pending subscriptions in context"); - let set_requests: WriteSignal<HashSet<BareJID>> = use_context().expect("no pending subscriptions write signal in context"); - let roster: Store<Roster> = use_context().expect("no roster in context"); + let requests: ReadSignal<HashSet<BareJID>> = + use_context().expect("no pending subscriptions in context"); + let set_requests: WriteSignal<HashSet<BareJID>> = + use_context().expect("no pending subscriptions write signal in context"); + let roster: Store<Roster> = use_context().expect("no roster in context"); let jid = RwSignal::new("".to_string()); // TODO: compartmentalise into error component, form component... @@ -44,7 +52,7 @@ pub fn AddContact() -> impl IntoView { let client3 = client.clone(); let client4 = client.clone(); - let add_contact= Action::new_local(move |_| { + let add_contact = Action::new_local(move |_| { let client = client.clone(); async move { set_add_contact_pending.set(true); @@ -71,8 +79,8 @@ pub fn AddContact() -> impl IntoView { Err(e) => { set_error.set(Some(e.into())); set_add_contact_pending.set(false); - return; - }, + return; + } }; set_add_contact_pending.set(false); @@ -90,19 +98,26 @@ pub fn AddContact() -> impl IntoView { } }); - let outgoing = move || roster.contacts().get().into_iter().filter(|(jid, contact)| { - match *contact.contact.subscription().read() { - filamento::roster::Subscription::None => false, - filamento::roster::Subscription::PendingOut => true, - filamento::roster::Subscription::PendingIn => false, - filamento::roster::Subscription::PendingInPendingOut => true, - filamento::roster::Subscription::OnlyOut => false, - filamento::roster::Subscription::OnlyIn => false, - filamento::roster::Subscription::OutPendingIn => false, - filamento::roster::Subscription::InPendingOut => true, - filamento::roster::Subscription::Buddy => false, - } - }).collect::<Vec<_>>(); + let outgoing = move || { + roster + .contacts() + .get() + .into_iter() + .filter( + |(jid, contact)| match *contact.contact.subscription().read() { + filamento::roster::Subscription::None => false, + filamento::roster::Subscription::PendingOut => true, + filamento::roster::Subscription::PendingIn => false, + filamento::roster::Subscription::PendingInPendingOut => true, + filamento::roster::Subscription::OnlyOut => false, + filamento::roster::Subscription::OnlyIn => false, + filamento::roster::Subscription::OutPendingIn => false, + filamento::roster::Subscription::InPendingOut => true, + filamento::roster::Subscription::Buddy => false, + }, + ) + .collect::<Vec<_>>() + }; let accept_friend_request = Action::new_local(move |jid: &BareJID| { let client = client2.clone(); @@ -129,70 +144,116 @@ pub fn AddContact() -> impl IntoView { async move { // TODO: error client.unsubscribe_from_contact(jid).await; - } }); view! { <div class="add-contact-menu"> - <div> - {error_message} - <form on:submit=move |ev| { - ev.prevent_default(); - add_contact.dispatch(()); - }> - <input - disabled=add_contact_pending - placeholder="JID" - type="text" - node_ref=jid_input - bind:value=jid - name="jid" - id="jid" - autofocus="true" - /> - <input disabled=add_contact_pending class="button" type="submit" value="Send Friend Request" /> - </form> - </div> - {move || if !requests.read().is_empty() { - view! { - <div> - <h3>Incoming Subscription Requests</h3> - <For each=move || requests.get() key=|request| request.clone() let(request)> - { - let request2 = request.clone(); - let request3 = request.clone(); - let jid_string = move || request.to_string(); - view! { - <div class="jid-with-button"><div class="jid">{jid_string}</div> - <div><div class="button" on:click=move |_| { accept_friend_request.dispatch(request2.clone()); } >Accept</div><div class="button" on:click=move |_| { reject_friend_request.dispatch(request3.clone()); } >Reject</div></div></div> - } - } - </For> - </div> - }.into_any() - } else { - view! {}.into_any() - }} - {move || if !outgoing().is_empty() { - view! { - <div> - <h3>Pending Outgoing Subscription Requests</h3> - <For each=move || outgoing() key=|(jid, _contact)| jid.clone() let((jid, contact))> - { - let jid2 = jid.clone(); - let jid_string = move || jid.to_string(); - view! { - <div class="jid-with-button"><div class="jid">{jid_string}</div><div class="button" on:click=move |_| { cancel_subscription_request.dispatch(jid2.clone()); } >Cancel</div></div> - } - } - </For> - </div> - }.into_any() - } else { - view! {}.into_any() - }} + <div> + {error_message} + <form on:submit=move |ev| { + ev.prevent_default(); + add_contact.dispatch(()); + }> + <input + disabled=add_contact_pending + placeholder="JID" + type="text" + node_ref=jid_input + bind:value=jid + name="jid" + id="jid" + autofocus="true" + /> + <input + disabled=add_contact_pending + class="button" + type="submit" + value="Send Friend Request" + /> + </form> + </div> + {move || { + if !requests.read().is_empty() { + view! { + <div> + <h3>Incoming Subscription Requests</h3> + <For + each=move || requests.get() + key=|request| request.clone() + let(request) + > + { + let request2 = request.clone(); + let request3 = request.clone(); + let jid_string = move || request.to_string(); + view! { + <div class="jid-with-button"> + <div class="jid">{jid_string}</div> + <div> + <div + class="button" + on:click=move |_| { + accept_friend_request.dispatch(request2.clone()); + } + > + Accept + </div> + <div + class="button" + on:click=move |_| { + reject_friend_request.dispatch(request3.clone()); + } + > + Reject + </div> + </div> + </div> + } + } + </For> + </div> + } + .into_any() + } else { + view! {}.into_any() + } + }} + {move || { + if !outgoing().is_empty() { + view! { + <div> + <h3>Pending Outgoing Subscription Requests</h3> + <For + each=move || outgoing() + key=|(jid, _contact)| jid.clone() + let((jid, contact)) + > + { + let jid2 = jid.clone(); + let jid_string = move || jid.to_string(); + view! { + <div class="jid-with-button"> + <div class="jid">{jid_string}</div> + <div + class="button" + on:click=move |_| { + cancel_subscription_request.dispatch(jid2.clone()); + } + > + Cancel + </div> + </div> + } + } + </For> + </div> + } + .into_any() + } else { + view! {}.into_any() + } + }} </div> } } - diff --git a/src/components/roster_list/roster_list_item.rs b/src/components/roster_list/roster_list_item.rs index 538e664..c90455c 100644 --- a/src/components/roster_list/roster_list_item.rs +++ b/src/components/roster_list/roster_list_item.rs @@ -1,12 +1,24 @@ use std::ops::Deref; -use filamento::{chat::Chat, roster::{Contact, ContactStoreFields}, user::{User, UserStoreFields}}; +use filamento::{ + chat::Chat, + roster::{Contact, ContactStoreFields}, + user::{User, UserStoreFields}, +}; use jid::BareJID; use leptos::prelude::*; use reactive_stores::{ArcStore, Store}; use tracing::debug; -use crate::{chat::{ArcMacawChat, MacawChat}, client::Client, components::{avatar::AvatarWithPresence, sidebar::Open}, contact::MacawContact, open_chats::{OpenChatsPanel, OpenChatsPanelStoreFields}, state_store::StateStore, user::{fetch_avatar, get_name, ArcMacawUser}}; +use crate::{ + chat::{ArcMacawChat, MacawChat}, + client::Client, + components::{avatar::AvatarWithPresence, sidebar::Open}, + contact::MacawContact, + open_chats::{OpenChatsPanel, OpenChatsPanelStoreFields}, + state_store::StateStore, + user::{ArcMacawUser, fetch_avatar, get_name}, +}; #[component] pub fn RosterListItem(contact: MacawContact) -> impl IntoView { @@ -23,7 +35,7 @@ pub fn RosterListItem(contact: MacawContact) -> impl IntoView { use_context().expect("no user state store"); let open_chat = Action::new_local(move |_| { - let client= client.clone(); + let client = client.clone(); async move { let to = contact.user.get().jid().get(); let (chat, user) = match client.get_chat_and_user(to).await { @@ -33,7 +45,7 @@ pub fn RosterListItem(contact: MacawContact) -> impl IntoView { // set_error.set(Some(e.into())); // set_new_chat_pending.set(false); return; - }, + } }; let chat = { @@ -41,7 +53,9 @@ pub fn RosterListItem(contact: MacawContact) -> impl IntoView { // let user = user_state_store.store(user.jid.clone(), ArcStore::new(user)); let old_user = user_state_store.get_listener(user.jid.clone()); let user = if let Some(old_user) = old_user { - old_user.update(|(old_user, _avatar)| { old_user.set(user); }); + old_user.update(|(old_user, _avatar)| { + old_user.set(user); + }); old_user } else { let avatar = fetch_avatar(user.avatar.as_deref()).await; @@ -56,7 +70,8 @@ pub fn RosterListItem(contact: MacawContact) -> impl IntoView { } }); - let current_open_chat: Memo<Option<BareJID>> = use_context().expect("no open chat memo in context"); + let current_open_chat: Memo<Option<BareJID>> = + use_context().expect("no open chat memo in context"); let open = move || { if let Some(open_chat) = &*current_open_chat.read() { @@ -78,17 +93,26 @@ pub fn RosterListItem(contact: MacawContact) -> impl IntoView { let open = move || open().is_open(); view! { - <div class="roster-list-item" class:open=move || open() class:focused=move || focused() on:click=move |_| { open_chat.dispatch(()); }> + <div + class="roster-list-item" + class:open=move || open() + class:focused=move || focused() + on:click=move |_| { + open_chat.dispatch(()); + } + > {move || { - view! { - <AvatarWithPresence user=contact.user /> - } + view! { <AvatarWithPresence user=contact.user /> } }} <div class="item-info"> - <div class="main-info"><p class="name">{name}<span class="jid"> - {move || contact.user_jid().read().to_string()}</span></p></div> + <div class="main-info"> + <p class="name"> + {name} + <span class="jid">- {move || contact.user_jid().read().to_string()}</span> + </p> + </div> <div class="sub-info">{move || contact.subscription().read().to_string()}</div> </div> </div> } } - diff --git a/src/components/sidebar.rs b/src/components/sidebar.rs index 487fb76..0be31fd 100644 --- a/src/components/sidebar.rs +++ b/src/components/sidebar.rs @@ -5,9 +5,7 @@ use leptos::prelude::*; use reactive_stores::Store; use crate::components::{ - personal_status::PersonalStatus, - chats_list::ChatsList, - roster_list::RosterList, + chats_list::ChatsList, personal_status::PersonalStatus, roster_list::RosterList, }; #[derive(PartialEq, Eq, Clone, Copy, Hash)] @@ -62,20 +60,21 @@ pub fn toggle_open(state: &mut Option<SidebarOpen>, open: SidebarOpen) -> bool { } } None => { - *state = Some(open); - true - }, + *state = Some(open); + true + } } } #[component] pub fn Sidebar() -> impl IntoView { - let requests: ReadSignal<HashSet<BareJID>> = use_context().expect("no pending subscriptions in context"); + let requests: ReadSignal<HashSet<BareJID>> = + use_context().expect("no pending subscriptions in context"); // for what has been clicked open (in the background) let (open, set_open) = signal(None::<SidebarOpen>); // for what is just in the hovered state (not clicked to be pinned open yet necessarily) - let open= Memo::new(move |_| open.get()); + let open = Memo::new(move |_| open.get()); let (hovered, set_hovered) = signal(None::<SidebarOpen>); let hovered = Memo::new(move |_| hovered.get()); let (just_closed, set_just_closed) = signal(false); @@ -93,59 +92,69 @@ pub fn Sidebar() -> impl IntoView { }); view! { - <div class="sidebar" on:mouseleave=move |_| { - set_hovered.set(None); - set_just_closed.set(false); - }> + <div + class="sidebar" + on:mouseleave=move |_| { + set_hovered.set(None); + set_just_closed.set(false); + } + > <div class="dock panel"> <div class="shortcuts"> - <div class="roster-tab dock-item" class:focused=move || *open.read() == Some(SidebarOpen::Roster) class:hovering=move || *hovered.read() == Some(SidebarOpen::Roster) - on:mouseenter=move |_| { - set_just_closed.set(false); - set_hovered.set(Some(SidebarOpen::Roster)) - } - on:click=move |_| { - set_open.update(|state| { - if !toggle_open(state, SidebarOpen::Roster) { - set_just_closed.set(true); - } - }) - }> + <div + class="roster-tab dock-item" + class:focused=move || *open.read() == Some(SidebarOpen::Roster) + class:hovering=move || *hovered.read() == Some(SidebarOpen::Roster) + on:mouseenter=move |_| { + set_just_closed.set(false); + set_hovered.set(Some(SidebarOpen::Roster)) + } + on:click=move |_| { + set_open + .update(|state| { + if !toggle_open(state, SidebarOpen::Roster) { + set_just_closed.set(true); + } + }) + } + > <div class="dock-pill"></div> <div class="dock-icon"> <div class="icon-with-badge"> - <img src="/assets/caw.png" /> - {move || { - let len = requests.read().len(); - if len > 0 { - view! { - <div class="badge">{len}</div> - }.into_any() - } else { - view! {}.into_any() - } - }} + <img src="/assets/caw.png" /> + {move || { + let len = requests.read().len(); + if len > 0 { + view! { <div class="badge">{len}</div> }.into_any() + } else { + view! {}.into_any() + } + }} </div> </div> </div> - <div class="chats-tab dock-item" class:focused=move || *open.read() == Some(SidebarOpen::Chats) class:hovering=move || *hovered.read() == Some(SidebarOpen::Chats) - on:mouseenter=move |_| { - set_just_closed.set(false); - set_hovered.set(Some(SidebarOpen::Chats)) - } - on:click=move |_| { - set_open.update(|state| { - if !toggle_open(state, SidebarOpen::Chats) { - set_just_closed.set(true); - } - }) - }> + <div + class="chats-tab dock-item" + class:focused=move || *open.read() == Some(SidebarOpen::Chats) + class:hovering=move || *hovered.read() == Some(SidebarOpen::Chats) + on:mouseenter=move |_| { + set_just_closed.set(false); + set_hovered.set(Some(SidebarOpen::Chats)) + } + on:click=move |_| { + set_open + .update(|state| { + if !toggle_open(state, SidebarOpen::Chats) { + set_just_closed.set(true); + } + }) + } + > <div class="dock-pill"></div> <img src="/assets/bubble.png" /> </div> </div> - <div class="pins"> - </div> + <div class="pins"></div> <div class="personal"> <PersonalStatus /> </div> @@ -154,24 +163,39 @@ pub fn Sidebar() -> impl IntoView { if !just_closed.get() { view! { <For each=move || pages.get() key=|page| *page let(page)> - {move || match page { - SidebarOpen::Roster => view! { - <div class:sidebar-drawer=true class:sidebar-hovering-drawer=move || { - !(*open.read() == Some(SidebarOpen::Roster)) && (*hovered.read() == Some(SidebarOpen::Roster)) - }> - <RosterList /> - </div> - }.into_any(), - SidebarOpen::Chats => view! { - <div class:sidebar-drawer=true class:sidebar-hovering-drawer=move || { - !(*open.read() == Some(SidebarOpen::Chats)) && (*hovered.read() == Some(SidebarOpen::Chats)) - }> - <ChatsList /> - </div> - }.into_any(), - }} + {move || match page { + SidebarOpen::Roster => { + view! { + <div + class:sidebar-drawer=true + class:sidebar-hovering-drawer=move || { + !(*open.read() == Some(SidebarOpen::Roster)) + && (*hovered.read() == Some(SidebarOpen::Roster)) + } + > + <RosterList /> + </div> + } + .into_any() + } + SidebarOpen::Chats => { + view! { + <div + class:sidebar-drawer=true + class:sidebar-hovering-drawer=move || { + !(*open.read() == Some(SidebarOpen::Chats)) + && (*hovered.read() == Some(SidebarOpen::Chats)) + } + > + <ChatsList /> + </div> + } + .into_any() + } + }} </For> - }.into_any() + } + .into_any() } else { view! {}.into_any() } @@ -179,4 +203,3 @@ pub fn Sidebar() -> impl IntoView { </div> } } - diff --git a/src/contact.rs b/src/contact.rs index e0e0d78..017b00c 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -61,4 +61,3 @@ impl DerefMut for ArcMacawContact { &mut self.contact } } - diff --git a/src/files.rs b/src/files.rs index b9e50df..21fdcde 100644 --- a/src/files.rs +++ b/src/files.rs @@ -1,5 +1,5 @@ -use base64::{prelude::BASE64_STANDARD, Engine}; -use filamento::files::{opfs::OPFSError, FileStore, FilesMem, FilesOPFS}; +use base64::{Engine, prelude::BASE64_STANDARD}; +use filamento::files::{FileStore, FilesMem, FilesOPFS, opfs::OPFSError}; #[derive(Clone, Debug)] pub enum Files { @@ -47,4 +47,3 @@ impl Files { } } } - diff --git a/src/icon.rs b/src/icon.rs index b0ef60e..be66cec 100644 --- a/src/icon.rs +++ b/src/icon.rs @@ -108,4 +108,3 @@ impl Icon { } } } - @@ -1,17 +1,16 @@ pub use views::App; -mod state_store; -mod icon; -mod user; mod chat; -mod open_chats; -mod components; -mod views; -mod files; mod client; -mod roster; +mod components; mod contact; +mod files; +mod icon; mod message; mod message_subscriptions; +mod open_chats; +mod roster; +mod state_store; +mod user; mod user_presences; - +mod views; diff --git a/src/message.rs b/src/message.rs index 23ca6be..e2e4034 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,11 +1,14 @@ use std::ops::{Deref, DerefMut}; use filamento::{chat::Message, user::User}; +use leptos::prelude::*; use reactive_stores::ArcStore; use uuid::Uuid; -use leptos::prelude::*; -use crate::{state_store::{StateListener, StateStore}, user::{ArcMacawUser, MacawUser}}; +use crate::{ + state_store::{StateListener, StateStore}, + user::{ArcMacawUser, MacawUser}, +}; #[derive(Clone, Copy)] pub struct MacawMessage { @@ -71,4 +74,3 @@ impl DerefMut for ArcMacawMessage { &mut self.message } } - diff --git a/src/message_subscriptions.rs b/src/message_subscriptions.rs index 17d924f..aac1725 100644 --- a/src/message_subscriptions.rs +++ b/src/message_subscriptions.rs @@ -83,4 +83,3 @@ impl MessageSubscriptions { } } } - diff --git a/src/open_chats.rs b/src/open_chats.rs index 6d08c5d..8b2f2cb 100644 --- a/src/open_chats.rs +++ b/src/open_chats.rs @@ -1,9 +1,9 @@ use filamento::chat::ChatStoreFields; use indexmap::IndexMap; use jid::BareJID; +use leptos::prelude::*; use reactive_stores::{ArcStore, Store}; use tracing::debug; -use leptos::prelude::*; use crate::chat::{ArcMacawChat, MacawChat}; @@ -18,28 +18,19 @@ pub struct OpenChatsPanel { pub fn open_chat(open_chats: Store<OpenChatsPanel>, chat: ArcMacawChat) { if let Some(jid) = &*open_chats.chat_view().read() { if let Some((index, _jid, entry)) = open_chats.chats().write().shift_remove_full(jid) { - let new_jid = chat.get() - .correspondent() - .read() - .clone(); + let new_jid = chat.get().correspondent().read().clone(); open_chats .chats() .write() .insert_before(index, new_jid.clone(), chat); *open_chats.chat_view().write() = Some(new_jid); } else { - let new_jid = chat.get() - .correspondent() - .read() - .clone(); + let new_jid = chat.get().correspondent().read().clone(); open_chats.chats().write().insert(new_jid.clone(), chat); *open_chats.chat_view().write() = Some(new_jid); } } else { - let new_jid = chat.get() - .correspondent() - .read() - .clone(); + let new_jid = chat.get().correspondent().read().clone(); open_chats.chats().write().insert(new_jid.clone(), chat); *open_chats.chat_view().write() = Some(new_jid); } @@ -50,25 +41,16 @@ impl OpenChatsPanel { if let Some(jid) = &mut self.chat_view { debug!("a chat was already open"); if let Some((index, _jid, entry)) = self.chats.shift_remove_full(jid) { - let new_jid = chat.get() - .correspondent() - .read() - .clone(); + let new_jid = chat.get().correspondent().read().clone(); self.chats.insert_before(index, new_jid.clone(), chat); *&mut self.chat_view = Some(new_jid); } else { - let new_jid = chat.get() - .correspondent() - .read() - .clone(); + let new_jid = chat.get().correspondent().read().clone(); self.chats.insert(new_jid.clone(), chat); *&mut self.chat_view = Some(new_jid); } } else { - let new_jid = chat.get() - .correspondent() - .read() - .clone(); + let new_jid = chat.get().correspondent().read().clone(); self.chats.insert(new_jid.clone(), chat); *&mut self.chat_view = Some(new_jid); } @@ -84,5 +66,3 @@ impl OpenChatsPanel { // } } - - diff --git a/src/roster.rs b/src/roster.rs index 75f4c3b..d49a152 100644 --- a/src/roster.rs +++ b/src/roster.rs @@ -18,4 +18,3 @@ impl Roster { } } } - diff --git a/src/state_store.rs b/src/state_store.rs index d45001f..e0ba979 100644 --- a/src/state_store.rs +++ b/src/state_store.rs @@ -1,4 +1,8 @@ -use std::{collections::HashMap, ops::{Deref, DerefMut}, sync::{Arc, RwLock}}; +use std::{ + collections::HashMap, + ops::{Deref, DerefMut}, + sync::{Arc, RwLock}, +}; use leptos::prelude::*; use tracing::debug; @@ -181,7 +185,6 @@ where v.update(|v| modify(v)); } } - } #[derive(Clone)] diff --git a/src/user.rs b/src/user.rs index fb722c6..b65e4c9 100644 --- a/src/user.rs +++ b/src/user.rs @@ -2,10 +2,14 @@ use std::ops::{Deref, DerefMut}; use filamento::user::{User, UserStoreFields}; use jid::BareJID; -use reactive_stores::{ArcStore, Store}; use leptos::prelude::*; +use reactive_stores::{ArcStore, Store}; -use crate::{client::Client, roster::{Roster, RosterStoreFields}, state_store::{StateListener, StateStore}}; +use crate::{ + client::Client, + roster::{Roster, RosterStoreFields}, + state_store::{StateListener, StateStore}, +}; #[derive(Clone, Copy)] pub struct MacawUser { @@ -65,7 +69,9 @@ impl ArcMacawUser { use_context().expect("no user state store"); let old_user = user_state_store.get_listener(user.jid.clone()); let user = if let Some(old_user) = old_user { - old_user.update(|(old_user, _avatar)| { old_user.set(user); }); + old_user.update(|(old_user, _avatar)| { + old_user.set(user); + }); old_user } else { let avatar = fetch_avatar(user.avatar.as_deref()).await; @@ -124,7 +130,7 @@ pub fn get_name(user: Store<User>, note_to_self: bool) -> String { if note_to_self { let client: Client = use_context().expect("no client in context"); if *client.jid == *user.jid().read() { - return "Note to self".to_string() + return "Note to self".to_string(); } } if let Some(name) = roster @@ -141,4 +147,3 @@ pub fn get_name(user: Store<User>, note_to_self: bool) -> String { user.read().jid.to_string() } } - diff --git a/src/user_presences.rs b/src/user_presences.rs index 1a719a2..e864dbf 100644 --- a/src/user_presences.rs +++ b/src/user_presences.rs @@ -43,7 +43,7 @@ impl UserPresences { pub struct Presences { /// presences are sorted by time, first by type, then by last activity. - presences: IndexMap<String, Presence> + presences: IndexMap<String, Presence>, } impl Presences { @@ -55,47 +55,89 @@ impl Presences { /// gets the highest priority presence pub fn presence(&self) -> Option<(String, Presence)> { - if let Some((resource, presence)) = self.presences.iter().filter(|(_resource, presence)| if let PresenceType::Online(online) = &presence.presence { - online.show == Some(Show::DoNotDisturb) - } else { - false - }).next() { - return Some((resource.clone(), presence.clone())) + if let Some((resource, presence)) = self + .presences + .iter() + .filter(|(_resource, presence)| { + if let PresenceType::Online(online) = &presence.presence { + online.show == Some(Show::DoNotDisturb) + } else { + false + } + }) + .next() + { + return Some((resource.clone(), presence.clone())); } - if let Some((resource, presence)) = self.presences.iter().filter(|(_resource, presence)| if let PresenceType::Online(online) = &presence.presence { - online.show == Some(Show::Chat) - } else { - false - }).next() { - return Some((resource.clone(), presence.clone())) + if let Some((resource, presence)) = self + .presences + .iter() + .filter(|(_resource, presence)| { + if let PresenceType::Online(online) = &presence.presence { + online.show == Some(Show::Chat) + } else { + false + } + }) + .next() + { + return Some((resource.clone(), presence.clone())); } - if let Some((resource, presence)) = self.presences.iter().filter(|(_resource, presence)| if let PresenceType::Online(online) = &presence.presence { - online.show == None - } else { - false - }).next() { - return Some((resource.clone(), presence.clone())) + if let Some((resource, presence)) = self + .presences + .iter() + .filter(|(_resource, presence)| { + if let PresenceType::Online(online) = &presence.presence { + online.show == None + } else { + false + } + }) + .next() + { + return Some((resource.clone(), presence.clone())); } - if let Some((resource, presence)) = self.presences.iter().filter(|(_resource, presence)| if let PresenceType::Online(online) = &presence.presence { - online.show == Some(Show::Away) - } else { - false - }).next() { - return Some((resource.clone(), presence.clone())) + if let Some((resource, presence)) = self + .presences + .iter() + .filter(|(_resource, presence)| { + if let PresenceType::Online(online) = &presence.presence { + online.show == Some(Show::Away) + } else { + false + } + }) + .next() + { + return Some((resource.clone(), presence.clone())); } - if let Some((resource, presence)) = self.presences.iter().filter(|(_resource, presence)| if let PresenceType::Online(online) = &presence.presence { - online.show == Some(Show::ExtendedAway) - } else { - false - }).next() { - return Some((resource.clone(), presence.clone())) + if let Some((resource, presence)) = self + .presences + .iter() + .filter(|(_resource, presence)| { + if let PresenceType::Online(online) = &presence.presence { + online.show == Some(Show::ExtendedAway) + } else { + false + } + }) + .next() + { + return Some((resource.clone(), presence.clone())); } - if let Some((resource, presence)) = self.presences.iter().filter(|(_resource, presence)| if let PresenceType::Offline(_offline) = &presence.presence { - true - } else { - false - }).next() { - return Some((resource.clone(), presence.clone())) + if let Some((resource, presence)) = self + .presences + .iter() + .filter(|(_resource, presence)| { + if let PresenceType::Offline(_offline) = &presence.presence { + true + } else { + false + } + }) + .next() + { + return Some((resource.clone(), presence.clone())); } else { None } @@ -103,19 +145,14 @@ impl Presences { pub fn update_presence(&mut self, resource: String, presence: Presence) { let index = match self.presences.binary_search_by(|_, existing_presence| { - presence.timestamp - .cmp( - &existing_presence.timestamp - ) + presence.timestamp.cmp(&existing_presence.timestamp) }) { Ok(i) => i, Err(i) => i, }; self.presences.insert_before( // TODO: check if this logic is correct - index, - resource, - presence, + index, resource, presence, ); } @@ -130,4 +167,3 @@ impl Presences { } } } - diff --git a/src/views/login_page.rs b/src/views/login_page.rs index 2edd4b5..9a5fc4c 100644 --- a/src/views/login_page.rs +++ b/src/views/login_page.rs @@ -1,9 +1,14 @@ use std::{str::FromStr, sync::Arc}; -use filamento::{db::Db, error::{CommandError, ConnectionError}, files::{opfs::OPFSError, FilesMem, FilesOPFS}, UpdateMessage}; +use filamento::{ + UpdateMessage, + db::Db, + error::{CommandError, ConnectionError}, + files::{FilesMem, FilesOPFS, opfs::OPFSError}, +}; use jid::JID; -use thiserror::Error; use leptos::prelude::*; +use thiserror::Error; use tokio::sync::mpsc::Receiver; use tracing::debug; @@ -113,9 +118,7 @@ pub fn LoginPage( if *connect_on_login.read_untracked() { match client.connect().await { - Ok(r) => { - resource.set(Some(r)) - } + Ok(r) => resource.set(Some(r)), Err(e) => { set_error.set(Some(e.into())); set_login_pending.set(false); diff --git a/src/views/macaw.rs b/src/views/macaw.rs index 4a5b794..0ef8255 100644 --- a/src/views/macaw.rs +++ b/src/views/macaw.rs @@ -1,6 +1,10 @@ use std::collections::{HashMap, HashSet}; -use filamento::{chat::{Chat, Message, MessageStoreFields}, user::User, UpdateMessage}; +use filamento::{ + UpdateMessage, + chat::{Chat, Message, MessageStoreFields}, + user::User, +}; use jid::BareJID; use leptos::{prelude::*, task::spawn_local}; use open_chats_panel::OpenChatsPanelView; @@ -10,12 +14,23 @@ use tokio::sync::mpsc::Receiver; use tracing::debug; use uuid::Uuid; -use crate::{client::Client, components::sidebar::Sidebar, contact::{ArcMacawContact, MacawContact}, message::{ArcMacawMessage, MacawMessage}, message_subscriptions::MessageSubscriptions, open_chats::OpenChatsPanel, roster::{Roster, RosterStoreFields}, state_store::StateStore, user::{fetch_avatar, ArcMacawUser, MacawUser}, user_presences::{Presences, UserPresences}}; +use crate::{ + client::Client, + components::sidebar::Sidebar, + contact::{ArcMacawContact, MacawContact}, + message::{ArcMacawMessage, MacawMessage}, + message_subscriptions::MessageSubscriptions, + open_chats::OpenChatsPanel, + roster::{Roster, RosterStoreFields}, + state_store::StateStore, + user::{ArcMacawUser, MacawUser, fetch_avatar}, + user_presences::{Presences, UserPresences}, +}; use super::AppState; -pub mod settings; mod open_chats_panel; +pub mod settings; #[component] pub fn Macaw( @@ -44,23 +59,21 @@ pub fn Macaw( let open_chats = Store::new(OpenChatsPanel::default()); provide_context(open_chats); - let show_settings = RwSignal::new(None::<SettingsPage>); + let show_settings = RwSignal::new(None::<SettingsPage>); provide_context(show_settings); let user_presences = Store::new(UserPresences::new()); provide_context(user_presences); - let client_user: LocalResource<MacawUser> = LocalResource::new(move || { - async move { - let client = use_context::<Client>().expect("client not in context"); - let user = client.get_user((*client.jid).clone()).await.unwrap(); - ArcMacawUser::got_user(user).await.into() - } + let client_user: LocalResource<MacawUser> = LocalResource::new(move || async move { + let client = use_context::<Client>().expect("client not in context"); + let user = client.get_user((*client.jid).clone()).await.unwrap(); + ArcMacawUser::got_user(user).await.into() }); provide_context(client_user); // TODO: timestamp incoming/outgoing subscription requests - let (subscription_requests, set_subscription_requests)= signal(HashSet::<BareJID>::new()); + let (subscription_requests, set_subscription_requests) = signal(HashSet::<BareJID>::new()); provide_context(subscription_requests); provide_context(set_subscription_requests); @@ -73,7 +86,12 @@ pub fn Macaw( UpdateMessage::Online(online, items) => { let mut contacts = HashMap::new(); for (contact, user) in items { - contacts.insert(contact.user_jid.clone(), ArcMacawContact::got_contact_and_user(contact, user).await.into()); + contacts.insert( + contact.user_jid.clone(), + ArcMacawContact::got_contact_and_user(contact, user) + .await + .into(), + ); } roster.contacts().set(contacts); } @@ -82,7 +100,9 @@ pub fn Macaw( user_presences.write().clear(); } UpdateMessage::RosterUpdate(contact, user) => { - let new_contact = ArcMacawContact::got_contact_and_user(contact.clone(), user).await.into(); + let new_contact = ArcMacawContact::got_contact_and_user(contact.clone(), user) + .await + .into(); roster.contacts().update(|roster| { if let Some(macaw_contact) = roster.get_mut(&contact.user_jid) { macaw_contact.set(contact); @@ -99,15 +119,24 @@ pub fn Macaw( } UpdateMessage::Presence { from, presence } => { let bare_jid = from.to_bare(); - if let Some(presences) = user_presences.read_untracked().user_presences.get(&bare_jid) { + if let Some(presences) = user_presences + .read_untracked() + .user_presences + .get(&bare_jid) + { if let Some(resource) = from.resourcepart() { - presences.write().update_presence(resource.clone(), presence); + presences + .write() + .update_presence(resource.clone(), presence); } } else { if let Some(resource) = from.resourcepart() { let mut presences = Presences::new(); presences.update_presence(resource.clone(), presence); - user_presences.write().user_presences.insert(bare_jid, ArcRwSignal::new(presences)); + user_presences + .write() + .user_presences + .insert(bare_jid, ArcRwSignal::new(presences)); } } } @@ -125,12 +154,15 @@ pub fn Macaw( } UpdateMessage::MessageDelivery { id, chat, delivery } => { messages_store.modify(&id, |message| { - <ArcStore<filamento::chat::Message> as Clone>::clone(&message).delivery() + <ArcStore<filamento::chat::Message> as Clone>::clone(&message) + .delivery() .set(Some(delivery)) }); } UpdateMessage::SubscriptionRequest(jid) => { - set_subscription_requests.update(|req| { req.insert(jid); }); + set_subscription_requests.update(|req| { + req.insert(jid); + }); } UpdateMessage::NickChanged { jid, nick } => { users_store.modify(&jid, |(user, _avatar)| { @@ -152,11 +184,12 @@ pub fn Macaw( <Sidebar /> // <ChatsList /> <OpenChatsPanelView /> - {move || if let Some(_) = *show_settings.read() { - view! { <Settings /> }.into_any() - } else { - view! {}.into_any() + {move || { + if let Some(_) = *show_settings.read() { + view! { <Settings /> }.into_any() + } else { + view! {}.into_any() + } }} } } - diff --git a/src/views/macaw/open_chats_panel.rs b/src/views/macaw/open_chats_panel.rs index bdb0084..43ce59e 100644 --- a/src/views/macaw/open_chats_panel.rs +++ b/src/views/macaw/open_chats_panel.rs @@ -53,7 +53,13 @@ mod open_chat { use leptos::prelude::*; use reactive_stores::{ArcStore, Store}; - use crate::{chat::MacawChat, components::{chat_header::ChatViewHeader, message_composer::ChatViewMessageComposer, message_history_buffer::MessageHistoryBuffer}}; + use crate::{ + chat::MacawChat, + components::{ + chat_header::ChatViewHeader, message_composer::ChatViewMessageComposer, + message_history_buffer::MessageHistoryBuffer, + }, + }; #[component] pub fn OpenChatView(chat: MacawChat) -> impl IntoView { @@ -63,12 +69,9 @@ mod open_chat { <MessageHistoryBuffer chat=chat.clone() /> {move || { let chat_jid = chat.get().correspondent().get(); - view! { - <ChatViewMessageComposer chat=chat_jid /> - } + view! { <ChatViewMessageComposer chat=chat_jid /> } }} </div> } } } - diff --git a/src/views/macaw/settings.rs b/src/views/macaw/settings.rs index c4cc99b..3e87e3e 100644 --- a/src/views/macaw/settings.rs +++ b/src/views/macaw/settings.rs @@ -1,15 +1,29 @@ use leptos::prelude::*; use profile_settings::ProfileSettings; -use crate::{components::{icon::IconComponent, modal::Modal}, icon::Icon}; +use crate::{ + components::{icon::IconComponent, modal::Modal}, + icon::Icon, +}; mod profile_settings { - use filamento::{error::{AvatarPublishError, CommandError, NickError}, user::User}; - use thiserror::Error; + use filamento::{ + error::{AvatarPublishError, CommandError, NickError}, + user::User, + }; use leptos::prelude::*; - use web_sys::{js_sys::Uint8Array, wasm_bindgen::{prelude::Closure, JsCast, UnwrapThrowExt}, Event, FileReader, HtmlInputElement, ProgressEvent, Url}; + use thiserror::Error; + use web_sys::{ + Event, FileReader, HtmlInputElement, ProgressEvent, Url, + js_sys::Uint8Array, + wasm_bindgen::{JsCast, UnwrapThrowExt, prelude::Closure}, + }; - use crate::{client::Client, files::Files, user::{fetch_avatar, NO_AVATAR}}; + use crate::{ + client::Client, + files::Files, + user::{NO_AVATAR, fetch_avatar}, + }; #[derive(Debug, Clone, Error)] pub enum ProfileSaveError { @@ -36,9 +50,7 @@ mod profile_settings { view! { {move || { if let Some(old_profile) = old_profile.get() { - view! { - <ProfileForm old_profile /> - }.into_any() + view! { <ProfileForm old_profile /> }.into_any() } else { view! {}.into_any() } @@ -48,7 +60,6 @@ mod profile_settings { #[component] pub fn ProfileForm(old_profile: User) -> impl IntoView { - let client: Client = use_context().expect("no client in context"); // TODO: compartmentalise into error component, form component... @@ -75,14 +86,14 @@ mod profile_settings { let (profile_save_pending, set_profile_save_pending) = signal(false); let profile_upload_data = RwSignal::new(None::<Vec<u8>>); - let new_nick= RwSignal::new(old_profile.nick.clone().unwrap_or_default().to_string()); + let new_nick = RwSignal::new(old_profile.nick.clone().unwrap_or_default().to_string()); let has_avatar = RwSignal::new(old_profile.avatar.is_some()); let new_avatar_preview_url = RwSignal::new(None::<String>); let remove_avatar = RwSignal::new(false); let from_input = move |ev: Event| { let elem = ev.target().unwrap().unchecked_into::<HtmlInputElement>(); - + // let UploadSignal(file_signal) = expect_context(); // file_signal.update(Vec::clear); // Clear list from previous change let files = elem.files().unwrap_throw(); @@ -104,7 +115,7 @@ mod profile_settings { // `.result` valid after the `read_*` completes on FileReader // https://developer.mozilla.org/en-US/docs/Web/API/FileReader/result let result = reader.result().unwrap_throw(); - let data= Uint8Array::new(&result).to_vec(); + let data = Uint8Array::new(&result).to_vec(); // Do whatever you want with the Vec<u8> profile_upload_data.set(Some(data)); }) @@ -152,32 +163,32 @@ mod profile_settings { }; if new_nick != old_nick { match client.change_nick(new_nick).await { - Ok(_) => {}, + Ok(_) => {} Err(e) => { set_error.set(Some(ProfileSaveError::Nick(e))); set_profile_save_pending.set(false); return; - }, + } } } if let Some(profile_data) = profile_upload_data.get() { match client.change_avatar(Some(profile_data)).await { - Ok(_) => {}, + Ok(_) => {} Err(e) => { set_error.set(Some(ProfileSaveError::Avatar(e))); set_profile_save_pending.set(false); return; - }, + } } } else if remove_avatar.get() { match client.change_avatar(None).await { - Ok(_) => {}, + Ok(_) => {} Err(e) => { set_error.set(Some(ProfileSaveError::Avatar(e))); set_profile_save_pending.set(false); return; - }, + } } } @@ -200,45 +211,65 @@ mod profile_settings { <h2>Profile Preview</h2> <div class="preview"> <img class="avatar" src=new_avatar_preview_url /> - <div class="nick">{move || { - let nick = new_nick.get(); - if nick.is_empty() { - old_profile.jid.to_string() - } else { - nick - } - }}</div> + <div class="nick"> + {move || { + let nick = new_nick.get(); + if nick.is_empty() { old_profile.jid.to_string() } else { nick } + }} + </div> </div> </div> - <form class="profile-form" on:submit=move |ev| { - ev.prevent_default(); - save_profile.dispatch(()); - }> + <form + class="profile-form" + on:submit=move |ev| { + ev.prevent_default(); + save_profile.dispatch(()); + } + > {success_message} {error_message} <div> - <h3>Nick</h3> - <input disabled=profile_save_pending placeholder="Nick" type="text" id="client-user-nick" bind:value=new_nick name="client-user-nick" /> + <h3>Nick</h3> + <input + disabled=profile_save_pending + placeholder="Nick" + type="text" + id="client-user-nick" + bind:value=new_nick + name="client-user-nick" + /> </div> <div> <h3>Avatar</h3> <div class="change-avatar"> - <label for="client-user-avatar"><div class="button">Change Avatar</div></label> - <input type="file" id="client-user-avatar" on:change=move |e| { - has_avatar.set(true); - remove_avatar.set(false); - from_input(e); - } /> + <label for="client-user-avatar"> + <div class="button">Change Avatar</div> + </label> + <input + type="file" + id="client-user-avatar" + on:change=move |e| { + has_avatar.set(true); + remove_avatar.set(false); + from_input(e); + } + /> {move || { if has_avatar.get() { view! { - <a on:click=move |_| { - profile_upload_data.set(None); - remove_avatar.set(true); - has_avatar.set(false); - new_avatar_preview_url.set(Some(NO_AVATAR.to_string())); - } style="cursor: pointer">Remove Avatar</a> - }.into_any() + <a + on:click=move |_| { + profile_upload_data.set(None); + remove_avatar.set(true); + has_avatar.set(false); + new_avatar_preview_url.set(Some(NO_AVATAR.to_string())); + } + style="cursor: pointer" + > + Remove Avatar + </a> + } + .into_any() } else { view! {}.into_any() } @@ -246,7 +277,12 @@ mod profile_settings { </div> </div> <hr /> - <input disabled=profile_save_pending class="button" type="submit" value="Save Changes" /> + <input + disabled=profile_save_pending + class="button" + type="submit" + value="Save Changes" + /> </form> </div> } @@ -266,31 +302,81 @@ pub fn Settings() -> impl IntoView { let show_settings: RwSignal<Option<SettingsPage>> = use_context().unwrap(); view! { - <Modal on_background_click=move |_| { show_settings.set(None); }> + <Modal on_background_click=move |_| { + show_settings.set(None); + }> <div class="settings panel"> <div class="header"> <h2>Settings</h2> <div class="header-icon close"> - <IconComponent icon=Icon::Close24 on:click=move |_| show_settings.set(None)/> + <IconComponent + icon=Icon::Close24 + on:click=move |_| show_settings.set(None) + /> </div> </div> <div class="settings-main"> <div class="settings-sidebar"> - <div class:open=move || *show_settings.read() == Some(SettingsPage::Account) on:click=move |_| show_settings.set(Some(SettingsPage::Account))>Account</div> - <div class:open=move || *show_settings.read() == Some(SettingsPage::Chat) on:click=move |_| show_settings.set(Some(SettingsPage::Chat))>Chat</div> - <div class:open=move || *show_settings.read() == Some(SettingsPage::Privacy) on:click=move |_| show_settings.set(Some(SettingsPage::Privacy))>Privacy</div> - <div class:open=move || *show_settings.read() == Some(SettingsPage::Profile) on:click=move |_| show_settings.set(Some(SettingsPage::Profile))>Profile</div> + <div + class:open=move || *show_settings.read() == Some(SettingsPage::Account) + on:click=move |_| show_settings.set(Some(SettingsPage::Account)) + > + Account + </div> + <div + class:open=move || *show_settings.read() == Some(SettingsPage::Chat) + on:click=move |_| show_settings.set(Some(SettingsPage::Chat)) + > + Chat + </div> + <div + class:open=move || *show_settings.read() == Some(SettingsPage::Privacy) + on:click=move |_| show_settings.set(Some(SettingsPage::Privacy)) + > + Privacy + </div> + <div + class:open=move || *show_settings.read() == Some(SettingsPage::Profile) + on:click=move |_| show_settings.set(Some(SettingsPage::Profile)) + > + Profile + </div> </div> <div class="settings-page"> - {move || if let Some(page) = show_settings.get() { - match page { - SettingsPage::Account => view! { <div style="padding: 16px">"Account settings coming soon!"</div> }.into_any(), - SettingsPage::Chat => view! { <div style="padding: 16px">"Chat settings coming soon!"</div> }.into_any(), - SettingsPage::Profile => view! { <ProfileSettings /> }.into_any(), - SettingsPage::Privacy => view! { <div style="padding: 16px">"Privacy settings coming soon!"</div> }.into_any(), + {move || { + if let Some(page) = show_settings.get() { + match page { + SettingsPage::Account => { + view! { + <div style="padding: 16px"> + "Account settings coming soon!" + </div> + } + .into_any() + } + SettingsPage::Chat => { + view! { + <div style="padding: 16px"> + "Chat settings coming soon!" + </div> + } + .into_any() + } + SettingsPage::Profile => { + view! { <ProfileSettings /> }.into_any() + } + SettingsPage::Privacy => { + view! { + <div style="padding: 16px"> + "Privacy settings coming soon!" + </div> + } + .into_any() + } + } + } else { + view! {}.into_any() } - } else { - view! {}.into_any() }} </div> </div> @@ -298,4 +384,3 @@ pub fn Settings() -> impl IntoView { </Modal> } } - diff --git a/src/views/mod.rs b/src/views/mod.rs index 112f930..fa988cd 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -33,4 +33,3 @@ pub fn App() -> impl IntoView { }} } } - |