210 lines
5.6 KiB
Rust
210 lines
5.6 KiB
Rust
#![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<ChannelId>,
|
|
},
|
|
Disconnect,
|
|
}
|
|
|
|
use Command::*;
|
|
use ConnectionState::*;
|
|
|
|
#[derive(Default)]
|
|
pub struct ChannelState {
|
|
pub name: String,
|
|
pub children: OrderSet<ChannelId>,
|
|
pub users: OrderSet<UserId>,
|
|
pub parent: Option<ChannelId>,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct UserState {
|
|
pub name: String,
|
|
pub channel: ChannelId,
|
|
}
|
|
|
|
pub struct Chat {
|
|
pub raw: String,
|
|
pub dangerous_html: String,
|
|
pub sender: Option<UserId>,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct ServerState {
|
|
pub channels: HashMap<ChannelId, ChannelState>,
|
|
pub users: HashMap<UserId, UserState>,
|
|
pub chat: Vec<Chat>,
|
|
pub session: Option<UserId>,
|
|
}
|
|
|
|
impl ServerState {
|
|
pub fn this_user(&self) -> Option<&UserState> {
|
|
self.users.get(&self.session?)
|
|
}
|
|
}
|
|
|
|
pub struct State {
|
|
pub status: GlobalSignal<ConnectionState>,
|
|
pub server: GlobalSignal<ServerState>,
|
|
}
|
|
|
|
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<Command> = 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<Command>| 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 {}
|
|
}
|
|
)
|
|
}
|