summaryrefslogtreecommitdiffstats
path: root/src/views/macaw/settings.rs
diff options
context:
space:
mode:
authorLibravatar cel 🌸 <cel@bunny.garden>2025-06-01 16:10:26 +0100
committerLibravatar cel 🌸 <cel@bunny.garden>2025-06-01 17:27:40 +0100
commit6ee4190a26f32bfa953302ee363ad3bb6c384ebb (patch)
tree2c3182c29d5780a0ad9c9770b5e546312bea49b4 /src/views/macaw/settings.rs
parentf76c80c1d23177ab00c81240ee3a75d3bcda0e3b (diff)
downloadmacaw-web-6ee4190a26f32bfa953302ee363ad3bb6c384ebb.tar.gz
macaw-web-6ee4190a26f32bfa953302ee363ad3bb6c384ebb.tar.bz2
macaw-web-6ee4190a26f32bfa953302ee363ad3bb6c384ebb.zip
refactor: reorganise code
Diffstat (limited to 'src/views/macaw/settings.rs')
-rw-r--r--src/views/macaw/settings.rs170
1 files changed, 170 insertions, 0 deletions
diff --git a/src/views/macaw/settings.rs b/src/views/macaw/settings.rs
new file mode 100644
index 0000000..11a3fc3
--- /dev/null
+++ b/src/views/macaw/settings.rs
@@ -0,0 +1,170 @@
+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>
+ }
+}
+