#![allow(non_snake_case)] use std::collections::{BTreeMap, BTreeSet, HashMap}; use dioxus::prelude::*; use ordermap::OrderSet; pub type ChannelId = u32; pub type UserId = u32; pub enum ConnectionState { Disconnected, Connecting, Connected, } pub enum Command { Connect { address: String, username: String, hash: String, }, SendChat { markdown: String, channels: Vec, }, Disconnect, } use Command::*; use ConnectionState::*; #[derive(Default)] pub struct ChannelState { pub name: String, pub children: OrderSet, pub users: OrderSet, pub parent: Option, } #[derive(Default)] pub struct UserState { pub name: String, pub channel: ChannelId, } pub struct Chat { pub raw: String, pub dangerous_html: String, pub sender: Option, } #[derive(Default)] pub struct ServerState { pub channels: HashMap, pub users: HashMap, pub chat: Vec, pub session: Option, } impl ServerState { pub fn this_user(&self) -> Option<&UserState> { self.users.get(&self.session?) } } pub struct State { pub status: GlobalSignal, pub server: GlobalSignal, } pub static STATE: State = State { status: Signal::global(|| Disconnected), server: Signal::global(|| Default::default()), }; #[component] pub fn User(id: UserId) -> Element { let server = STATE.server.read(); let state = server.users.get(&id)?; rsx!( span { style: "border: solid black 1px; border-radius: 4px;", "{state.name}" } ) } #[component] pub fn Channel(id: ChannelId) -> Element { let server = STATE.server.read(); let state = server.channels.get(&id)?; rsx!( "{state.name}" div { style: "border-left: solid black 1px; padding-left: 8px;", for child in state.children.iter() { Channel { id: *child } } for id in state.users.iter() { User { id: *id } } } ) } #[component] pub fn ChatView() -> Element { let server = STATE.server.read(); let mut draft = use_signal(|| "".to_string()); //let net: Coroutine = use_coroutine_handle(); rsx!( div { style: "margin: 16px; padding: 16px; border: solid black 1px;", for chat in server.chat.iter() { if let Some(sender) = chat.sender.and_then(|u| server.users.get(&u)) { span { style: "border: solid black 1px; border-radius: 4px;", "{sender.name}" } "\u{8194}" } span { dangerous_inner_html: "{chat.dangerous_html}", } hr {} } input { placeholder: "say something", value: "{draft.read()}", oninput: move |evt| draft.set(evt.value().clone()), } button { onclick: move |_| { /*if let Some(user) = server.this_user() { net.send(SendChat { markdown: draft.write().split_off(0), channels: vec![user.channel], }); }*/ }, "Send" } } ) } #[component] pub fn ServerView() -> Element { let server = STATE.server.read(); rsx!( div { style: "margin: 16px; padding: 16px; border: solid black 1px;", for (id, state) in server.channels.iter() { if state.parent.is_none() { Channel { id: *id } } } } ChatView {} ) } pub fn app() -> Element { let net = use_coroutine(|rx: UnboundedReceiver| super::network_entrypoint(rx)); let mut username = use_signal(|| "".to_string()); let default_address = option_env!("MUMBLEWEB2_WEBTRANSPORT_SERVER_ADDRESS").unwrap_or(""); let mut address = use_signal(|| default_address.to_string()); let status = &STATE.status; rsx!( div { input { placeholder: "username", value: "{username.read()}", autofocus: "true", oninput: move |evt| username.set(evt.value().clone()), } br {} input { placeholder: "server address", value: "{address.read()}", autofocus: "true", oninput: move |evt| address.set(evt.value().clone()), } br {} match *status.read() { Disconnected => rsx!{ button { onclick: move |event| net.send(Connect{address: address.read().clone(), username: username.read().clone(), hash: "[39, 96, 204, 127, 26, 59, 35, 209, 197, 103, 192, 6, 3, 98, 203, 228, 124, 46, 247, 72, 44, 224, 123, 238, 218, 140, 128, 100, 115, 14, 23, 233]".to_string()}), "Connect!" } }, Connecting => rsx!{ "Connecting..." }, Connected => rsx!{ button { onclick: move |event| net.send(Disconnect), "Disconnect" } br {} ServerView {} } } br {} } ) }