diff --git a/.gitignore b/.gitignore index ea8c4bf..a803f50 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/dist \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 821a4d8..ea39ba6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,47 @@ #![allow(non_snake_case)] use std::collections::HashMap; +use std::future::Future; use std::str::FromStr; use std::sync::Arc; use futures_util::stream::StreamExt; use dioxus::html::textarea; // import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types use dioxus::prelude::*; +use dioxus_desktop::Config; use dioxus_elements::{h1, li, ul}; use log::info; use tokio::select; use xmpp::{BareJid, ClientBuilder, ClientType, RoomNick}; 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(); // launch the dioxus app in a webview - dioxus_desktop::launch(App); + dioxus_desktop::launch_cfg(App, + Config::default() + .with_custom_head(format!(r#""#, STYLESHEET)) + .with_background_color((32,64,32,255))); } #[derive(Props)] @@ -30,7 +53,6 @@ struct RoomListProps<'a> { /// A Dioxus component that renders a list of rooms fn RoomList<'a>(cx: Scope<'a, RoomListProps>) -> Element<'a> { render! { - h1 { "Rooms" } ul { for room in cx.props.rooms.iter() { rsx! { li { @@ -43,48 +65,94 @@ fn RoomList<'a>(cx: Scope<'a, RoomListProps>) -> Element<'a> { } } +#[derive(Debug, Clone)] +struct Message { + sender: String, + body: String, +} + #[derive(Props)] struct RoomViewProps<'a> { room: BareJid, - messages: Vec, + messages: Vec, 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> { - let message = use_state(cx, || "".to_owned()); render! { - div { - h1 { "Messages" } ul { for message in cx.props.messages.iter() { rsx! { li { - "{message}" + "{message.sender}: {message.body}" } } } } - textarea { - oninput: |evt| { - message.set(evt.value.clone()); + div { + display: "flex", + flex_direction: "row", + textarea { + flex_grow: 1, + oninput: |evt| { + message.set(evt.value.clone()); + } + } + button { + height: "100%", + onclick: move |_| { + cx.props.on_message_sent.call(message.current().to_string()); + }, + "Send" } } - button { - onclick: move |_| { - cx.props.on_message_sent.call(message.current().to_string()); - }, - "Send" - } + } +} + +async fn handle_event(event: xmpp::Event, + agent: &mut xmpp::Agent, + rooms: &mut UseState>, + messages: &mut UseState>>) { + match event { + xmpp::Event::JoinRoom(jid, conference) => { + println!("Joining room: {}", &jid); + rooms.make_mut().push(jid.clone()); + agent.join_room(jid, None, None, "en/us", "online").await; + }, + xmpp::Event::ChatMessage(id, sender, body) => { + println!("Message from {}: {}", &sender, &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) => { + println!("Message in {} from {}: {}", &room_jid, &sender_nick, &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) => { + println!("Private message in {} from {}: {}", &room_jid, &sender_nick, &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); } } } + +#[derive(Debug)] +struct SendMessage { + recipient: BareJid, + message: String, +} + // define a component that renders a div with the text "Hello, world!" fn App(cx: Scope) -> Element { @@ -92,65 +160,79 @@ fn App(cx: Scope) -> Element { let messages = use_state(cx, || HashMap::new()); let current_room = use_state(cx, || None::); - #[derive(Debug)] - struct SendMessage { - recipient: BareJid, - message: String, + let cr = use_coroutine(cx, + |rx: UnboundedReceiver| run_xmpp(rooms.clone(), messages.clone(), rx) + ); + + render! { + div { + margin: 0, + padding: 0, + display: "flex", + width: "100%", + height: "100%", + + div { + background_color: "#1d852d", + RoomList { + rooms: rooms.to_vec(), + on_room_picked: move |x:BareJid| { + println!("Room selected: {:?}", x); + current_room.set(Some(x)); + }, + } + } + div { + flex_grow: 1, + background_color: "#166322", + if let Some(room) = current_room.get() { + let messages = messages.get().get(&room).expect("Selected non-existant room").to_vec(); + + rsx! { + RoomView { + room: room.clone(), + messages: messages, + on_message_sent: move |x:String| { + println!("Message sent: {:?}", x); + cr.send(SendMessage { + recipient: room.clone(), + message: x, + }); + }, + } + } + } else { + rsx! { + div { + "No room selected" + } + } + } + } + } } +} - let cr = use_coroutine(cx, |mut commands: UnboundedReceiver| { - let mut rooms = rooms.to_owned(); - let mut messages = messages.to_owned(); - let current_room = current_room.to_owned(); +fn run_xmpp(mut rooms: UseState>, + mut messages: UseState>>, + mut commands: UnboundedReceiver) -> impl Future + Sized { - async move { + async move { + let jid = BareJid::from_str("bot@mizah.xyz").unwrap(); - let jid = BareJid::from_str("bot@mizah.xyz").unwrap(); + let password = "G0cEiYp98G8nBzOdkKMF"; - let password = "G0cEiYp98G8nBzOdkKMF"; + let mut agent = ClientBuilder::new(jid, &password) + .set_client(ClientType::Pc, "dergchat") + .set_default_nick("dergchat") + .build(); - 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! { + loop { + select! { events = agent.wait_for_events() => { if let Some(events) = events { for event in events { - match event { - xmpp::Event::JoinRoom(jid, conference) => { - println!("Joining room: {}", &jid); - rooms.make_mut().push(jid.clone()); - agent.join_room(jid, None, None, "en/us", "online").await; - }, - xmpp::Event::ChatMessage(id, sender, body) => { - println!("Message from {}: {}", &sender, &body.0); - messages.make_mut().entry(sender).or_insert(vec![]).push(body.0); - }, - xmpp::Event::RoomMessage(id, room_jid, sender_nick, body) => { - println!("Message in {} from {}: {}", &room_jid, &sender_nick, &body.0); - messages.make_mut().entry(room_jid).or_insert(vec![]).push(body.0); - } - xmpp::Event::RoomPrivateMessage(id, room_jid, sender_nick, body) => { - println!("Private message in {} from {}: {}", &room_jid, &sender_nick, &body.0); - messages.make_mut().entry(room_jid).or_insert(vec![]).push(body.0); - }, - _ => { - log::debug!("Received unsupported event {:?}", event); - } - } + handle_event(event, &mut agent, &mut rooms, &mut messages).await; } } else { info!("Disconnected"); @@ -166,36 +248,6 @@ fn App(cx: Scope) -> Element { } }, } - - } - } - }); - - render! { - RoomList { - rooms: rooms.to_vec(), - on_room_picked: move |x:BareJid| { - println!("Room selected: {:?}", x); - current_room.set(Some(x)); - }, - } - if let Some(room) = current_room.get() { - - let messages = messages.get().get(&room).expect("Selected non-existant room").to_vec(); - - rsx! { - RoomView { - room: room.clone(), - messages: messages, - on_message_sent: move |x:String| { - println!("Message sent: {:?}", x); - cr.send(SendMessage { - recipient: room.clone(), - message: x, - }); - }, - } - } } } }