3 Commits

Author SHA1 Message Date
samuel127849 5585304d22 Scale box bounderies
Build Mumble Web 2 / linux_build (push) Successful in 2m28s
Build Mumble Web 2 / windows_build (push) Successful in 2m37s
2026-01-12 16:19:18 -07:00
samuel127849 6df5626d6f Vibe coded some UI scaling so that the buttons always work. 2026-01-12 15:48:49 -07:00
samuel127849 37c0bce57e Fix typo
Build Mumble Web 2 / linux_build (push) Successful in 1m41s
Build Mumble Web 2 / windows_build (push) Successful in 2m24s
Build Mumble Web 2 release builder containers / windows-release-builder-container-build (push) Successful in 13s
2026-01-10 21:01:29 -07:00
7 changed files with 184 additions and 109 deletions
+4 -3
View File
@@ -1,9 +1,10 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct ProxyOverides {
pub proxy_url: String,
pub cert_hash: Vec<u8>,
pub struct ClientConfig {
pub proxy_url: Option<String>,
pub cert_hash: Option<Vec<u8>>,
pub any_server: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
+95 -10
View File
@@ -168,26 +168,68 @@ a:visited {
background-color: oklch(0.53 0.1431 264.18);
border-radius: 50%;
aspect-ratio: 1 / 1;
flex-shrink: 0;
.material-symbols-outlined {
font-variation-settings: 'FILL' 1, 'wght' 700, 'GRAD' 0, 'opsz' 48;
}
}
.button_row {
display: flex;
gap: 10px;
gap: clamp(4px, 1vw, 10px);
align-items: center;
flex-wrap: nowrap;
min-height: 0;
.spacer {
flex-grow: 1;
flex-shrink: 1;
min-width: 0;
}
.connection_status {
display: flex;
flex-direction: column;
min-width: 0;
flex-shrink: 1;
.material-symbols-outlined {
font-variation-settings: 'FILL' 1, 'wght' 700, 'GRAD' 0, 'opsz' 48;
vertical-align: middle;
}
}
.user_info {
display: flex;
flex-direction: column;
min-width: 0;
overflow: hidden;
flex-shrink: 1;
.user_name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user_data {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.toggle_button {
padding: 8px;
height: 100%;
padding: clamp(4px, 0.5vw, 8px);
aspect-ratio: 1 / 1;
flex-shrink: 0;
background-color: unset;
border: solid rgb(255 255 255 / 0.1) 3px;
border-radius: 10px;
border: solid rgb(255 255 255 / 0.1) clamp(1px, 0.3vw, 3px);
border-radius: clamp(4px, 0.8vw, 10px);
color: rgb(255 255 255 / 50%);
transition: all 0.5s ease-in-out;
@@ -200,7 +242,6 @@ a:visited {
.material-symbols-outlined {
font-variation-settings: 'FILL' 1, 'wght' 700, 'GRAD' 0, 'opsz' 48;
vertical-align: middle;
font-size: 35px;
}
}
@@ -245,16 +286,60 @@ a:visited {
}
&_control_box {
padding: 16px;
margin: 16px;
padding: clamp(6px, 0.8vw, 12px);
margin: clamp(6px, 0.8vw, 12px);
background-color: var(--light-bg-color);
border-radius: 10px;
border-radius: clamp(6px, 1vw, 10px);
overflow: hidden;
grid-area: control;
display: flex;
gap: 10px;
gap: clamp(4px, 0.8vw, 8px);
flex-direction: column;
// Dynamic font sizing for control elements
--control-icon-size: clamp(16px, 2.5vw, 30px);
--control-text-size: clamp(12px, 2vw, 25px);
--control-small-text-size: clamp(10px, 1.5vw, 20px);
--user-icon-size: clamp(24px, 4vw, 45px);
--toggle-icon-size: clamp(18px, 3vw, 35px);
.connection_status {
.material-symbols-outlined {
font-size: var(--control-icon-size);
}
.status_text {
font-size: var(--control-text-size);
}
.channel_text {
font-size: var(--control-small-text-size);
}
}
.user_edit_button {
.material-symbols-outlined {
font-size: var(--user-icon-size);
}
}
.user_info {
.user_name {
font-size: var(--control-text-size);
}
.user_data {
font-size: var(--control-small-text-size);
}
}
.toggle_button {
.material-symbols-outlined {
font-size: var(--toggle-icon-size);
}
}
hr {
margin: 0;
}
}
}
+57 -72
View File
@@ -2,7 +2,7 @@
use dioxus::prelude::*;
use mime_guess::Mime;
use mumble_web2_common::{ProxyOverides, ServerStatus};
use mumble_web2_common::{ClientConfig, ServerStatus};
use ordermap::OrderSet;
use std::collections::HashMap;
@@ -23,7 +23,7 @@ pub enum Command {
Connect {
address: String,
username: String,
config: ProxyOverides,
config: ClientConfig,
},
SendChat {
markdown: String,
@@ -309,7 +309,7 @@ pub fn ChatView() -> Element {
}
#[component]
pub fn ControlView(config: Resource<Option<ProxyOverides>>) -> Element {
pub fn ControlView(config: Resource<ClientConfig>) -> Element {
let net: Coroutine<Command> = use_coroutine_handle();
let status = &STATE.status;
let server = STATE.server.read();
@@ -331,8 +331,7 @@ pub fn ControlView(config: Resource<Option<ProxyOverides>>) -> Element {
let proxy_url = config
.read_unchecked()
.as_ref()
.and_then(|opt| opt.as_ref())
.map(|gui_config| gui_config.proxy_url.clone());
.and_then(|gui_config| gui_config.proxy_url.clone());
let connecting_color = "yellow";
let connected_color = "oklch(0.55 0.1184 141.35)";
@@ -342,79 +341,69 @@ pub fn ControlView(config: Resource<Option<ProxyOverides>>) -> Element {
let connection_status = match &*status.read() {
Connecting => rsx! {
div {
style: "color: \"{connecting_color}\";",
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"
class: "connection_status",
style: "color: {connecting_color};",
div {
span {
class: "material-symbols-outlined",
"signal_cellular_alt_2_bar"
}
span {
class: "status_text",
" Connecting"
}
}
}
},
Connected => rsx! {
div {
class: "connection_status",
div {
style: "color: \"{connected_color}\";",
style: "color: {connected_color};",
span {
class: "material-symbols-outlined",
style: "vertical-align: middle; font-size: 30px;",
"signal_cellular_alt"
}
span {
style: "width: 5px; display: inline-block;"
}
span {
style: "vertical-align: middle; font-size: 25px;",
"Connected"
class: "status_text",
" Connected"
}
}
div {
span { style: "width: 3px; display: inline-block;"}
class: "channel_text",
span { "{current_channel_name}" }
if let Some(proxy_url) = proxy_url {
span { "" }
span { "{proxy_url}" }
}
}
}
},
Disconnected => rsx! {
div {
style: "color: \"{disconnected_color}\";",
span {
class: "material-symbols-outlined",
style: "vertical-align: middle;",
"signal_disconnected"
}
span {
style: "width: 5px; display: inline-block;"
}
span {
style: "vertical-align: middle;",
"Disconnected"
class: "connection_status",
style: "color: {disconnected_color};",
div {
span {
class: "material-symbols-outlined",
"signal_disconnected"
}
span {
class: "status_text",
" Disconnected"
}
}
}
},
Failed(_) => rsx! {
div {
style: "color: \"{failed_color}\";",
span {
class: "material-symbols-outlined",
style: "vertical-align: middle;",
"error"
}
span {
style: "width: 5px; display: inline-block;"
}
span {
style: "vertical-align: middle;",
"Failed"
class: "connection_status",
style: "color: {failed_color};",
div {
span {
class: "material-symbols-outlined",
"error"
}
span {
class: "status_text",
" Failed"
}
}
}
},
@@ -446,16 +435,17 @@ pub fn ControlView(config: Resource<Option<ProxyOverides>>) -> Element {
class: "user_edit_button",
span {
class: "material-symbols-outlined",
style: "color: oklch(0.65 0.2245 28.06); font-size: 45px; font-variation-settings: 'FILL' 1, 'wght' 700, 'GRAD' 0, 'opsz' 48;",
style: "color: oklch(0.65 0.2245 28.06);",
"person_edit"
}
}
div {
class: "user_info",
div {
span { style: "font-size: 25px;", "{name}" }
span { class: "user_name", "{name}" }
}
div {
span { style: "font-size: 20px; color: gray;", "some data" }
span { class: "user_data", "some data" }
}
}
span { class: "spacer" }
@@ -509,7 +499,7 @@ pub fn ControlView(config: Resource<Option<ProxyOverides>>) -> Element {
}
#[component]
pub fn ServerView(config: Resource<Option<ProxyOverides>>) -> Element {
pub fn ServerView(config: Resource<ClientConfig>) -> Element {
let net: Coroutine<Command> = use_coroutine_handle();
let server = STATE.server.read();
let Some(&UserState {
@@ -547,7 +537,7 @@ pub fn ServerView(config: Resource<Option<ProxyOverides>>) -> Element {
}
#[component]
pub fn LoginView(config: Resource<Option<ProxyOverides>>) -> Element {
pub fn LoginView(config: Resource<ClientConfig>) -> Element {
let net: Coroutine<Command> = use_coroutine_handle();
let last_status = use_signal(|| None::<color_eyre::Result<ServerStatus>>);
@@ -565,8 +555,7 @@ pub fn LoginView(config: Resource<Option<ProxyOverides>>) -> Element {
addr.clone()
} else {
config()
.flatten()
.map(|c| c.proxy_url.clone())
.and_then(|c| c.proxy_url.clone())
.unwrap_or_default()
}
});
@@ -577,13 +566,13 @@ pub fn LoginView(config: Resource<Option<ProxyOverides>>) -> Element {
let do_connect = move |_| {
//let _ = set_default_username(&username.read());
let _ = imp::set_default_username(&username.read());
if config.read().as_ref().is_some_and(|opt| opt.is_none()) {
if config.read().as_ref().is_some_and(|cfg| cfg.any_server) {
imp::set_default_server(&address.read());
}
net.send(Connect {
address: address.read().clone(),
username: username.read().clone(),
config: config.read().clone().flatten().unwrap_or_default(),
config: config.read().clone().unwrap_or_default(),
})
};
let status = &STATE.status;
@@ -628,7 +617,7 @@ pub fn LoginView(config: Resource<Option<ProxyOverides>>) -> Element {
None => rsx!(),
}
}
if config.read().as_ref().is_some_and(|opt| opt.is_none()) {
if config.read().as_ref().is_some_and(|cfg| cfg.any_server) {
div {
label {
for: "address-entry",
@@ -725,9 +714,9 @@ pub fn app() -> Element {
use_coroutine(|rx: UnboundedReceiver<Command>| super::network_entrypoint(rx));
let config = use_resource(|| async move {
match imp::load_proxy_overides().await {
match imp::load_config().await {
Ok(config) => config,
Err(_) => None,
Err(_) => ClientConfig::default(),
}
});
@@ -736,13 +725,9 @@ pub fn app() -> Element {
document::Link{ rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" }
document::Link{ rel: "stylesheet", href: STYLE }
if config.read().is_none() {
div { class: "loading", "Loading..." }
} else {
match *STATE.status.read() {
Connected => rsx!(ServerView { config }),
_ => rsx!(LoginView { config }),
}
match *STATE.status.read() {
Connected => rsx!(ServerView { config }),
_ => rsx!(LoginView { config }),
}
)
}
+9 -5
View File
@@ -6,7 +6,7 @@ use dioxus::hooks::UnboundedReceiver;
use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs};
use futures::io::{AsyncRead, AsyncWrite};
use mumble_protocol::control::ClientControlCodec;
use mumble_web2_common::{ProxyOverides, ServerStatus};
use mumble_web2_common::{ClientConfig, ServerStatus};
use std::collections::HashMap;
use std::mem::replace;
use std::net::ToSocketAddrs;
@@ -149,7 +149,7 @@ impl AudioSystem {
}
pub fn create_player(&mut self) -> Result<AudioPlayer, Error> {
let config = self.choose_config(self.input.supported_input_configs()?)?;
let config = self.choose_config(self.output.supported_output_configs()?)?;
info!(
"creating player on {:?} with {:#?}",
self.output.name().ok(),
@@ -284,7 +284,7 @@ pub async fn network_connect(
address: String,
username: String,
event_rx: &mut UnboundedReceiver<Command>,
gui_config: &ProxyOverides,
gui_config: &ClientConfig,
) -> Result<(), Error> {
info!("connecting");
@@ -366,8 +366,12 @@ pub fn load_server_url() -> Option<String> {
config.get("server").cloned()
}
pub async fn load_proxy_overides() -> color_eyre::Result<Option<ProxyOverides>> {
Ok(None)
pub async fn load_config() -> color_eyre::Result<ClientConfig> {
Ok(ClientConfig {
proxy_url: None,
cert_hash: None,
any_server: true,
})
}
pub async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
+7 -7
View File
@@ -6,7 +6,7 @@ use futures::{AsyncRead, AsyncWrite};
use gloo_timers::future::TimeoutFuture;
use js_sys::Float32Array;
use mumble_protocol::control::ClientControlCodec;
use mumble_web2_common::{ProxyOverides, ServerStatus};
use mumble_web2_common::{ClientConfig, ServerStatus};
use reqwest::Url;
use std::future::Future;
use std::time::Duration;
@@ -329,7 +329,7 @@ pub async fn network_connect(
address: String,
username: String,
event_rx: &mut UnboundedReceiver<Command>,
gui_config: &ProxyOverides,
gui_config: &ClientConfig,
) -> Result<(), Error> {
info!("connecting");
@@ -342,8 +342,8 @@ pub async fn network_connect(
)
.ey()?;
if !gui_config.cert_hash.is_empty() {
let hash = web_sys::js_sys::Uint8Array::from(gui_config.cert_hash.as_slice());
if let Some(server_hash) = &gui_config.cert_hash {
let hash = web_sys::js_sys::Uint8Array::from(server_hash.as_slice());
web_sys::js_sys::Reflect::set(&object, &"value".into(), &hash).ey()?;
}
@@ -422,7 +422,7 @@ pub fn absolute_url(path: &str) -> Result<Url, Error> {
Ok(Url::parse(&location.href().ey()?)?.join(path)?)
}
pub async fn load_proxy_overides() -> color_eyre::Result<Option<ProxyOverides>> {
pub async fn load_config() -> color_eyre::Result<ClientConfig> {
let config_url = match option_env!("MUMBLE_WEB2_GUI_CONFIG_URL") {
Some(url) => Url::parse(url)?,
None => absolute_url("config")?,
@@ -431,10 +431,10 @@ pub async fn load_proxy_overides() -> color_eyre::Result<Option<ProxyOverides>>
let config = reqwest::get(config_url)
.await?
.json::<ProxyOverides>()
.json::<ClientConfig>()
.await?;
Ok(Some(config))
Ok(config)
}
pub async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
+1 -1
View File
@@ -20,7 +20,7 @@ use mumble_protocol::voice::VoicePacket;
use mumble_protocol::voice::VoicePacketPayload;
use mumble_protocol::Clientbound;
use mumble_protocol::Serverbound;
use mumble_web2_common::ProxyOverides;
use mumble_web2_common::ClientConfig;
use once_cell::sync::Lazy;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
+11 -11
View File
@@ -1,5 +1,5 @@
use color_eyre::eyre::{anyhow, bail, Context, Result};
use mumble_web2_common::{ProxyOverides, ServerStatus};
use mumble_web2_common::{ClientConfig, ServerStatus};
use rand::Rng;
use salvo::conn::rustls::{Keycert, RustlsConfig};
use salvo::cors::{AllowOrigin, Cors};
@@ -77,13 +77,13 @@ async fn main() -> Result<()> {
.install_default()
.map_err(|e| anyhow!("could not install crypto provider {e:?}"))?;
let mut client_config = ProxyOverides {
proxy_url: server_config
.proxy_url
.as_ref()
.map(|url| url.to_string())
.unwrap_or_default(),
cert_hash: Vec::new(),
let mut client_config = ClientConfig {
proxy_url: match &server_config.proxy_url {
Some(url) => Some(url.to_string()),
None => None,
},
cert_hash: None,
any_server: false,
};
let (cert, key) = match (&server_config.cert_path, &server_config.key_path) {
@@ -102,7 +102,7 @@ async fn main() -> Result<()> {
let cert = cert_params.self_signed(&key_pair)?;
let hash = hmac_sha256::Hash::hash(cert.der().as_ref());
client_config.cert_hash = hash.into();
client_config.cert_hash = Some(hash.into());
(cert.pem().into(), key_pair.serialize_pem().into())
}
@@ -252,13 +252,13 @@ impl StatusCraft {
#[derive(Clone)]
pub struct ConfigCraft {
server_config: Arc<Config>,
client_config: ProxyOverides,
client_config: ClientConfig,
}
#[craft]
impl ConfigCraft {
#[craft(handler)]
async fn get_config(&self) -> Json<ProxyOverides> {
async fn get_config(&self) -> Json<ClientConfig> {
Json(self.client_config.clone())
}