2 Commits

Author SHA1 Message Date
aaafa91cbe ⬆️ Bump turf version (0.8.0 -> 0.9.3) 2024-08-21 23:29:35 +02:00
9a5f7ae504 ⬆️ Use of Dioxus main branch instead of 0.5 release 2024-08-21 23:29:33 +02:00
10 changed files with 319 additions and 188 deletions

View File

@@ -40,33 +40,38 @@ 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 = { 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"] }

View File

@@ -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<Senders>) {
let room_id = room.room_id();
let avatar = match room
.avatar(MediaFormat::Thumbnail(MediaThumbnailSize {
.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 {
.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 {
.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 {
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 {
.avatar(MediaFormat::Thumbnail(MediaThumbnailSettings {
size: MediaThumbnailSize {
method: Method::Scale,
width: uint!(256),
height: uint!(256),
},
animated: false,
}))
.await
{

View File

@@ -21,19 +21,14 @@ turf::style_sheet!("src/ui/components/conversations.scss");
#[component]
fn AccountAvatar(content: Option<Vec<u8>>, class_name: Option<String>) -> Element {
match content {
Some(content) => {
let encoded = general_purpose::STANDARD.encode(content);
rsx! {
if let Some(content) = content {
div {
class: class_name,
background_image: format!("url(data:image/jpeg;base64,{encoded})")
background_image: format!("url(data:image/jpeg;base64,{})", general_purpose::STANDARD.encode(content))
}
}
}
// TODO: Manage acount without avatar
None => None,
}
}
#[component]
@@ -54,11 +49,11 @@ fn PresenceState(state: Option<DomainPresenceState>, class_name: Option<String>)
rsx! {
div {
class: classes,
LogoIcon {},
LogoIcon {}
}
}
}
None => None,
None => VNode::empty(),
}
}
@@ -70,12 +65,12 @@ fn DisplayName(display_name: Option<String>, class_name: Option<String>) -> 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<RoomId>,
on_pressed: EventHandler<RoomId>,
on_selected: Option<EventHandler<RoomId>>,
on_pressed: Option<EventHandler<RoomId>>,
) -> 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::<Signal<Option<RoomId>>>();
let selected_room_id = use_signal(|| None::<RoomId>);
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<SpaceId>, on_pressed_conversation: EventHandler<RoomId>)
let classes_str = space_classes.join(" ");
rsx! {
style { {STYLE_SHEET} },
div {
class: "{classes_str}",
@@ -382,17 +372,17 @@ pub fn Space(id: Option<SpaceId>, on_pressed_conversation: EventHandler<RoomId>)
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<RoomId>) -> 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,
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("<No topic set>".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::<Element>);
let mut room_id = use_signal(|| None::<RoomId>);
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}
}
}

View File

@@ -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 {

View File

@@ -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);
}
},
},
},
},
},
}
}
}
}
}
}
}

View File

@@ -67,7 +67,7 @@ pub fn TextInput(props: InputProps<TextInputState>) -> 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<TextInputState>) -> Element {
cb.call(evt);
}
},
},
}
div {
class: ClassName::TEXT_INPUT_HELPER_TEXT,
@@ -159,7 +159,7 @@ pub fn PasswordTextInput(props: InputProps<PasswordInputState>) -> 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<PasswordInputState>) -> Element {
cb.call(evt);
}
},
},
}
if let Some(score) = score {
div {
@@ -184,7 +184,7 @@ pub fn PasswordTextInput(props: InputProps<PasswordInputState>) -> Element {
ratio: score,
}
}
},
}
div {
class: ClassName::PASSWORD_TEXT_INPUT_SHOW_TOGGLE,
@@ -203,7 +203,7 @@ pub fn PasswordTextInput(props: InputProps<PasswordInputState>) -> Element {
icon: IoEye,
}
}
},
}
div {
class: ClassName::PASSWORD_TEXT_INPUT_HELPER_TEXT,
@@ -212,7 +212,7 @@ pub fn PasswordTextInput(props: InputProps<PasswordInputState>) -> Element {
class: criticity_class,
{helper_text}
}
},
}
}
}
}

View File

@@ -1,4 +1,4 @@
@import "../_base.scss"
@import "../_base.scss";
%base-text-input {
$horizontal-padding: 1vw;

View File

@@ -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<PlatformEventData>| {
if !timer.finished() {
timer.cancel();
on_press_cb.call();
on_press_cb.call(());
}
};

View File

@@ -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::{
use crate::{
domain::model::room::RoomId,
ui::{
components::{
chat_panel::ChatPanel, conversations::Conversations as ConversationsComponent,
wallpaper::Wallpaper,
},
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<MountedData>| 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,11 +211,16 @@ 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,
div {
class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATIONS_PANELS,
onmounted: move |cx| async move {
let data = cx.data();
@@ -239,8 +248,8 @@ fn LayoutBig() -> Element {
div {
class: ClassName::CONVERSATIONS_VIEW_TAIL,
}
},
}
}
}
}
}
@@ -249,29 +258,29 @@ pub fn Conversations() -> Element {
let mut layout = use_signal(|| None::<VNode>);
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;
}
}
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}

View File

@@ -110,11 +110,29 @@ $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: column;
gap: $gap;
&__tabs-bar {
height: 5%;
width: 100%;
flex-grow: 0;
// overflow: scroll;
// scrollbar-width: none;
}
&__panels {
flex-grow: 1;
display: flex;
flex-direction: row;
overflow-x: scroll;
@@ -136,3 +154,4 @@ $inner-panel-height-ratio: 0.95;
}
}
}
}