From f76c80c1d23177ab00c81240ee3a75d3bcda0e3b Mon Sep 17 00:00:00 2001 From: cel 🌸 Date: Sun, 1 Jun 2025 15:07:03 +0100 Subject: WIP: profile update --- src/lib.rs | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 108 insertions(+), 5 deletions(-) (limited to 'src/lib.rs') diff --git a/src/lib.rs b/src/lib.rs index 26ea17b..772ea60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ use std::{ use base64::{Engine, prelude::BASE64_STANDARD}; use chrono::{Local, NaiveDateTime, TimeDelta, Utc}; use filamento::{ - chat::{Body, Chat, ChatStoreFields, Delivery, Message, MessageStoreFields}, db::Db, error::{CommandError, ConnectionError, DatabaseError, SubscribeError}, files::{opfs::OPFSError, FileStore, FilesMem, FilesOPFS}, presence::{Offline, Online, Presence, PresenceType, Show}, roster::{Contact, ContactStoreFields}, user::{User, UserStoreFields}, UpdateMessage + chat::{Body, Chat, ChatStoreFields, Delivery, Message, MessageStoreFields}, db::Db, error::{AvatarPublishError, CommandError, ConnectionError, DatabaseError, SubscribeError}, files::{opfs::OPFSError, FileStore, FilesMem, FilesOPFS}, presence::{Offline, Online, Presence, PresenceType, Show}, roster::{Contact, ContactStoreFields}, user::{User, UserStoreFields}, UpdateMessage }; use futures::stream::StreamExt; use indexmap::IndexMap; @@ -23,7 +23,7 @@ use leptos::{ ev::{Event, KeyboardEvent, MouseEvent, SubmitEvent}, html::{self, Div, Input, Pre, Textarea}, prelude::*, - tachys::{dom::document, reactive_graph::bind::GetValue}, + tachys::{dom::document, reactive_graph::bind::GetValue, renderer::dom::Element}, task::{spawn, spawn_local}, }; use reactive_stores::{ArcStore, Store, StoreField}; @@ -35,6 +35,7 @@ use tokio::sync::{ }; use tracing::{debug, error}; use uuid::Uuid; +use web_sys::{js_sys::Uint8Array, wasm_bindgen::{prelude::Closure, JsCast, UnwrapThrowExt}, FileReader, HtmlInputElement, ProgressEvent}; const NO_AVATAR: &str = "/assets/no-avatar.png"; @@ -51,7 +52,7 @@ pub struct Client { file_store: Files, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum Files { Mem(FilesMem), Opfs(FilesOPFS), @@ -1133,11 +1134,113 @@ pub fn Settings() -> impl IntoView { } } +#[derive(Debug, Clone, Error)] +pub enum ProfileSaveError { + #[error("avatar publish: {0}")] + Avatar(#[from] CommandError>), +} + #[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::); + let error_message = move || { + error.with(|error| { + if let Some(error) = error { + view! {
{error.to_string()}
}.into_any() + } else { + view! {}.into_any() + } + }) + }; + let (profile_save_pending, set_profile_save_pending) = signal(false); + + let profile_upload_data = RwSignal::new(None::>); + let from_input = move |ev: Event| { + let elem = ev.target().unwrap().unchecked_into::(); + + // 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 + 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! { -
-
+
+
+ {error_message} +
+ +
+ + +
+
+
+

Profile Preview

+
+ +
nick
+
+
} } -- cgit