🎉 First commit (💩)
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
Cargo.lock
|
22
Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "beau-gosse-du-92"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dioxus = "0.3.2"
|
||||||
|
sir = { version = "0.3.0", features = ["dioxus"] }
|
||||||
|
dioxus-desktop = "0.3.0"
|
||||||
|
# matrix-sdk = { version = "0.6.2", features = ["js"] }
|
||||||
|
matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", branch = "main" , features = ["js"]}
|
||||||
|
anyhow = "1.0.71"
|
||||||
|
url = "2.4.0"
|
||||||
|
tokio = "1.29.1"
|
||||||
|
dirs = "5.0.1"
|
||||||
|
ctrlc-async = "3.2.2"
|
||||||
|
tracing-subscriber = "0.3.17"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
target = "x86_64-unknown-linux-gnu"
|
BIN
images/add_user.png
Normal file
After Width: | Height: | Size: 698 B |
BIN
images/aerobutton_border.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
images/aerobutton_border_down.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
images/brush.png
Normal file
After Width: | Height: | Size: 494 B |
BIN
images/button_border.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
images/button_border_disabled.png
Normal file
After Width: | Height: | Size: 221 B |
BIN
images/button_border_focus.png
Normal file
After Width: | Height: | Size: 223 B |
BIN
images/default-avatar.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
images/directory.png
Normal file
After Width: | Height: | Size: 755 B |
BIN
images/letter.png
Normal file
After Width: | Height: | Size: 306 B |
BIN
images/logo-msn.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
images/news.png
Normal file
After Width: | Height: | Size: 430 B |
BIN
images/settings.png
Normal file
After Width: | Height: | Size: 388 B |
BIN
images/status_away.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
images/status_busy.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
images/status_online.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
images/tbc_transfert.png
Normal file
After Width: | Height: | Size: 323 B |
29
src/app_settings.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use crate::matrix_client::MatrixClient;
|
||||||
|
use crate::matrix_client::Requester;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AppSettings {
|
||||||
|
// pub matrix_client: Option<Arc<Mutex<MatrixClient>>>,
|
||||||
|
// pub matrix_client: Arc<MatrixClient>,
|
||||||
|
// pub matrix_client: Rc<RefCell<MatrixClient>>,
|
||||||
|
// pub matrix_client: Arc<MatrixClient>,
|
||||||
|
//pub matrix_client: Arc<Mutex<MatrixClient>>,
|
||||||
|
pub requester: Option<Arc<Requester>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppSettings {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
// matrix_client: Arc::new(MatrixClient::new()),
|
||||||
|
//matrix_client: Arc::new(Mutex::new(MatrixClient::new())),
|
||||||
|
requester: None,
|
||||||
|
// matrix_client: Arc::new(Mutex::new(MatrixClient::new())),
|
||||||
|
// matrix_client: Arc::new(MatrixClient::new()),
|
||||||
|
// matrix_client: Rc::new(RefCell::new(MatrixClient::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
src/base.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum Info {}
|
||||||
|
|
||||||
|
// pub type ProgramStore = Store<Info>;
|
||||||
|
// pub type AsyncProgramStore = Arc<AsyncMutex<ProgramStore>>;
|
4
src/components.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
pub mod avatar_selector;
|
||||||
|
pub mod control_window;
|
||||||
|
pub mod header;
|
||||||
|
pub mod login;
|
77
src/components/avatar_selector.rs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use sir::css;
|
||||||
|
|
||||||
|
pub fn AvatarSelector(cx: Scope) -> Element {
|
||||||
|
let selector = css!(
|
||||||
|
"
|
||||||
|
position: relative;
|
||||||
|
//left: 35%;
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
let picture = css!(
|
||||||
|
"
|
||||||
|
position: absolute;
|
||||||
|
height: 75%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
|
||||||
|
bottom: 17.5%;
|
||||||
|
right: 18%;
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
class: "{selector}",
|
||||||
|
svg {
|
||||||
|
view_box: "0 0 100 100",
|
||||||
|
linearGradient {
|
||||||
|
id: "avatar-gradient",
|
||||||
|
x1: 1,
|
||||||
|
y1: 1,
|
||||||
|
x2: 0,
|
||||||
|
y2: 0,
|
||||||
|
stop {
|
||||||
|
offset: "0%",
|
||||||
|
stop_color: "rgb(138, 191, 209)",
|
||||||
|
}
|
||||||
|
stop {
|
||||||
|
offset: "60%",
|
||||||
|
stop_color: "rgb(236, 246, 249)",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
filter {
|
||||||
|
id: "avatar-shadow",
|
||||||
|
feDropShadow {
|
||||||
|
dx: 2,
|
||||||
|
dy: 2,
|
||||||
|
std_deviation: 3,
|
||||||
|
flood_opacity: 0.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rect {
|
||||||
|
width: "90",
|
||||||
|
height: "90",
|
||||||
|
rx: "12",
|
||||||
|
fill: "url('#avatar-gradient')",
|
||||||
|
filter: "url('#avatar-shadow')",
|
||||||
|
stroke: "grey",
|
||||||
|
},
|
||||||
|
// rect {
|
||||||
|
// x: "7.5",
|
||||||
|
// y: "7.5",
|
||||||
|
// width: "75",
|
||||||
|
// height: "75",
|
||||||
|
// fill: "transparent",
|
||||||
|
// stroke: "grey",
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
img {
|
||||||
|
class: "{picture}",
|
||||||
|
src: "./images/default-avatar.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
467
src/components/control_window.rs
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use sir::{css, global_css};
|
||||||
|
|
||||||
|
use crate::app_settings::AppSettings;
|
||||||
|
use crate::components::avatar_selector::AvatarSelector;
|
||||||
|
use crate::components::header::Header;
|
||||||
|
|
||||||
|
pub fn ControlWindow(cx: Scope) -> Element {
|
||||||
|
let app_context = use_shared_state::<AppSettings>(cx).unwrap();
|
||||||
|
println!("app_context={:?}", app_context.read());
|
||||||
|
|
||||||
|
// TODO: Use @extend once implemented (https://github.com/kaj/rsass/issues/65)
|
||||||
|
global_css!(
|
||||||
|
"
|
||||||
|
.aero-button {
|
||||||
|
height: 50%;
|
||||||
|
min-height: 16px;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
background-size: contain !important;
|
||||||
|
margin-right: 1%;
|
||||||
|
}
|
||||||
|
.aero-button:hover {
|
||||||
|
border-image: url(./images/aerobutton_border.png) 2 round;
|
||||||
|
}
|
||||||
|
.aero-button:active {
|
||||||
|
border-image: url(./images/aerobutton_border_down.png) 2 round;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
height: 50%;
|
||||||
|
min-height: 16px;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
background-size: contain !important;
|
||||||
|
margin-right: 1%;
|
||||||
|
}
|
||||||
|
.button:hover {
|
||||||
|
border-image: url(./images/button_border.png) 2 round;
|
||||||
|
}
|
||||||
|
.button:active {
|
||||||
|
border-image: url(./images/button_border_down.png) 2 round;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-right {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
position: relative;
|
||||||
|
height: 75%;
|
||||||
|
width: 99%;
|
||||||
|
top: -75%;
|
||||||
|
left: 1%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.avatar-selector {
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.user-id {
|
||||||
|
height: 30%;
|
||||||
|
width: fit-content;
|
||||||
|
display: flex;
|
||||||
|
text-align: begin;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
display: inline-block;
|
||||||
|
width: fit-content;
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-status {
|
||||||
|
display: inline-block;
|
||||||
|
color: #B9DDE7;
|
||||||
|
width: fit-content;
|
||||||
|
//margin-left: 1%;
|
||||||
|
//padding-left: 1%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-message {
|
||||||
|
width: fit-content;
|
||||||
|
height: 30%;
|
||||||
|
display: flex;
|
||||||
|
text-align: begin;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// From https://codepen.io/zoomodesign/pen/yNbVVZ
|
||||||
|
.contacts {
|
||||||
|
height: 72%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: white;
|
||||||
|
font-size: 8pt;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
.arrow {
|
||||||
|
transform: rotate(45deg) translate(-5px,-5px);
|
||||||
|
&:before {
|
||||||
|
transform: translate(10px,0);
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
transform: rotate(90deg) translate(10px,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
height: 2%;
|
||||||
|
width: 98%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 1%;
|
||||||
|
padding-top: 1%;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 1;
|
||||||
|
transition: 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style-type: none;
|
||||||
|
height: 2%;
|
||||||
|
margin: 0 auto;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
width: calc((100%/1080)*10);
|
||||||
|
aspect-ratio: 1;
|
||||||
|
display: inline-block;
|
||||||
|
transition: 0.4s ease;
|
||||||
|
margin-top: calc((100%/1080)*2);
|
||||||
|
margin-right: 2%;
|
||||||
|
text-align: left;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
float: right;
|
||||||
|
&:before, &:after {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
height: 15%;
|
||||||
|
background-color: #000;
|
||||||
|
transition: 0.4s ease;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
position: absolute;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
top: -5px;
|
||||||
|
left: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact {
|
||||||
|
list-style-type: none
|
||||||
|
margin: 0 auto
|
||||||
|
text-align: left
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
let root = css!(
|
||||||
|
"
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
background-color: #ECF6F9;
|
||||||
|
font-family: \"Tahoma\", sans-serif;
|
||||||
|
|
||||||
|
border: thin solid #707070;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 5px #00000050;
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
let header = css!(
|
||||||
|
"
|
||||||
|
height: 10%;
|
||||||
|
width: 100%;
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
let title_bar = css!(
|
||||||
|
"
|
||||||
|
height: 60%;
|
||||||
|
width: 100%;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, #7DC5E3, #3883A3);
|
||||||
|
"
|
||||||
|
);
|
||||||
|
let user_info = css!(
|
||||||
|
"
|
||||||
|
height: 40%;
|
||||||
|
width: 100%;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, #00658B, #0077A6);
|
||||||
|
"
|
||||||
|
);
|
||||||
|
let contacts_nav = css!(
|
||||||
|
"
|
||||||
|
height: calc(31/1080*100%);
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, #00658B, #0077A6);
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
let contacts_nav_inner = css!(
|
||||||
|
"
|
||||||
|
margin-left: 1%;
|
||||||
|
margin-right: 1%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
let search = css!(
|
||||||
|
"
|
||||||
|
height: calc(38/1080*100%);
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
border-bottom: thin solid #e2eaf3;
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: Remove following div
|
||||||
|
let search_inner = css!(
|
||||||
|
"
|
||||||
|
height: 100%;
|
||||||
|
width: 98%;
|
||||||
|
padding-left: 1%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
let search_input = css!(
|
||||||
|
"
|
||||||
|
height: calc(23/38*100%);
|
||||||
|
width: 100%;
|
||||||
|
margin-right: 1%;
|
||||||
|
border: thin solid #c7c7c7;
|
||||||
|
box-shadow: inset 0 0 calc(3/1080*100%) #0000002a;
|
||||||
|
font-size: 8pt;
|
||||||
|
|
||||||
|
padding-left: 1%;
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
let footer = css!(
|
||||||
|
"
|
||||||
|
height: 10%;
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
let show_contacts = use_state(cx, || false);
|
||||||
|
|
||||||
|
let contacts_active = if **show_contacts { "active" } else { "" };
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
class: "{root}",
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "{header}",
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "{title_bar}",
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "{user_info}",
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "user-info",
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "avatar-selector",
|
||||||
|
AvatarSelector {},
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "info-container",
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "aero-button user-id",
|
||||||
|
p {
|
||||||
|
class: "user-name",
|
||||||
|
"SUPER USER"
|
||||||
|
},
|
||||||
|
p {
|
||||||
|
class: "user-status",
|
||||||
|
"(Busy)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "aero-button user-message",
|
||||||
|
p {
|
||||||
|
"My message",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "{contacts_nav}",
|
||||||
|
div {
|
||||||
|
class: "{contacts_nav_inner}",
|
||||||
|
|
||||||
|
button {
|
||||||
|
class: "aero-button",
|
||||||
|
style: "background: url(./images/letter.png) center no-repeat",
|
||||||
|
},
|
||||||
|
button {
|
||||||
|
class: "aero-button",
|
||||||
|
style: "background: url(./images/directory.png) no-repeat center",
|
||||||
|
},
|
||||||
|
button {
|
||||||
|
class: "aero-button",
|
||||||
|
style: "background: url(./images/news.png) no-repeat center",
|
||||||
|
},
|
||||||
|
|
||||||
|
button {
|
||||||
|
class: "aero-button flex-right",
|
||||||
|
style: "background: url(./images/brush.png) no-repeat center",
|
||||||
|
},
|
||||||
|
button {
|
||||||
|
class: "aero-button",
|
||||||
|
style: "background: url(./images/settings.png) no-repeat center",
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "{search}",
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "{search_inner}",
|
||||||
|
|
||||||
|
input {
|
||||||
|
class: "{search_input}",
|
||||||
|
placeholder: "Find a contact...",
|
||||||
|
r#type: "text",
|
||||||
|
},
|
||||||
|
|
||||||
|
button {
|
||||||
|
class: "button",
|
||||||
|
style: "background: url(./images/add_user.png) no-repeat center",
|
||||||
|
},
|
||||||
|
|
||||||
|
button {
|
||||||
|
class: "button",
|
||||||
|
style: "background: url(./images/tbc_transfert.png) no-repeat center",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "contacts {contacts_active}",
|
||||||
|
|
||||||
|
p {
|
||||||
|
class: "header",
|
||||||
|
onclick: move |_| show_contacts.set(!show_contacts),
|
||||||
|
span {
|
||||||
|
class: "arrow",
|
||||||
|
},
|
||||||
|
"Online (4)",
|
||||||
|
},
|
||||||
|
|
||||||
|
// TODO: Test overflow
|
||||||
|
ul {
|
||||||
|
li {
|
||||||
|
img {
|
||||||
|
src: "./images/status_online.png",
|
||||||
|
},
|
||||||
|
p {
|
||||||
|
"Contact AAAAAAAA -",
|
||||||
|
},
|
||||||
|
p {
|
||||||
|
style: "color: darkgrey;",
|
||||||
|
"i'm sad all day until i get to talk with friends, online friends that is",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
li {
|
||||||
|
img {
|
||||||
|
src: "./images/status_busy.png",
|
||||||
|
},
|
||||||
|
p {
|
||||||
|
"Contact BBBBBB -",
|
||||||
|
},
|
||||||
|
p {
|
||||||
|
style: "color: darkgrey;",
|
||||||
|
"i'm sad all day until i get to talk with friends, online friends that is",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
li {
|
||||||
|
img {
|
||||||
|
src: "./images/status_away.png",
|
||||||
|
},
|
||||||
|
p {
|
||||||
|
"Contact CCC -",
|
||||||
|
},
|
||||||
|
p {
|
||||||
|
style: "color: darkgrey;",
|
||||||
|
"i'm sad all day until i get to talk with friends, online friends that is",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "{footer}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
38
src/components/header.rs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use sir::css;
|
||||||
|
|
||||||
|
pub fn Header(cx: Scope) -> Element {
|
||||||
|
let header = css!(
|
||||||
|
"
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
svg {
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
class: "{header}",
|
||||||
|
img {
|
||||||
|
// src: "./assets/live-logo2.png"
|
||||||
|
src: "./images/logo-msn.png"
|
||||||
|
}
|
||||||
|
svg {
|
||||||
|
view_box: "0 0 100 10",
|
||||||
|
text {
|
||||||
|
y: "55%",
|
||||||
|
dominant_baseline: "middle",
|
||||||
|
font_size: "5",
|
||||||
|
"Windows Live Messenger",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
170
src/components/login.rs
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use sir::css;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::app_settings::AppSettings;
|
||||||
|
use crate::components::avatar_selector::AvatarSelector;
|
||||||
|
use crate::components::header::Header;
|
||||||
|
use crate::matrix_client::{LoginStyle, MatrixClient};
|
||||||
|
|
||||||
|
pub fn Login(cx: Scope) -> Element {
|
||||||
|
let app_context = use_shared_state::<AppSettings>(cx).unwrap();
|
||||||
|
println!("app_context={:?}", app_context.read());
|
||||||
|
|
||||||
|
let login = use_ref(cx, || Login::new());
|
||||||
|
|
||||||
|
let empty_placeholder = String::from("");
|
||||||
|
|
||||||
|
let root = css!(
|
||||||
|
"
|
||||||
|
width: 90%;
|
||||||
|
height: 98%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
padding: 5%;
|
||||||
|
padding-top: 2%;
|
||||||
|
|
||||||
|
background: linear-gradient(rgb(138, 191, 209), rgb(236, 246, 249) 10%);
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
let header = css!(
|
||||||
|
"
|
||||||
|
height: 5%;
|
||||||
|
width: 100%;
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
let body = css!(
|
||||||
|
"
|
||||||
|
height: 50%;
|
||||||
|
width: 50%;
|
||||||
|
max-width: 400px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
padding-bottom: 3%;
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
let footer_buttons = css!(
|
||||||
|
"
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
padding-top: 5%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
let avatar_selector = css!(
|
||||||
|
"
|
||||||
|
height: 30%;
|
||||||
|
width: 100%;
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
let run_matrix_client = move |_| {
|
||||||
|
to_owned![app_context, login];
|
||||||
|
|
||||||
|
let homeserver_url = login.read().homeserver_url.clone().unwrap();
|
||||||
|
let username = login.read().email.clone().unwrap();
|
||||||
|
let password = login.read().password.clone().unwrap();
|
||||||
|
|
||||||
|
cx.spawn(async {
|
||||||
|
let requester = MatrixClient::spawn(homeserver_url).await;
|
||||||
|
|
||||||
|
requester.init();
|
||||||
|
requester.login(LoginStyle::Password(username, password));
|
||||||
|
|
||||||
|
let context = app_context;
|
||||||
|
context.write().requester = Some(Arc::new(requester));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
class: "{root}",
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "{header}",
|
||||||
|
Header {},
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "{body}",
|
||||||
|
div {
|
||||||
|
class: "{avatar_selector}",
|
||||||
|
AvatarSelector {},
|
||||||
|
},
|
||||||
|
|
||||||
|
p {
|
||||||
|
"Matrix homeserver:"
|
||||||
|
},
|
||||||
|
input {
|
||||||
|
id: "input-homeserver-url",
|
||||||
|
r#type: "text",
|
||||||
|
name: "homeserver URL",
|
||||||
|
value: "{(login.read().homeserver_url.as_ref().unwrap_or(&empty_placeholder))}",
|
||||||
|
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: "{login.read().email.as_ref().unwrap_or(&empty_placeholder)}",
|
||||||
|
oninput: move |evt| login.write().email = Some(evt.value.clone()),
|
||||||
|
},
|
||||||
|
p {
|
||||||
|
"Password:"
|
||||||
|
},
|
||||||
|
input {
|
||||||
|
id: "login-input-password",
|
||||||
|
r#type: "password",
|
||||||
|
name: "Password",
|
||||||
|
value: "{login.read().password.as_ref().unwrap_or(&empty_placeholder)}",
|
||||||
|
oninput: move |evt| login.write().password = Some(evt.value.clone()),
|
||||||
|
},
|
||||||
|
|
||||||
|
div {
|
||||||
|
class: "{footer_buttons}",
|
||||||
|
input {
|
||||||
|
class: "button",
|
||||||
|
onclick: run_matrix_client,
|
||||||
|
r#type: "submit",
|
||||||
|
value: "sign in",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Login {
|
||||||
|
homeserver_url: Option<String>,
|
||||||
|
email: Option<String>,
|
||||||
|
password: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Login {
|
||||||
|
fn new() -> Self {
|
||||||
|
let login = Self {
|
||||||
|
homeserver_url: None,
|
||||||
|
email: None,
|
||||||
|
password: None,
|
||||||
|
};
|
||||||
|
login
|
||||||
|
}
|
||||||
|
}
|
55
src/main.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#![allow(non_snake_case)]
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_desktop::Config;
|
||||||
|
use sir::{global_css, AppStyle};
|
||||||
|
|
||||||
|
pub mod app_settings;
|
||||||
|
pub mod components;
|
||||||
|
pub mod matrix_client;
|
||||||
|
|
||||||
|
use crate::app_settings::AppSettings;
|
||||||
|
use crate::components::control_window::ControlWindow;
|
||||||
|
use crate::components::login::Login;
|
||||||
|
|
||||||
|
mod base;
|
||||||
|
|
||||||
|
fn App(cx: Scope<AppSettings>) -> Element {
|
||||||
|
use_shared_state_provider(cx, || cx.props.clone());
|
||||||
|
|
||||||
|
global_css!(
|
||||||
|
"
|
||||||
|
body {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
outline: 0px;
|
||||||
|
font-family: Tahoma, sans-serif;
|
||||||
|
}
|
||||||
|
#main {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}"
|
||||||
|
);
|
||||||
|
|
||||||
|
// let window = dioxus_desktop::use_window(cx);
|
||||||
|
// let dom = VirtualDom::new(ControlWindow);
|
||||||
|
// window.new_window(dom, cx.props.clone());
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
AppStyle {},
|
||||||
|
// Login {}
|
||||||
|
ControlWindow {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let app_settings = AppSettings::new();
|
||||||
|
|
||||||
|
dioxus_desktop::launch_with_props(App, app_settings, Config::default());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
208
src/matrix_client.rs
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
use dirs;
|
||||||
|
use matrix_sdk::{
|
||||||
|
config::SyncSettings,
|
||||||
|
room::Room,
|
||||||
|
ruma::events::room::{
|
||||||
|
member::StrippedRoomMemberEvent,
|
||||||
|
message::{OriginalSyncRoomMessageEvent, RoomMessageEventContent, SyncRoomMessageEvent},
|
||||||
|
},
|
||||||
|
Client, Error as MatrixError,
|
||||||
|
};
|
||||||
|
use std::fmt::{Debug, Formatter};
|
||||||
|
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
use tokio::time::{sleep, Duration};
|
||||||
|
|
||||||
|
use crate::base::Info;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum LoginStyle {
|
||||||
|
// SessionRestore(Session),
|
||||||
|
Password(String, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ClientResponse<T>(Receiver<T>);
|
||||||
|
pub struct ClientReply<T>(SyncSender<T>);
|
||||||
|
|
||||||
|
impl<T> ClientResponse<T> {
|
||||||
|
fn recv(self) -> T {
|
||||||
|
self.0
|
||||||
|
.recv()
|
||||||
|
.expect("failed to receive response from client thread")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ClientReply<T> {
|
||||||
|
fn send(self, t: T) {
|
||||||
|
self.0.send(t).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum WorkerTask {
|
||||||
|
// Init(AsyncProgramStore, ClientReply<()>),
|
||||||
|
// Init(ClientReply<()>),
|
||||||
|
Init(ClientReply<()>),
|
||||||
|
//Login(LoginStyle, ClientReply<EditInfo>),
|
||||||
|
Login(LoginStyle, ClientReply<()>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for WorkerTask {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
|
match self {
|
||||||
|
WorkerTask::Init(_) => f
|
||||||
|
.debug_tuple("WorkerTask::Init")
|
||||||
|
.field(&format_args!("_"))
|
||||||
|
// .field(&format_args!("_"))
|
||||||
|
.finish(),
|
||||||
|
WorkerTask::Login(style, _) => f
|
||||||
|
.debug_tuple("WorkerTask::Login")
|
||||||
|
.field(style)
|
||||||
|
// .field(&format_args!("_"))
|
||||||
|
.finish(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn oneshot<T>() -> (ClientReply<T>, ClientResponse<T>) {
|
||||||
|
let (tx, rx) = sync_channel(1);
|
||||||
|
let reply = ClientReply(tx);
|
||||||
|
let response = ClientResponse(rx);
|
||||||
|
|
||||||
|
return (reply, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Requester {
|
||||||
|
pub client: Client,
|
||||||
|
// pub matrix_client: MatrixClient,
|
||||||
|
pub tx: UnboundedSender<WorkerTask>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Requester {
|
||||||
|
pub fn init(&self) {
|
||||||
|
println!("Requester.init BEG");
|
||||||
|
let (reply, response) = oneshot();
|
||||||
|
|
||||||
|
self.tx.send(WorkerTask::Init(reply)).unwrap();
|
||||||
|
|
||||||
|
println!("Requester.init END");
|
||||||
|
return response.recv();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn login(&self, style: LoginStyle) {
|
||||||
|
println!("Requester.login BEG");
|
||||||
|
let (reply, response) = oneshot();
|
||||||
|
|
||||||
|
self.tx.send(WorkerTask::Login(style, reply)).unwrap();
|
||||||
|
|
||||||
|
println!("Requester.login END");
|
||||||
|
return response.recv();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MatrixClient {
|
||||||
|
initialized: bool,
|
||||||
|
// client: Client,
|
||||||
|
client: Option<Arc<Client>>,
|
||||||
|
sync_token: Option<String>,
|
||||||
|
load_handle: Option<JoinHandle<()>>,
|
||||||
|
sync_handle: Option<JoinHandle<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MatrixClient {
|
||||||
|
pub async fn spawn(homeserver_url: String) -> Requester {
|
||||||
|
let (tx, rx) = unbounded_channel::<WorkerTask>();
|
||||||
|
|
||||||
|
let client = Client::builder()
|
||||||
|
.homeserver_url(&homeserver_url)
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut matrix_client = MatrixClient {
|
||||||
|
client: None,
|
||||||
|
initialized: false,
|
||||||
|
sync_token: None,
|
||||||
|
load_handle: None,
|
||||||
|
sync_handle: None,
|
||||||
|
};
|
||||||
|
matrix_client.client = Some(Arc::new(client.clone()));
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
matrix_client.work(rx).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
Requester { client, tx }
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn work(&mut self, mut rx: UnboundedReceiver<WorkerTask>) {
|
||||||
|
println!("MatrixClient.work BEG");
|
||||||
|
loop {
|
||||||
|
let task = rx.recv().await;
|
||||||
|
println!("task={:?}", task);
|
||||||
|
|
||||||
|
match task {
|
||||||
|
Some(task) => self.run(task).await,
|
||||||
|
None => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("MatrixClient.work END");
|
||||||
|
|
||||||
|
if let Some(handle) = self.sync_handle.take() {
|
||||||
|
handle.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(&mut self, task: WorkerTask) {
|
||||||
|
println!("Run({:?}", task);
|
||||||
|
match task {
|
||||||
|
WorkerTask::Init(reply) => {
|
||||||
|
assert_eq!(self.initialized, false);
|
||||||
|
self.init().await;
|
||||||
|
reply.send(());
|
||||||
|
}
|
||||||
|
WorkerTask::Login(style, reply) => {
|
||||||
|
assert!(self.initialized);
|
||||||
|
reply.send(self.login_and_sync(style).await.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn init(&mut self) {
|
||||||
|
self.initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn login_and_sync(&mut self, style: LoginStyle) -> anyhow::Result<()> {
|
||||||
|
let client = self.client.clone().unwrap();
|
||||||
|
|
||||||
|
match style {
|
||||||
|
LoginStyle::Password(username, password) => {
|
||||||
|
let resp = client
|
||||||
|
.matrix_auth()
|
||||||
|
.login_username(&username, &password)
|
||||||
|
.initial_device_display_name("TODO")
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
dbg!(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sync_handle = tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
let settings = SyncSettings::default();
|
||||||
|
|
||||||
|
let _ = client.sync(settings).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
|
||||||
|
println!("User connected to the homeserver");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|