356 lines
11 KiB
Rust
356 lines
11 KiB
Rust
use std::{collections::HashSet, rc::Rc};
|
|
|
|
use base64::{engine::general_purpose, Engine as _};
|
|
use dioxus::prelude::*;
|
|
use futures::join;
|
|
use tracing::warn;
|
|
|
|
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");
|
|
|
|
include!(concat!(env!("OUT_DIR"), "/style_component_panel.rs"));
|
|
include!(concat!(env!("OUT_DIR"), "/style_layout_conversations.rs"));
|
|
|
|
use conversations::INNER_PANEL_HEIGHT_RATIO;
|
|
use panel::ASPECT_RATIO;
|
|
|
|
async fn on_carousel_scroll(
|
|
parent_div: &Rc<MountedData>,
|
|
first_div: &Rc<MountedData>,
|
|
last_div: &Rc<MountedData>,
|
|
) {
|
|
let results = join!(
|
|
parent_div.get_scroll_offset(),
|
|
parent_div.get_scroll_size(),
|
|
last_div.get_client_rect()
|
|
);
|
|
if let (Ok(offset), Ok(size), Ok(last_div_rect)) = results {
|
|
let left = offset.x;
|
|
let width = size.width;
|
|
// The left border of the first div has been exceeded, scrool to the last div.
|
|
if left <= 0.0 {
|
|
let _ = last_div.scroll_to(ScrollBehavior::Smooth).await;
|
|
}
|
|
// The left border of the last div has been exceeded, scrool to the first div.
|
|
else {
|
|
let last_div_width = last_div_rect.size.width;
|
|
let distance_to_tail = width - left - last_div_width;
|
|
|
|
if distance_to_tail < 1.0 {
|
|
let first_div = first_div.as_ref(); //.unwrap();
|
|
let _ = first_div.scroll_to(ScrollBehavior::Smooth).await;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn LayoutSmall() -> Element {
|
|
let mut first_div = use_signal(|| None::<Rc<MountedData>>);
|
|
let mut last_div = use_signal(|| None::<Rc<MountedData>>);
|
|
let mut carousel_div = use_signal(|| None::<Rc<MountedData>>);
|
|
|
|
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();
|
|
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<MountedData>| last_div.set(Some(cx.data())),
|
|
{inner}
|
|
}
|
|
}
|
|
} else {
|
|
rsx! {
|
|
div {
|
|
class: ClassName::CONVERSATIONS_VIEW_SMALL_PANEL,
|
|
{inner}
|
|
}
|
|
}
|
|
};
|
|
|
|
if let Ok(panel) = panel {
|
|
conversation_panels.push(panel);
|
|
}
|
|
} else {
|
|
warn!("No {} room found", room_id);
|
|
}
|
|
}
|
|
|
|
// 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} }
|
|
|
|
div {
|
|
class: ClassName::CONVERSATIONS_VIEW_SMALL,
|
|
|
|
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_SMALL_CONVERSATIONS_PANEL,
|
|
onmounted: move |cx| async move {
|
|
let data = cx.data();
|
|
let _ = data.as_ref().scroll_to(ScrollBehavior::Smooth).await;
|
|
first_div.set(Some(data));
|
|
},
|
|
|
|
div {
|
|
class: ClassName::CONVERSATIONS_VIEW_SMALL_CONVERSATIONS_PANEL_INNER,
|
|
ConversationsComponent {}
|
|
}
|
|
}
|
|
|
|
{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::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<RoomId>) -> Element {
|
|
let tabs = room_ids
|
|
.iter()
|
|
.map(|room_id| rsx! { Tab { room_id: room_id.clone() }});
|
|
|
|
rsx! {
|
|
div {
|
|
class: ClassName::TABS_BAR,
|
|
|
|
{tabs}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn LayoutBig() -> Element {
|
|
let mut carousel_div = use_signal(|| None::<Rc<MountedData>>);
|
|
let mut first_div = use_signal(|| None::<Rc<MountedData>>);
|
|
let mut last_div = use_signal(|| None::<Rc<MountedData>>);
|
|
|
|
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_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 }
|
|
}
|
|
}
|
|
} else if displayed_room_ids_it.peek().is_none() {
|
|
rsx! {
|
|
div {
|
|
class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATIONS_PANELS_PANEL,
|
|
onmounted: move |cx: Event<MountedData>| last_div.set(Some(cx.data())),
|
|
ChatPanel { name: room_name_repr }
|
|
}
|
|
}
|
|
} else {
|
|
rsx! {
|
|
div {
|
|
class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATIONS_PANELS_PANEL,
|
|
ChatPanel { name: room_name_repr }
|
|
}
|
|
}
|
|
};
|
|
|
|
if let Ok(panel) = panel {
|
|
conversation_panels.push(panel);
|
|
}
|
|
} else {
|
|
warn!("No {} room found", room_id);
|
|
}
|
|
}
|
|
|
|
// 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} }
|
|
|
|
div {
|
|
class: ClassName::CONVERSATIONS_VIEW_BIG,
|
|
|
|
div {
|
|
class: ClassName::CONVERSATIONS_VIEW_BIG_CONVERSATIONS_PANEL,
|
|
|
|
ConversationsComponent {}
|
|
}
|
|
|
|
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,
|
|
|
|
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,
|
|
}
|
|
|
|
{conversation_panels.iter()}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn Conversations() -> Element {
|
|
let mut use_big_layout = use_signal(|| false);
|
|
|
|
rsx! {
|
|
style { {STYLE_SHEET} }
|
|
|
|
Wallpaper {
|
|
display_version: true
|
|
}
|
|
|
|
div {
|
|
class: ClassName::CONVERSATIONS_VIEW,
|
|
|
|
onresize: move |cx| {
|
|
let data = cx.data();
|
|
|
|
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.set(size.width > breakpoint_width);
|
|
}
|
|
},
|
|
|
|
if use_big_layout() {
|
|
LayoutBig {}
|
|
} else {
|
|
LayoutSmall {}
|
|
}
|
|
}
|
|
}
|
|
}
|