diff options
Diffstat (limited to 'src/views')
| -rw-r--r-- | src/views/macaw/settings.rs | 173 | 
1 files changed, 152 insertions, 21 deletions
diff --git a/src/views/macaw/settings.rs b/src/views/macaw/settings.rs index 11a3fc3..c4cc99b 100644 --- a/src/views/macaw/settings.rs +++ b/src/views/macaw/settings.rs @@ -4,23 +4,53 @@ use profile_settings::ProfileSettings;  use crate::{components::{icon::IconComponent, modal::Modal}, icon::Icon};  mod profile_settings { -    use filamento::error::{AvatarPublishError, CommandError}; +    use filamento::{error::{AvatarPublishError, CommandError, NickError}, user::User};      use thiserror::Error;      use leptos::prelude::*; -    use web_sys::{js_sys::Uint8Array, wasm_bindgen::{prelude::Closure, JsCast, UnwrapThrowExt}, Event, FileReader, HtmlInputElement, ProgressEvent}; +    use web_sys::{js_sys::Uint8Array, wasm_bindgen::{prelude::Closure, JsCast, UnwrapThrowExt}, Event, FileReader, HtmlInputElement, ProgressEvent, Url}; -    use crate::{client::Client, files::Files}; +    use crate::{client::Client, files::Files, user::{fetch_avatar, NO_AVATAR}};      #[derive(Debug, Clone, Error)]      pub enum ProfileSaveError {          #[error("avatar publish: {0}")]          Avatar(#[from] CommandError<AvatarPublishError<Files>>), +        #[error("nick publish: {0}")] +        Nick(#[from] CommandError<NickError>),      }      #[component]      pub fn ProfileSettings() -> impl IntoView {          let client: Client = use_context().expect("no client in context"); +        let old_profile = LocalResource::new(move || { +            let value = client.clone(); +            async move { +                // TODO: error +                let jid = &*value.jid; +                let old_profile = value.get_user(jid.clone()).await.unwrap(); +                old_profile +            } +        }); + +        view! { +            {move || { +                if let Some(old_profile) = old_profile.get() { +                    view! { +                        <ProfileForm old_profile /> +                    }.into_any() +                } else { +                    view! {}.into_any() +                } +            }} +        } +    } + +    #[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...          let (error, set_error) = signal(None::<ProfileSaveError>);          let error_message = move || { @@ -32,9 +62,24 @@ mod profile_settings {                  }              })          }; + +        let (success_message, set_success_message) = signal(None::<String>); +        let success_message = move || { +            if let Some(message) = success_message.get() { +                view! { <div class="success">{message}</div> }.into_any() +            } else { +                view! {}.into_any() +            } +        }; +          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 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>(); @@ -43,6 +88,9 @@ mod profile_settings {              let files = elem.files().unwrap_throw();              if let Some(file) = files.get(0) { +                let url = Url::create_object_url_with_blob(&file).unwrap_throw(); + +                new_avatar_preview_url.set(Some(url));                  let reader = FileReader::new().unwrap_throw();                  // let name = file.name(); @@ -92,32 +140,115 @@ mod profile_settings {          let save_profile = Action::new_local(move |_| {              let client = client.clone(); -            async move {} +            let old_nick = old_profile.nick.clone(); +            async move { +                set_profile_save_pending.set(true); + +                let new_nick = new_nick.get(); +                let new_nick = if new_nick.is_empty() { +                    None +                } else { +                    Some(new_nick) +                }; +                if new_nick != old_nick { +                    match client.change_nick(new_nick).await { +                        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(_) => {}, +                        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(_) => {}, +                        Err(e) => { +                            set_error.set(Some(ProfileSaveError::Avatar(e))); +                            set_profile_save_pending.set(false); +                            return; +                        }, +                    } +                } + +                set_profile_save_pending.set(false); +                set_success_message.set(Some("Profile Updated!".to_string())); +            }          }); -        let new_nick= RwSignal::new("".to_string()); +        let _old_account_avatar = LocalResource::new(move || { +            let avatar = old_profile.avatar.clone(); +            async move { +                let url = fetch_avatar(avatar.as_deref()).await; +                new_avatar_preview_url.set(Some(url)); +            } +        });          view! {              <div class="profile-settings"> -                <form on:submit=move |ev| { -                        ev.prevent_default(); -                        save_profile.dispatch(()); +                <div class="profile-preview"> +                    <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> +                </div> +                <form class="profile-form" on:submit=move |ev| { +                    ev.prevent_default(); +                    save_profile.dispatch(());                  }> +                    {success_message}                      {error_message} -                    <div class="change-avatar"> -                        <input type="file" id="client-user-avatar" on:change=from_input /> +                    <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" /> +                    </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); +                            } /> +                            {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() +                                } else { +                                    view! {}.into_any() +                                } +                            }} +                        </div>                      </div> -                    <input disabled=profile_save_pending placeholder="Nickname" type="text" id="client-user-nick" bind:value=new_nick name="client-user-nick" /> +                    <hr />                      <input disabled=profile_save_pending class="button" type="submit" value="Save Changes" />                  </form>              </div> -            <div class="profile-preview"> -                <h2>Profile Preview</h2> -                <div class="preview"> -                    <img /> -                    <div>nick</div> -                </div> -            </div>          }      }  } @@ -153,10 +284,10 @@ pub fn Settings() -> impl IntoView {                      <div class="settings-page">                          {move || if let Some(page) = show_settings.get() {                              match page { -                            SettingsPage::Account => view! { <div>"account"</div> }.into_any(), -                            SettingsPage::Chat => view! { <div>"chat"</div> }.into_any(), +                            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>"privacy"</div> }.into_any(), +                            SettingsPage::Privacy => view! { <div style="padding: 16px">"Privacy settings coming soon!"</div> }.into_any(),                              }                          } else {                              view! {}.into_any()  | 
