diff options
| author | 2025-05-15 19:32:01 +0100 | |
|---|---|---|
| committer | 2025-05-15 19:32:01 +0100 | |
| commit | 227661fb8339743f87fc36ca3be1f935050db2d4 (patch) | |
| tree | d1efc7d406595c0daf76d8b0997d24311cac3167 /src | |
| parent | 62aaa8cb8583d9189358a6c15ca69257b342c4ea (diff) | |
| download | macaw-web-227661fb8339743f87fc36ca3be1f935050db2d4.tar.gz macaw-web-227661fb8339743f87fc36ca3be1f935050db2d4.tar.bz2 macaw-web-227661fb8339743f87fc36ca3be1f935050db2d4.zip  | |
feat: dock user menu
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib.rs | 167 | 
1 files changed, 164 insertions, 3 deletions
@@ -12,9 +12,9 @@ use std::{  };  use base64::{Engine, prelude::BASE64_STANDARD}; -use chrono::{NaiveDateTime, TimeDelta}; +use chrono::{NaiveDateTime, TimeDelta, Utc};  use filamento::{ -    chat::{Body, Chat, ChatStoreFields, Delivery, Message, MessageStoreFields}, db::Db, error::{CommandError, ConnectionError, DatabaseError}, files::{opfs::OPFSError, FileStore, FilesMem, FilesOPFS}, presence::{Presence, PresenceType, Show}, roster::{Contact, ContactStoreFields}, user::{User, UserStoreFields}, UpdateMessage +    chat::{Body, Chat, ChatStoreFields, Delivery, Message, MessageStoreFields}, db::Db, error::{CommandError, ConnectionError, DatabaseError}, files::{opfs::OPFSError, FileStore, FilesMem, FilesOPFS}, presence::{Offline, Online, Presence, PresenceType, Show}, roster::{Contact, ContactStoreFields}, user::{User, UserStoreFields}, UpdateMessage  };  use futures::stream::StreamExt;  use indexmap::IndexMap; @@ -46,6 +46,7 @@ pub enum AppState {  #[derive(Clone)]  pub struct Client {      client: filamento::Client<Files>, +    resource: ArcRwSignal<Option<String>>,      jid: Arc<JID>,      file_store: Files,  } @@ -224,15 +225,19 @@ fn LoginPage(                  files.clone(),              );              // TODO: remember_me +            let resource = ArcRwSignal::new(None::<String>);              let client = Client {                  client, +                resource: resource.clone(),                  jid: Arc::new(jid),                  file_store: files,              };              if *connect_on_login.read_untracked() {                  match client.connect().await { -                    Ok(_) => {} +                    Ok(r) => { +                        resource.set(Some(r)) +                    }                      Err(e) => {                          set_error.set(Some(e.into()));                          set_login_pending.set(false); @@ -591,6 +596,17 @@ impl Presences {              presence,          );      } + +    pub fn resource_presence(&mut self, resource: String) -> Presence { +        if let Some(presence) = self.presences.get(&resource) { +            presence.clone() +        } else { +            Presence { +                timestamp: Utc::now(), +                presence: PresenceType::Offline(Offline::default()), +            } +        } +    }  }  #[component] @@ -851,11 +867,24 @@ pub fn PersonalStatus() -> impl IntoView {          let user: Store<User> = <ArcStore<filamento::user::User> as Clone>::clone(&(*user.user)).into();          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=user />                  <div class="dock-pill"></div>              </div> +            {move || { +                let open = open.get(); +                debug!("open = {:?}", open); +                if open { +                view! { +                    <Overlay set_open> +                        <PersonalStatusMenu user /> +                    </Overlay> +                }.into_any() +            } else { +                view! {}.into_any() +            }}}          }.into_any()      } else {          view! {}.into_any() @@ -863,6 +892,138 @@ pub fn PersonalStatus() -> impl IntoView {  }  #[component] +pub fn PersonalStatusMenu(user: Store<User>) -> impl IntoView { +    let user_presences: Store<UserPresences> = use_context().expect("no user presence store"); +     +    let client = use_context::<Client>().expect("client not in context"); +    let (show_value, set_show_value) = signal({ +        let show = match user_presences.write().get_user_presences(&user.jid().read().as_bare()).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, +        }, +        PresenceType::Offline(_offline) => 5, +    }; +    debug!("initial show = {show}"); +    show +    }); + +    let show_select: NodeRef<html::Select> = NodeRef::new(); + +    let set_status = Action::new_local(move |show_value: &i32| { +        let show_value = show_value.to_owned();     +        let client = client.clone(); +        async move { +            if let Err(e) = match show_value { +                0 => { +                    if let Ok(r) = client.connect().await { +                        client.resource.set(Some(r)) +                    }; +                    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 +                }, +                2 => { +                    if let Ok(r) = client.connect().await { +                        client.resource.set(Some(r)) +                    }; +                    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 +                }, +                4 => { +                    if let Ok(r) = client.connect().await { +                        client.resource.set(Some(r)) +                    }; +                    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 +                } +                _ => { +                    error!("invalid availability select"); +                    return +                } +            } { +                error!("show set error: {e}"); +                return +            } +            set_show_value.set(show_value); +        } +    }); + +    view! { +        <div class="personal-status-menu menu"> +            <div class="user"> +                <AvatarWithPresence user=user /> +                <div class="user-info"> +                    <div class="nick">{move || get_name(user)}</div> +                    <div class="jid">{move || user.jid().with(|jid| jid.to_string())}</div> +                </div> +            </div> +            <div class="status-edit"> +                <select +                    node_ref=show_select +                    on:change:target=move |ev| { +                        let show_value = ev.target().value().parse().unwrap(); +                        set_status.dispatch(show_value); +                    } +                    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> +                </select> +            </div> +            <hr /> +            <div class="menu-item"> +                Profile +            </div> +            <div class="menu-item"> +                Settings +            </div> +            <hr /> +            <div class="menu-item"> +                Log out +            </div> +        </div> +    } +} + +#[component] +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-content">{children()}</div> +        </div> +    } +} + +#[component]  pub fn OpenChatsPanelView() -> impl IntoView {      let open_chats: Store<OpenChatsPanel> = use_context().expect("no open chats panel in context");  | 
