From 204e11b8b119919ef866862b5d281bfa4062e867 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 26 May 2024 18:09:05 +0200 Subject: [PATCH 01/20] =?UTF-8?q?=F0=9F=90=9B=20tracing=5Fforest=20crate?= =?UTF-8?q?=20isn't=20used=20for=20wasm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index c29d3f3..6bb9464 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,6 @@ use std::rc::Rc; use dioxus::prelude::*; use futures_util::stream::StreamExt; use tracing::{debug, error, warn}; -use tracing_forest::ForestLayer; use tracing_subscriber::{prelude::*, EnvFilter}; use crate::{ @@ -29,10 +28,12 @@ cfg_if! { if #[cfg(target_family = "wasm")] { use tracing_web::MakeWebConsoleWriter; } else { - use dioxus::desktop::Config; use std::fs::File; + + use dioxus::desktop::Config; use time::format_description::well_known::Iso8601; use tracing_subscriber::fmt::time::UtcTime; + use tracing_forest::ForestLayer; } } From 1ad4d444fb149fb48e0f8a23fcfc42367f18504c Mon Sep 17 00:00:00 2001 From: Adrien Date: Fri, 7 Jun 2024 22:18:15 +0200 Subject: [PATCH 02/20] =?UTF-8?q?=F0=9F=92=84=20Add=20first=20breakpoint?= =?UTF-8?q?=20management=20by=20Conversations=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/layouts/conversations.rs | 35 +++++++++++++-------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/ui/layouts/conversations.rs b/src/ui/layouts/conversations.rs index 80cb846..ee9c37d 100644 --- a/src/ui/layouts/conversations.rs +++ b/src/ui/layouts/conversations.rs @@ -210,22 +210,7 @@ fn LayoutBig() -> Element { } pub fn Conversations() -> Element { - let mut view_size = use_signal(|| None::<(f64, f64)>); - - // TODO: Make the layout reactive (on window resize) - let layout = { - move || { - if let Some((width, height)) = view_size.read().as_ref() { - let component_width = height * WIDGET_HEIGHT_RATIO * ASPECT_RATIO; - let breakpoint_width = component_width * 2_f64; - - if *width >= breakpoint_width { - return rsx! { LayoutBig {} }; - } - } - rsx! {LayoutSmall {}} - } - }(); + let mut layout = use_signal(|| None::); rsx! { style { {STYLE_SHEET} }, @@ -236,14 +221,22 @@ pub fn Conversations() -> Element { div { class: ClassName::CONVERSATIONS_VIEW, - onmounted: move |cx| { - async move { - let data = cx.data(); - if let Ok(client_rect) = data.get_client_rect().await { - view_size.set(Some((client_rect.size.width, client_rect.size.height))); + onresized: move |cx| { + let data = cx.data(); + let mut use_big_layout = false; + + if let Ok(size) = data.get_border_box_size() { + if let Some(size) = size.first() { + // Use LayoutBig if the layout can contain 2 panels side by side + let component_width = size.height * WIDGET_HEIGHT_RATIO * ASPECT_RATIO; + let breakpoint_width = component_width * 2_f64; + use_big_layout = size.width > breakpoint_width; } } + + layout.set(rsx! { if use_big_layout { LayoutBig {} } else { LayoutSmall {} }}); + }, {layout} From d566a4927fd067a0537dd95c8a97d33f2d1ff8e8 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sat, 8 Jun 2024 13:04:17 +0200 Subject: [PATCH 03/20] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20Use=20of=20the=20?= =?UTF-8?q?SCSS=20variables=20to=20compute=20the=20width=20of=20the=20inne?= =?UTF-8?q?r=20panels?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.rs | 138 +++++++++++++++++++++++------- src/ui/components/_panel.scss | 2 +- src/ui/components/icons.rs | 2 +- src/ui/components/login.rs | 2 +- src/ui/components/modal.rs | 2 +- src/ui/layouts/conversations.rs | 18 ++-- src/ui/layouts/conversations.scss | 14 +-- src/ui/layouts/login.scss | 8 +- 8 files changed, 132 insertions(+), 54 deletions(-) diff --git a/build.rs b/build.rs index 58ada3f..80f3543 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,5 @@ use std::env; +use std::fmt::Display; use std::fs::File; use std::io::Write; use std::io::{self, BufRead}; @@ -10,14 +11,32 @@ use regex::Regex; fn main() { // Tell Cargo to rerun this build script if any SCSS file // in the 'src' directory or its subdirectories changes. - println!("cargo:rerun-if-changed=src/**/*.scss"); + println!("cargo:rerun-if-changed=src/ui/**/*.scss"); let out_dir = env::var("OUT_DIR").unwrap(); - let style_src_path = PathBuf::from("src/ui/_base.scss"); - let style_dst_path = Path::new(&out_dir).join("style_vars.rs"); + let mut tasks = Vec::new(); - export_color_variables(&style_src_path, &style_dst_path) + // Global tokens + tasks.push(Task::new( + PathBuf::from("src/ui/_base.scss"), + Path::new(&out_dir).join("style_tokens.rs"), + "style".to_string(), + )); + // variables defined by the Panel component + tasks.push(Task::new( + PathBuf::from("src/ui/components/_panel.scss"), + Path::new(&out_dir).join("style_component_panel.rs"), + "panel".to_string(), + )); + // Variables set by the Conversations layout + tasks.push(Task::new( + PathBuf::from("src/ui/layouts/conversations.scss"), + Path::new(&out_dir).join("style_layout_conversations.rs"), + "conversations".to_string(), + )); + + export_variables(tasks) } // From https://doc.rust-lang.org/rust-by-example/std_misc/file/read_lines.html @@ -32,14 +51,21 @@ where } #[derive(Debug)] -struct CssColorVariable<'a> { - name: &'a str, - value: &'a str, +struct ColorVariable { + name: String, + value: String, } -impl<'a> CssColorVariable<'a> { - pub fn to_rust(&self) -> String { - format!( +impl ColorVariable { + pub fn new(name: String, value: String) -> Self { + Self { name, value } + } +} + +impl Display for ColorVariable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, "const {name}: &str = \"{value}\";", name = self.name.replace('-', "_").to_uppercase(), value = self.value @@ -47,35 +73,83 @@ impl<'a> CssColorVariable<'a> { } } -fn export_color_variables(src_path: &PathBuf, dst_path: &PathBuf) { - let mut dst_file = File::create(dst_path).unwrap(); - if let Err(err) = dst_file.write(b"#[allow(dead_code)]\nmod style {") { - println!("{}", err); - return; - }; +#[derive(Debug)] +struct FloatVariable { + name: String, + value: f64, +} - let re = Regex::new(r"^\$([^:]+):[[:space:]]*#([^$]+);[[:space:]]*$").unwrap(); +impl FloatVariable { + pub fn new(name: String, value: f64) -> Self { + Self { name, value } + } +} - if let Ok(lines) = read_lines(src_path) { - for line in lines.map_while(Result::ok) { - let Some(groups) = re.captures(&line) else { - continue; - }; +impl Display for FloatVariable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "const {name}: f64 = {value};", + name = self.name.replace('-', "_").to_uppercase(), + value = self.value + ) + } +} - let var = CssColorVariable { - name: &groups[1], - value: &groups[2], - }; +struct Task { + src_path: PathBuf, + dst_path: PathBuf, + module_name: String, +} - let rust_export = var.to_rust(); - if let Err(err) = dst_file.write_fmt(format_args!(" pub {}\n", rust_export)) { +impl Task { + pub fn new(src_path: PathBuf, dst_path: PathBuf, module_name: String) -> Self { + Self { + src_path, + dst_path, + module_name, + } + } +} + +// fn export_variables(src_path: &PathBuf, dst_path: &PathBuf) { +fn export_variables(tasks: Vec) { + let color_re = Regex::new(r"^\$([^:]+):[[:space:]]*#([^$]+);[[:space:]]*$").unwrap(); + let variable_re = Regex::new(r"^\$([^:]+):[[:space:]]*([^;]+)[[:space:]]*;").unwrap(); + + for task in tasks { + let mut dst_file = File::create(task.dst_path).unwrap(); + if let Err(err) = dst_file.write_fmt(format_args!( + "#[allow(dead_code)]\nmod {} {{\n", + task.module_name + )) { + println!("{}", err); + return; + }; + + let mut variables = Vec::>::new(); + if let Ok(lines) = read_lines(task.src_path) { + for line in lines.map_while(Result::ok) { + if let Some(groups) = color_re.captures(&line) { + let var = ColorVariable::new(groups[1].to_string(), groups[2].to_string()); + variables.push(Box::new(var)); + } else if let Some(groups) = variable_re.captures(&line) { + if let Ok(value) = groups[2].parse::() { + variables.push(Box::new(FloatVariable::new(groups[1].to_string(), value))); + } + } + } + } + + for variable in variables { + if let Err(err) = dst_file.write_fmt(format_args!(" pub {}\n", variable)) { println!("{}", err); break; } } - } - if let Err(err) = dst_file.write(b"}\n") { - println!("{}", err); - }; + if let Err(err) = dst_file.write(b"}\n") { + println!("{}", err); + }; + } } diff --git a/src/ui/components/_panel.scss b/src/ui/components/_panel.scss index 9dc3efd..088acbf 100644 --- a/src/ui/components/_panel.scss +++ b/src/ui/components/_panel.scss @@ -1,6 +1,6 @@ @import "../base.scss"; -$panel-aspect-ratio: 1/1.618; +$aspect-ratio: 0.618; // 1/1.618; @mixin panel($padding-v: 2%, $padding-h: 2%) { padding: $padding-v $padding-h; diff --git a/src/ui/components/icons.rs b/src/ui/components/icons.rs index 99fcb3d..7a14764 100644 --- a/src/ui/components/icons.rs +++ b/src/ui/components/icons.rs @@ -7,7 +7,7 @@ use dioxus_free_icons::{Icon, IconShape}; turf::style_sheet!("src/ui/components/icons.scss"); -include!(concat!(env!("OUT_DIR"), "/style_vars.rs")); +include!(concat!(env!("OUT_DIR"), "/style_tokens.rs")); use style::{COLOR_PRIMARY_100, COLOR_TERNARY_100}; diff --git a/src/ui/components/login.rs b/src/ui/components/login.rs index fce17f0..b6f967e 100644 --- a/src/ui/components/login.rs +++ b/src/ui/components/login.rs @@ -19,7 +19,7 @@ use super::{ text_input::{PasswordInputState, PasswordTextInput, TextInput, TextInputState}, }; -include!(concat!(env!("OUT_DIR"), "/style_vars.rs")); +include!(concat!(env!("OUT_DIR"), "/style_tokens.rs")); use style::{ COLOR_PRIMARY_100, COLOR_PRIMARY_110, COLOR_PRIMARY_120, COLOR_PRIMARY_140, COLOR_PRIMARY_150, diff --git a/src/ui/components/modal.rs b/src/ui/components/modal.rs index 483c8ea..1b5b764 100644 --- a/src/ui/components/modal.rs +++ b/src/ui/components/modal.rs @@ -9,7 +9,7 @@ use crate::infrastructure::services::random_svg_generators::{ generate_random_svg_avatar, AvatarConfig, AvatarFeeling, }; -include!(concat!(env!("OUT_DIR"), "/style_vars.rs")); +include!(concat!(env!("OUT_DIR"), "/style_tokens.rs")); use style::{COLOR_CRITICAL_100, COLOR_SUCCESS_100, COLOR_WARNING_100}; diff --git a/src/ui/layouts/conversations.rs b/src/ui/layouts/conversations.rs index ee9c37d..5741189 100644 --- a/src/ui/layouts/conversations.rs +++ b/src/ui/layouts/conversations.rs @@ -3,15 +3,18 @@ use std::rc::Rc; use dioxus::prelude::*; use futures::join; +use crate::ui::components::{ + chat_panel::ChatPanel, conversations::Conversations as ConversationsComponent, + wallpaper::Wallpaper, +}; + turf::style_sheet!("src/ui/layouts/conversations.scss"); -use crate::ui::components::chat_panel::ChatPanel; -use crate::ui::components::conversations::Conversations as ConversationsComponent; -use crate::ui::components::wallpaper::Wallpaper; +include!(concat!(env!("OUT_DIR"), "/style_component_panel.rs")); +include!(concat!(env!("OUT_DIR"), "/style_layout_conversations.rs")); -// TODO: Get from SCSS -const WIDGET_HEIGHT_RATIO: f64 = 0.95; -const ASPECT_RATIO: f64 = 1.0 / 1.618; +use conversations::INNER_PANEL_HEIGHT_RATIO; +use panel::ASPECT_RATIO; async fn on_carousel_scroll( parent_div: &Rc, @@ -229,14 +232,13 @@ pub fn Conversations() -> Element { if let Ok(size) = data.get_border_box_size() { if let Some(size) = size.first() { // Use LayoutBig if the layout can contain 2 panels side by side - let component_width = size.height * WIDGET_HEIGHT_RATIO * ASPECT_RATIO; + let component_width = size.height * INNER_PANEL_HEIGHT_RATIO * ASPECT_RATIO; let breakpoint_width = component_width * 2_f64; use_big_layout = size.width > breakpoint_width; } } layout.set(rsx! { if use_big_layout { LayoutBig {} } else { LayoutSmall {} }}); - }, {layout} diff --git a/src/ui/layouts/conversations.scss b/src/ui/layouts/conversations.scss index 8844a62..146f19c 100644 --- a/src/ui/layouts/conversations.scss +++ b/src/ui/layouts/conversations.scss @@ -17,11 +17,13 @@ scroll-snap-align: start; } +$inner-panel-height-ratio: 0.95; + .conversations-view { $height: 100vh; $width: 100vw; - $conversations-panel-height: calc($height * 0.95); - $conversations-panel-width: calc($conversations-panel-height * $panel-aspect-ratio); + $conversations-panel-height: calc($height * $inner-panel-height-ratio); + $conversations-panel-width: calc($conversations-panel-height * $aspect-ratio); $gap: 1%; $content-height: 95%; $ratio: 2; @@ -63,11 +65,11 @@ height: 100%; // TODO: Is aspect-ratio the best criteria to defined that inner shall take all the available space ? - @media (max-aspect-ratio: $panel-aspect-ratio) { + @media (max-aspect-ratio: $aspect-ratio) { width: 100%; } - @media (min-aspect-ratio: $panel-aspect-ratio) { - aspect-ratio: $panel-aspect-ratio; + @media (min-aspect-ratio: $aspect-ratio) { + aspect-ratio: $aspect-ratio; } } } @@ -105,7 +107,7 @@ &__conversations-panel { height: $content-height; - aspect-ratio: $panel-aspect-ratio; + aspect-ratio: $aspect-ratio; } &__conversation-panels { diff --git a/src/ui/layouts/login.scss b/src/ui/layouts/login.scss index 4b40530..5dde0b4 100644 --- a/src/ui/layouts/login.scss +++ b/src/ui/layouts/login.scss @@ -18,14 +18,14 @@ align-items: safe center; &__login-panel { - @media (max-aspect-ratio: $panel-aspect-ratio) { + @media (max-aspect-ratio: $aspect-ratio) { width: 95%; } - @media (min-aspect-ratio: $panel-aspect-ratio) { + @media (min-aspect-ratio: $aspect-ratio) { height: 100%; } - aspect-ratio: $panel-aspect-ratio; + aspect-ratio: $aspect-ratio; max-height: $panel-max-height; flex-shrink: 0; @@ -36,6 +36,6 @@ justify-content: center; // Variables inherited by children - --aspect-ratio: #{$panel-aspect-ratio}; + --aspect-ratio: #{$aspect-ratio}; } } From 9baa7f290ae47783d6f056adf97c53b6bf72ac00 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 9 Jun 2024 09:43:46 +0200 Subject: [PATCH 04/20] =?UTF-8?q?=F0=9F=92=84=20Open/close=20ChatPanel=20o?= =?UTF-8?q?n=20click?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/components/conversations.rs | 7 +++ src/ui/layouts/conversations.rs | 70 +++++++++++++++++++----------- src/ui/store/mod.rs | 12 +++++ 3 files changed, 63 insertions(+), 26 deletions(-) diff --git a/src/ui/components/conversations.rs b/src/ui/components/conversations.rs index 597c2fe..0cc0029 100644 --- a/src/ui/components/conversations.rs +++ b/src/ui/components/conversations.rs @@ -306,6 +306,9 @@ pub fn Space(id: SpaceId) -> Element { let on_selected_conversation = move |room_id: RoomId| { trace!(""); + + STORE.write().on_selected_room(room_id.clone()); + selected_room_id.set(Some(room_id)); }; @@ -380,6 +383,10 @@ pub fn HomeSpace() -> Element { }); let on_selected_conversation = move |room_id: RoomId| { + trace!(""); + + STORE.write().on_selected_room(room_id.clone()); + selected_room_id.set(Some(room_id)); }; diff --git a/src/ui/layouts/conversations.rs b/src/ui/layouts/conversations.rs index 5741189..0c6fbf3 100644 --- a/src/ui/layouts/conversations.rs +++ b/src/ui/layouts/conversations.rs @@ -2,10 +2,14 @@ use std::rc::Rc; use dioxus::prelude::*; use futures::join; +use tracing::{error, warn}; -use crate::ui::components::{ - chat_panel::ChatPanel, conversations::Conversations as ConversationsComponent, - wallpaper::Wallpaper, +use crate::ui::{ + components::{ + chat_panel::ChatPanel, conversations::Conversations as ConversationsComponent, + wallpaper::Wallpaper, + }, + STORE, }; turf::style_sheet!("src/ui/layouts/conversations.scss"); @@ -47,36 +51,50 @@ async fn on_carousel_scroll( } fn LayoutSmall() -> Element { - let mut carousel_div = use_signal(|| None::>); let mut first_div = use_signal(|| None::>); let mut last_div = use_signal(|| None::>); + let mut carousel_div = use_signal(|| None::>); - let conversation_panels_nb = 3; - let conversation_panels = (0..conversation_panels_nb + 1).map(|i| { - let inner = rsx! { - div { - class: ClassName::CONVERSATIONS_VIEW_SMALL_PANEL_INNER, - ChatPanel { name: format!("CHAT #{i}") }, - } - }; + let displayed_room_ids = STORE.read().displayed_room_ids(); - if i == conversation_panels_nb { - rsx! { - div { - class: ClassName::CONVERSATIONS_VIEW_SMALL_PANEL, - onmounted: move |cx: Event| last_div.set(Some(cx.data())), - {inner} + let mut conversation_panels = Vec::new(); + let mut displayed_room_ids_it = displayed_room_ids.iter().peekable(); + while let Some(room_id) = displayed_room_ids_it.next() { + if let Some(room) = STORE.read().rooms().get(room_id) { + let room = room.signal(); + let room_name_repr = room.name().unwrap_or(room.id().to_string()); + let inner = rsx! { + div { + class: ClassName::CONVERSATIONS_VIEW_SMALL_PANEL_INNER, + ChatPanel { name: format!("CHAT {room_name_repr}") }, + } + }; + + // If this is the last iteration + let panel = if displayed_room_ids_it.peek().is_none() { + rsx! { + div { + class: ClassName::CONVERSATIONS_VIEW_SMALL_PANEL, + onmounted: move |cx: Event| last_div.set(Some(cx.data())), + {inner} + } } + } else { + rsx! { + div { + class: ClassName::CONVERSATIONS_VIEW_SMALL_PANEL, + {inner} + } + } + }; + + if let Some(panel) = panel { + conversation_panels.push(panel); } } else { - rsx! { - div { - class: ClassName::CONVERSATIONS_VIEW_SMALL_PANEL, - {inner} - } - } + warn!("No {} room found", room_id); } - }); + } rsx! { style { {STYLE_SHEET} }, @@ -119,7 +137,7 @@ fn LayoutSmall() -> Element { }, }, - {conversation_panels} + {conversation_panels.iter()} div { class: ClassName::CONVERSATIONS_VIEW_TAIL, diff --git a/src/ui/store/mod.rs b/src/ui/store/mod.rs index 208ae74..c929257 100644 --- a/src/ui/store/mod.rs +++ b/src/ui/store/mod.rs @@ -1,6 +1,7 @@ pub(crate) mod room; pub(crate) mod space; +use std::collections::HashSet; use std::{collections::HashMap, rc::Rc}; use async_trait::async_trait; @@ -21,6 +22,17 @@ use space::Space; pub struct Store { rooms: HashMap>, spaces: HashMap>, + + displayed_room_ids: HashSet, +} + +impl Store { + pub fn on_selected_room(&mut self, room_id: RoomId) { + // Toggle the room_id selection + if !self.displayed_room_ids.write().remove(&room_id) { + self.displayed_room_ids.write().insert(room_id); + } + } } #[async_trait(?Send)] From ffe759e749584f999de166df4132129fa4cbfad5 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 9 Jun 2024 11:22:48 +0200 Subject: [PATCH 05/20] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20Merge=20Space=20a?= =?UTF-8?q?nd=20HomeSpace=20components?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/components/conversations.rs | 120 ++++++----------------------- 1 file changed, 25 insertions(+), 95 deletions(-) diff --git a/src/ui/components/conversations.rs b/src/ui/components/conversations.rs index 0cc0029..6ca9c83 100644 --- a/src/ui/components/conversations.rs +++ b/src/ui/components/conversations.rs @@ -284,109 +284,39 @@ pub fn ConversationsCarousel(on_selected_conversation: EventHandler) -> } } +// If id is None, the Space will handle all the Conversation which have no parent (Space). #[component] -pub fn Space(id: SpaceId) -> Element { - let space = STORE.read().spaces().get(&id).unwrap().signal(); - - let name = space.name(); - +pub fn Space(id: Option) -> Element { 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 name = if let Some(id) = id { + let space = STORE.read().spaces().get(&id).unwrap().signal(); + use_effect(move || { + 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!(""); - - STORE.write().on_selected_room(room_id.clone()); - - selected_room_id.set(Some(room_id)); + }); + space.name() + } else { + 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); + } + } + }); + Some("Home".to_string()) }; - 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| { - trace!(""); - STORE.write().on_selected_room(room_id.clone()); - selected_room_id.set(Some(room_id)); }; @@ -461,7 +391,7 @@ pub fn Spaces() -> Element { {rendered_spaces}, - HomeSpace {}, + Space {}, } } } From 271e865d40899c1c9f3787af8e8e9398e9bad734 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 9 Jun 2024 12:31:18 +0200 Subject: [PATCH 06/20] =?UTF-8?q?=F0=9F=92=84=20Open/close=20ChatPanel=20o?= =?UTF-8?q?n=20click=20(Conversations=20layout/big=20breakpoint)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/layouts/conversations.rs | 71 ++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/src/ui/layouts/conversations.rs b/src/ui/layouts/conversations.rs index 0c6fbf3..8bb742e 100644 --- a/src/ui/layouts/conversations.rs +++ b/src/ui/layouts/conversations.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use dioxus::prelude::*; use futures::join; -use tracing::{error, warn}; +use tracing::warn; use crate::ui::{ components::{ @@ -151,37 +151,52 @@ fn LayoutBig() -> Element { let mut first_div = use_signal(|| None::>); let mut last_div = use_signal(|| None::>); - let conversation_panels_nb = 3; - let conversation_panels = (0..conversation_panels_nb + 1).map(|i| { - if i == 0 { - rsx! { - div { - class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATION_PANELS_PANEL, - onmounted: move |cx| async move { - let data = cx.data(); - let _ = data.as_ref().scroll_to(ScrollBehavior::Smooth).await; - first_div.set(Some(data)); - }, - ChatPanel { name: format!("CHAT #{i}") }, + let displayed_room_ids = STORE.read().displayed_room_ids(); + + let mut conversation_panels = Vec::new(); + let mut displayed_room_ids_it = displayed_room_ids.iter().peekable(); + let mut is_first = true; + while let Some(room_id) = displayed_room_ids_it.next() { + if let Some(room) = STORE.read().rooms().get(room_id) { + let room = room.signal(); + let room_name_repr = format!("CHAT {}", room.name().unwrap_or(room.id().to_string())); + let panel = if is_first { + is_first = false; + rsx! { + div { + class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATION_PANELS_PANEL, + onmounted: move |cx| async move { + let data = cx.data(); + let _ = data.as_ref().scroll_to(ScrollBehavior::Smooth).await; + first_div.set(Some(data)); + }, + ChatPanel { name: room_name_repr }, + } } - } - } else if i == conversation_panels_nb { - rsx! { - div { - class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATION_PANELS_PANEL, - onmounted: move |cx: Event| last_div.set(Some(cx.data())), - ChatPanel { name: format!("CHAT #{i}") }, + } else if displayed_room_ids_it.peek().is_none() { + rsx! { + div { + class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATION_PANELS_PANEL, + onmounted: move |cx: Event| last_div.set(Some(cx.data())), + ChatPanel { name: room_name_repr }, + } } + } else { + rsx! { + div { + class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATION_PANELS_PANEL, + ChatPanel { name: room_name_repr }, + } + } + }; + + if let Some(panel) = panel { + conversation_panels.push(panel); } } else { - rsx! { - div { - class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATION_PANELS_PANEL, - ChatPanel { name: format!("CHAT #{i}") }, - } - } + warn!("No {} room found", room_id); } - }); + } rsx! { style { {STYLE_SHEET} }, @@ -219,7 +234,7 @@ fn LayoutBig() -> Element { class: ClassName::CONVERSATIONS_VIEW_HEAD, } - {conversation_panels} + {conversation_panels.iter()} div { class: ClassName::CONVERSATIONS_VIEW_TAIL, From cea60ce69581ac7873ce34aa0429542a870ea025 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sat, 15 Jun 2024 16:19:20 +0200 Subject: [PATCH 07/20] =?UTF-8?q?=E2=9C=A8=20Add=20ui=20use=5Flong=5Fpress?= =?UTF-8?q?=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/hooks/mod.rs | 3 ++ src/ui/hooks/use_long_press.rs | 60 ++++++++++++++++++++++++++++++++++ src/ui/mod.rs | 1 + 3 files changed, 64 insertions(+) create mode 100644 src/ui/hooks/mod.rs create mode 100644 src/ui/hooks/use_long_press.rs diff --git a/src/ui/hooks/mod.rs b/src/ui/hooks/mod.rs new file mode 100644 index 0000000..4a7932f --- /dev/null +++ b/src/ui/hooks/mod.rs @@ -0,0 +1,3 @@ +pub use use_long_press::use_long_press; + +mod use_long_press; diff --git a/src/ui/hooks/use_long_press.rs b/src/ui/hooks/use_long_press.rs new file mode 100644 index 0000000..2044bdd --- /dev/null +++ b/src/ui/hooks/use_long_press.rs @@ -0,0 +1,60 @@ +use std::{cell::RefCell, time::Duration}; + +use async_std::task; +use dioxus::prelude::*; + +pub struct UseLongPress { + _timer: UseFuture, + pub handlers: Vec, +} + +pub fn use_long_press( + duration: Duration, + on_press: impl FnMut() + 'static, + on_long_press: impl FnMut() + 'static, +) -> UseLongPress { + let on_press = std::rc::Rc::new(RefCell::new(on_press)); + let on_press_cb = use_callback(move || { + let mut on_press = on_press.as_ref().borrow_mut(); + on_press(); + }); + + let on_long_press = std::rc::Rc::new(RefCell::new(on_long_press)); + let on_long_press_cb = use_callback(move || { + let mut on_long_press = on_long_press.as_ref().borrow_mut(); + on_long_press(); + }); + + let mut timer = use_future(move || async move { + task::sleep(duration).await; + on_long_press_cb.call(); + }); + + timer.cancel(); + + let selection_begin_cb = move |_: Event| { + timer.restart(); + }; + + let selection_end_cb = move |_: Event| { + if !timer.finished() { + timer.cancel(); + on_press_cb.call(); + } + }; + + let mut handlers = Vec::new(); + for event_name in ["onmousedown", "ontouchstart"] { + let value = dioxus_core::AttributeValue::listener(selection_begin_cb); + handlers.push(Attribute::new(event_name, value, None, false)); + } + for event_name in ["onmouseup", "ontouchend"] { + let value = dioxus_core::AttributeValue::listener(selection_end_cb); + handlers.push(Attribute::new(event_name, value, None, false)); + } + + UseLongPress { + _timer: timer, + handlers, + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index d3dc2dd..134b666 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod components; +pub(crate) mod hooks; pub(crate) mod layouts; pub(crate) mod store; From e55992aed5e27a4e24aef60b33ccbe9748db3c4a Mon Sep 17 00:00:00 2001 From: Adrien Date: Sat, 22 Jun 2024 20:52:08 +0200 Subject: [PATCH 08/20] =?UTF-8?q?=F0=9F=92=84=20Add=20Reject=20and=20Join?= =?UTF-8?q?=20buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/components/button.rs | 6 ++++++ src/ui/components/button.scss | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/src/ui/components/button.rs b/src/ui/components/button.rs index 225b29d..45fd74e 100644 --- a/src/ui/components/button.rs +++ b/src/ui/components/button.rs @@ -107,6 +107,12 @@ svg_text_button!(RegisterButton, REGISTER_BUTTON, RegisterText); svg_text_icon!(LoginText, "LOGIN"); svg_text_button!(LoginButton, LOGIN_BUTTON, LoginText); +svg_text_icon!(JoinText, "JOIN"); +svg_text_button!(JoinButton, JOIN_BUTTON, JoinText); + +svg_text_icon!(RejectText, "REJECT"); +svg_text_button!(RejectButton, REJECT_BUTTON, RejectText); + svg_text_icon!(SuccessText, "OK"); svg_text_button!(SuccessButton, SUCCESS_BUTTON, SuccessText); diff --git a/src/ui/components/button.scss b/src/ui/components/button.scss index e16ecd6..7c02291 100644 --- a/src/ui/components/button.scss +++ b/src/ui/components/button.scss @@ -53,6 +53,14 @@ @include button(secondary, 90); } +.join-button { + @include button(secondary, 90); +} + +.reject-button { + @include button(critical, 90); +} + .success-button { @include button(success, 100); } From f0463213cf8be3c4d8be2cce4e194f30e577a289 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sat, 22 Jun 2024 20:55:19 +0200 Subject: [PATCH 09/20] =?UTF-8?q?=F0=9F=9A=A7=20Use=20of=20the=20use=5Flon?= =?UTF-8?q?g=5Fpress=20to=20open=20a=20future=20room=20configuration=20men?= =?UTF-8?q?u?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/components/conversations.rs | 69 +++++++++++++++++++----------- src/ui/components/login.rs | 1 - 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/ui/components/conversations.rs b/src/ui/components/conversations.rs index 6ca9c83..93a86bb 100644 --- a/src/ui/components/conversations.rs +++ b/src/ui/components/conversations.rs @@ -1,12 +1,18 @@ +use std::{rc::Rc, time::Duration}; + use base64::{engine::general_purpose, Engine as _}; use dioxus::prelude::*; -use tracing::{debug, trace, warn}; +use tracing::{debug, 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}, + components::{ + button::{JoinButton, RejectButton}, + icons::{ChatsIcon, LogoIcon, RoomsIcon, SpacesIcon}, + }, + hooks::use_long_press, ACCOUNT, STORE, }, }; @@ -167,13 +173,16 @@ pub fn Account() -> Element { } #[component] -pub fn ConversationAvatar(room_id: RoomId, on_clicked: EventHandler) -> Element { +pub fn ConversationAvatar( + room_id: RoomId, + on_selected: EventHandler, + on_pressed: EventHandler, +) -> Element { + let long_press_duration = Duration::from_millis(500); + let rooms = STORE.read().rooms(); - let toto = rooms.get(&room_id).unwrap(); - - let room = toto.signal(); - - let room_id = room.id(); + let room = rooms.get(&room_id).unwrap().signal(); + let room_id = Rc::new(room_id); let room_name = room.name(); let selected_room_id = use_context::>>(); @@ -193,7 +202,7 @@ pub fn ConversationAvatar(room_id: RoomId, on_clicked: EventHandler) -> }; let is_selected = match selected_room_id.read().as_ref() { - Some(selected_room_id) => *selected_room_id == room_id, + Some(selected_room_id) => *selected_room_id == *room_id, None => false, }; @@ -232,14 +241,24 @@ pub fn ConversationAvatar(room_id: RoomId, on_clicked: EventHandler) -> ]; let classes_str = classes.join(" "); + let on_press = { + let room_id = room_id.clone(); + move || { + on_selected.call(room_id.as_ref().clone()); + } + }; + + let on_long_press = move || { + on_pressed.call(room_id.as_ref().clone()); + }; + + let long_press_hook = use_long_press(long_press_duration, on_press, on_long_press); + rsx! { div { class: "{classes_str}", - onclick: move |evt| { - on_clicked.call(room_id.clone()); - evt.stop_propagation(); - }, + ..long_press_hook.handlers, {avatar} {invited_badge} @@ -248,7 +267,10 @@ pub fn ConversationAvatar(room_id: RoomId, on_clicked: EventHandler) -> } #[component] -pub fn ConversationsCarousel(on_selected_conversation: EventHandler) -> Element { +pub fn ConversationsCarousel( + on_selected_conversation: EventHandler, + on_pressed_conversation: EventHandler, +) -> Element { let mut ordered_rooms = use_signal(Vec::::new); use_effect(move || { @@ -267,7 +289,8 @@ pub fn ConversationsCarousel(on_selected_conversation: EventHandler) -> rsx! { ConversationAvatar { room_id: room.clone(), - on_clicked: on_selected_conversation, + on_selected: on_selected_conversation, + on_pressed: on_pressed_conversation, } } }); @@ -286,7 +309,7 @@ pub fn ConversationsCarousel(on_selected_conversation: EventHandler) -> // If id is None, the Space will handle all the Conversation which have no parent (Space). #[component] -pub fn Space(id: Option) -> Element { +pub fn Space(id: Option, on_pressed_conversation: EventHandler) -> Element { let mut selected_room_id = use_context_provider(|| Signal::new(None::)); let mut displayed_rooms = use_context_provider(|| Signal::new(Vec::::new())); @@ -362,44 +385,42 @@ pub fn Space(id: Option) -> Element { }, ConversationsCarousel { on_selected_conversation, + on_pressed_conversation, }, div { class: ClassName::SPACE_CONVERSATION_NAME, p { {selected_room_name}, } - } + }, } } } -pub fn Spaces() -> Element { +#[component] +pub fn Spaces(on_pressed_conversation: EventHandler) -> 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() } + Space { id: id.clone(), on_pressed_conversation } } }); rsx! { - style { {STYLE_SHEET} }, - div { class: ClassName::SPACES, {rendered_spaces}, - Space {}, + Space { on_pressed_conversation }, } } } pub fn Search() -> Element { rsx! { - style { {STYLE_SHEET} }, - div { class: ClassName::SEARCH, diff --git a/src/ui/components/login.rs b/src/ui/components/login.rs index b6f967e..75f328f 100644 --- a/src/ui/components/login.rs +++ b/src/ui/components/login.rs @@ -619,7 +619,6 @@ pub fn Login() -> Element { }); if *spinner_animated.read() && SESSION.read().is_logged { - debug!("Stop spinner"); spinner_animated.set(false); } From 73c5b70ba8253f75ef05bcab6326b3a76c663024 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sat, 22 Jun 2024 20:57:44 +0200 Subject: [PATCH 10/20] =?UTF-8?q?=F0=9F=9A=A7=20First=20design=20template?= =?UTF-8?q?=20for=20ConversationOptionsMenu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/components/conversations.rs | 58 +++++++++++++- src/ui/components/conversations.scss | 111 +++++++++++++++++++++++---- 2 files changed, 154 insertions(+), 15 deletions(-) diff --git a/src/ui/components/conversations.rs b/src/ui/components/conversations.rs index 93a86bb..a0e4ad1 100644 --- a/src/ui/components/conversations.rs +++ b/src/ui/components/conversations.rs @@ -436,7 +436,60 @@ pub fn Search() -> Element { } } +#[component] +fn ConversationOptionsMenu(room_id: RoomId, on_close: EventHandler) -> Element { + rsx! { + div { + class: ClassName::CONVERSATION_OPTIONS_MENU, + + div { + class: ClassName::CONVERSATION_OPTIONS_MENU_INNER, + + div { + class: ClassName::CONVERSATION_OPTIONS_MENU_INNER_AVATAR, + } + + div { + class: ClassName::CONVERSATION_OPTIONS_MENU_INNER_NAME, + } + + div { + class: ClassName::CONVERSATION_OPTIONS_MENU_INNER_TOPIC, + } + + div { + class: ClassName::CONVERSATION_OPTIONS_MENU_INNER_CONFIG, + } + + div { + class: ClassName::CONVERSATION_OPTIONS_MENU_INNER_CLOSE_BUTTON, + RejectButton { + onclick: move |_| on_close(()), + } + } + + div { + class: ClassName::CONVERSATION_OPTIONS_MENU_INNER_JOIN_BUTTON, + JoinButton { + onclick: move |_| on_close(()), + } + } + } + } + } +} + pub fn Conversations() -> Element { + let mut conversation_options_menu = use_signal(|| None::); + + let on_conversation_options_menu_close = move |_| { + conversation_options_menu.set(None); + }; + + let on_pressed_conversation = move |room_id: RoomId| { + conversation_options_menu.set(Some(rsx! { ConversationOptionsMenu { room_id, on_close: on_conversation_options_menu_close } })); + }; + rsx! { style { {STYLE_SHEET} }, @@ -450,13 +503,14 @@ pub fn Conversations() -> Element { div { class: ClassName::CONVERSATIONS_SPACES, - Spaces {}, + Spaces { on_pressed_conversation }, }, div { class: ClassName::CONVERSATIONS_SEARCH, Search {}, }, - } + }, + {conversation_options_menu} } } diff --git a/src/ui/components/conversations.scss b/src/ui/components/conversations.scss index c565a92..81621cc 100644 --- a/src/ui/components/conversations.scss +++ b/src/ui/components/conversations.scss @@ -10,6 +10,10 @@ } } +@mixin extra-marged-button() { + @include button-class(); +} + .account { $colum-spacing: 5%; $col-width: 8.75%; @@ -89,10 +93,6 @@ grid-area: status; } - @mixin extra-marged-button() { - @include button-class(); - } - &__spaces { grid-area: spaces; @@ -284,22 +284,107 @@ $account-height: 15%; $search-height: 5%; - display: flex; - flex-direction: column; - justify-content: space-between; - gap: $gap; + display: grid; + grid-template-columns: auto; + grid-template-rows: min($account-height, 384px) $gap auto $gap min($search-height, 128px); + grid-template-areas: + "account" + "." + "spaces" + "." + "search" + ; &__account { - height: $account-height; - max-height: 384px; + grid-area: account; } &__spaces { - min-height: calc(100% - $account-height - $search-height - (2 * $gap)); + grid-area: spaces; } &__search { - height: $search-height; - max-height: 128px; + grid-area: search; + } + + &__menu { + grid-area: spaces; + z-index: 1; + } +} + +.conversation-options-menu { + width: 100%; + height: 100%; + + position: relative; + top: -100%; + margin-bottom: calc(-100% / $aspect-ratio); + + border-radius: $border-radius; + + display: flex; + align-items: center; + justify-content: center; + + background: rgba(0, 0, 0, 0.5); + + &__inner { + $padding: 5%; + // TODO: Thin border + @include panel($padding, $padding); + + width: 95%; + height: 60%; + + display: grid; + grid-template-columns: 10% 10% 5% 15% 20% 10% 20% 10%; + grid-template-rows: 7.5% 7.5% 5% 5% auto 5% 10%; + grid-template-areas: + "avatar avatar . name name name name name" + "avatar avatar . topic topic topic topic topic" + "avatar avatar . . . . . ." + ". . . . . . . ." + "config config config config config config config config" + ". . . . . . . ." + ". close close close . join join ." + ; + + &__avatar { + grid-area: avatar; + + width: 100%; + aspect-ratio: 1; + + background-color: green; + } + + &__name { + grid-area: name; + background-color: orange; + } + + &__topic { + grid-area: topic; + background-color: aqua; + } + + &__config { + grid-area: config; + background-color: purple; + } + + button { + height: 100%; + width: 100%; + } + + &__close-button { + grid-area: close; + } + + &__join-button { + grid-area: join; + } } } From d5d996eec3f210219e1822e88a9b4839e7ce3b8e Mon Sep 17 00:00:00 2001 From: Adrien Date: Thu, 27 Jun 2024 08:25:11 +0200 Subject: [PATCH 11/20] =?UTF-8?q?=E2=9C=A8=20Add=20topic=20to=20the=20UI?= =?UTF-8?q?=20store=20Room?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/model/room.rs | 9 ++++----- src/domain/model/store_interface.rs | 2 ++ src/ui/store/room.rs | 10 +++++++++- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/domain/model/room.rs b/src/domain/model/room.rs index d9089a2..119d8e5 100644 --- a/src/domain/model/room.rs +++ b/src/domain/model/room.rs @@ -123,11 +123,6 @@ impl Room { self.name.borrow().clone() } - #[allow(dead_code)] - pub fn topic(&self) -> &Option { - &self.topic - } - #[allow(dead_code)] pub fn set_topic(&mut self, topic: Option) { self.topic = topic; @@ -308,6 +303,10 @@ impl RoomStoreConsumerInterface for Room { self.name.borrow().clone() } + fn topic(&self) -> Option { + self.topic.clone() + } + async fn avatar(&self) -> Option { self.get_avatar().await } diff --git a/src/domain/model/store_interface.rs b/src/domain/model/store_interface.rs index 3cc5747..10aad7a 100644 --- a/src/domain/model/store_interface.rs +++ b/src/domain/model/store_interface.rs @@ -28,6 +28,7 @@ pub trait RoomStoreConsumerInterface { fn id(&self) -> &RoomId; fn is_direct(&self) -> Option; fn name(&self) -> Option; + fn topic(&self) -> Option; #[allow(dead_code)] async fn avatar(&self) -> Option; @@ -38,6 +39,7 @@ pub trait RoomStoreConsumerInterface { pub trait RoomStoreProviderInterface { fn on_new_name(&self, name: Option); fn on_new_avatar(&self, avatar: Option); + fn on_new_topic(&self, topic: Option); fn on_new_member(&self, member: RoomMember); fn on_invitation(&self, invitation: Invitation); } diff --git a/src/ui/store/room.rs b/src/ui/store/room.rs index 413ec93..f65e37d 100644 --- a/src/ui/store/room.rs +++ b/src/ui/store/room.rs @@ -12,13 +12,15 @@ use crate::domain::model::{ store_interface::{RoomStoreConsumerInterface, RoomStoreProviderInterface}, }; -#[modx::props(id, is_direct, name, spaces)] +#[modx::props(id, is_direct, name, topic, spaces)] #[modx::store] pub struct Store { id: RoomId, is_direct: Option, name: Option, + topic: Option, + avatar: Option, members: Vec, invitations: Vec, @@ -45,6 +47,7 @@ impl Room { room.id().clone(), room.is_direct(), room.name(), + room.topic(), room.spaces().clone(), ); @@ -71,6 +74,11 @@ impl RoomStoreProviderInterface for Room { store.avatar.set(avatar); } + fn on_new_topic(&self, topic: Option) { + let mut store = self.store.borrow_mut(); + store.topic.set(topic); + } + fn on_new_member(&self, member: RoomMember) { let mut store = self.store.borrow_mut(); store.members.write().push(member); From 9a5f7ae50476fa5546b02bc2fe1384a389cae201 Mon Sep 17 00:00:00 2001 From: Adrien Date: Wed, 21 Aug 2024 22:57:34 +0200 Subject: [PATCH 12/20] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20=20Use=20of=20Dioxus?= =?UTF-8?q?=20main=20branch=20instead=20of=200.5=20release?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 17 +- src/infrastructure/messaging/matrix/client.rs | 57 ++++--- src/ui/components/conversations.rs | 157 +++++++++++------- src/ui/components/conversations.scss | 60 ++++++- src/ui/components/modal.rs | 24 +-- src/ui/components/text_input.rs | 14 +- src/ui/components/text_input.scss | 2 +- src/ui/hooks/use_long_press.rs | 8 +- src/ui/layouts/conversations.rs | 121 +++++++------- src/ui/layouts/conversations.scss | 45 +++-- 10 files changed, 318 insertions(+), 187 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0050bd0..93f6817 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,30 +43,35 @@ tracing-forest = "0.1.6" turf = "0.8.0" # Dioxus -dioxus = { version = "0.5", default-features = false } -dioxus-free-icons = { version = "0.8", features = ["ionicons", "font-awesome-solid"] } +# dioxus-free-icons = { version = "0.8", features = ["ionicons", "font-awesome-solid"] } +dioxus-free-icons = { git = "https://github.com/ASR-ASU/dioxus-free-icons.git", branch = "asr/dioxus-0.6", features = ["ionicons", "font-awesome-solid"] } modx = "0.1.2" -[patch.crates-io] -dioxus = { git = "https://github.com/DioxusLabs/dioxus.git" } +# Matrix rich text editor +wysiwyg = { path = "../matrix.org/matrix-rich-text-editor/crates/wysiwyg/" } [target.'cfg(target_family = "wasm")'.dependencies] # Logging/tracing tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-web = "0.1.3" + # Dioxus -dioxus = { features = ["web"] } +dioxus = { git = "https://github.com/DioxusLabs/dioxus.git", branch = "main", features = ["web"] } web-sys = "0.3.69" + # Matrix matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", default-features = false, features = ["rustls-tls", "js"] } [target.'cfg(not(target_family = "wasm"))'.dependencies] # Utils time = "0.3.36" + # Logging/tracing tracing-subscriber = { version = "0.3.18", features = ["env-filter", "time"] } + # Dioxus -dioxus = { features = ["desktop"] } +dioxus = { git = "https://github.com/DioxusLabs/dioxus.git", branch = "main", features = ["desktop"] } + # Matrix matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", default-features = false, features = ["rustls-tls"] } diff --git a/src/infrastructure/messaging/matrix/client.rs b/src/infrastructure/messaging/matrix/client.rs index 008ce76..7942580 100644 --- a/src/infrastructure/messaging/matrix/client.rs +++ b/src/infrastructure/messaging/matrix/client.rs @@ -9,7 +9,7 @@ use dioxus::prelude::Task; use matrix_sdk::{ config::SyncSettings, event_handler::Ctx, - media::{MediaFormat, MediaRequest, MediaThumbnailSize}, + media::{MediaFormat, MediaRequest, MediaThumbnailSettings, MediaThumbnailSize}, room::{ParentSpace, Room}, ruma::{ api::client::media::get_content_thumbnail::v3::Method, @@ -448,10 +448,13 @@ impl Client { async fn on_room_avatar_event(room: &Room, senders: &Ctx) { let room_id = room.room_id(); let avatar = match room - .avatar(MediaFormat::Thumbnail(MediaThumbnailSize { - method: Method::Scale, - width: uint!(256), - height: uint!(256), + .avatar(MediaFormat::Thumbnail(MediaThumbnailSettings { + size: MediaThumbnailSize { + method: Method::Scale, + width: uint!(256), + height: uint!(256), + }, + animated: false, })) .await { @@ -668,10 +671,13 @@ impl Client { match client .account() - .get_avatar(MediaFormat::Thumbnail(MediaThumbnailSize { - method: Method::Scale, - width: uint!(256), - height: uint!(256), + .get_avatar(MediaFormat::Thumbnail(MediaThumbnailSettings { + size: MediaThumbnailSize { + method: Method::Scale, + width: uint!(256), + height: uint!(256), + }, + animated: false, })) .await { @@ -685,10 +691,13 @@ impl Client { if let Some(room) = client.get_room(room_id) { match room - .avatar(MediaFormat::Thumbnail(MediaThumbnailSize { - method: Method::Scale, - width: uint!(256), - height: uint!(256), + .avatar(MediaFormat::Thumbnail(MediaThumbnailSettings { + size: MediaThumbnailSize { + method: Method::Scale, + width: uint!(256), + height: uint!(256), + }, + animated: false, })) .await { @@ -709,10 +718,13 @@ impl Client { let request = MediaRequest { source: MediaSource::Plain(media_url), - format: MediaFormat::Thumbnail(MediaThumbnailSize { - method: Method::Scale, - width: uint!(256), - height: uint!(256), + format: MediaFormat::Thumbnail(MediaThumbnailSettings { + size: MediaThumbnailSize { + method: Method::Scale, + width: uint!(256), + height: uint!(256), + }, + animated: false, }), }; @@ -739,10 +751,13 @@ impl Client { Ok(room_member) => { if let Some(room_member) = room_member { let res = match room_member - .avatar(MediaFormat::Thumbnail(MediaThumbnailSize { - method: Method::Scale, - width: uint!(256), - height: uint!(256), + .avatar(MediaFormat::Thumbnail(MediaThumbnailSettings { + size: MediaThumbnailSize { + method: Method::Scale, + width: uint!(256), + height: uint!(256), + }, + animated: false, })) .await { diff --git a/src/ui/components/conversations.rs b/src/ui/components/conversations.rs index a0e4ad1..2ea9917 100644 --- a/src/ui/components/conversations.rs +++ b/src/ui/components/conversations.rs @@ -21,18 +21,13 @@ 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})") - } + rsx! { + if let Some(content) = content { + div { + class: class_name, + background_image: format!("url(data:image/jpeg;base64,{})", general_purpose::STANDARD.encode(content)) } } - // TODO: Manage acount without avatar - None => None, } } @@ -54,11 +49,11 @@ fn PresenceState(state: Option, class_name: Option) rsx! { div { class: classes, - LogoIcon {}, + LogoIcon {} } } } - None => None, + None => VNode::empty(), } } @@ -70,12 +65,12 @@ fn DisplayName(display_name: Option, class_name: Option) -> Elem div { class: class_name, p { - {display_name}, + {display_name} } } } } - None => None, + None => VNode::empty(), } } @@ -137,37 +132,35 @@ pub fn Account() -> Element { }); rsx! { - style { {STYLE_SHEET} }, - div { class: ClassName::ACCOUNT, - {avatar}, - {presence_state}, - {display_name}, + {avatar} + {presence_state} + {display_name} - {status}, + {status} div { class: ClassName::ACCOUNT_SPACES, Button { - SpacesIcon {}, + SpacesIcon {} } - }, + } div { class: ClassName::ACCOUNT_CHAT, Button { - ChatsIcon {}, + ChatsIcon {} } - }, + } div { class: ClassName::ACCOUNT_ROOM, Button { - RoomsIcon {}, + RoomsIcon {} } - }, + } } } } @@ -175,8 +168,8 @@ pub fn Account() -> Element { #[component] pub fn ConversationAvatar( room_id: RoomId, - on_selected: EventHandler, - on_pressed: EventHandler, + on_selected: Option>, + on_pressed: Option>, ) -> Element { let long_press_duration = Duration::from_millis(500); @@ -185,7 +178,7 @@ pub fn ConversationAvatar( let room_id = Rc::new(room_id); let room_name = room.name(); - let selected_room_id = use_context::>>(); + let selected_room_id = use_signal(|| None::); let invited_badge = if room.is_invited() { rsx! { @@ -193,12 +186,12 @@ pub fn ConversationAvatar( class: ClassName::CONVERSATION_AVATAR_INVITED_BADGE, p { - "Invited", + "Invited" } } } } else { - None + VNode::empty() }; let is_selected = match selected_room_id.read().as_ref() { @@ -206,7 +199,6 @@ pub fn ConversationAvatar( 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! { @@ -230,7 +222,7 @@ pub fn ConversationAvatar( div { class: ClassName::CONVERSATION_AVATAR_IMAGE, - {placeholder}, + {placeholder} } } }; @@ -244,12 +236,12 @@ pub fn ConversationAvatar( let on_press = { let room_id = room_id.clone(); move || { - on_selected.call(room_id.as_ref().clone()); + on_selected.map(|c| c.call(room_id.as_ref().clone())); } }; let on_long_press = move || { - on_pressed.call(room_id.as_ref().clone()); + on_pressed.map(|c| c.call(room_id.as_ref().clone())); }; let long_press_hook = use_long_press(long_press_duration, on_press, on_long_press); @@ -302,7 +294,7 @@ pub fn ConversationsCarousel( onscroll: move |_| { // Catch scrolling events. }, - {rendered_avatars}, + {rendered_avatars} } } } @@ -367,8 +359,6 @@ pub fn Space(id: Option, on_pressed_conversation: EventHandler) let classes_str = space_classes.join(" "); rsx! { - style { {STYLE_SHEET} }, - div { class: "{classes_str}", @@ -382,17 +372,17 @@ pub fn Space(id: Option, on_pressed_conversation: EventHandler) p { {name} } - }, + } ConversationsCarousel { on_selected_conversation, on_pressed_conversation, - }, + } div { class: ClassName::SPACE_CONVERSATION_NAME, p { - {selected_room_name}, + {selected_room_name} } - }, + } } } } @@ -412,19 +402,24 @@ pub fn Spaces(on_pressed_conversation: EventHandler) -> Element { div { class: ClassName::SPACES, - {rendered_spaces}, + {rendered_spaces} - Space { on_pressed_conversation }, + Space { on_pressed_conversation } } } } +#[component] pub fn Search() -> Element { rsx! { div { class: ClassName::SEARCH, - TextInput {} + div { + class: ClassName::SEARCH_TEXT, + + TextInput {} + } div { class: ClassName::SEARCH_BUTTON, @@ -437,7 +432,15 @@ pub fn Search() -> Element { } #[component] -fn ConversationOptionsMenu(room_id: RoomId, on_close: EventHandler) -> Element { +fn ConversationOptionsMenu( + room_id: RoomId, + on_close: EventHandler, + on_join: EventHandler, +) -> Element { + let room = STORE.read().rooms().get(&room_id).unwrap().signal(); + + let topic = room.topic().unwrap_or("".to_string()); + rsx! { div { class: ClassName::CONVERSATION_OPTIONS_MENU, @@ -447,31 +450,44 @@ fn ConversationOptionsMenu(room_id: RoomId, on_close: EventHandler) -> Element { div { class: ClassName::CONVERSATION_OPTIONS_MENU_INNER_AVATAR, + ConversationAvatar { room_id } } div { class: ClassName::CONVERSATION_OPTIONS_MENU_INNER_NAME, + p { + {room.name()} + } } div { class: ClassName::CONVERSATION_OPTIONS_MENU_INNER_TOPIC, + p { + {topic} + } } div { class: ClassName::CONVERSATION_OPTIONS_MENU_INNER_CONFIG, + p { + "Coming soon..." + } } div { class: ClassName::CONVERSATION_OPTIONS_MENU_INNER_CLOSE_BUTTON, RejectButton { - onclick: move |_| on_close(()), + onclick: move |_| on_close(()) } } div { class: ClassName::CONVERSATION_OPTIONS_MENU_INNER_JOIN_BUTTON, JoinButton { - onclick: move |_| on_close(()), + onclick: move |_| { + on_join(()); + on_close(()); + } } } } @@ -480,37 +496,54 @@ fn ConversationOptionsMenu(room_id: RoomId, on_close: EventHandler) -> Element { } pub fn Conversations() -> Element { - let mut conversation_options_menu = use_signal(|| None::); + let mut room_id = use_signal(|| None::); - let on_conversation_options_menu_close = move |_| { - conversation_options_menu.set(None); + let on_menu_close = move |_| { + room_id.set(None); }; - let on_pressed_conversation = move |room_id: RoomId| { - conversation_options_menu.set(Some(rsx! { ConversationOptionsMenu { room_id, on_close: on_conversation_options_menu_close } })); + let on_menu_join = move |_| async move { + let rooms = STORE.read().rooms(); + if let Some(room_id) = room_id.read().to_owned() { + if let Some(room) = rooms.get(&room_id) {} + } + }; + + let on_pressed_conversation = move |id: RoomId| { + room_id.set(Some(id)); + }; + + let menu = match room_id.read().as_ref() { + Some(room_id) => { + let room_id = room_id.clone(); + rsx! { + ConversationOptionsMenu { room_id, on_close: on_menu_close, on_join: on_menu_join } + } + } + None => VNode::empty(), }; rsx! { - style { {STYLE_SHEET} }, + style { {STYLE_SHEET} } div { class: ClassName::CONVERSATIONS, div { class: ClassName::CONVERSATIONS_ACCOUNT, - Account {}, - }, + Account {} + } div { class: ClassName::CONVERSATIONS_SPACES, - Spaces { on_pressed_conversation }, - }, + Spaces { on_pressed_conversation } + } div { class: ClassName::CONVERSATIONS_SEARCH, - Search {}, - }, - }, - {conversation_options_menu} + Search {} + } + } + {menu} } } diff --git a/src/ui/components/conversations.scss b/src/ui/components/conversations.scss index 81621cc..847b0ff 100644 --- a/src/ui/components/conversations.scss +++ b/src/ui/components/conversations.scss @@ -268,6 +268,11 @@ display: flex; gap: 5%; + &__text { + height: 100%; + width: 100%; + } + &__button { @include button-class(); @@ -313,6 +318,24 @@ } } +%base-helper-text { + margin: 0; + margin-top: 0.3vh; + + font-size: 1.2vh; + + // TODO: Set color used for text in _base.scss file + color: get-color(greyscale, 90); + + p { + margin: 0; + + &.invalid { + color: get-color(critical, 100); + } + } +} + .conversation-options-menu { width: 100%; height: 100%; @@ -355,23 +378,50 @@ width: 100%; aspect-ratio: 1; - - background-color: green; } &__name { grid-area: name; - background-color: orange; + + // TODO: Merge with &__display-name + display: flex; + align-items: center; + justify-content: center; + + p { + font-size: 2.5vh; + margin: 0; + text-align: center; + } } &__topic { grid-area: topic; - background-color: aqua; + + // TODO: Merge with &__display-name + display: flex; + align-items: center; + justify-content: center; + + @extend %base-helper-text; + p { + font-size: 2vh; + margin: 0; + text-align: center; + } } &__config { grid-area: config; - background-color: purple; + + // TODO: Merge with &__display-name + display: flex; + align-items: center; + justify-content: center; + + border: $border-thin; + border-color: get-color(ternary, 90); + border-radius: $border-radius; } button { diff --git a/src/ui/components/modal.rs b/src/ui/components/modal.rs index 1b5b764..7200f66 100644 --- a/src/ui/components/modal.rs +++ b/src/ui/components/modal.rs @@ -76,10 +76,10 @@ pub fn Modal(props: ModalProps) -> Element { Severity::Critical => ErrorButton, }; - icon.as_ref()?; + icon.as_ref().ok_or(VNode::empty()); rsx! { - style { {STYLE_SHEET} }, + style { {STYLE_SHEET} } div { class: ClassName::MODAL, @@ -90,17 +90,17 @@ pub fn Modal(props: ModalProps) -> Element { div { class: ClassName::MODAL_CONTENT_ICON, {icon} - }, + } div { class: ClassName::MODAL_CONTENT_TITLE, - {props.title}, - }, + {props.title} + } div { class: ClassName::MODAL_CONTENT_MSG, - {props.children}, - }, + {props.children} + } div { class: ClassName::MODAL_CONTENT_BUTTONS, @@ -109,10 +109,10 @@ pub fn Modal(props: ModalProps) -> Element { if let Some(cb) = &props.on_confirm { cb.call(evt); } - }, - }, - }, - }, - }, + } + } + } + } + } } } diff --git a/src/ui/components/text_input.rs b/src/ui/components/text_input.rs index 07e4fcb..8f34dd8 100644 --- a/src/ui/components/text_input.rs +++ b/src/ui/components/text_input.rs @@ -67,7 +67,7 @@ pub fn TextInput(props: InputProps) -> Element { let input_classes_str = [ClassName::TEXT_INPUT_INPUT, criticity_class].join(" "); rsx! { - style { {STYLE_SHEET} }, + style { {STYLE_SHEET} } div { class: ClassName::TEXT_INPUT, @@ -83,7 +83,7 @@ pub fn TextInput(props: InputProps) -> Element { cb.call(evt); } }, - }, + } div { class: ClassName::TEXT_INPUT_HELPER_TEXT, @@ -159,7 +159,7 @@ pub fn PasswordTextInput(props: InputProps) -> Element { let input_classes = [ClassName::PASSWORD_TEXT_INPUT_INPUT, criticity_class].join(" "); rsx! { - style { {STYLE_SHEET} }, + style { {STYLE_SHEET} } div { class: "{text_input_classes}", @@ -175,7 +175,7 @@ pub fn PasswordTextInput(props: InputProps) -> Element { cb.call(evt); } }, - }, + } if let Some(score) = score { div { @@ -184,7 +184,7 @@ pub fn PasswordTextInput(props: InputProps) -> Element { ratio: score, } } - }, + } div { class: ClassName::PASSWORD_TEXT_INPUT_SHOW_TOGGLE, @@ -203,7 +203,7 @@ pub fn PasswordTextInput(props: InputProps) -> Element { icon: IoEye, } } - }, + } div { class: ClassName::PASSWORD_TEXT_INPUT_HELPER_TEXT, @@ -212,7 +212,7 @@ pub fn PasswordTextInput(props: InputProps) -> Element { class: criticity_class, {helper_text} } - }, + } } } } diff --git a/src/ui/components/text_input.scss b/src/ui/components/text_input.scss index ce09044..7c3dae8 100644 --- a/src/ui/components/text_input.scss +++ b/src/ui/components/text_input.scss @@ -1,4 +1,4 @@ -@import "../_base.scss" +@import "../_base.scss"; %base-text-input { $horizontal-padding: 1vw; diff --git a/src/ui/hooks/use_long_press.rs b/src/ui/hooks/use_long_press.rs index 2044bdd..a507747 100644 --- a/src/ui/hooks/use_long_press.rs +++ b/src/ui/hooks/use_long_press.rs @@ -14,20 +14,20 @@ pub fn use_long_press( on_long_press: impl FnMut() + 'static, ) -> UseLongPress { let on_press = std::rc::Rc::new(RefCell::new(on_press)); - let on_press_cb = use_callback(move || { + let on_press_cb = use_callback(move |_| { let mut on_press = on_press.as_ref().borrow_mut(); on_press(); }); let on_long_press = std::rc::Rc::new(RefCell::new(on_long_press)); - let on_long_press_cb = use_callback(move || { + let on_long_press_cb = use_callback(move |_| { let mut on_long_press = on_long_press.as_ref().borrow_mut(); on_long_press(); }); let mut timer = use_future(move || async move { task::sleep(duration).await; - on_long_press_cb.call(); + on_long_press_cb.call(()); }); timer.cancel(); @@ -39,7 +39,7 @@ pub fn use_long_press( let selection_end_cb = move |_: Event| { if !timer.finished() { timer.cancel(); - on_press_cb.call(); + on_press_cb.call(()); } }; diff --git a/src/ui/layouts/conversations.rs b/src/ui/layouts/conversations.rs index 8bb742e..bd475c7 100644 --- a/src/ui/layouts/conversations.rs +++ b/src/ui/layouts/conversations.rs @@ -1,15 +1,19 @@ -use std::rc::Rc; +use std::{collections::HashSet, rc::Rc}; +use base64::{engine::general_purpose, Engine as _}; use dioxus::prelude::*; use futures::join; use tracing::warn; -use crate::ui::{ - components::{ - chat_panel::ChatPanel, conversations::Conversations as ConversationsComponent, - wallpaper::Wallpaper, +use crate::{ + domain::model::room::RoomId, + ui::{ + components::{ + chat_panel::ChatPanel, conversations::Conversations as ConversationsComponent, + wallpaper::Wallpaper, + }, + STORE, }, - STORE, }; turf::style_sheet!("src/ui/layouts/conversations.scss"); @@ -66,7 +70,7 @@ fn LayoutSmall() -> Element { let inner = rsx! { div { class: ClassName::CONVERSATIONS_VIEW_SMALL_PANEL_INNER, - ChatPanel { name: format!("CHAT {room_name_repr}") }, + ChatPanel { name: format!("CHAT {room_name_repr}") } } }; @@ -88,7 +92,7 @@ fn LayoutSmall() -> Element { } }; - if let Some(panel) = panel { + if let Ok(panel) = panel { conversation_panels.push(panel); } } else { @@ -97,7 +101,7 @@ fn LayoutSmall() -> Element { } rsx! { - style { {STYLE_SHEET} }, + style { {STYLE_SHEET} } div { class: ClassName::CONVERSATIONS_VIEW_SMALL, @@ -133,9 +137,9 @@ fn LayoutSmall() -> Element { div { class: ClassName::CONVERSATIONS_VIEW_SMALL_CONVERSATIONS_PANEL_INNER, - ConversationsComponent {}, - }, - }, + ConversationsComponent {} + } + } {conversation_panels.iter()} @@ -164,33 +168,33 @@ fn LayoutBig() -> Element { is_first = false; rsx! { div { - class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATION_PANELS_PANEL, + class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATIONS_PANELS_PANEL, onmounted: move |cx| async move { let data = cx.data(); let _ = data.as_ref().scroll_to(ScrollBehavior::Smooth).await; first_div.set(Some(data)); }, - ChatPanel { name: room_name_repr }, + ChatPanel { name: room_name_repr } } } } else if displayed_room_ids_it.peek().is_none() { rsx! { div { - class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATION_PANELS_PANEL, + class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATIONS_PANELS_PANEL, onmounted: move |cx: Event| last_div.set(Some(cx.data())), - ChatPanel { name: room_name_repr }, + ChatPanel { name: room_name_repr } } } } else { rsx! { div { - class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATION_PANELS_PANEL, - ChatPanel { name: room_name_repr }, + class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATIONS_PANELS_PANEL, + ChatPanel { name: room_name_repr } } } }; - if let Some(panel) = panel { + if let Ok(panel) = panel { conversation_panels.push(panel); } } else { @@ -199,7 +203,7 @@ fn LayoutBig() -> Element { } rsx! { - style { {STYLE_SHEET} }, + style { {STYLE_SHEET} } div { class: ClassName::CONVERSATIONS_VIEW_BIG, @@ -207,40 +211,45 @@ fn LayoutBig() -> Element { div { class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATIONS_PANEL, - ConversationsComponent {}, - }, + ConversationsComponent {} + } div { - class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATION_PANELS, + class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATIONS, - onmounted: move |cx| async move { - let data = cx.data(); - carousel_div.set(Some(data)); - }, - onscroll: move |_| { - async move { - if let (Some(carousel_div), Some(first_div), Some(last_div)) = ( - carousel_div.read().as_ref(), - first_div.read().as_ref(), - last_div.read().as_ref(), - ) { - on_carousel_scroll(carousel_div, first_div, last_div).await; + + div { + class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATIONS_PANELS, + + onmounted: move |cx| async move { + let data = cx.data(); + carousel_div.set(Some(data)); + }, + + onscroll: move |_| { + async move { + if let (Some(carousel_div), Some(first_div), Some(last_div)) = ( + carousel_div.read().as_ref(), + first_div.read().as_ref(), + last_div.read().as_ref(), + ) { + on_carousel_scroll(carousel_div, first_div, last_div).await; + } } + }, + + div { + class: ClassName::CONVERSATIONS_VIEW_HEAD, } - }, - div { - class: ClassName::CONVERSATIONS_VIEW_HEAD, + {conversation_panels.iter()} + + div { + class: ClassName::CONVERSATIONS_VIEW_TAIL, + } } - - {conversation_panels.iter()} - - div { - class: ClassName::CONVERSATIONS_VIEW_TAIL, - } - - }, + } } } } @@ -249,29 +258,29 @@ pub fn Conversations() -> Element { let mut layout = use_signal(|| None::); rsx! { - style { {STYLE_SHEET} }, + style { {STYLE_SHEET} } Wallpaper { display_version: true - }, + } div { class: ClassName::CONVERSATIONS_VIEW, - onresized: move |cx| { + onresize: move |cx| { let data = cx.data(); let mut use_big_layout = false; if let Ok(size) = data.get_border_box_size() { - if let Some(size) = size.first() { - // Use LayoutBig if the layout can contain 2 panels side by side - let component_width = size.height * INNER_PANEL_HEIGHT_RATIO * ASPECT_RATIO; - let breakpoint_width = component_width * 2_f64; - use_big_layout = size.width > breakpoint_width; - } + // Use LayoutBig if the layout can contain 2 panels side by side + let component_width = size.height * INNER_PANEL_HEIGHT_RATIO * ASPECT_RATIO; + let breakpoint_width = component_width * 2_f64; + use_big_layout = size.width > breakpoint_width; } - layout.set(rsx! { if use_big_layout { LayoutBig {} } else { LayoutSmall {} }}); + // let layout_result = rsx! { if use_big_layout { LayoutBig {} } else { LayoutSmall {} }}; + let layout_result = rsx! { LayoutBig {} }; + layout.set(Some(layout_result.unwrap())); }, {layout} diff --git a/src/ui/layouts/conversations.scss b/src/ui/layouts/conversations.scss index 146f19c..c0ad69d 100644 --- a/src/ui/layouts/conversations.scss +++ b/src/ui/layouts/conversations.scss @@ -110,28 +110,47 @@ $inner-panel-height-ratio: 0.95; aspect-ratio: $aspect-ratio; } - &__conversation-panels { + &__conversations { height: $content-height; + min-width: 64px; flex-grow: 1; display: flex; - flex-direction: row; - overflow-x: scroll; - - justify-content: safe center; - align-items: safe center; - scroll-snap-type: x mandatory; - + flex-direction: column; gap: $gap; - &__panel { - flex-shrink: 0; - - height: 100%; + &__tabs-bar { + height: 5%; width: 100%; - scroll-snap-align: center; + flex-grow: 0; + + // overflow: scroll; + // scrollbar-width: none; + } + + &__panels { + flex-grow: 1; + + display: flex; + flex-direction: row; + overflow-x: scroll; + + justify-content: safe center; + align-items: safe center; + scroll-snap-type: x mandatory; + + gap: $gap; + + &__panel { + flex-shrink: 0; + + height: 100%; + width: 100%; + + scroll-snap-align: center; + } } } } From aaafa91cbeb0afc9b75b78a9ab1bc02696c73146 Mon Sep 17 00:00:00 2001 From: Adrien Date: Wed, 21 Aug 2024 22:59:57 +0200 Subject: [PATCH 13/20] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20=20Bump=20turf=20ver?= =?UTF-8?q?sion=20(0.8.0=20->=200.9.3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 93f6817..29e7bba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ tracing = "0.1.40" tracing-forest = "0.1.6" # SCSS -> CSS + usage in rust code -turf = "0.8.0" +turf = "0.9.3" # Dioxus # dioxus-free-icons = { version = "0.8", features = ["ionicons", "font-awesome-solid"] } From b7b98dff15e28bf909b56993ed7933794bb097f2 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sat, 7 Sep 2024 12:58:39 +0200 Subject: [PATCH 14/20] =?UTF-8?q?=E2=9C=A8=20Add=20TabsBar=20to=20the=20La?= =?UTF-8?q?youtBig=20(Conversations=20layout)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/layouts/conversations.rs | 50 +++++++++++++++++++++++- src/ui/layouts/conversations.scss | 64 +++++++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 4 deletions(-) diff --git a/src/ui/layouts/conversations.rs b/src/ui/layouts/conversations.rs index bd475c7..62f7eff 100644 --- a/src/ui/layouts/conversations.rs +++ b/src/ui/layouts/conversations.rs @@ -143,10 +143,54 @@ fn LayoutSmall() -> Element { {conversation_panels.iter()} +#[component] +fn Tab(room_id: RoomId) -> Element { + let rooms = STORE.read().rooms(); + let room = rooms.get(&room_id).unwrap().signal(); + + let room_avatar = if let Some(content) = room.avatar() { + let encoded = general_purpose::STANDARD.encode(content); + rsx! { div { - class: ClassName::CONVERSATIONS_VIEW_TAIL, + class: ClassName::TAB_AVATAR_IMAGE, + + div { + class: ClassName::TAB_AVATAR_IMAGE, + background_image: format!("url(data:image/jpeg;base64,{encoded})"), + } } } + } else { + VNode::empty() + }; + + rsx! { + div { + class: ClassName::TAB, + + {room_avatar} + + div { + class: ClassName::TAB_NAME, + + {room.name()} + } + } + } +} + +#[component] +fn TabsBar(room_ids: HashSet) -> Element { + let tabs = room_ids + .iter() + .map(|room_id| rsx! { Tab { room_id: room_id.clone() }}); + + rsx! { + div { + class: ClassName::TABS_BAR, + + {tabs} + } } } @@ -217,7 +261,11 @@ fn LayoutBig() -> Element { div { class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATIONS, + div { + class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATIONS_TABS_BAR, + TabsBar { room_ids: displayed_room_ids} + } div { class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATIONS_PANELS, diff --git a/src/ui/layouts/conversations.scss b/src/ui/layouts/conversations.scss index c0ad69d..8038d9e 100644 --- a/src/ui/layouts/conversations.scss +++ b/src/ui/layouts/conversations.scss @@ -125,9 +125,6 @@ $inner-panel-height-ratio: 0.95; width: 100%; flex-grow: 0; - - // overflow: scroll; - // scrollbar-width: none; } &__panels { @@ -155,3 +152,64 @@ $inner-panel-height-ratio: 0.95; } } } + +.tabs-bar { + $gap: min(1vw, 8px); + + height: 100%; + width: 100%; + + display: flex; + gap: $gap; + overflow: scroll; + + scrollbar-width: none; + + white-space: nowrap; +} + +.tab { + $gap: min(1vw, 8px); + + height: 100%; + min-width: 0px; + max-width: 100%; + + flex-shrink: 0; + + display: inline-flex; + align-items: center; + gap: $gap; + + border: $border-normal; + border-color: get-color(primary, 90); + border-radius: $border-radius; + + padding: calc($gap / 2) $gap; + + font-size: 2vh; + + background-color: get-color(greyscale, 0); + + &__avatar-image { + height: 100%; + aspect-ratio: 1; + + border: $border-thin; + border-color: get-color(greyscale, 90); + border-radius: $border-radius; + + background-size: cover; + } + + &__name { + display: inline-block; + margin: 0px; + + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + cursor: default3; + } +} From 648be8ba72412113f043d42118468320e04d53b2 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sat, 7 Sep 2024 13:01:26 +0200 Subject: [PATCH 15/20] =?UTF-8?q?=F0=9F=90=9B=20Add=20tail=20div=20to=20dy?= =?UTF-8?q?namic=20conversation=5Fpanels=20to=20fix=20some=20side=20effect?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/layouts/conversations.rs | 42 +++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/ui/layouts/conversations.rs b/src/ui/layouts/conversations.rs index 62f7eff..58972a4 100644 --- a/src/ui/layouts/conversations.rs +++ b/src/ui/layouts/conversations.rs @@ -100,6 +100,16 @@ fn LayoutSmall() -> Element { } } + // Add tail div to dynamic rendered conversation_panels avoids side effects on layout changes + conversation_panels.push( + rsx! { + div { + class: ClassName::CONVERSATIONS_VIEW_TAIL, + } + } + .unwrap(), + ); + rsx! { style { {STYLE_SHEET} } @@ -142,6 +152,9 @@ fn LayoutSmall() -> Element { } {conversation_panels.iter()} + } + } +} #[component] fn Tab(room_id: RoomId) -> Element { @@ -246,6 +259,16 @@ fn LayoutBig() -> Element { } } + // Add tail div to dynamic rendered conversation_panels avoids side effects on layout changes + conversation_panels.push( + rsx! { + div { + class: ClassName::CONVERSATIONS_VIEW_TAIL, + } + } + .unwrap(), + ); + rsx! { style { {STYLE_SHEET} } @@ -292,10 +315,6 @@ fn LayoutBig() -> Element { } {conversation_panels.iter()} - - div { - class: ClassName::CONVERSATIONS_VIEW_TAIL, - } } } } @@ -303,7 +322,7 @@ fn LayoutBig() -> Element { } pub fn Conversations() -> Element { - let mut layout = use_signal(|| None::); + let mut use_big_layout = use_signal(|| false); rsx! { style { {STYLE_SHEET} } @@ -317,21 +336,20 @@ pub fn Conversations() -> Element { onresize: move |cx| { let data = cx.data(); - let mut use_big_layout = false; if let Ok(size) = data.get_border_box_size() { // Use LayoutBig if the layout can contain 2 panels side by side let component_width = size.height * INNER_PANEL_HEIGHT_RATIO * ASPECT_RATIO; let breakpoint_width = component_width * 2_f64; - use_big_layout = size.width > breakpoint_width; + use_big_layout.set(size.width > breakpoint_width); } - - // let layout_result = rsx! { if use_big_layout { LayoutBig {} } else { LayoutSmall {} }}; - let layout_result = rsx! { LayoutBig {} }; - layout.set(Some(layout_result.unwrap())); }, - {layout} + if use_big_layout() { + LayoutBig {} + } else { + LayoutSmall {} + } } } } From 9d95bd4481832b86d98ec9d051de32ead617aa43 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 8 Sep 2024 16:07:13 +0200 Subject: [PATCH 16/20] =?UTF-8?q?=E2=9C=A8=20Add=20the=20capability=20to?= =?UTF-8?q?=20join=20a=20conversation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/domain/model/messaging_interface.rs | 1 + src/domain/model/room.rs | 6 +++ src/domain/model/store_interface.rs | 4 +- src/infrastructure/messaging/matrix/client.rs | 16 ++++++ .../messaging/matrix/requester.rs | 4 ++ .../messaging/matrix/worker_tasks.rs | 5 ++ src/ui/components/conversations.rs | 51 ++++++++++++------- src/ui/store/room.rs | 10 +++- 8 files changed, 76 insertions(+), 21 deletions(-) diff --git a/src/domain/model/messaging_interface.rs b/src/domain/model/messaging_interface.rs index 0d7e4ef..ea26778 100644 --- a/src/domain/model/messaging_interface.rs +++ b/src/domain/model/messaging_interface.rs @@ -44,6 +44,7 @@ pub trait RoomMessagingConsumerInterface { #[async_trait(?Send)] pub trait RoomMessagingProviderInterface { async fn get_avatar(&self, id: &RoomId) -> anyhow::Result>; + async fn join(&self, room_id: &RoomId) -> anyhow::Result; } #[async_trait(?Send)] diff --git a/src/domain/model/room.rs b/src/domain/model/room.rs index 119d8e5..91ce56a 100644 --- a/src/domain/model/room.rs +++ b/src/domain/model/room.rs @@ -314,4 +314,10 @@ impl RoomStoreConsumerInterface for Room { fn spaces(&self) -> &Vec { &self.spaces } + + async fn join(&self) { + if let Some(messaging_provider) = &self.messaging_provider { + let _ = messaging_provider.join(&self.id).await; + } + } } diff --git a/src/domain/model/store_interface.rs b/src/domain/model/store_interface.rs index 10aad7a..4a30354 100644 --- a/src/domain/model/store_interface.rs +++ b/src/domain/model/store_interface.rs @@ -29,11 +29,11 @@ pub trait RoomStoreConsumerInterface { fn is_direct(&self) -> Option; fn name(&self) -> Option; fn topic(&self) -> Option; + fn spaces(&self) -> &Vec; #[allow(dead_code)] async fn avatar(&self) -> Option; - - fn spaces(&self) -> &Vec; + async fn join(&self); } pub trait RoomStoreProviderInterface { diff --git a/src/infrastructure/messaging/matrix/client.rs b/src/infrastructure/messaging/matrix/client.rs index 7942580..a49a675 100644 --- a/src/infrastructure/messaging/matrix/client.rs +++ b/src/infrastructure/messaging/matrix/client.rs @@ -776,6 +776,19 @@ impl Client { Ok(None) } + async fn join_room(&self, room_id: &RoomId) -> anyhow::Result { + let client = self.client.as_ref().unwrap(); + + if let Some(room) = client.get_room(room_id) { + return match room.join().await { + Ok(_) => Ok(true), + Err(err) => Err(err.into()), + }; + } + + Ok(false) + } + async fn work(&mut self, mut rx: UnboundedReceiver) { while let Some(task) = rx.recv().await { self.run(task).await; @@ -820,6 +833,9 @@ impl Client { ) .await; } + WorkerTask::JoinRoom(id, reply) => { + reply.send(self.join_room(&id).await).await; + } } } } diff --git a/src/infrastructure/messaging/matrix/requester.rs b/src/infrastructure/messaging/matrix/requester.rs index 85f7f29..232cb10 100644 --- a/src/infrastructure/messaging/matrix/requester.rs +++ b/src/infrastructure/messaging/matrix/requester.rs @@ -362,6 +362,10 @@ impl RoomMessagingProviderInterface for Requester { async fn get_avatar(&self, room_id: &RoomId) -> anyhow::Result> { request_to_worker!(self, WorkerTask::GetRoomAvatar, room_id.clone()) } + + async fn join(&self, room_id: &RoomId) -> anyhow::Result { + request_to_worker!(self, WorkerTask::JoinRoom, room_id.clone()) + } } #[async_trait(?Send)] diff --git a/src/infrastructure/messaging/matrix/worker_tasks.rs b/src/infrastructure/messaging/matrix/worker_tasks.rs index e8bb291..85be9fc 100644 --- a/src/infrastructure/messaging/matrix/worker_tasks.rs +++ b/src/infrastructure/messaging/matrix/worker_tasks.rs @@ -23,6 +23,7 @@ pub enum WorkerTask { OwnedUserId, Sender>>>, ), + JoinRoom(OwnedRoomId, Sender>), } impl Debug for WorkerTask { @@ -61,6 +62,10 @@ impl Debug for WorkerTask { .field(room_id) .field(user_id) .finish(), + WorkerTask::JoinRoom(room_id, _) => f + .debug_tuple("WorkerTask::JoinRoom") + .field(room_id) + .finish(), } } } diff --git a/src/ui/components/conversations.rs b/src/ui/components/conversations.rs index 2ea9917..ee71bdd 100644 --- a/src/ui/components/conversations.rs +++ b/src/ui/components/conversations.rs @@ -2,6 +2,7 @@ use std::{rc::Rc, time::Duration}; use base64::{engine::general_purpose, Engine as _}; use dioxus::prelude::*; +use futures_util::StreamExt; use tracing::{debug, warn}; use super::{button::Button, icons::SearchIcon, text_input::TextInput}; @@ -431,11 +432,16 @@ pub fn Search() -> Element { } } +#[derive(PartialEq)] +enum ConversationOptionsMenuActions { + Join(RoomId), + Close, +} + #[component] fn ConversationOptionsMenu( room_id: RoomId, - on_close: EventHandler, - on_join: EventHandler, + callbacks: Coroutine, ) -> Element { let room = STORE.read().rooms().get(&room_id).unwrap().signal(); @@ -450,7 +456,7 @@ fn ConversationOptionsMenu( div { class: ClassName::CONVERSATION_OPTIONS_MENU_INNER_AVATAR, - ConversationAvatar { room_id } + ConversationAvatar { room_id: room_id.clone() } } div { @@ -477,7 +483,9 @@ fn ConversationOptionsMenu( div { class: ClassName::CONVERSATION_OPTIONS_MENU_INNER_CLOSE_BUTTON, RejectButton { - onclick: move |_| on_close(()) + onclick: move |_| { + callbacks.send(ConversationOptionsMenuActions::Close); + } } } @@ -485,8 +493,8 @@ fn ConversationOptionsMenu( class: ClassName::CONVERSATION_OPTIONS_MENU_INNER_JOIN_BUTTON, JoinButton { onclick: move |_| { - on_join(()); - on_close(()); + callbacks.send(ConversationOptionsMenuActions::Join(room_id.clone())); + callbacks.send(ConversationOptionsMenuActions::Close); } } } @@ -498,26 +506,33 @@ fn ConversationOptionsMenu( pub fn Conversations() -> Element { let mut room_id = use_signal(|| None::); - let on_menu_close = move |_| { - room_id.set(None); - }; - - let on_menu_join = move |_| async move { - let rooms = STORE.read().rooms(); - if let Some(room_id) = room_id.read().to_owned() { - if let Some(room) = rooms.get(&room_id) {} - } - }; - let on_pressed_conversation = move |id: RoomId| { room_id.set(Some(id)); }; + let callbacks = use_coroutine( + move |mut rx: UnboundedReceiver| async move { + while let Some(action) = rx.next().await { + match action { + ConversationOptionsMenuActions::Join(room_id) => { + let rooms = STORE.read().rooms(); + if let Some(room) = rooms.get(&room_id) { + room.join().await; + } + } + ConversationOptionsMenuActions::Close => { + room_id.set(None); + } + } + } + }, + ); + let menu = match room_id.read().as_ref() { Some(room_id) => { let room_id = room_id.clone(); rsx! { - ConversationOptionsMenu { room_id, on_close: on_menu_close, on_join: on_menu_join } + ConversationOptionsMenu { room_id, callbacks } } } None => VNode::empty(), diff --git a/src/ui/store/room.rs b/src/ui/store/room.rs index f65e37d..6d9145e 100644 --- a/src/ui/store/room.rs +++ b/src/ui/store/room.rs @@ -33,7 +33,6 @@ pub struct Store { pub struct Room { store: RefCell, - #[allow(dead_code)] domain: Rc, } @@ -57,6 +56,10 @@ impl Room { } } + pub async fn join(&self) { + self.domain.join().await; + } + #[allow(dead_code)] pub async fn get_avatar(&self) -> Option { self.domain.avatar().await @@ -81,6 +84,11 @@ impl RoomStoreProviderInterface for Room { fn on_new_member(&self, member: RoomMember) { let mut store = self.store.borrow_mut(); + + if member.is_account_user() { + store.is_invited.set(false); + } + store.members.write().push(member); } From 27934c7fc9b193905166621b930427bf3007aed3 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 8 Sep 2024 16:12:33 +0200 Subject: [PATCH 17/20] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20some=20clippy=20warn?= =?UTF-8?q?ings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/infrastructure/services/mozaik_builder.rs | 5 +- src/main.rs | 2 +- src/ui/components/button.rs | 8 +-- src/ui/components/icons.rs | 12 ++-- src/ui/components/login.rs | 65 +++++++------------ src/ui/components/modal.rs | 2 +- src/ui/components/spinner.rs | 3 +- src/ui/components/wallpaper.rs | 6 +- src/ui/layouts/login.rs | 2 +- 9 files changed, 47 insertions(+), 58 deletions(-) diff --git a/src/infrastructure/services/mozaik_builder.rs b/src/infrastructure/services/mozaik_builder.rs index fb946b4..0d58b52 100644 --- a/src/infrastructure/services/mozaik_builder.rs +++ b/src/infrastructure/services/mozaik_builder.rs @@ -1,8 +1,7 @@ use std::io::Cursor; use image::imageops::FilterType; -use image::io::Reader; -use image::{DynamicImage, ImageFormat}; +use image::{DynamicImage, ImageFormat, ImageReader}; use image::{GenericImage, RgbImage}; use tracing::{error, warn}; @@ -13,7 +12,7 @@ cfg_if! { } fn from_raw_to_image(raw: &Vec) -> Option { - match Reader::new(Cursor::new(raw)).with_guessed_format() { + match ImageReader::new(Cursor::new(raw)).with_guessed_format() { Ok(reader) => match reader.decode() { Ok(image) => return Some(image), Err(err) => error!("Unable to decode the image: {}", err), diff --git a/src/main.rs b/src/main.rs index 6bb9464..5cc8a45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -99,7 +99,7 @@ fn app() -> Element { } } else { rsx! { - Login {}, + Login {} } } } diff --git a/src/ui/components/button.rs b/src/ui/components/button.rs index 45fd74e..d8fd046 100644 --- a/src/ui/components/button.rs +++ b/src/ui/components/button.rs @@ -43,7 +43,7 @@ macro_rules! svg_text_button { ($name:ident,$style:ident,$icon:ident) => { pub fn $name(props: ButtonProps) -> Element { rsx! { - style { {STYLE_SHEET} }, + style { {STYLE_SHEET} } Button { id: props.id, @@ -79,7 +79,7 @@ pub struct ButtonProps { pub fn Button(props: ButtonProps) -> Element { rsx! { - style { {STYLE_SHEET} }, + style { {STYLE_SHEET} } button { id: props.id, @@ -96,8 +96,8 @@ pub fn Button(props: ButtonProps) -> Element { } }, - {props.children}, - }, + {props.children} + } } } diff --git a/src/ui/components/icons.rs b/src/ui/components/icons.rs index 7a14764..7516049 100644 --- a/src/ui/components/icons.rs +++ b/src/ui/components/icons.rs @@ -15,7 +15,8 @@ macro_rules! transparent_icon { ($name:ident, $icon:ident) => { pub fn $name() -> Element { rsx! { - style { {STYLE_SHEET} }, + style { {STYLE_SHEET} } + Icon { class: ClassName::TRANSPARENT_ICON, icon: $icon, @@ -52,7 +53,7 @@ impl IconShape for LogoShape { pub fn LogoIcon() -> Element { rsx! { - style { {STYLE_SHEET} }, + style { {STYLE_SHEET} } Icon { icon: LogoShape, @@ -133,14 +134,14 @@ impl IconShape for PyramidShape { L {_PYRAMID_EDGES_E1_X} {_PYRAMID_CENTRAL_EDGE_E2_Y} \ M {_PYRAMID_EDGES_E1_X} {_PYRAMID_EDGES_E1_Y} \ V {_PYRAMID_CENTRAL_EDGE_Y_LEN}", - }, + } path { d: "\ M {_PYRAMID_CENTRAL_EDGE_E2_X} {_PYRAMID_CENTRAL_EDGE_E2_Y} \ V {central_edge_ratio_e2_y} \ L {left_edge_ratio_e1_x} {no_central_edge_ratio_e1_y} \ L {_PYRAMID_LEFT_EDGE_E2_X} {_PYRAMID_LEFT_EDGE_E2_Y} Z", - }, + } path { d: "\ M {_PYRAMID_CENTRAL_EDGE_E2_X} {_PYRAMID_CENTRAL_EDGE_E2_Y} \ @@ -168,10 +169,11 @@ pub fn Pyramid(props: PyramidProps) -> Element { .unwrap_or(COLOR_TERNARY_100.to_string()); rsx! { - style { {STYLE_SHEET} }, + style { {STYLE_SHEET} } Icon { class: ClassName::PYRAMID_ICON, + icon: PyramidShape { ratio: props.ratio, color, progress_color }, } } diff --git a/src/ui/components/login.rs b/src/ui/components/login.rs index 75f328f..6545eeb 100644 --- a/src/ui/components/login.rs +++ b/src/ui/components/login.rs @@ -97,27 +97,6 @@ impl Clone for Box { } } -#[derive(Clone)] -struct TextInputHandler { - state: Signal, -} - -impl TextInputHandler {} - -impl OnValidationError for TextInputHandler { - fn reset(&mut self) { - self.state.write().reset(); - } - - fn invalidate(&mut self, helper_text: String) { - self.state.write().invalidate(helper_text); - } - - fn box_clone(&self) -> Box { - Box::new(self.clone()) - } -} - #[derive(Clone)] struct UrlInputHandler { state: Signal, @@ -744,7 +723,7 @@ pub fn Login() -> Element { let confirm_password_classes_str = confirm_password_classes.join(" "); rsx! { - style { {STYLE_SHEET} }, + style { {STYLE_SHEET} } div { class: "{classes_str}", @@ -756,73 +735,79 @@ pub fn Login() -> Element { random_avatar_future.restart() }, - {avatar}, - }, + {avatar} + } div { class: ClassName::LOGIN_HOMESERVER, + TextInput { placeholder: "Homeserver URL", value: "{homeserver_url}", state: homeserver_url_state, oninput: on_input![data, homeserver_url], - }, - }, + } + } div { class: ClassName::LOGIN_ID, + TextInput { placeholder: "{id_placeholder}", value: "{id}", state: id_state, oninput: on_input![data, id], - }, - }, + } + } div { class: "{password_classes_str}", + PasswordTextInput { placeholder: "Password", value: "{password}", state: password_state, oninput: on_input![data, password], - }, - - }, + } + } div { class: "{confirm_password_classes_str}", + PasswordTextInput { placeholder: "Confirm Password", value: "{confirm_password}", state: confirm_password_state, oninput: on_input![data, confirm_password], } - }, + } div { class: ClassName::LOGIN_SPINNER, + Spinner { animate: *spinner_animated.read(), - }, - }, + } + } div { class: ClassName::LOGIN_REGISTER_BUTTON, + RegisterButton { onclick: on_clicked_register, - }, - }, + } + } div { class: ClassName::LOGIN_LOGIN_BUTTON, + LoginButton { focus: true, onclick: on_clicked_login, - }, - }, - }, + } + } + } - {rendered_modal}, + {rendered_modal} } } diff --git a/src/ui/components/modal.rs b/src/ui/components/modal.rs index 7200f66..f919c83 100644 --- a/src/ui/components/modal.rs +++ b/src/ui/components/modal.rs @@ -76,7 +76,7 @@ pub fn Modal(props: ModalProps) -> Element { Severity::Critical => ErrorButton, }; - icon.as_ref().ok_or(VNode::empty()); + let _ = icon.as_ref().ok_or(VNode::empty()); rsx! { style { {STYLE_SHEET} } diff --git a/src/ui/components/spinner.rs b/src/ui/components/spinner.rs index 68750b1..a45f2d9 100644 --- a/src/ui/components/spinner.rs +++ b/src/ui/components/spinner.rs @@ -13,13 +13,14 @@ pub struct SpinnerProps { pub fn Spinner(props: SpinnerProps) -> Element { rsx! { - style { {STYLE_SHEET} }, + style { {STYLE_SHEET} } div { class: ClassName::SPINNER, Icon { class: if props.animate { "" } else { ClassName::PAUSED }, + icon: LogoShape, } } diff --git a/src/ui/components/wallpaper.rs b/src/ui/components/wallpaper.rs index d880507..5578c73 100644 --- a/src/ui/components/wallpaper.rs +++ b/src/ui/components/wallpaper.rs @@ -10,7 +10,8 @@ pub fn Wallpaper(display_version: Option) -> Element { let version = display_version.map(|flag| if flag { Some(GIT_VERSION) } else { None }); rsx! { - style { {STYLE_SHEET} }, + style { {STYLE_SHEET} } + div { class: ClassName::WALLPAPER, @@ -20,7 +21,8 @@ pub fn Wallpaper(display_version: Option) -> Element { div { class: ClassName::WALLPAPER_VERSION, - {version}, + + {version} } } } diff --git a/src/ui/layouts/login.rs b/src/ui/layouts/login.rs index 807661b..58001fe 100644 --- a/src/ui/layouts/login.rs +++ b/src/ui/layouts/login.rs @@ -7,7 +7,7 @@ turf::style_sheet!("src/ui/layouts/login.scss"); pub fn Login() -> Element { rsx! { - style { {STYLE_SHEET} }, + style { {STYLE_SHEET} } Wallpaper { display_version: true From 5206fb13c80a4492f6a52e6b9f3ceea053b2d367 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 22 Sep 2024 18:49:56 +0200 Subject: [PATCH 18/20] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20Use=20of=20mangan?= =?UTF-8?q?is=20for=20wallpaper=20pattern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ui/components/wallpaper.rs | 6 ++++++ src/ui/components/wallpaper.scss | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ui/components/wallpaper.rs b/src/ui/components/wallpaper.rs index 5578c73..c2d16dc 100644 --- a/src/ui/components/wallpaper.rs +++ b/src/ui/components/wallpaper.rs @@ -7,6 +7,11 @@ turf::style_sheet!("src/ui/components/wallpaper.scss"); #[component] pub fn Wallpaper(display_version: Option) -> Element { + let background_image = format!( + "url({})", + manganis::mg!(file("public/images/wallpaper-pattern.svg")) + ); + let version = display_version.map(|flag| if flag { Some(GIT_VERSION) } else { None }); rsx! { @@ -17,6 +22,7 @@ pub fn Wallpaper(display_version: Option) -> Element { div { class: ClassName::WALLPAPER_CONTENT, + background_image: "{background_image}", } div { diff --git a/src/ui/components/wallpaper.scss b/src/ui/components/wallpaper.scss index 1aea4d0..35c133b 100644 --- a/src/ui/components/wallpaper.scss +++ b/src/ui/components/wallpaper.scss @@ -12,7 +12,6 @@ overflow: hidden; &__content { - background-image: url("./images/wallpaper-pattern.svg"); background-position: center; width: 150%; From 44ba3d4d23f5f72c24f84d6c62b7fad7949f2532 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 22 Sep 2024 19:14:59 +0200 Subject: [PATCH 19/20] =?UTF-8?q?=F0=9F=90=9B=20script=5Finclude=20is=20no?= =?UTF-8?q?t=20used=20anymore=20(since=20Dioxus=20#2258)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/public/index.html b/public/index.html index 5dcbf50..19d57c1 100644 --- a/public/index.html +++ b/public/index.html @@ -10,7 +10,6 @@ {style_include} - {script_include}
From d2108fa6fc3864d9e3d3b4f0b3799fc55da71638 Mon Sep 17 00:00:00 2001 From: Adrien Date: Sun, 22 Sep 2024 19:38:03 +0200 Subject: [PATCH 20/20] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=20Remove=20duplicat?= =?UTF-8?q?ed=20if=20statement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5cc8a45..bc39666 100644 --- a/src/main.rs +++ b/src/main.rs @@ -91,15 +91,12 @@ fn app() -> Element { if !SESSION.read().is_logged { login_coro.send(false); - } - - if SESSION.read().is_logged { rsx! { - Conversations {} + Login {} } } else { rsx! { - Login {} + Conversations {} } } }