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