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() }
>
}
}