diff options
Diffstat (limited to 'src/views')
| -rw-r--r-- | src/views/login_page.rs | 13 | ||||
| -rw-r--r-- | src/views/macaw.rs | 79 | ||||
| -rw-r--r-- | src/views/macaw/open_chats_panel.rs | 13 | ||||
| -rw-r--r-- | src/views/macaw/settings.rs | 207 | ||||
| -rw-r--r-- | src/views/mod.rs | 1 | 
5 files changed, 218 insertions, 95 deletions
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 {          }}      }  } -  | 
