use crate::{routing::Route, utils::ok_json}; use anyhow::Context; use gloo_net::http::Request; use log::error; use log::info; use protocol::UserInfo; use std::future::Future; use uuid::Uuid; use wasm_bindgen_futures::spawn_local; use yew::prelude::*; use yew_router::prelude::*; #[derive(Properties, Clone, PartialEq, Debug)] pub struct ErrorInfoProperties { pub message: String, } #[derive(Clone, PartialEq)] pub struct AppState { user: UseStateHandle>, error: UseStateHandle>, } #[derive(Clone, PartialEq)] pub struct AppContext { state: AppState, history: AnyHistory, } impl AppContext { pub fn spawn_async(&self, f: F) where F: Future> + 'static, { let error = self.state.error.clone(); spawn_local(async move { if let Err(err) = f.await { error!("Error occurred: {err:?}"); error.set(Some(ErrorInfoProperties { message: format!("{err:?}"), })); } }); } pub fn user(&self) -> Option<&UserInfo> { self.state.user.as_ref() } pub fn error(&self) -> Option<&ErrorInfoProperties> { self.state.error.as_ref() } pub fn set_error(&self, error: anyhow::Error) { self.state.error.set(Some(ErrorInfoProperties { message: format!("{error:?}"), })); } pub fn create_table(&self) { let user = self.state.user.clone(); let history = self.history.clone(); self.spawn_async(async move { let response = Request::post("/api/table").send().await?; let table_id: Uuid = ok_json(response).await.context("creating table")?; info!("Created table {table_id}"); if let Some(user_info) = user.as_ref() { user.set(Some(UserInfo { table: Some(protocol::Table { id: table_id }), ..(user_info.clone()) })); } history.push(Route::Home); Ok(()) }); } pub fn leave_table(&self) { let user = self.state.user.clone(); let history = self.history.clone(); self.spawn_async(async move { let response = Request::delete("/api/table").send().await?; if !response.ok() { anyhow::bail!("error while leaving table"); } if let Some(user_info) = user.as_ref() { user.set(Some(UserInfo { table: None, ..(user_info.clone()) })); } history.push(Route::Home); Ok(()) }); } } #[derive(Properties, Clone, PartialEq)] pub struct Props { pub children: Children, } async fn initialize_user_info() -> Result, anyhow::Error> { let response = Request::get("/api/user/info") .send() .await .context("fetching user_info")?; if response.status() == 401 { web_sys::window() .unwrap() .location() .assign("/api/login") .unwrap(); }; ok_json(response).await.context("requesting user_info") } pub fn use_app_context() -> AppContext { let state: AppState = use_context::().unwrap(); let history = use_history().unwrap(); AppContext { state, history } } #[function_component(AppContextProvider)] pub fn app_context_provider(props: &Props) -> Html { let initialized = use_state(|| false); let user: UseStateHandle> = use_state(|| None); let error: UseStateHandle> = use_state(|| None); { let initialized = initialized.clone(); let user = user.clone(); let error = error.clone(); use_effect_with_deps( move |_| { spawn_local(async move { initialized.set(true); match initialize_user_info().await { Ok(user_info) => user.set(user_info), Err(e) => error.set(Some(ErrorInfoProperties { message: format!( "Could not contact server: {:?}", e ), })), }; }); || () }, (), ); } if !*initialized { return html! {

{ "Loading app..." }

}; } info!("Recomputing state"); info!("User is {:?}", *user); let state = AppState { user, error }; html! { context={state}> { for props.children.iter() } > } }