From eebba4d7bcae8ef3f9f878a80237f5e4feda1380 Mon Sep 17 00:00:00 2001 From: Alex Mickelson Date: Thu, 22 Jan 2026 21:12:34 -0700 Subject: [PATCH] updates --- protossbot/src/main.rs | 49 ++++++--- protossbot/web/Cargo.toml | 12 ++- protossbot/web/Cargo.toml.leptos | 2 +- protossbot/web/src/lib.rs | 100 +++++++++++-------- protossbot/web/style/main.css | 78 +++++++++------ protossbot/web/style/protoss-bot-web.js | 5 + protossbot/web/style/protoss-bot-web_bg.wasm | 0 7 files changed, 154 insertions(+), 92 deletions(-) create mode 100644 protossbot/web/style/protoss-bot-web.js create mode 100644 protossbot/web/style/protoss-bot-web_bg.wasm diff --git a/protossbot/src/main.rs b/protossbot/src/main.rs index 3499397..9ac31e0 100644 --- a/protossbot/src/main.rs +++ b/protossbot/src/main.rs @@ -11,11 +11,15 @@ fn main() { let game_state = Arc::new(Mutex::new(GameState::default())); - // // Start the webserver in a separate thread - // std::thread::spawn(|| { - // let rt = tokio::runtime::Runtime::new().unwrap(); - // rt.block_on(start_webserver()); - // }); + // Start the webserver in a separate thread + std::thread::spawn(|| { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + start_webserver().await; + // Keep the runtime alive indefinitely to prevent TLS cleanup issues + std::future::pending::<()>().await; + }); + }); rsbwapi::start(move |_game| ProtosBot::new(game_state.clone())); } @@ -25,19 +29,40 @@ async fn start_webserver() { use leptos::*; use leptos_axum::{generate_route_list, LeptosRoutes}; use protoss_bot_web::App; + use tower_http::services::ServeDir; - let conf = get_configuration(None).await.unwrap(); - let leptos_options = conf.leptos_options; + let leptos_options = LeptosOptions { + output_name: "protoss-bot-web".to_string(), + site_root: "target/site".to_string(), + site_pkg_dir: "pkg".to_string(), + env: leptos_config::Env::DEV, + site_addr: "127.0.0.1:3333".parse().unwrap(), + reload_port: 3001, + hash_file: "".to_string(), + hash_files: false, + ..Default::default() + }; let addr = leptos_options.site_addr; let routes = generate_route_list(App); let app = Router::new() .leptos_routes(&leptos_options, routes, App) + .nest_service("/pkg", ServeDir::new("web/style")) .with_state(leptos_options); - println!("Web server listening on http://{}", &addr); - let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); - axum::serve(listener, app.into_make_service()) - .await - .unwrap(); + match tokio::net::TcpListener::bind(&addr).await { + Ok(listener) => { + println!("Web server listening on http://{}", &addr); + if let Err(e) = axum::serve(listener, app.into_make_service()).await { + eprintln!("Web server error: {}", e); + } + } + Err(e) => { + eprintln!( + "Failed to bind to {}: {}. Is the port already in use?", + &addr, e + ); + eprintln!("Skipping web server startup."); + } + } } diff --git a/protossbot/web/Cargo.toml b/protossbot/web/Cargo.toml index 3564c2e..89174e3 100644 --- a/protossbot/web/Cargo.toml +++ b/protossbot/web/Cargo.toml @@ -4,10 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] -leptos = { version = "0.6", features = ["csr"] } -leptos_axum = { version = "0.6" } -leptos_meta = { version = "0.6" } -leptos_router = { version = "0.6" } +leptos = { version = "0.6", default-features = false, features = ["ssr"] } +leptos_axum = { version = "0.6", default-features = false } +leptos_meta = { version = "0.6", default-features = false, features = ["ssr"] } +leptos_router = { version = "0.6", default-features = false, features = ["ssr"] } axum = "0.7" tokio = { version = "1", features = ["full"] } tower = "0.4" @@ -16,5 +16,9 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" lazy_static = "1.4" +[features] +default = ["ssr"] +ssr = ["leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr"] + [lib] crate-type = ["cdylib", "rlib"] diff --git a/protossbot/web/Cargo.toml.leptos b/protossbot/web/Cargo.toml.leptos index 0edfae0..b011625 100644 --- a/protossbot/web/Cargo.toml.leptos +++ b/protossbot/web/Cargo.toml.leptos @@ -9,7 +9,7 @@ site-root = "target/site" site-pkg-dir = "pkg" style-file = "style/main.css" assets-dir = "public" -site-addr = "127.0.0.1:3000" +site-addr = "127.0.0.1:3001" reload-port = 3001 browserquery = "defaults" watch = false diff --git a/protossbot/web/src/lib.rs b/protossbot/web/src/lib.rs index 599789a..c589eb2 100644 --- a/protossbot/web/src/lib.rs +++ b/protossbot/web/src/lib.rs @@ -10,57 +10,73 @@ lazy_static::lazy_static! { #[component] pub fn App() -> impl IntoView { - provide_meta_context(); + provide_meta_context(); - view! { - - - <Router> - <main> - <Routes> - <Route path="" view=HomePage/> - </Routes> - </main> - </Router> - } + view! { + <Stylesheet id="leptos" href="/pkg/main.css"/> + <Title text="Protoss Bot Control"/> + <Router> + <main> + <Routes> + <Route path="" view=HomePage/> + </Routes> + </main> + </Router> + } } #[component] fn HomePage() -> impl IntoView { - let (game_speed, set_game_speed) = create_signal(20); + let (game_speed, set_game_speed) = create_signal(0); - let set_speed = move |speed: i32| { - set_game_speed.set(speed); - spawn_local(async move { - let _ = set_game_speed_server(speed).await; - }); - }; - - view! { - <div class="container"> - <h1>"Protoss Bot Control Panel"</h1> - - <div class="speed-control"> - <h2>"Game Speed Control"</h2> - <p>"Current Speed: " {game_speed}</p> - - <div class="button-group"> - <button on:click=move |_| set_speed(0)>"Slowest (0)"</button> - <button on:click=move |_| set_speed(10)>"Slower (10)"</button> - <button on:click=move |_| set_speed(20)>"Normal (20)"</button> - <button on:click=move |_| set_speed(30)>"Fast (30)"</button> - <button on:click=move |_| set_speed(42)>"Fastest (42)"</button> - </div> - </div> - </div> + let set_speed = create_action(move |speed: &i32| { + let speed = *speed; + async move { + set_game_speed.set(speed); + let _ = set_game_speed_server(speed).await; } + }); + + view! { + <div class="container"> + <h1>"Protoss Bot Control Panel"</h1> + + <div class="speed-control"> + <h2>"Game Speed Control"</h2> + <p>"Current Speed: " {game_speed}</p> + + <div class="button-group"> + <button + class:selected=move || game_speed.get() == -1 + on:click=move |_| set_speed.dispatch(-1)> + "Fastest (-1)" + </button> + <button + class:selected=move || game_speed.get() == 0 + on:click=move |_| set_speed.dispatch(0)> + "Fast (0)" + </button> + <button + class:selected=move || game_speed.get() == 1 + on:click=move |_| set_speed.dispatch(1)> + "Normal (1)" + </button> + <button + class:selected=move || game_speed.get() == 42 + on:click=move |_| set_speed.dispatch(42)> + "Slowest (42)" + </button> + </div> + </div> + </div> + } } #[server(SetGameSpeed, "/api")] pub async fn set_game_speed_server(speed: i32) -> Result<(), ServerFnError> { - if let Ok(mut game_speed) = GAME_SPEED.write() { - *game_speed = speed; - println!("Game speed set to: {}", speed); - } - Ok(()) + if let Ok(mut game_speed) = GAME_SPEED.write() { + *game_speed = speed; + println!("Game speed set to: {}", speed); + } + Ok(()) } diff --git a/protossbot/web/style/main.css b/protossbot/web/style/main.css index 04d9197..be7b4a6 100644 --- a/protossbot/web/style/main.css +++ b/protossbot/web/style/main.css @@ -1,62 +1,74 @@ body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; - margin: 0; - padding: 20px; - background-color: #1a1a1a; - color: #ffffff; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, + Cantarell, sans-serif; + margin: 0; + padding: 20px; + background-color: #1a1a1a; + color: #ffffff; } .container { - max-width: 800px; - margin: 0 auto; - padding: 20px; + max-width: 800px; + margin: 0 auto; + padding: 20px; } h1 { - color: #4a9eff; - margin-bottom: 30px; + color: #4a9eff; + margin-bottom: 30px; } h2 { - color: #ffffff; - margin-bottom: 15px; + color: #ffffff; + margin-bottom: 15px; } .speed-control { - background-color: #2a2a2a; - padding: 20px; - border-radius: 8px; - margin-bottom: 20px; + background-color: #2a2a2a; + padding: 20px; + border-radius: 8px; + margin-bottom: 20px; } .button-group { - display: flex; - gap: 10px; - flex-wrap: wrap; - margin-top: 15px; + display: flex; + gap: 10px; + flex-wrap: wrap; + margin-top: 15px; } button { - background-color: #4a9eff; - color: white; - border: none; - padding: 12px 24px; - border-radius: 6px; - cursor: pointer; - font-size: 14px; - font-weight: 500; - transition: background-color 0.2s; + background-color: #4a9eff; + color: white; + border: none; + padding: 12px 24px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: all 0.2s; } button:hover { - background-color: #357abd; + background-color: #357abd; } button:active { - background-color: #2a5f9a; + background-color: #2a5f9a; +} + +button.selected { + background-color: #28a745; + box-shadow: 0 0 10px rgba(40, 167, 69, 0.5); + font-weight: 700; +} + +button.selected:hover { + background-color: #218838; } p { - color: #cccccc; - margin: 10px 0; + color: #cccccc; + margin: 10px 0; } diff --git a/protossbot/web/style/protoss-bot-web.js b/protossbot/web/style/protoss-bot-web.js new file mode 100644 index 0000000..bfceb3f --- /dev/null +++ b/protossbot/web/style/protoss-bot-web.js @@ -0,0 +1,5 @@ +// Dummy module for SSR-only mode +export default function () { + console.log("SSR-only mode - no client-side hydration"); + return Promise.resolve({}); +} diff --git a/protossbot/web/style/protoss-bot-web_bg.wasm b/protossbot/web/style/protoss-bot-web_bg.wasm new file mode 100644 index 0000000..e69de29