✨ Add a first Conversations component
This commit is contained in:
@@ -174,6 +174,9 @@ $border-big-width: 4px;
|
|||||||
$border-big: solid $border-big-width $border-default-color;
|
$border-big: solid $border-big-width $border-default-color;
|
||||||
$border-normal-width: 2px;
|
$border-normal-width: 2px;
|
||||||
$border-normal: solid $border-normal-width $border-default-color;
|
$border-normal: solid $border-normal-width $border-default-color;
|
||||||
|
$border-thin-width: 1px;
|
||||||
|
$border-thin: solid $border-thin-width $border-default-color;
|
||||||
|
|
||||||
|
|
||||||
// TODO: Radius should be a percentage(eg: 1024/16px).
|
// TODO: Radius should be a percentage(eg: 1024/16px).
|
||||||
$border-radius: 16px;
|
$border-radius: 16px;
|
||||||
|
502
src/ui/components/conversations.rs
Normal file
502
src/ui/components/conversations.rs
Normal file
@@ -0,0 +1,502 @@
|
|||||||
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use tracing::{debug, trace, warn};
|
||||||
|
|
||||||
|
use super::{button::Button, icons::SearchIcon, text_input::TextInput};
|
||||||
|
use crate::{
|
||||||
|
base::{ACCOUNT, STORE},
|
||||||
|
domain::model::{common::PresenceState as DomainPresenceState, room::RoomId, space::SpaceId},
|
||||||
|
ui::components::icons::{ChatsIcon, LogoIcon, RoomsIcon, SpacesIcon},
|
||||||
|
};
|
||||||
|
|
||||||
|
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! {
|
||||||
|
div {
|
||||||
|
class: class_name,
|
||||||
|
background_image: format!("url(data:image/jpeg;base64,{encoded})")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Manage acount without avatar
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn PresenceState(state: Option<DomainPresenceState>, class_name: Option<String>) -> Element {
|
||||||
|
let class_name = class_name.unwrap_or("".to_string());
|
||||||
|
|
||||||
|
match state {
|
||||||
|
Some(state) => {
|
||||||
|
let state_class = match state {
|
||||||
|
DomainPresenceState::Online => ClassName::ONLINE,
|
||||||
|
DomainPresenceState::Offline => ClassName::OFFLINE,
|
||||||
|
DomainPresenceState::Unavailable => ClassName::UNAVAILABLE,
|
||||||
|
_ => ClassName::UNAVAILABLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
let classes = [class_name.as_str(), state_class].join(" ");
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: classes,
|
||||||
|
LogoIcon {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn DisplayName(display_name: Option<String>, class_name: Option<String>) -> Element {
|
||||||
|
match display_name {
|
||||||
|
Some(display_name) => {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: class_name,
|
||||||
|
p {
|
||||||
|
{display_name},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn Status(status: Option<String>, class_name: Option<String>) -> Element {
|
||||||
|
let status = status.unwrap_or("Type your status".to_string());
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: class_name,
|
||||||
|
TextInput {
|
||||||
|
placeholder: status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Account() -> Element {
|
||||||
|
let avatar = use_resource(move || async move {
|
||||||
|
let account = ACCOUNT.read();
|
||||||
|
let avatar = account.get_avatar().await;
|
||||||
|
rsx! {
|
||||||
|
AccountAvatar {
|
||||||
|
class_name: ClassName::ACCOUNT_AVATAR,
|
||||||
|
content: avatar.borrow().clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let presence_state = use_resource(move || async move {
|
||||||
|
// TODO: Fetch the state from the domain
|
||||||
|
rsx! {
|
||||||
|
PresenceState {
|
||||||
|
state: DomainPresenceState::Online,
|
||||||
|
class_name: ClassName::ACCOUNT_PRESENCE_STATE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let display_name = use_resource(move || async move {
|
||||||
|
let account = ACCOUNT.read();
|
||||||
|
let display_name = account.get_display_name().await;
|
||||||
|
rsx! {
|
||||||
|
DisplayName {
|
||||||
|
class_name: ClassName::ACCOUNT_DISPLAY_NAME,
|
||||||
|
display_name: display_name.borrow().clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let status = use_resource(move || async move {
|
||||||
|
// TODO: Fetch the status from the domain
|
||||||
|
rsx! {
|
||||||
|
Status {
|
||||||
|
class_name: ClassName::ACCOUNT_STATUS,
|
||||||
|
status: "Coucou, Je suis BG92".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
style { {STYLE_SHEET} },
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::ACCOUNT,
|
||||||
|
|
||||||
|
{avatar},
|
||||||
|
{presence_state},
|
||||||
|
{display_name},
|
||||||
|
|
||||||
|
{status},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::ACCOUNT_SPACES,
|
||||||
|
Button {
|
||||||
|
SpacesIcon {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::ACCOUNT_CHAT,
|
||||||
|
Button {
|
||||||
|
ChatsIcon {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::ACCOUNT_ROOM,
|
||||||
|
Button {
|
||||||
|
RoomsIcon {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn ConversationAvatar(room_id: RoomId, on_clicked: EventHandler<RoomId>) -> Element {
|
||||||
|
let rooms = STORE.read().rooms();
|
||||||
|
let toto = rooms.get(&room_id).unwrap();
|
||||||
|
|
||||||
|
let room = toto.signal();
|
||||||
|
|
||||||
|
let room_id = room.id();
|
||||||
|
let room_name = room.name();
|
||||||
|
|
||||||
|
let selected_room_id = use_context::<Signal<Option<RoomId>>>();
|
||||||
|
|
||||||
|
let invited_badge = if room.is_invited() {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: ClassName::CONVERSATION_AVATAR_INVITED_BADGE,
|
||||||
|
|
||||||
|
p {
|
||||||
|
"Invited",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_selected = match selected_room_id.read().as_ref() {
|
||||||
|
Some(selected_room_id) => *selected_room_id == room_id,
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// let avatar = if let Some(Some(content)) = &*avatar.read() {
|
||||||
|
let avatar = if let Some(content) = room.avatar() {
|
||||||
|
let encoded = general_purpose::STANDARD.encode(content);
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: ClassName::CONVERSATION_AVATAR_IMAGE,
|
||||||
|
background_image: format!("url(data:image/jpeg;base64,{encoded})"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let placeholder = room_name
|
||||||
|
.unwrap_or("?".to_string())
|
||||||
|
.to_uppercase()
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.unwrap_or('?')
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
debug!("Use of {} placeholder for {}", placeholder, room_id);
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: ClassName::CONVERSATION_AVATAR_IMAGE,
|
||||||
|
|
||||||
|
{placeholder},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let classes = [
|
||||||
|
ClassName::CONVERSATION_AVATAR,
|
||||||
|
if is_selected { ClassName::SELECTED } else { "" },
|
||||||
|
];
|
||||||
|
let classes_str = classes.join(" ");
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: "{classes_str}",
|
||||||
|
|
||||||
|
onclick: move |evt| {
|
||||||
|
on_clicked.call(room_id.clone());
|
||||||
|
evt.stop_propagation();
|
||||||
|
},
|
||||||
|
|
||||||
|
{avatar}
|
||||||
|
{invited_badge}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn ConversationsCarousel(on_selected_conversation: EventHandler<RoomId>) -> Element {
|
||||||
|
let mut ordered_rooms = use_signal(Vec::<RoomId>::new);
|
||||||
|
|
||||||
|
use_effect(move || {
|
||||||
|
let rooms = use_context::<Signal<Vec<RoomId>>>();
|
||||||
|
let rooms = rooms.read();
|
||||||
|
for room in rooms.iter() {
|
||||||
|
if !ordered_rooms.peek().contains(room) {
|
||||||
|
ordered_rooms.push(room.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ordered_rooms.retain(|room| rooms.contains(room));
|
||||||
|
});
|
||||||
|
|
||||||
|
let ordered_rooms = ordered_rooms.read();
|
||||||
|
let rendered_avatars = ordered_rooms.iter().map(|room| {
|
||||||
|
rsx! {
|
||||||
|
ConversationAvatar {
|
||||||
|
room_id: room.clone(),
|
||||||
|
on_clicked: on_selected_conversation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: ClassName::SPACE_CONVERSATIONS_CAROUSEL,
|
||||||
|
// TODO: Needed?
|
||||||
|
onscroll: move |_| {
|
||||||
|
// Catch scrolling events.
|
||||||
|
},
|
||||||
|
{rendered_avatars},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Space(id: SpaceId) -> Element {
|
||||||
|
let space = STORE.read().spaces().get(&id).unwrap().signal();
|
||||||
|
|
||||||
|
let name = space.name();
|
||||||
|
|
||||||
|
let mut selected_room_id = use_context_provider(|| Signal::new(None::<RoomId>));
|
||||||
|
let mut displayed_rooms = use_context_provider(|| Signal::new(Vec::<RoomId>::new()));
|
||||||
|
|
||||||
|
use_effect(move || {
|
||||||
|
// let rooms = STORE.read().rooms();
|
||||||
|
let rooms = STORE.peek().rooms();
|
||||||
|
let room_ids = space.room_ids();
|
||||||
|
for room_id in room_ids {
|
||||||
|
if rooms.contains_key(&room_id) {
|
||||||
|
displayed_rooms.write().push(room_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let on_selected_conversation = move |room_id: RoomId| {
|
||||||
|
trace!("");
|
||||||
|
selected_room_id.set(Some(room_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut space_classes: [&str; 2] = [ClassName::SPACE, ""];
|
||||||
|
|
||||||
|
let mut selected_room_name = "".to_string();
|
||||||
|
|
||||||
|
if let Some(room_id) = selected_room_id.read().as_ref() {
|
||||||
|
space_classes[1] = ClassName::DISPLAY_CONVERSATION_NAME;
|
||||||
|
|
||||||
|
if let Some(room) = STORE.read().rooms().get(room_id) {
|
||||||
|
let room = room.signal();
|
||||||
|
|
||||||
|
if let Some(name) = room.name() {
|
||||||
|
selected_room_name = name;
|
||||||
|
} else {
|
||||||
|
debug!("No name set for {} room", &room_id);
|
||||||
|
selected_room_name = room_id.to_string();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("No room found for the {} id", &room_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let classes_str = space_classes.join(" ");
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
style { {STYLE_SHEET} },
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "{classes_str}",
|
||||||
|
|
||||||
|
// Deselect the conversation on clicks outside of the ConversationAvatar
|
||||||
|
onclick: move |_| {
|
||||||
|
selected_room_id.set(None);
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::SPACE_NAME,
|
||||||
|
p {
|
||||||
|
{name}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ConversationsCarousel {
|
||||||
|
on_selected_conversation,
|
||||||
|
},
|
||||||
|
div {
|
||||||
|
class: ClassName::SPACE_CONVERSATION_NAME,
|
||||||
|
p {
|
||||||
|
{selected_room_name},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn HomeSpace() -> Element {
|
||||||
|
let name = "Home";
|
||||||
|
|
||||||
|
let mut selected_room_id = use_context_provider(|| Signal::new(None::<RoomId>));
|
||||||
|
let mut displayed_rooms = use_context_provider(|| Signal::new(Vec::<RoomId>::new()));
|
||||||
|
|
||||||
|
use_effect(move || {
|
||||||
|
let rooms = STORE.read().rooms();
|
||||||
|
for room in rooms.values() {
|
||||||
|
if room.signal().spaces().is_empty() {
|
||||||
|
let room_id = room.signal().id();
|
||||||
|
displayed_rooms.write().push(room_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let on_selected_conversation = move |room_id: RoomId| {
|
||||||
|
selected_room_id.set(Some(room_id));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut space_classes: [&str; 2] = [ClassName::SPACE, ""];
|
||||||
|
|
||||||
|
let mut selected_room_name = "".to_string();
|
||||||
|
|
||||||
|
if let Some(room_id) = selected_room_id.read().as_ref() {
|
||||||
|
space_classes[1] = ClassName::DISPLAY_CONVERSATION_NAME;
|
||||||
|
|
||||||
|
if let Some(room) = STORE.read().rooms().get(room_id) {
|
||||||
|
let room = room.signal();
|
||||||
|
|
||||||
|
if let Some(name) = room.name() {
|
||||||
|
selected_room_name = name;
|
||||||
|
} else {
|
||||||
|
debug!("No name set for {} room", &room_id);
|
||||||
|
selected_room_name = room_id.to_string();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("No room found for the {} id", &room_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let classes_str = space_classes.join(" ");
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
style { {STYLE_SHEET} },
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "{classes_str}",
|
||||||
|
|
||||||
|
// Deselect the conversation on clicks outside of the ConversationAvatar
|
||||||
|
onclick: move |_| {
|
||||||
|
selected_room_id.set(None);
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::SPACE_NAME,
|
||||||
|
p {
|
||||||
|
{name}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ConversationsCarousel {
|
||||||
|
on_selected_conversation,
|
||||||
|
},
|
||||||
|
div {
|
||||||
|
class: ClassName::SPACE_CONVERSATION_NAME,
|
||||||
|
p {
|
||||||
|
{selected_room_name},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Spaces() -> Element {
|
||||||
|
let spaces = STORE.read().spaces();
|
||||||
|
let space_ids = spaces.keys().clone().last();
|
||||||
|
|
||||||
|
let rendered_spaces = space_ids.map(|id| {
|
||||||
|
rsx! {
|
||||||
|
Space { id: id.clone() }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
style { {STYLE_SHEET} },
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::SPACES,
|
||||||
|
|
||||||
|
{rendered_spaces},
|
||||||
|
|
||||||
|
HomeSpace {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Search() -> Element {
|
||||||
|
rsx! {
|
||||||
|
style { {STYLE_SHEET} },
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::SEARCH,
|
||||||
|
|
||||||
|
TextInput {}
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::SEARCH_BUTTON,
|
||||||
|
Button {
|
||||||
|
SearchIcon {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Conversations() -> Element {
|
||||||
|
rsx! {
|
||||||
|
style { {STYLE_SHEET} },
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::CONVERSATIONS,
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::CONVERSATIONS_ACCOUNT,
|
||||||
|
Account {},
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::CONVERSATIONS_SPACES,
|
||||||
|
Spaces {},
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::CONVERSATIONS_SEARCH,
|
||||||
|
Search {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
305
src/ui/components/conversations.scss
Normal file
305
src/ui/components/conversations.scss
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
@import "../base.scss";
|
||||||
|
@import "./_panel.scss";
|
||||||
|
@import "./button.scss";
|
||||||
|
|
||||||
|
@mixin button-class {
|
||||||
|
button {
|
||||||
|
@include button(secondary, 90);
|
||||||
|
width: 100%;
|
||||||
|
max-height: 128px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.account {
|
||||||
|
$colum-spacing: 5%;
|
||||||
|
$col-width: 8.75%;
|
||||||
|
$button-width: 20%;
|
||||||
|
$button-height: calc(100% / 3);
|
||||||
|
$buttons-row-margin-top: 10%;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto $colum-spacing repeat(2, calc($button-width/2)) $colum-spacing $button-width $colum-spacing $button-width;
|
||||||
|
grid-template-rows: 30% auto $button-height;
|
||||||
|
row-gap: 5%;
|
||||||
|
grid-template-areas:
|
||||||
|
"avatar . state name name name name name"
|
||||||
|
"avatar . status status status status status status"
|
||||||
|
"avatar . spaces spaces . chat . room"
|
||||||
|
;
|
||||||
|
|
||||||
|
&__avatar {
|
||||||
|
grid-area: avatar;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
border: $border-normal;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__presence-state {
|
||||||
|
grid-area: state;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
stroke: get-color(greyscale, 90);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.online {
|
||||||
|
svg {
|
||||||
|
fill: get-color(primary, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.offline {
|
||||||
|
svg {
|
||||||
|
fill: get-color(ternary, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.unavailable {
|
||||||
|
svg {
|
||||||
|
fill: get-color(greyscale, 80);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__display-name {
|
||||||
|
grid-area: name;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 2.5vh;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__status {
|
||||||
|
grid-area: status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin extra-marged-button() {
|
||||||
|
@include button-class();
|
||||||
|
}
|
||||||
|
|
||||||
|
&__spaces {
|
||||||
|
grid-area: spaces;
|
||||||
|
|
||||||
|
@include extra-marged-button();
|
||||||
|
}
|
||||||
|
|
||||||
|
&__chat {
|
||||||
|
grid-area: chat;
|
||||||
|
|
||||||
|
@include extra-marged-button();
|
||||||
|
}
|
||||||
|
|
||||||
|
&__room {
|
||||||
|
grid-area: room;
|
||||||
|
|
||||||
|
@include extra-marged-button();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spaces {
|
||||||
|
$gap: 1%;
|
||||||
|
$spaces-to-display: 5;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $gap;
|
||||||
|
|
||||||
|
overflow-y: scroll;
|
||||||
|
// TODO: Manage android, Safari, ...
|
||||||
|
scrollbar-width: none;
|
||||||
|
|
||||||
|
$space-height: calc((100% - (($spaces-to-display - 1) * (1%))) / $spaces-to-display);
|
||||||
|
|
||||||
|
--space-height: #{$space-height};
|
||||||
|
}
|
||||||
|
|
||||||
|
.space {
|
||||||
|
$gap: 5%;
|
||||||
|
$vertical-padding: 1%;
|
||||||
|
$horizontal-padding: 1%;
|
||||||
|
|
||||||
|
height: var(--space-height);
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
border: $border-normal;
|
||||||
|
border-color: get-color(primary, 100);
|
||||||
|
border-radius: $border-radius;
|
||||||
|
|
||||||
|
padding: $vertical-padding $horizontal-padding;
|
||||||
|
|
||||||
|
$name-height: 15%;
|
||||||
|
$conversation-name-height: 15%;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
grid-template-columns: 100%;
|
||||||
|
grid-template-rows: $name-height $gap auto 0% 0%;
|
||||||
|
grid-template-areas:
|
||||||
|
"name"
|
||||||
|
"."
|
||||||
|
"conversations-carousel"
|
||||||
|
"."
|
||||||
|
"conversation-name"
|
||||||
|
;
|
||||||
|
|
||||||
|
transition: $transition-duration;
|
||||||
|
|
||||||
|
&.display-conversation-name {
|
||||||
|
grid-template-rows: $name-height $gap auto $gap $conversation-name-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
grid-area: name;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: left;
|
||||||
|
|
||||||
|
font-size: 2vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__conversations-carousel {
|
||||||
|
grid-area: conversations-carousel;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
gap: 1%;
|
||||||
|
|
||||||
|
overflow-x: scroll;
|
||||||
|
|
||||||
|
// TODO: Manage android, Safari, ...
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__conversation-name {
|
||||||
|
grid-area: conversation-name;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
font-size: 2vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversation-avatar {
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
border: $border-thin;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
filter: brightness(90%);
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
filter: brightness(120%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__image {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
background-size: cover;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
font-size: 6vh;
|
||||||
|
color: get-color(primary, 80);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__invited-badge {
|
||||||
|
$height: 20%;
|
||||||
|
|
||||||
|
height: $height;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
top: calc($height * -1);
|
||||||
|
|
||||||
|
color: get-color(greyscale, 0);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
font-size: 1.5vh;
|
||||||
|
|
||||||
|
background-color: get-color(ternary, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
gap: 5%;
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
@include button-class();
|
||||||
|
|
||||||
|
width: 20%;
|
||||||
|
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversations {
|
||||||
|
@include panel();
|
||||||
|
|
||||||
|
$gap: 1%;
|
||||||
|
$account-height: 15%;
|
||||||
|
$search-height: 5%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: $gap;
|
||||||
|
|
||||||
|
&__account {
|
||||||
|
height: $account-height;
|
||||||
|
max-height: 384px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__spaces {
|
||||||
|
min-height: calc(100% - $account-height - $search-height - (2 * $gap));
|
||||||
|
}
|
||||||
|
|
||||||
|
&__search {
|
||||||
|
height: $search-height;
|
||||||
|
max-height: 128px;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user