Update UI layout and design in chat application.

Minor refactorings.
This commit is contained in:
Werner Kroneman 2023-12-07 23:33:08 +01:00
parent 0d80360b58
commit 4da80010ff
2 changed files with 156 additions and 103 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target /target
/dist

View File

@ -1,24 +1,47 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use std::collections::HashMap; use std::collections::HashMap;
use std::future::Future;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use futures_util::stream::StreamExt; use futures_util::stream::StreamExt;
use dioxus::html::textarea; use dioxus::html::textarea;
// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types // import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_desktop::Config;
use dioxus_elements::{h1, li, ul}; use dioxus_elements::{h1, li, ul};
use log::info; use log::info;
use tokio::select; use tokio::select;
use xmpp::{BareJid, ClientBuilder, ClientType, RoomNick}; use xmpp::{BareJid, ClientBuilder, ClientType, RoomNick};
use xmpp::parsers::message::MessageType; use xmpp::parsers::message::MessageType;
fn main() { const STYLESHEET: &str = r#"
body {
margin: 0;
padding: 0;
position: fixed;
width: 100%;
height: 100%;
color: lightgray;
}
#main {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
}
"#;
fn main() {
env_logger::init(); env_logger::init();
// launch the dioxus app in a webview // launch the dioxus app in a webview
dioxus_desktop::launch(App); dioxus_desktop::launch_cfg(App,
Config::default()
.with_custom_head(format!(r#"<style>{}</style>"#, STYLESHEET))
.with_background_color((32,64,32,255)));
} }
#[derive(Props)] #[derive(Props)]
@ -30,7 +53,6 @@ struct RoomListProps<'a> {
/// A Dioxus component that renders a list of rooms /// A Dioxus component that renders a list of rooms
fn RoomList<'a>(cx: Scope<'a, RoomListProps>) -> Element<'a> { fn RoomList<'a>(cx: Scope<'a, RoomListProps>) -> Element<'a> {
render! { render! {
h1 { "Rooms" }
ul { ul {
for room in cx.props.rooms.iter() { for room in cx.props.rooms.iter() {
rsx! { li { rsx! { li {
@ -43,39 +65,41 @@ fn RoomList<'a>(cx: Scope<'a, RoomListProps>) -> Element<'a> {
} }
} }
#[derive(Debug, Clone)]
struct Message {
sender: String,
body: String,
}
#[derive(Props)] #[derive(Props)]
struct RoomViewProps<'a> { struct RoomViewProps<'a> {
room: BareJid, room: BareJid,
messages: Vec<String>, messages: Vec<Message>,
on_message_sent: EventHandler<'a, String>, on_message_sent: EventHandler<'a, String>,
} }
// impl PartialEq for RoomViewProps {
// fn eq(&self, other: &Self) -> bool {
// self.room == other.room && self.messages == other.messages
// }
// }
fn RoomView<'a>(cx: Scope<'a, RoomViewProps>) -> Element<'a> { fn RoomView<'a>(cx: Scope<'a, RoomViewProps>) -> Element<'a> {
let message = use_state(cx, || "".to_owned()); let message = use_state(cx, || "".to_owned());
render! { render! {
div {
h1 { "Messages" }
ul { ul {
for message in cx.props.messages.iter() { for message in cx.props.messages.iter() {
rsx! { li { rsx! { li {
"{message}" "{message.sender}: {message.body}"
} } } }
} }
} }
div {
display: "flex",
flex_direction: "row",
textarea { textarea {
flex_grow: 1,
oninput: |evt| { oninput: |evt| {
message.set(evt.value.clone()); message.set(evt.value.clone());
} }
} }
button { button {
height: "100%",
onclick: move |_| { onclick: move |_| {
cx.props.on_message_sent.call(message.current().to_string()); cx.props.on_message_sent.call(message.current().to_string());
}, },
@ -85,50 +109,10 @@ fn RoomView<'a>(cx: Scope<'a, RoomViewProps>) -> Element<'a> {
} }
} }
// define a component that renders a div with the text "Hello, world!" async fn handle_event(event: xmpp::Event,
fn App(cx: Scope) -> Element { agent: &mut xmpp::Agent,
rooms: &mut UseState<Vec<BareJid>>,
let rooms = use_state(cx, || vec![]); messages: &mut UseState<HashMap<BareJid, Vec<Message>>>) {
let messages = use_state(cx, || HashMap::new());
let current_room = use_state(cx, || None::<BareJid>);
#[derive(Debug)]
struct SendMessage {
recipient: BareJid,
message: String,
}
let cr = use_coroutine(cx, |mut commands: UnboundedReceiver<SendMessage>| {
let mut rooms = rooms.to_owned();
let mut messages = messages.to_owned();
let current_room = current_room.to_owned();
async move {
let jid = BareJid::from_str("bot@mizah.xyz").unwrap();
let password = "G0cEiYp98G8nBzOdkKMF";
let mut agent = ClientBuilder::new(jid, &password)
.set_client(ClientType::Pc, "dergchat")
.set_default_nick("dergchat")
.build();
// tokio::spawn({
// async move {
// while let Some(command) = commands.next().await {
// println!("Sending command: {:?}", command);
// agent.borrow_mut().send_message(command.recipient.into(), MessageType::Chat, "en/us", &command.message).await;
// }
// }
// });
loop {
select! {
events = agent.wait_for_events() => {
if let Some(events) = events {
for event in events {
match event { match event {
xmpp::Event::JoinRoom(jid, conference) => { xmpp::Event::JoinRoom(jid, conference) => {
println!("Joining room: {}", &jid); println!("Joining room: {}", &jid);
@ -137,41 +121,59 @@ fn App(cx: Scope) -> Element {
}, },
xmpp::Event::ChatMessage(id, sender, body) => { xmpp::Event::ChatMessage(id, sender, body) => {
println!("Message from {}: {}", &sender, &body.0); println!("Message from {}: {}", &sender, &body.0);
messages.make_mut().entry(sender).or_insert(vec![]).push(body.0); messages.make_mut().entry(sender.clone()).or_insert(vec![]).push(Message {
sender: sender.to_string(),
body: body.0,
});
}, },
xmpp::Event::RoomMessage(id, room_jid, sender_nick, body) => { xmpp::Event::RoomMessage(id, room_jid, sender_nick, body) => {
println!("Message in {} from {}: {}", &room_jid, &sender_nick, &body.0); println!("Message in {} from {}: {}", &room_jid, &sender_nick, &body.0);
messages.make_mut().entry(room_jid).or_insert(vec![]).push(body.0); messages.make_mut().entry(room_jid).or_insert(vec![]).push(Message {
sender: sender_nick,
body: body.0,
});
} }
xmpp::Event::RoomPrivateMessage(id, room_jid, sender_nick, body) => { xmpp::Event::RoomPrivateMessage(id, room_jid, sender_nick, body) => {
println!("Private message in {} from {}: {}", &room_jid, &sender_nick, &body.0); println!("Private message in {} from {}: {}", &room_jid, &sender_nick, &body.0);
messages.make_mut().entry(room_jid).or_insert(vec![]).push(body.0); messages.make_mut().entry(room_jid).or_insert(vec![]).push(Message {
sender: sender_nick,
body: body.0,
});
}, },
_ => { _ => {
log::debug!("Received unsupported event {:?}", event); log::debug!("Received unsupported event {:?}", event);
} }
} }
} }
} else {
info!("Disconnected");
} #[derive(Debug)]
}, struct SendMessage {
command = commands.next() => { recipient: BareJid,
if let Some(command) = command { message: String,
println!("Sending command: {:?}", command);
agent.send_message(command.recipient.into(), MessageType::Groupchat, "en-us", &command.message).await;
} else {
info!("Command channel closed");
break;
}
},
} }
} // define a component that renders a div with the text "Hello, world!"
} fn App(cx: Scope) -> Element {
});
let rooms = use_state(cx, || vec![]);
let messages = use_state(cx, || HashMap::new());
let current_room = use_state(cx, || None::<BareJid>);
let cr = use_coroutine(cx,
|rx: UnboundedReceiver<SendMessage>| run_xmpp(rooms.clone(), messages.clone(), rx)
);
render! { render! {
div {
margin: 0,
padding: 0,
display: "flex",
width: "100%",
height: "100%",
div {
background_color: "#1d852d",
RoomList { RoomList {
rooms: rooms.to_vec(), rooms: rooms.to_vec(),
on_room_picked: move |x:BareJid| { on_room_picked: move |x:BareJid| {
@ -179,8 +181,11 @@ fn App(cx: Scope) -> Element {
current_room.set(Some(x)); current_room.set(Some(x));
}, },
} }
}
div {
flex_grow: 1,
background_color: "#166322",
if let Some(room) = current_room.get() { if let Some(room) = current_room.get() {
let messages = messages.get().get(&room).expect("Selected non-existant room").to_vec(); let messages = messages.get().get(&room).expect("Selected non-existant room").to_vec();
rsx! { rsx! {
@ -196,6 +201,53 @@ fn App(cx: Scope) -> Element {
}, },
} }
} }
} else {
rsx! {
div {
"No room selected"
}
}
}
}
}
}
}
fn run_xmpp(mut rooms: UseState<Vec<BareJid>>,
mut messages: UseState<HashMap<BareJid, Vec<Message>>>,
mut commands: UnboundedReceiver<SendMessage>) -> impl Future<Output=()> + Sized {
async move {
let jid = BareJid::from_str("bot@mizah.xyz").unwrap();
let password = "G0cEiYp98G8nBzOdkKMF";
let mut agent = ClientBuilder::new(jid, &password)
.set_client(ClientType::Pc, "dergchat")
.set_default_nick("dergchat")
.build();
loop {
select! {
events = agent.wait_for_events() => {
if let Some(events) = events {
for event in events {
handle_event(event, &mut agent, &mut rooms, &mut messages).await;
}
} else {
info!("Disconnected");
}
},
command = commands.next() => {
if let Some(command) = command {
println!("Sending command: {:?}", command);
agent.send_message(command.recipient.into(), MessageType::Groupchat, "en-us", &command.message).await;
} else {
info!("Command channel closed");
break;
}
},
}
} }
} }
} }