Compare commits
4 Commits
204e11b8b1
...
ffe759e749
Author | SHA1 | Date | |
---|---|---|---|
ffe759e749
|
|||
9baa7f290a
|
|||
d566a4927f
|
|||
1ad4d444fb
|
128
build.rs
128
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 {") {
|
||||
#[derive(Debug)]
|
||||
struct FloatVariable {
|
||||
name: String,
|
||||
value: f64,
|
||||
}
|
||||
|
||||
impl FloatVariable {
|
||||
pub fn new(name: String, value: f64) -> Self {
|
||||
Self { name, value }
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct Task {
|
||||
src_path: PathBuf,
|
||||
dst_path: PathBuf,
|
||||
module_name: String,
|
||||
}
|
||||
|
||||
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<Task>) {
|
||||
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 re = Regex::new(r"^\$([^:]+):[[:space:]]*#([^$]+);[[:space:]]*$").unwrap();
|
||||
|
||||
if let Ok(lines) = read_lines(src_path) {
|
||||
let mut variables = Vec::<Box<dyn Display>>::new();
|
||||
if let Ok(lines) = read_lines(task.src_path) {
|
||||
for line in lines.map_while(Result::ok) {
|
||||
let Some(groups) = re.captures(&line) else {
|
||||
continue;
|
||||
};
|
||||
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::<f64>() {
|
||||
variables.push(Box::new(FloatVariable::new(groups[1].to_string(), value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let var = CssColorVariable {
|
||||
name: &groups[1],
|
||||
value: &groups[2],
|
||||
};
|
||||
|
||||
let rust_export = var.to_rust();
|
||||
if let Err(err) = dst_file.write_fmt(format_args!(" pub {}\n", rust_export)) {
|
||||
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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -284,17 +284,15 @@ pub fn ConversationsCarousel(on_selected_conversation: EventHandler<RoomId>) ->
|
||||
}
|
||||
}
|
||||
|
||||
// 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<SpaceId>) -> Element {
|
||||
let mut selected_room_id = use_context_provider(|| Signal::new(None::<RoomId>));
|
||||
let mut displayed_rooms = use_context_provider(|| Signal::new(Vec::<RoomId>::new()));
|
||||
|
||||
let name = if let Some(id) = id {
|
||||
let space = STORE.read().spaces().get(&id).unwrap().signal();
|
||||
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 {
|
||||
@@ -303,72 +301,8 @@ pub fn Space(id: SpaceId) -> Element {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
space.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() {
|
||||
@@ -378,8 +312,11 @@ pub fn HomeSpace() -> Element {
|
||||
}
|
||||
}
|
||||
});
|
||||
Some("Home".to_string())
|
||||
};
|
||||
|
||||
let on_selected_conversation = move |room_id: RoomId| {
|
||||
STORE.write().on_selected_room(room_id.clone());
|
||||
selected_room_id.set(Some(room_id));
|
||||
};
|
||||
|
||||
@@ -454,7 +391,7 @@ pub fn Spaces() -> Element {
|
||||
|
||||
{rendered_spaces},
|
||||
|
||||
HomeSpace {},
|
||||
Space {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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};
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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};
|
||||
|
||||
|
@@ -2,16 +2,23 @@ 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,
|
||||
},
|
||||
STORE,
|
||||
};
|
||||
|
||||
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<MountedData>,
|
||||
@@ -44,20 +51,27 @@ async fn on_carousel_scroll(
|
||||
}
|
||||
|
||||
fn LayoutSmall() -> 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 mut carousel_div = use_signal(|| None::<Rc<MountedData>>);
|
||||
|
||||
let conversation_panels_nb = 3;
|
||||
let conversation_panels = (0..conversation_panels_nb + 1).map(|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();
|
||||
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 #{i}") },
|
||||
ChatPanel { name: format!("CHAT {room_name_repr}") },
|
||||
}
|
||||
};
|
||||
|
||||
if i == conversation_panels_nb {
|
||||
// If this is the last iteration
|
||||
let panel = if displayed_room_ids_it.peek().is_none() {
|
||||
rsx! {
|
||||
div {
|
||||
class: ClassName::CONVERSATIONS_VIEW_SMALL_PANEL,
|
||||
@@ -72,8 +86,15 @@ fn LayoutSmall() -> Element {
|
||||
{inner}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(panel) = panel {
|
||||
conversation_panels.push(panel);
|
||||
}
|
||||
} else {
|
||||
warn!("No {} room found", room_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
rsx! {
|
||||
style { {STYLE_SHEET} },
|
||||
@@ -116,7 +137,7 @@ fn LayoutSmall() -> Element {
|
||||
},
|
||||
},
|
||||
|
||||
{conversation_panels}
|
||||
{conversation_panels.iter()}
|
||||
|
||||
div {
|
||||
class: ClassName::CONVERSATIONS_VIEW_TAIL,
|
||||
@@ -210,22 +231,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::<VNode>);
|
||||
|
||||
rsx! {
|
||||
style { {STYLE_SHEET} },
|
||||
@@ -236,14 +242,21 @@ 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 * 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}
|
||||
|
@@ -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 {
|
||||
|
@@ -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};
|
||||
}
|
||||
}
|
||||
|
@@ -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<RoomId, Rc<Room>>,
|
||||
spaces: HashMap<SpaceId, Rc<Space>>,
|
||||
|
||||
displayed_room_ids: HashSet<RoomId>,
|
||||
}
|
||||
|
||||
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)]
|
||||
|
Reference in New Issue
Block a user