summaryrefslogblamecommitdiffstats
path: root/src/views/macaw/settings.rs
blob: 11a3fc3ba478591510968735d3cfc89f5abc839c (plain) (tree)









































































































































































                                                                                                                                                                                   
use leptos::prelude::*;
use profile_settings::ProfileSettings;

use crate::{components::{icon::IconComponent, modal::Modal}, icon::Icon};

mod profile_settings {
    use filamento::error::{AvatarPublishError, CommandError};
    use thiserror::Error;
    use leptos::prelude::*;
    use web_sys::{js_sys::Uint8Array, wasm_bindgen::{prelude::Closure, JsCast, UnwrapThrowExt}, Event, FileReader, HtmlInputElement, ProgressEvent};

    use crate::{client::Client, files::Files};

    #[derive(Debug, Clone, Error)]
    pub enum ProfileSaveError {
        #[error("avatar publish: {0}")]
        Avatar(#[from] CommandError<AvatarPublishError<Files>>),
    }

    #[component]
    pub fn ProfileSettings() -> 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 || {
            error.with(|error| {
                if let Some(error) = error {
                    view! { <div class="error">{error.to_string()}</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 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();

            if let Some(file) = files.get(0) {
                let reader = FileReader::new().unwrap_throw();
                // let name = file.name();

                // This closure only needs to be called a single time as we will just
                // remake it on each loop
                // * web_sys drops it for us when using this specific constructor
                let read_file = {
                    // FileReader is cloned prior to moving into the closure
                    let reader = reader.to_owned();
                    Closure::once_into_js(move |_: ProgressEvent| {
                        // `.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();
                        // Do whatever you want with the Vec<u8>
                        profile_upload_data.set(Some(data));
                    })
                };
                reader.set_onloadend(Some(read_file.as_ref().unchecked_ref()));

                // read_as_array_buffer takes a &Blob
                //
                // Per https://w3c.github.io/FileAPI/#file-section
                // > A File object is a Blob object with a name attribute..
                //
                // File is a subclass (inherits) from the Blob interface, so a File
                // can be used anywhere a Blob is required.
                reader.read_as_array_buffer(&file).unwrap_throw();

                // You can also use `.read_as_text(&file)` instead if you just want a string.
                // This example shows how to extract an array buffer as it is more flexible
                //
                // If you use `.read_as_text` change the closure from ...
                //
                // let result = reader.result().unwrap_throw();
                // let vec_of_u8_bytes = Uint8Array::new(&result).to_vec();
                // let content = String::from_utf8(vec_of_u8_bytes).unwrap_throw();
                //
                // to ...
                //
                // let result = reader.result().unwrap_throw();
                // let content = result.as_string().unwrap_throw();
            } else {
                profile_upload_data.set(None);
            }
        };

        let save_profile = Action::new_local(move |_| {
            let client = client.clone();
            async move {}
        });

        let new_nick= RwSignal::new("".to_string());

        view! {
            <div class="profile-settings">
                <form on:submit=move |ev| {
                        ev.prevent_default();
                        save_profile.dispatch(());
                }>
                    {error_message}
                    <div class="change-avatar">
                        <input type="file" id="client-user-avatar" on:change=from_input />
                    </div>
                    <input disabled=profile_save_pending placeholder="Nickname" type="text" id="client-user-nick" bind:value=new_nick name="client-user-nick" />
                    <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>
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SettingsPage {
    Account,
    Chat,
    Profile,
    Privacy,
}

#[component]
pub fn Settings() -> impl IntoView {
    let show_settings: RwSignal<Option<SettingsPage>> = use_context().unwrap();

    view! {
        <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)/>
                    </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>
                    <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::Profile => view! { <ProfileSettings /> }.into_any(),
                            SettingsPage::Privacy => view! { <div>"privacy"</div> }.into_any(),
                            }
                        } else {
                            view! {}.into_any()
                        }}
                    </div>
                </div>
            </div>
        </Modal>
    }
}