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 {          }}      }  } -  | 
