add /config endpoint, add docker proxy setup, and style chat box

This commit is contained in:
2025-07-13 19:33:55 -06:00
parent 74fe399cdc
commit 1793504467
12 changed files with 561 additions and 94 deletions
+129 -37
View File
@@ -3,9 +3,11 @@
use base64::{display::Base64Display, prelude::BASE64_URL_SAFE};
use dioxus::prelude::*;
use mime_guess::Mime;
use mumble_web2_common::GuiConfig;
use ordermap::OrderSet;
use sir::{css, global_css};
use std::collections::HashMap;
use tracing::error;
use crate::{imp, CONFIG};
@@ -279,6 +281,13 @@ pub fn ChatView() -> Element {
let server = STATE.server.read();
let mut draft = use_signal(|| "".to_string());
let chat_panel = css!(
"
display: flex;
flex-direction: column;
"
);
let chat_history = css!(
"
overflow-y: auto;
@@ -296,18 +305,44 @@ pub fn ChatView() -> Element {
"
);
let chat_box_wrapper = css!(
"
padding: 16px;
border-top: solid var(--line-color) var(--line-width);
"
);
let chat_box = css!(
"
display: flex;
flex-direction: row;
padding: 16px;
gap: 8px;
border-top: solid var(--line-color) var(--line-width);
gap: 16px;
background-color: var(--light-bg-color);
padding-top: 16px;
padding-bottom: 16px;
padding-left: 8px;
padding-right: 16px;
border-radius: 8px;
input {
color: white;
background-color: var(--light-bg-color);
font-size: larger;
flex-grow: 1;
padding: 8px;
border: none;
}
input:focus {
outline: none;
}
"
);
@@ -322,49 +357,88 @@ pub fn ChatView() -> Element {
rsx!(
div {
class: "{chat_history}",
for chat in server.chat.iter() {
div {
class: "{chat_message}",
if let Some(sender) = chat.sender.and_then(|u| server.users.get(&u)) {
UserPill {
name: sender.name.clone(),
icon: UserIcon::None,
isself: false,
class: "{chat_panel}",
div {
class: "{chat_history}",
for chat in server.chat.iter() {
div {
class: "{chat_message}",
if let Some(sender) = chat.sender.and_then(|u| server.users.get(&u)) {
UserPill {
name: sender.name.clone(),
icon: UserIcon::None,
isself: false,
}
}
span {
dangerous_inner_html: "{chat.dangerous_html}",
}
}
span {
dangerous_inner_html: "{chat.dangerous_html}",
}
}
}
}
div {
class: "{chat_box}",
input {
placeholder: "say something",
value: "{draft.read()}",
oninput: move |evt| draft.set(evt.value().clone()),
onkeypress: move |evt: Event<KeyboardData>| {
if evt.code() == Code::Enter && evt.modifiers().is_empty() {
do_send();
div {
class: "{chat_box_wrapper}",
div {
class: "{chat_box}",
input {
placeholder: "say something",
value: "{draft.read()}",
oninput: move |evt| draft.set(evt.value().clone()),
onkeypress: move |evt: Event<KeyboardData>| {
if evt.code() == Code::Enter && evt.modifiers().is_empty() {
do_send();
}
}
}
div {
span {
onclick: move |_| pick_and_send_file(&net),
class: "material-symbols-outlined",
style: "color: rgba(255, 255, 255, 0.5); font-variation-settings: 'FILL' 1, 'wght' 700, 'GRAD' 0, 'opsz' 48; vertical-align: middle; font-size: 35px; user-select: none;",
"attach_file",
}
}
div {
span {
onclick: move |_| do_send(),
class: "material-symbols-outlined",
style: "color: rgba(255, 255, 255, 0.5); font-variation-settings: 'FILL' 1, 'wght' 700, 'GRAD' 0, 'opsz' 48; vertical-align: middle; font-size: 35px; user-select: none;",
"send",
}
}
}
}
button {
onclick: move |_| pick_and_send_file(&net),
"File"
}
button {
onclick: move |_| do_send(),
"Send"
//button {
// onclick: move |_| do_send(),
// "Send"
//}
}
}
)
}
// true => rsx!(span { class: "material-symbols-outlined", style: "{button_style}", "mic_off"}),
//Connecting => rsx! {
// div {
// class: "{connecting_status}",
// span {
// class: "material-symbols-outlined",
// style: "vertical-align: middle; font-size: 30px;",
// "signal_cellular_alt_2_bar"
// }
// span {
// style: "width: 5px; display: inline-block;"
// }
// span {
// style: "vertical-align: middle; font-size: 30px;",
// "Connecting"
// }
// }
//},
#[component]
pub fn ControlView() -> Element {
let config_future = use_resource(|| CONFIG.get());
let net: Coroutine<Command> = use_coroutine_handle();
let status = &STATE.status;
let server = STATE.server.read();
@@ -382,7 +456,12 @@ pub fn ControlView() -> Element {
};
let current_channel_name = server.channels[&channel].name.clone();
let Some(proxy_url) = &CONFIG.proxy_url else {
let Some(proxy_url) = config_future
.read_unchecked()
.as_ref()
.and_then(|gui_config| gui_config.proxy_url.clone())
else {
return rsx!();
};
@@ -675,7 +754,7 @@ pub fn ServerView() -> Element {
let chat_box = css!(
"
display: flex;
flex-direction: column;
flex-direction: row;
grid-area: chat;
border-left: solid var(--line-color) var(--line-width);
@@ -727,7 +806,15 @@ pub fn ServerView() -> Element {
#[component]
pub fn LoginView() -> Element {
let net: Coroutine<Command> = use_coroutine_handle();
let default_address = CONFIG.proxy_url.as_deref().unwrap_or("");
let config_future = use_resource(|| CONFIG.get());
let default_address = &*config_future
.read_unchecked()
.as_ref()
.and_then(|gui_config| gui_config.proxy_url.clone())
.unwrap_or("".to_string());
let mut address = use_signal(|| default_address.to_string());
let previous_username = imp::load_username();
@@ -840,6 +927,11 @@ pub fn LoginView() -> Element {
pub fn app() -> Element {
use_coroutine(|rx: UnboundedReceiver<Command>| super::network_entrypoint(rx));
use_future(|| async move {
if let Err(err) = imp::load_config().await {
error!("{}", err)
}
});
global_css!(
"
+4 -2
View File
@@ -224,8 +224,10 @@ pub fn load_username() -> Option<String> {
return None;
}
pub fn load_config() -> Option<GuiConfig> {
None
pub async fn load_config() -> color_eyre::Result<GuiConfig> {
color_eyre::eyre::bail!(
"there is no config on desktop because desktops cannot be configured as they are tables"
)
}
pub fn init_logging() {
+15 -3
View File
@@ -320,7 +320,8 @@ pub async fn network_connect(
)
.ey()?;
if let Some(server_hash) = &CONFIG.cert_hash {
if let Some(server_hash) = &CONFIG.try_get().and_then(|cfg| cfg.cert_hash) {
error!("{:?}", server_hash);
let hash = web_sys::js_sys::Uint8Array::from(server_hash.as_slice());
web_sys::js_sys::Reflect::set(&object, &"value".into(), &hash).ey()?;
}
@@ -394,8 +395,19 @@ fn load_config_from_env() -> Option<GuiConfig> {
serde_json::from_str(option_env!("MUMBLE_WEB2_GUI_CONFIG")?).ok()?
}
pub fn load_config() -> Option<GuiConfig> {
load_config_from_window().or_else(load_config_from_env)
pub async fn load_config() -> color_eyre::Result<()> {
let config_url = option_env!("MUMBLE_WEB2_GUI_CONFIG_URL").ok_or(eyre!("foo"))?;
let config = reqwest::get(config_url)
.await
.unwrap()
.json::<GuiConfig>()
.await
.unwrap();
crate::CONFIG.set(config);
Ok(())
}
pub fn init_logging() {
+2 -1
View File
@@ -33,7 +33,8 @@ pub mod app;
pub mod imp;
mod msghtml;
pub static CONFIG: Lazy<GuiConfig> = Lazy::new(|| imp::load_config().unwrap_or_default());
//pub static CONFIG: Lazy<GuiConfig> = Lazy::new(|| imp::load_config().unwrap_or_default());
pub static CONFIG: async_cell::sync::AsyncCell<GuiConfig> = async_cell::sync::AsyncCell::new();
pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
loop {