Compare commits
5 Commits
f52733d9a6
...
c746fb6552
Author | SHA1 | Date | |
---|---|---|---|
c746fb6552
|
|||
1073a592ed
|
|||
dd0754073c
|
|||
b05e3efce4
|
|||
0a4969e079
|
@@ -27,7 +27,12 @@ log = "0.4.20"
|
|||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
futures-util = "0.3.29"
|
futures-util = "0.3.29"
|
||||||
futures = "0.3.29"
|
futures = "0.3.29"
|
||||||
|
rand = "0.8.5"
|
||||||
|
reqwest = "0.11.24"
|
||||||
|
constcat = "0.5.0"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
target = "x86_64-unknown-linux-gnu"
|
target = "x86_64-unknown-linux-gnu"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
regex = "1.10.3"
|
||||||
|
76
build.rs
76
build.rs
@@ -1,5 +1,81 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::io::{self, BufRead};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Tell Cargo to rerun this build script if any SCSS file
|
// Tell Cargo to rerun this build script if any SCSS file
|
||||||
// in the 'src' directory or its subdirectories changes.
|
// in the 'src' directory or its subdirectories changes.
|
||||||
println!("cargo:rerun-if-changed=src/**/*.scss");
|
println!("cargo:rerun-if-changed=src/**/*.scss");
|
||||||
|
|
||||||
|
let out_dir = env::var("OUT_DIR").unwrap();
|
||||||
|
|
||||||
|
let style_src_path = PathBuf::from("src/_base.scss");
|
||||||
|
let style_dst_path = Path::new(&out_dir).join("style_vars.rs");
|
||||||
|
|
||||||
|
export_color_variables(&style_src_path, &style_dst_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// From https://doc.rust-lang.org/rust-by-example/std_misc/file/read_lines.html
|
||||||
|
// The output is wrapped in a Result to allow matching on errors.
|
||||||
|
// Returns an Iterator to the Reader of the lines of the file.
|
||||||
|
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let file = File::open(filename)?;
|
||||||
|
Ok(io::BufReader::new(file).lines())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct CssColorVariable<'a> {
|
||||||
|
name: &'a str,
|
||||||
|
value: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CssColorVariable<'a> {
|
||||||
|
pub fn to_rust(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"const {name}: &str = \"{value}\";",
|
||||||
|
name = self.name.replace("-", "_").to_uppercase(),
|
||||||
|
value = self.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
let re = Regex::new(r"^\$([^:]+):[[:space:]]*#([^$]+);[[:space:]]*$").unwrap();
|
||||||
|
|
||||||
|
if let Ok(lines) = read_lines(src_path) {
|
||||||
|
for line in lines.flatten() {
|
||||||
|
let Some(groups) = re.captures(&line) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
println!("{}", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(err) = dst_file.write(b"}\n") {
|
||||||
|
println!("{}", err);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
1
images/login-profile-placeholder.svg
Normal file
1
images/login-profile-placeholder.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none" shape-rendering="auto"><desc>"Shapes" by "Florian Körner", licensed under "CC0 1.0". / Remix of the original. - Created with dicebear.com</desc><metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:rdf><cc:work><dc:title>Shapes</dc:title><dc:creator><cc:agent rdf:about="https://www.dicebear.com"><dc:title>Florian Körner</dc:title></cc:agent></dc:creator><dc:source>https://www.dicebear.com</dc:source><cc:license rdf:resource="https://creativecommons.org/publicdomain/zero/1.0/"></cc:license></cc:work></rdf:rdf></metadata><mask id="w6sj6i8m"><rect width="100" height="100" rx="0" ry="0" x="0" y="0" fill="#fff"></rect></mask><g mask="url(#w6sj6i8m)"><rect fill="#E2F2F7" width="100" height="100" x="0" y="0"></rect><g transform="matrix(1.2 0 0 1.2 -10 -10)"><g transform="translate(51, -23) rotate(-38 50 50)"><path d="M0 0h100v100H0V0Z" fill="#83CADE"></path></g></g><g transform="matrix(.8 0 0 .8 10 10)"><g transform="translate(-2, 35) rotate(99 50 50)"><path d="M100 50A50 50 0 1 1 0 50a50 50 0 0 1 100 0Z" fill="#6957A0"></path></g></g><g transform="matrix(.4 0 0 .4 30 30)"><g transform="translate(-18, -6) rotate(-97 50 50)"><path d="m50 7 50 86.6H0L50 7Z" fill="#D53583"></path></g></g></g></svg>
|
After Width: | Height: | Size: 1.4 KiB |
14
images/wallpaper-pattern.svg
Normal file
14
images/wallpaper-pattern.svg
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<!-- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1152 128"> -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="128" width="384" viewBox="0 0 384 128">
|
||||||
|
<pattern id="p" width="384" height="128" patternUnits="userSpaceOnUse" stroke="#1B1B1B" stroke-linejoin="round" stroke-width="4">
|
||||||
|
<path fill="#1DB2CF" d="M 9.736 -15 L -30 3.337 l 23.642 -0.088 L -10.212 15 L 30 -3.425 H 6.834 L 9.736 -15 Z"/>
|
||||||
|
<path fill="#D53583" d="M 201.736 -15 L 162 3.337 l 23.642 -0.088 L 181.788 15 L 222 -3.425 H 198.834 L 201.736 -15 Z"/>
|
||||||
|
<path fill="#1DB2CF" d="M 393.736 -15 L 354 3.337 l 23.642 -0.088 L 373.788 15 L 414 -3.425 H 390.834 L 393.736 -15 Z"/>
|
||||||
|
<path fill="#7E6BB6" d="M 109.736 50 L 70 68.337 l 23.642 -0.088 L 89.788 80 L 130 61.575 H 106.834 L 109.736 50 Z"/>
|
||||||
|
<path fill="#7E6BB6" d="M 297.736 50 L 258 68.337 l 23.642 -0.088 L 277.788 80 L 318 61.575 H 294.834 L 297.736 50 Z"/>
|
||||||
|
<path fill="#1DB2CF" d="M 9.736 114 L -30 132.337 l 23.642 -0.088 L -10.212 144 L 30 125.575 H 6.834 L 9.736 114 Z"/>
|
||||||
|
<path fill="#D53583" d="M 201.736 114 L 162 132.337 l 23.642 -0.088 L 181.788 144 L 222 125.575 H 198.834 L 201.736 114 Z"/>
|
||||||
|
<path fill="#1DB2CF" d="M 393.736 114 L 354 132.337 l 23.642 -0.088 L 373.788 144 L 414 125.575 H 390.834 L 393.736 114 Z"/>
|
||||||
|
</pattern>
|
||||||
|
<rect fill="url(#p)" width="100%" height="100%"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
@@ -51,13 +51,16 @@ $color-ternary-70: #8B0046;
|
|||||||
$color-ternary-60: #720033;
|
$color-ternary-60: #720033;
|
||||||
$color-ternary-50: #5A0022;
|
$color-ternary-50: #5A0022;
|
||||||
|
|
||||||
|
$color-critical: #C91B13;
|
||||||
|
$color-warning: #FFA316;
|
||||||
|
$color-success: #4AAB79;
|
||||||
|
|
||||||
$border-default-color: $greyscale-90;
|
$border-default-color: $greyscale-90;
|
||||||
$border-big-width: 4px;
|
$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;
|
||||||
|
|
||||||
|
|
||||||
$form-max-height: 1024px;
|
$form-max-height: 1024px;
|
||||||
$form-aspect-ratio: 1/1.618;
|
$form-aspect-ratio: 1/1.618;
|
||||||
|
|
||||||
|
@@ -53,13 +53,11 @@ impl IconShape for RegisterText {
|
|||||||
fn child_elements(&self) -> LazyNodes {
|
fn child_elements(&self) -> LazyNodes {
|
||||||
rsx! {
|
rsx! {
|
||||||
text {
|
text {
|
||||||
x: "50%",
|
|
||||||
y: "50%",
|
y: "50%",
|
||||||
"dominant-baseline": "middle",
|
"dominant-baseline": "central",
|
||||||
"text-anchor": "middle",
|
|
||||||
"font-size": "50",
|
"font-size": "50",
|
||||||
style: "fill: #ffffff",
|
style: "fill: #ffffff",
|
||||||
"Register"
|
"REGISTER"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +101,7 @@ pub fn RegisterButton<'a>(cx: Scope<'a, ButtonProps>) -> Element<'a> {
|
|||||||
struct LoginText;
|
struct LoginText;
|
||||||
impl IconShape for LoginText {
|
impl IconShape for LoginText {
|
||||||
fn view_box(&self) -> String {
|
fn view_box(&self) -> String {
|
||||||
String::from("0 0 250 50")
|
String::from("0 0 150 50")
|
||||||
}
|
}
|
||||||
fn xmlns(&self) -> String {
|
fn xmlns(&self) -> String {
|
||||||
String::from("http://www.w3.org/2000/svg")
|
String::from("http://www.w3.org/2000/svg")
|
||||||
@@ -111,13 +109,11 @@ impl IconShape for LoginText {
|
|||||||
fn child_elements(&self) -> LazyNodes {
|
fn child_elements(&self) -> LazyNodes {
|
||||||
rsx! {
|
rsx! {
|
||||||
text {
|
text {
|
||||||
x: "50%",
|
|
||||||
y: "50%",
|
y: "50%",
|
||||||
"dominant-baseline": "middle",
|
"dominant-baseline": "central",
|
||||||
"text-anchor": "middle",
|
|
||||||
"font-size": "50",
|
"font-size": "50",
|
||||||
style: "fill: #ffffff",
|
style: "fill: #ffffff",
|
||||||
"Login"
|
"LOGIN"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
height: 100%;
|
height: 60%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,131 +1,110 @@
|
|||||||
use std::str::FromStr;
|
use constcat::concat as const_concat;
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use fermi::*;
|
use fermi::*;
|
||||||
use tracing::debug;
|
use rand::distributions::{Alphanumeric, DistString};
|
||||||
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
use crate::base::SESSION;
|
use crate::base::{Session, SESSION};
|
||||||
use crate::components::avatar_selector::AvatarSelector;
|
|
||||||
use crate::components::header::Header;
|
use super::button::{LoginButton, RegisterButton};
|
||||||
|
use super::spinner::Spinner;
|
||||||
|
use super::text_field::TextField;
|
||||||
|
|
||||||
|
use super::wallpaper::Wallpaper;
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/style_vars.rs"));
|
||||||
|
|
||||||
turf::style_sheet!("src/components/login.scss");
|
turf::style_sheet!("src/components/login.scss");
|
||||||
|
|
||||||
static EMPTY_PLACEHOLDER: &str = "Tmp placeholder";
|
const SEP: &str = ",";
|
||||||
|
|
||||||
pub fn Login(cx: Scope) -> Element {
|
const BACKGROUND_COLORS_STR: &str = const_concat!(
|
||||||
debug!("Login rendering");
|
style::COLOR_PRIMARY_150,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_PRIMARY_140,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_SECONDARY_150,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_SECONDARY_140,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_TERNARY_150,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_TERNARY_140,
|
||||||
|
);
|
||||||
|
|
||||||
let session = use_atom_ref(cx, &SESSION);
|
const SHAPE_1_COLORS_STR: &str = const_concat!(
|
||||||
|
style::COLOR_PRIMARY_120,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_PRIMARY_110,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_PRIMARY_100,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_PRIMARY_90,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_PRIMARY_80,
|
||||||
|
);
|
||||||
|
|
||||||
let invalid_login = use_state(cx, || false);
|
const SHAPE_2_COLORS_STR: &str = const_concat!(
|
||||||
|
style::COLOR_SECONDARY_120,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_SECONDARY_110,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_SECONDARY_100,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_SECONDARY_90,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_SECONDARY_80,
|
||||||
|
);
|
||||||
|
|
||||||
let login = use_ref(cx, Login::new);
|
const SHAPE_3_COLORS_STR: &str = const_concat!(
|
||||||
|
style::COLOR_TERNARY_120,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_TERNARY_110,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_TERNARY_100,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_TERNARY_90,
|
||||||
|
SEP,
|
||||||
|
style::COLOR_TERNARY_80,
|
||||||
|
);
|
||||||
|
|
||||||
let password_class = if **invalid_login {
|
async fn generate_random_avatar(url: &String) -> Option<String> {
|
||||||
ClassName::INVALID_INPUT
|
let seed = Alphanumeric.sample_string(&mut rand::thread_rng(), 16);
|
||||||
} else {
|
let req = format!(
|
||||||
""
|
"https://{url}/7.x/shapes/svg?\
|
||||||
};
|
seed={seed}&\
|
||||||
|
backgroundColor={BACKGROUND_COLORS_STR}&\
|
||||||
let run_matrix_client = move |_| {
|
shape1Color={SHAPE_1_COLORS_STR}&\
|
||||||
cx.spawn({
|
shape2Color={SHAPE_2_COLORS_STR}&\
|
||||||
|
shape3Color={SHAPE_3_COLORS_STR}"
|
||||||
to_owned![session, login];
|
|
||||||
|
|
||||||
async move {
|
|
||||||
let login_ref = login.read();
|
|
||||||
|
|
||||||
session.write().update(
|
|
||||||
login_ref.homeserver_url.clone(),
|
|
||||||
login_ref.email.clone(),
|
|
||||||
login_ref.password.clone(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut res: Option<String> = None;
|
||||||
|
match reqwest::get(req).await {
|
||||||
|
Ok(result) => {
|
||||||
|
match result.text().await {
|
||||||
|
Ok(svg) => {
|
||||||
|
res = Some(svg);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("Error during placeholder loading: {}", err);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
let login_ref = login.read();
|
Err(err) => {
|
||||||
let placeholder = EMPTY_PLACEHOLDER.to_string();
|
error!("Error during placeholder loading: {}", err);
|
||||||
let homeserver_url_value = login_ref.homeserver_url.as_ref().unwrap_or(&placeholder);
|
}
|
||||||
let email_value = login_ref.email.as_ref().unwrap_or(&placeholder);
|
};
|
||||||
let password_value = login_ref.password.as_ref().unwrap_or(&placeholder);
|
res
|
||||||
|
|
||||||
cx.render(rsx! {
|
|
||||||
style { STYLE_SHEET },
|
|
||||||
|
|
||||||
div {
|
|
||||||
class: ClassName::ROOT,
|
|
||||||
|
|
||||||
div {
|
|
||||||
class: ClassName::HEADER,
|
|
||||||
Header {},
|
|
||||||
},
|
|
||||||
|
|
||||||
div {
|
|
||||||
class: ClassName::BODY,
|
|
||||||
div {
|
|
||||||
class: ClassName::AVATAR_SELECTOR_CONTAINER,
|
|
||||||
AvatarSelector {},
|
|
||||||
},
|
|
||||||
|
|
||||||
p {
|
|
||||||
"Matrix homeserver:"
|
|
||||||
},
|
|
||||||
input {
|
|
||||||
id: "input-homeserver-url",
|
|
||||||
r#type: "text",
|
|
||||||
name: "homeserver URL",
|
|
||||||
value: "{homeserver_url_value}",
|
|
||||||
oninput: move |evt| login.write().homeserver_url = Some(evt.value.clone()),
|
|
||||||
},
|
|
||||||
|
|
||||||
p {
|
|
||||||
"E-mail address:"
|
|
||||||
},
|
|
||||||
input {
|
|
||||||
id: "login-input-email",
|
|
||||||
r#type: "text",
|
|
||||||
name: "email",
|
|
||||||
value: "{email_value}",
|
|
||||||
oninput: move |evt| login.write().email = Some(evt.value.clone()),
|
|
||||||
},
|
|
||||||
p {
|
|
||||||
"Password:"
|
|
||||||
},
|
|
||||||
input {
|
|
||||||
class: "{password_class}",
|
|
||||||
id: "login-input-password",
|
|
||||||
r#type: "password",
|
|
||||||
name: "Password",
|
|
||||||
value: "{password_value}",
|
|
||||||
oninput: move |evt| {
|
|
||||||
login.write().password = Some(evt.value.clone());
|
|
||||||
invalid_login.set(false);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
div {
|
|
||||||
class: ClassName::FOOTER_BUTTONS,
|
|
||||||
input {
|
|
||||||
class: ClassName::BUTTON,
|
|
||||||
onclick: run_matrix_client,
|
|
||||||
r#type: "submit",
|
|
||||||
value: "sign in",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
struct LoginData {
|
||||||
struct Login {
|
|
||||||
homeserver_url: Option<String>,
|
homeserver_url: Option<String>,
|
||||||
email: Option<String>,
|
email: Option<String>,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Login {
|
impl LoginData {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
homeserver_url: None,
|
homeserver_url: None,
|
||||||
@@ -134,3 +113,163 @@ impl Login {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn on_login(session_ref: &UseAtomRef<Session>, login_ref: &UseRef<LoginData>) {
|
||||||
|
let login = login_ref.read();
|
||||||
|
|
||||||
|
session_ref.write().update(
|
||||||
|
login.homeserver_url.clone(),
|
||||||
|
login.email.clone(),
|
||||||
|
login.password.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Props)]
|
||||||
|
pub struct LoginProps<'a> {
|
||||||
|
dicebear_hostname: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Login<'a>(cx: Scope<'a, LoginProps>) -> Element<'a> {
|
||||||
|
debug!("Login rendering");
|
||||||
|
|
||||||
|
let session = use_atom_ref(cx, &SESSION);
|
||||||
|
|
||||||
|
let login = use_ref(cx, LoginData::new);
|
||||||
|
let login_ref = login.read();
|
||||||
|
let homeserver_url = login_ref.homeserver_url.as_deref().unwrap_or("");
|
||||||
|
let email = login_ref.email.as_deref().unwrap_or("");
|
||||||
|
let password = login_ref.password.as_deref().unwrap_or("");
|
||||||
|
|
||||||
|
// TODO: Enable the spinner for registration and login steps.
|
||||||
|
let spinner_animated = use_state(cx, || false);
|
||||||
|
|
||||||
|
let url = cx
|
||||||
|
.props
|
||||||
|
.dicebear_hostname
|
||||||
|
.unwrap_or("dicebear.tools.adrien.run")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let random_avatar_future =
|
||||||
|
use_future(
|
||||||
|
cx,
|
||||||
|
&url,
|
||||||
|
|url| async move { generate_random_avatar(&url).await },
|
||||||
|
);
|
||||||
|
|
||||||
|
let avatar = match random_avatar_future.value() {
|
||||||
|
Some(Some(svg)) => {
|
||||||
|
rsx!(div {
|
||||||
|
class: ClassName::CONTENT,
|
||||||
|
dangerous_inner_html: svg.as_str(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Some(None) | None => {
|
||||||
|
warn!("No profile image set or generated, display the placeholder");
|
||||||
|
rsx!(div {
|
||||||
|
class: ClassName::CONTENT,
|
||||||
|
img {
|
||||||
|
src: "./images/login-profile-placeholder.svg"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if **spinner_animated && session.read().is_logged {
|
||||||
|
debug!("Stop spinner");
|
||||||
|
spinner_animated.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let on_login = move |_| {
|
||||||
|
cx.spawn({
|
||||||
|
|
||||||
|
to_owned![login, session, spinner_animated];
|
||||||
|
|
||||||
|
async move {
|
||||||
|
spinner_animated.set(true);
|
||||||
|
on_login(&session, &login).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
style { STYLE_SHEET },
|
||||||
|
|
||||||
|
Wallpaper {},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::ROOT,
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::FORM,
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::PHOTO,
|
||||||
|
|
||||||
|
onclick: move |_| {
|
||||||
|
random_avatar_future.restart()
|
||||||
|
},
|
||||||
|
|
||||||
|
{avatar},
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::HOMESERVER,
|
||||||
|
TextField {
|
||||||
|
id: "hs_url",
|
||||||
|
r#type: "text",
|
||||||
|
placeholder: "Homeserver URL",
|
||||||
|
value: "{homeserver_url}",
|
||||||
|
oninput: move |evt: FormEvent| login.write().homeserver_url = Some(evt.value.clone()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::EMAIL,
|
||||||
|
TextField {
|
||||||
|
id: "email",
|
||||||
|
r#type: "email",
|
||||||
|
placeholder: "Email",
|
||||||
|
value: "{email}",
|
||||||
|
is_value_valid: false,
|
||||||
|
oninput: move |evt: FormEvent| login.write().email = Some(evt.value.clone()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::PASSWORD,
|
||||||
|
TextField {
|
||||||
|
id: "password",
|
||||||
|
r#type: "password",
|
||||||
|
placeholder: "Password",
|
||||||
|
value: "{password}",
|
||||||
|
oninput: move |evt: FormEvent| login.write().password = Some(evt.value.clone()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::SPINNER,
|
||||||
|
Spinner {
|
||||||
|
animate: **spinner_animated,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::REGISTER,
|
||||||
|
RegisterButton {
|
||||||
|
id: "register",
|
||||||
|
// TODO: Handle the registration process
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: ClassName::LOGIN,
|
||||||
|
LoginButton {
|
||||||
|
id: "login",
|
||||||
|
focus: true,
|
||||||
|
onclick: on_login,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -1,53 +1,111 @@
|
|||||||
@import "../_base.scss";
|
@import "../_base.scss"
|
||||||
|
@import "./spinner.scss"
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
width: 90%;
|
height: 100%;
|
||||||
height: 98%;
|
width: 100%;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
padding: 5%;
|
|
||||||
padding-top: 2%;
|
|
||||||
|
|
||||||
background: linear-gradient(rgb(138, 191, 209), rgb(236, 246, 249) 10%);
|
|
||||||
|
|
||||||
.header {
|
|
||||||
height: 5%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body {
|
|
||||||
height: 50%;
|
|
||||||
width: 50%;
|
|
||||||
max-width: 400px;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
padding-bottom: 3%;
|
position: relative;
|
||||||
|
top: -100vh;
|
||||||
|
|
||||||
.invalidInput {
|
.form {
|
||||||
border-color: red;
|
$height: 95%;
|
||||||
}
|
height: $height;
|
||||||
|
|
||||||
.avatar-selector-container {
|
max-height: $form-max-height;
|
||||||
height: 30%;
|
aspect-ratio: $form-aspect-ratio;
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
padding-left: 25%;
|
border: $border-big;
|
||||||
}
|
border-color: $color-primary-90;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
|
||||||
|
background-color: $greyscale-0;
|
||||||
|
|
||||||
.footerButtons {
|
display: grid;
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
padding-top: 5%;
|
$padding-col: 5%;
|
||||||
|
$button-height: 8%;
|
||||||
|
$button-overlap: 5%;
|
||||||
|
$profile-img-width: 40%;
|
||||||
|
$edit-padding: 7.5%;
|
||||||
|
$photo-padding: 17.5%;
|
||||||
|
|
||||||
|
$padding-row: calc(5% * $form-aspect-ratio);
|
||||||
|
$spinner-col-width: calc(0% + ($button-overlap * 2));
|
||||||
|
$profile-img-height: calc($profile-img-width * $form-aspect-ratio);
|
||||||
|
$profile-img-ext-width: calc((($profile-img-width - $spinner-col-width) / 2) - $button-overlap);
|
||||||
|
$center-width: calc((50% - $padding-col - $edit-padding - $photo-padding - $button-overlap -
|
||||||
|
$profile-img-ext-width) * 2);
|
||||||
|
$spinner-height: calc(($spinner-col-width + $center-width) * $form-aspect-ratio / $logo-aspect-ratio);
|
||||||
|
|
||||||
|
grid-template-columns: $padding-col $edit-padding $photo-padding
|
||||||
|
$button-overlap $profile-img-ext-width $center-width $profile-img-ext-width $button-overlap
|
||||||
|
$photo-padding $edit-padding $padding-col;
|
||||||
|
grid-template-rows: $padding-row $profile-img-height auto 5% 5% 5% 5% 5% 8.5% $spinner-height 8.5% $button-height $padding-row;
|
||||||
|
grid-template-areas:
|
||||||
|
". . . . . . . . . . ."
|
||||||
|
". . . photo photo photo photo photo . . ."
|
||||||
|
". . . . . . . . . . ."
|
||||||
|
". . homeserver homeserver homeserver homeserver homeserver homeserver homeserver . ."
|
||||||
|
". . . . . . . . . . ."
|
||||||
|
". . username username username username username username username . ."
|
||||||
|
". . . . . . . . . . ."
|
||||||
|
". . status status status status status status status . ."
|
||||||
|
". . . . . . . . . . ."
|
||||||
|
". . . . spinner spinner spinner . . . ."
|
||||||
|
". . . . . . . . . . ."
|
||||||
|
". register register register register . login login login login ."
|
||||||
|
". . . . . . . . . . ."
|
||||||
|
;
|
||||||
|
|
||||||
|
.photo {
|
||||||
|
grid-area: photo;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
|
border: $border-normal;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
height: calc(100% + (2 * $border-big-width));
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.homeserver {
|
||||||
|
grid-area: homeserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email {
|
||||||
|
grid-area: username;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password {
|
||||||
|
grid-area: status;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
grid-area: spinner;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.register {
|
||||||
|
grid-area: register;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login {
|
||||||
|
grid-area: login;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@ use tracing::debug;
|
|||||||
|
|
||||||
use crate::base::SESSION;
|
use crate::base::SESSION;
|
||||||
use crate::components::contacts_window::ContactsWindow;
|
use crate::components::contacts_window::ContactsWindow;
|
||||||
use crate::components::login::Login;
|
|
||||||
|
|
||||||
pub fn MainWindow(cx: Scope) -> Element {
|
pub fn MainWindow(cx: Scope) -> Element {
|
||||||
debug!("MainWindow rendering");
|
debug!("MainWindow rendering");
|
||||||
@@ -16,8 +15,5 @@ pub fn MainWindow(cx: Scope) -> Element {
|
|||||||
if is_logged {
|
if is_logged {
|
||||||
rsx!(ContactsWindow {})
|
rsx!(ContactsWindow {})
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
rsx!(Login {})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -8,4 +8,5 @@ pub mod loading;
|
|||||||
pub mod login;
|
pub mod login;
|
||||||
pub mod main_window;
|
pub mod main_window;
|
||||||
pub mod spinner;
|
pub mod spinner;
|
||||||
|
pub mod text_field;
|
||||||
pub mod wallpaper;
|
pub mod wallpaper;
|
||||||
|
45
src/components/text_field.rs
Normal file
45
src/components/text_field.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
turf::style_sheet!("src/components/text_field.scss");
|
||||||
|
|
||||||
|
#[derive(Props)]
|
||||||
|
pub struct TextFieldProps<'a> {
|
||||||
|
id: Option<&'a str>,
|
||||||
|
oninput: Option<EventHandler<'a, Event<FormData>>>,
|
||||||
|
placeholder: Option<&'a str>,
|
||||||
|
r#type: Option<&'a str>,
|
||||||
|
value: Option<&'a str>,
|
||||||
|
#[props(default = true)]
|
||||||
|
is_value_valid: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn TextField<'a>(cx: Scope<'a, TextFieldProps<'a>>) -> Element<'a> {
|
||||||
|
let classes = [
|
||||||
|
ClassName::ROOT,
|
||||||
|
if !cx.props.is_value_valid {
|
||||||
|
ClassName::INVALID_DATA
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
style { STYLE_SHEET },
|
||||||
|
|
||||||
|
input {
|
||||||
|
class: "{classes}",
|
||||||
|
|
||||||
|
id: cx.props.id.unwrap_or(""),
|
||||||
|
|
||||||
|
oninput: move |evt| {
|
||||||
|
if let Some(cb) = &cx.props.oninput {
|
||||||
|
cb.call(evt);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
r#type: cx.props.r#type,
|
||||||
|
placeholder: cx.props.placeholder,
|
||||||
|
value: cx.props.value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
25
src/components/text_field.scss
Normal file
25
src/components/text_field.scss
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
@import "../_base.scss"
|
||||||
|
|
||||||
|
.root {
|
||||||
|
$horizontal-padding: 1vw;
|
||||||
|
|
||||||
|
padding-left: $horizontal-padding;
|
||||||
|
padding-right: $horizontal-padding;
|
||||||
|
padding-top: 0px;
|
||||||
|
padding-bottom: 0px;
|
||||||
|
|
||||||
|
height: calc(100% - (2 * ($border-normal-width)));
|
||||||
|
width: calc(100% - (2 * ($border-normal-width + $horizontal-padding)));
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
border: $border-normal;
|
||||||
|
border-color: $color-primary-90;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
|
||||||
|
font-size: 2vh;
|
||||||
|
|
||||||
|
&.invalid-data {
|
||||||
|
border-color: $color-critical;
|
||||||
|
}
|
||||||
|
}
|
@@ -12,7 +12,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
background-image: url("./images/background.svg");
|
background-image: url("./images/wallpaper-pattern.svg");
|
||||||
background-position: center;
|
background-position: center;
|
||||||
|
|
||||||
width: 150%;
|
width: 150%;
|
||||||
|
Reference in New Issue
Block a user