diff options
author | 2025-05-07 02:16:38 +0100 | |
---|---|---|
committer | 2025-05-07 02:16:38 +0100 | |
commit | 3302bdedebb734d9b7306bb361c2aa9168a527d7 (patch) | |
tree | fc02edf10cff35d102cf9bcb2604c7a16d097d82 | |
parent | d83baa78b0b029a5c25b781ee3f77ae3cbecf60d (diff) | |
download | macaw-web-3302bdedebb734d9b7306bb361c2aa9168a527d7.tar.gz macaw-web-3302bdedebb734d9b7306bb361c2aa9168a527d7.tar.bz2 macaw-web-3302bdedebb734d9b7306bb361c2aa9168a527d7.zip |
feat: filesmem base64 img srcs
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/lib.rs | 33 |
3 files changed, 27 insertions, 8 deletions
@@ -1904,6 +1904,7 @@ dependencies = [ name = "macaw-web" version = "0.1.0" dependencies = [ + "base64", "chrono", "chrono-humanize", "console_error_panic_hook", @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +base64 = "0.22.1" chrono = "0.4.41" chrono-humanize = "0.2.3" console_error_panic_hook = "0.1.7" @@ -11,6 +11,7 @@ use std::{ time::{self, Duration}, }; +use base64::{prelude::BASE64_STANDARD, Engine}; use chrono::{NaiveDateTime, TimeDelta}; use filamento::{ chat::{Body, Chat, ChatStoreFields, Delivery, Message, MessageStoreFields}, db::Db, error::{CommandError, ConnectionError, DatabaseError}, files::FilesMem, roster::{Contact, ContactStoreFields}, user::{User, UserStoreFields}, UpdateMessage @@ -342,6 +343,7 @@ pub fn open_chat(open_chats: Store<OpenChatsPanel>, chat: MacawChat) { impl OpenChatsPanel { pub fn open(&mut self, chat: MacawChat) { if let Some(jid) = &mut self.chat_view { + debug!("a chat was already open"); if let Some((index, _jid, entry)) = self.chats.shift_remove_full(jid) { let new_jid = chat.chat.correspondent().read().clone(); self.chats.insert_before(index, new_jid.clone(), chat); @@ -356,6 +358,7 @@ impl OpenChatsPanel { self.chats.insert(new_jid.clone(), chat); *&mut self.chat_view = Some(new_jid); } + debug!("opened chat"); } // TODO: @@ -505,13 +508,15 @@ pub fn OpenChatView(chat: MacawChat) -> impl IntoView { #[component] pub fn ChatViewHeader(chat: MacawChat) -> impl IntoView { let chat_user = *chat.user; - let avatar = move || get_avatar(chat_user); + let avatar = LocalResource::new(move || get_avatar(chat_user)); let name = move || get_name(chat_user); let jid = move || chat_user.jid().read().to_string(); view! { <div class="chat-view-header panel"> - <img class="avatar" src=avatar /> + <Transition fallback=|| view! { <img class="avatar" src=NO_AVATAR /> } > + <img class="avatar" src=move || avatar.read().as_deref().map(|avatar| avatar.clone()).unwrap_or_default() /> + </Transition> <div class="user-info"> <h2 class="name">{name}</h2> <h3>{jid}</h3> @@ -713,7 +718,7 @@ pub fn Delivery(delivery: Delivery) -> impl IntoView { pub fn Message(message: MacawMessage, major: bool, r#final: bool) -> impl IntoView { let message_message = *message.message; let message_user = *message.user; - let avatar = move || get_avatar(message_user); + let avatar = LocalResource::new(move || get_avatar(message_user)); let name = move || get_name(message_user); // TODO: chrono-humanize? @@ -722,7 +727,11 @@ pub fn Message(message: MacawMessage, major: bool, r#final: bool) -> impl IntoVi if major { view! { <div class:final=r#final class="chat-message major"> - <div class="left"><img class="avatar" src=avatar /></div> + <div class="left"> + <Transition fallback=|| view! { <img class="avatar" src=NO_AVATAR /> } > + <img class="avatar" src=move || avatar.read().as_deref().map(|avatar| avatar.clone()).unwrap_or_default() /> + </Transition> + </div> <div class="middle"> <div class="message-info"> <div class="message-user-name">{name}</div> @@ -1185,9 +1194,15 @@ fn ChatsList() -> impl IntoView { } } -pub fn get_avatar(user: Store<User>) -> String { +pub async fn get_avatar(user: Store<User>) -> String { if let Some(avatar) = &user.read().avatar { - NO_AVATAR.to_string() + let client = use_context::<Client>().expect("client not in context"); + if let Some(data) = client.file_store.get_file(avatar).await { + let data = BASE64_STANDARD.encode(data); + format!("data:image/jpg;base64, {}", data) + } else { + NO_AVATAR.to_string() + } // TODO: enable avatar fetching // format!("/files/{}", avatar) } else { @@ -1215,7 +1230,7 @@ pub fn get_name(user: Store<User>) -> String { #[component] fn ChatsListItem(chat: MacawChat, message: MacawMessage) -> impl IntoView { let chat_user = *chat.user; - let avatar = move || get_avatar(chat_user); + let avatar = LocalResource::new(move || get_avatar(chat_user)); let name = move || get_name(chat_user); // TODO: store fine-grained reactivity @@ -1229,7 +1244,9 @@ fn ChatsListItem(chat: MacawChat, message: MacawMessage) -> impl IntoView { view! { <div class="chats-list-item" on:click=open_chat> - <img class="avatar" src=avatar /> + <Transition fallback=|| view! { <img class="avatar" src=NO_AVATAR /> } > + <img class="avatar" src=move || avatar.read().as_deref().map(|avatar| avatar.clone()).unwrap_or_default() /> + </Transition> <div class="item-info"> <h3>{name}</h3> <p>{latest_message_body}</p> |