use base64::{engine::general_purpose, Engine as _}; use dioxus::prelude::*; use tracing::{debug, trace, warn}; use super::{button::Button, icons::SearchIcon, text_input::TextInput}; use crate::{ domain::model::{common::PresenceState as DomainPresenceState, room::RoomId, space::SpaceId}, ui::{ components::icons::{ChatsIcon, LogoIcon, RoomsIcon, SpacesIcon}, ACCOUNT, STORE, }, }; turf::style_sheet!("src/ui/components/conversations.scss"); #[component] fn AccountAvatar(content: Option>, class_name: Option) -> Element { match content { Some(content) => { let encoded = general_purpose::STANDARD.encode(content); rsx! { div { class: class_name, background_image: format!("url(data:image/jpeg;base64,{encoded})") } } } // TODO: Manage acount without avatar None => None, } } #[component] fn PresenceState(state: Option, class_name: Option) -> Element { let class_name = class_name.unwrap_or("".to_string()); match state { Some(state) => { let state_class = match state { DomainPresenceState::Online => ClassName::ONLINE, DomainPresenceState::Offline => ClassName::OFFLINE, DomainPresenceState::Unavailable => ClassName::UNAVAILABLE, _ => ClassName::UNAVAILABLE, }; let classes = [class_name.as_str(), state_class].join(" "); rsx! { div { class: classes, LogoIcon {}, } } } None => None, } } #[component] fn DisplayName(display_name: Option, class_name: Option) -> Element { match display_name { Some(display_name) => { rsx! { div { class: class_name, p { {display_name}, } } } } None => None, } } #[component] fn Status(status: Option, class_name: Option) -> Element { let status = status.unwrap_or("Type your status".to_string()); rsx! { div { class: class_name, TextInput { placeholder: status, } } } } pub fn Account() -> Element { let avatar = use_resource(move || async move { let account = ACCOUNT.read(); let avatar = account.get_avatar().await; rsx! { AccountAvatar { class_name: ClassName::ACCOUNT_AVATAR, content: avatar.borrow().clone(), } } }); let presence_state = use_resource(move || async move { // TODO: Fetch the state from the domain rsx! { PresenceState { state: DomainPresenceState::Online, class_name: ClassName::ACCOUNT_PRESENCE_STATE, } } }); let display_name = use_resource(move || async move { let account = ACCOUNT.read(); let display_name = account.get_display_name().await; rsx! { DisplayName { class_name: ClassName::ACCOUNT_DISPLAY_NAME, display_name: display_name.borrow().clone(), } } }); let status = use_resource(move || async move { // TODO: Fetch the status from the domain rsx! { Status { class_name: ClassName::ACCOUNT_STATUS, status: "Coucou, Je suis BG92".to_string(), } } }); rsx! { style { {STYLE_SHEET} }, div { class: ClassName::ACCOUNT, {avatar}, {presence_state}, {display_name}, {status}, div { class: ClassName::ACCOUNT_SPACES, Button { SpacesIcon {}, } }, div { class: ClassName::ACCOUNT_CHAT, Button { ChatsIcon {}, } }, div { class: ClassName::ACCOUNT_ROOM, Button { RoomsIcon {}, } }, } } } #[component] pub fn ConversationAvatar(room_id: RoomId, on_clicked: EventHandler) -> Element { let rooms = STORE.read().rooms(); let toto = rooms.get(&room_id).unwrap(); let room = toto.signal(); let room_id = room.id(); let room_name = room.name(); let selected_room_id = use_context::>>(); let invited_badge = if room.is_invited() { rsx! { div { class: ClassName::CONVERSATION_AVATAR_INVITED_BADGE, p { "Invited", } } } } else { None }; let is_selected = match selected_room_id.read().as_ref() { Some(selected_room_id) => *selected_room_id == room_id, None => false, }; // let avatar = if let Some(Some(content)) = &*avatar.read() { let avatar = if let Some(content) = room.avatar() { let encoded = general_purpose::STANDARD.encode(content); rsx! { div { class: ClassName::CONVERSATION_AVATAR_IMAGE, background_image: format!("url(data:image/jpeg;base64,{encoded})"), } } } else { let placeholder = room_name .unwrap_or("?".to_string()) .to_uppercase() .chars() .next() .unwrap_or('?') .to_string(); debug!("Use of {} placeholder for {}", placeholder, room_id); rsx! { div { class: ClassName::CONVERSATION_AVATAR_IMAGE, {placeholder}, } } }; let classes = [ ClassName::CONVERSATION_AVATAR, if is_selected { ClassName::SELECTED } else { "" }, ]; let classes_str = classes.join(" "); rsx! { div { class: "{classes_str}", onclick: move |evt| { on_clicked.call(room_id.clone()); evt.stop_propagation(); }, {avatar} {invited_badge} } } } #[component] pub fn ConversationsCarousel(on_selected_conversation: EventHandler) -> Element { let mut ordered_rooms = use_signal(Vec::::new); use_effect(move || { let rooms = use_context::>>(); let rooms = rooms.read(); for room in rooms.iter() { if !ordered_rooms.peek().contains(room) { ordered_rooms.push(room.clone()); } } ordered_rooms.retain(|room| rooms.contains(room)); }); let ordered_rooms = ordered_rooms.read(); let rendered_avatars = ordered_rooms.iter().map(|room| { rsx! { ConversationAvatar { room_id: room.clone(), on_clicked: on_selected_conversation, } } }); rsx! { div { class: ClassName::SPACE_CONVERSATIONS_CAROUSEL, // TODO: Needed? onscroll: move |_| { // Catch scrolling events. }, {rendered_avatars}, } } } #[component] pub fn Space(id: SpaceId) -> Element { let space = STORE.read().spaces().get(&id).unwrap().signal(); let name = space.name(); let mut selected_room_id = use_context_provider(|| Signal::new(None::)); let mut displayed_rooms = use_context_provider(|| Signal::new(Vec::::new())); use_effect(move || { // let rooms = STORE.read().rooms(); let rooms = STORE.peek().rooms(); let room_ids = space.room_ids(); for room_id in room_ids { if rooms.contains_key(&room_id) { displayed_rooms.write().push(room_id); } } }); let on_selected_conversation = move |room_id: RoomId| { trace!(""); selected_room_id.set(Some(room_id)); }; let mut space_classes: [&str; 2] = [ClassName::SPACE, ""]; let mut selected_room_name = "".to_string(); if let Some(room_id) = selected_room_id.read().as_ref() { space_classes[1] = ClassName::DISPLAY_CONVERSATION_NAME; if let Some(room) = STORE.read().rooms().get(room_id) { let room = room.signal(); if let Some(name) = room.name() { selected_room_name = name; } else { debug!("No name set for {} room", &room_id); selected_room_name = room_id.to_string(); } } else { warn!("No room found for the {} id", &room_id); } } let classes_str = space_classes.join(" "); rsx! { style { {STYLE_SHEET} }, div { class: "{classes_str}", // Deselect the conversation on clicks outside of the ConversationAvatar onclick: move |_| { selected_room_id.set(None); }, div { class: ClassName::SPACE_NAME, p { {name} } }, ConversationsCarousel { on_selected_conversation, }, div { class: ClassName::SPACE_CONVERSATION_NAME, p { {selected_room_name}, } } } } } #[component] pub fn HomeSpace() -> Element { let name = "Home"; let mut selected_room_id = use_context_provider(|| Signal::new(None::)); let mut displayed_rooms = use_context_provider(|| Signal::new(Vec::::new())); use_effect(move || { let rooms = STORE.read().rooms(); for room in rooms.values() { if room.signal().spaces().is_empty() { let room_id = room.signal().id(); displayed_rooms.write().push(room_id); } } }); let on_selected_conversation = move |room_id: RoomId| { selected_room_id.set(Some(room_id)); }; let mut space_classes: [&str; 2] = [ClassName::SPACE, ""]; let mut selected_room_name = "".to_string(); if let Some(room_id) = selected_room_id.read().as_ref() { space_classes[1] = ClassName::DISPLAY_CONVERSATION_NAME; if let Some(room) = STORE.read().rooms().get(room_id) { let room = room.signal(); if let Some(name) = room.name() { selected_room_name = name; } else { debug!("No name set for {} room", &room_id); selected_room_name = room_id.to_string(); } } else { warn!("No room found for the {} id", &room_id); } } let classes_str = space_classes.join(" "); rsx! { style { {STYLE_SHEET} }, div { class: "{classes_str}", // Deselect the conversation on clicks outside of the ConversationAvatar onclick: move |_| { selected_room_id.set(None); }, div { class: ClassName::SPACE_NAME, p { {name} } }, ConversationsCarousel { on_selected_conversation, }, div { class: ClassName::SPACE_CONVERSATION_NAME, p { {selected_room_name}, } } } } } pub fn Spaces() -> Element { let spaces = STORE.read().spaces(); let space_ids = spaces.keys().clone().last(); let rendered_spaces = space_ids.map(|id| { rsx! { Space { id: id.clone() } } }); rsx! { style { {STYLE_SHEET} }, div { class: ClassName::SPACES, {rendered_spaces}, HomeSpace {}, } } } pub fn Search() -> Element { rsx! { style { {STYLE_SHEET} }, div { class: ClassName::SEARCH, TextInput {} div { class: ClassName::SEARCH_BUTTON, Button { SearchIcon {} } } } } } pub fn Conversations() -> Element { rsx! { style { {STYLE_SHEET} }, div { class: ClassName::CONVERSATIONS, div { class: ClassName::CONVERSATIONS_ACCOUNT, Account {}, }, div { class: ClassName::CONVERSATIONS_SPACES, Spaces {}, }, div { class: ClassName::CONVERSATIONS_SEARCH, Search {}, }, } } }