18095ea742
Keep the workout example inside canonical example wording rules while preserving the native-shell host path and full xtask verification. req: examples/003 req: examples/005 req: host/001 req: host/005
98 lines
3.3 KiB
Rust
98 lines
3.3 KiB
Rust
use axum::extract::State;
|
|
use axum::response::IntoResponse;
|
|
use axum::routing::get;
|
|
use axum::Router;
|
|
use hemx_axum::{interactions, runtime_js, runtime_js_path, EffectResponse, InteractionRequest};
|
|
use hemx_workout_example::ui::workout;
|
|
use hemx_workout_example::{self as workout_app, AppState};
|
|
use std::net::SocketAddr;
|
|
use std::str::FromStr;
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let app = app(AppState::demo());
|
|
let addr = std::env::var("HEMX_WORKOUT_ADDR")
|
|
.ok()
|
|
.and_then(|value| SocketAddr::from_str(&value).ok())
|
|
.unwrap_or_else(|| SocketAddr::from(([127, 0, 0, 1], 3028)));
|
|
let listener = tokio::net::TcpListener::bind(addr).await.expect(
|
|
"bind workout example address; set HEMX_WORKOUT_ADDR=127.0.0.1:3030 if the default is busy",
|
|
);
|
|
eprintln!("hemx workout example: http://{addr}");
|
|
axum::serve(listener, app).await.unwrap();
|
|
}
|
|
|
|
fn app(state: AppState) -> Router {
|
|
Router::new()
|
|
.route("/", get(page).post(interact))
|
|
.route(runtime_js_path(), get(runtime))
|
|
.with_state(state)
|
|
}
|
|
|
|
async fn page(State(state): State<AppState>) -> impl IntoResponse {
|
|
axum::response::Html(state.with_workout(|workout| workout_app::render(workout).into_string()))
|
|
}
|
|
|
|
async fn interact(
|
|
State(state): State<AppState>,
|
|
request: InteractionRequest,
|
|
) -> Result<EffectResponse, impl IntoResponse> {
|
|
request
|
|
.dispatch_async(
|
|
interactions(hemx_workout_example::ui::BUILD_FINGERPRINT)
|
|
.on(workout::complete_set, {
|
|
let state = state.clone();
|
|
move |_| workout_app::complete_set(&state)
|
|
})
|
|
.on(workout::change_weight, {
|
|
let state = state.clone();
|
|
move |form| {
|
|
workout_app::change_weight(&state, form.parse::<f32>("kg").unwrap_or(0.0))
|
|
}
|
|
})
|
|
.on(workout::skip_exercise, {
|
|
let state = state.clone();
|
|
move |_| workout_app::skip_exercise(&state)
|
|
})
|
|
.on(workout::record_note, {
|
|
let state = state.clone();
|
|
move |form| {
|
|
workout_app::record_note(
|
|
&state,
|
|
form.value("text").unwrap_or("").to_owned(),
|
|
)
|
|
}
|
|
})
|
|
.on(workout::export_log, {
|
|
let state = state.clone();
|
|
move |_| workout_app::export_log(&state)
|
|
})
|
|
.on(workout::record_share_result, {
|
|
let state = state.clone();
|
|
move |_| workout_app::record_share_result(&state)
|
|
})
|
|
.on(workout::request_native_haptic, {
|
|
let state = state.clone();
|
|
move |_| workout_app::request_native_haptic(&state)
|
|
})
|
|
.on(workout::record_native_haptic_ack, move |_| {
|
|
workout_app::record_native_haptic_ack(&state)
|
|
}),
|
|
)
|
|
.await
|
|
}
|
|
|
|
async fn runtime() -> impl IntoResponse {
|
|
runtime_js()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn routes_can_be_built_for_run_and_deploy_smoke() {
|
|
let _app = app(AppState::demo());
|
|
}
|
|
}
|