diff options
author | Kjetil Orbekk <kj@orbekk.com> | 2022-12-23 11:29:37 -0500 |
---|---|---|
committer | Kjetil Orbekk <kj@orbekk.com> | 2022-12-23 11:30:01 -0500 |
commit | 6c9651194fda7a9167157e835fbe9fd691e9a1a9 (patch) | |
tree | 4e24f243bde8923f15b125db863ba58732617251 /webapp/src/components | |
parent | 868703627bfd27925a53fcfbdd3dbeef831660c8 (diff) |
Replace table with a struct component
This makes async & state handling much easier
Diffstat (limited to 'webapp/src/components')
-rw-r--r-- | webapp/src/components/app_context_provider.rs | 6 | ||||
-rw-r--r-- | webapp/src/components/table.rs | 228 |
2 files changed, 138 insertions, 96 deletions
diff --git a/webapp/src/components/app_context_provider.rs b/webapp/src/components/app_context_provider.rs index 7ca0cf3..6298c2b 100644 --- a/webapp/src/components/app_context_provider.rs +++ b/webapp/src/components/app_context_provider.rs @@ -51,6 +51,12 @@ impl AppContext { 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(); diff --git a/webapp/src/components/table.rs b/webapp/src/components/table.rs index 92d862c..bf66897 100644 --- a/webapp/src/components/table.rs +++ b/webapp/src/components/table.rs @@ -1,114 +1,146 @@ -use std::future::Future; -use std::pin::Pin; - +use crate::components::AppContext; use crate::components::{BiddingBox, BiddingTable, Hand, TrickInPlay}; -use crate::use_app_context; -use crate::utils::ok_json; -use anyhow::Context; -use gloo_net::http::Request; +use crate::{services, use_app_context}; +use futures::FutureExt; use log::info; use protocol::bridge_engine::{ - Bid, BiddingState, BiddingStatePlayerView, GameStatePlayerView, - PlayStatePlayerView, + Bid, BiddingStatePlayerView, GameStatePlayerView, + PlayStatePlayerView, Player, }; +use protocol::card::Card; use yew::prelude::*; +#[derive(PartialEq, Properties, Clone)] +pub struct OnlineTableProps { + pub table: protocol::Table, +} + #[function_component(OnlineTable)] pub fn online_table(props: &OnlineTableProps) -> Html { let ctx = use_app_context(); + html! { + <OnlineTableInner table={props.table.clone()} app_ctx={ctx}/> + } +} - let table_state: UseStateHandle<Option<GameStatePlayerView>> = - use_state(|| None); - - let update_table_state = { - let table_state = table_state.clone(); - let props = props.clone(); - || { - Box::pin(async move { - // let table_state = table_state.clone(); - let props = props.clone(); - let response = - Request::get(&format!("/api/table/{}", props.table.id)) - .send() - .await - .context("fetching table data")?; - let table = ok_json(response).await?; - table_state.set(Some(table)); - Ok(()) - }) - as Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>> - } - }; +#[derive(PartialEq, Properties, Clone)] +pub struct OnlineTableInnerProps { + pub table: protocol::Table, + pub app_ctx: AppContext, +} + +struct OnlineTableInner { + table_state: Option<GameStatePlayerView>, +} + +pub enum Msg { + TableStateUpdated(Result<GameStatePlayerView, anyhow::Error>), + Bid(Bid), + Play(Card), +} + +impl OnlineTableInner { + fn play(&mut self, ctx: &yew::Context<Self>, card: Card) { + let _play_state = match &self.table_state { + Some(GameStatePlayerView::Playing(play_state)) => play_state, + _ => { + info!( + "Cannot play card with table state: {:#?}", + self.table_state + ); + return; + } + }; + info!("Playing card {card:?}"); + let table = ctx.props().table.clone(); + ctx.link().send_future( + async move { + services::play(table.clone(), card).await?; + services::get_table_player_view(table).await + } + .map(Msg::TableStateUpdated), + ); + } +} + +impl Component for OnlineTableInner { + type Message = Msg; + + type Properties = OnlineTableInnerProps; - { - let ctx = ctx.clone(); - let update_table_state = update_table_state.clone(); - use_effect_with_deps( - move |_| { - ctx.spawn_async(async move { - update_table_state().await?; - Ok(()) - }); - || () - }, - (), + fn create(ctx: &yew::Context<Self>) -> Self { + ctx.link().send_future( + services::get_table_player_view(ctx.props().table.clone()) + .map(Msg::TableStateUpdated), ); + Self { table_state: None } } - let on_bid = { - let ctx = ctx.clone(); - let props = props.clone(); - let update_table_state = update_table_state.clone(); - Callback::from(move |bid| { - let update_table_state = update_table_state.clone(); - info!("Bid clicked: {:?}", bid); - ctx.spawn_async(async move { - let bid_response = Request::post(&format!( - "/api/table/{}/bid", - props.table.id - )) - .json(&bid)? - .send() - .await - .context("submitting bid")?; - let () = ok_json(bid_response).await?; - update_table_state().await?; - Ok(()) - }); - }) - }; + fn update(&mut self, ctx: &yew::Context<Self>, msg: Msg) -> bool { + match msg { + Msg::Bid(bid) => { + info!("Bid clicked: {bid:?}"); + let table = ctx.props().table.clone(); + ctx.link().send_future( + async move { + services::bid(table.clone(), bid).await?; + services::get_table_player_view(table).await + } + .map(Msg::TableStateUpdated), + ); + false + } + Msg::Play(card) => { + self.play(ctx, card); + false + } + Msg::TableStateUpdated(Ok(table_state)) => { + self.table_state = Some(table_state); + true + } + Msg::TableStateUpdated(Err(error)) => { + ctx.props().app_ctx.set_error(error); + false + } + } + } - let leave_table = { - let ctx = ctx.clone(); - Callback::from(move |_| { - ctx.leave_table(); - }) - }; + fn view(&self, ctx: &yew::Context<Self>) -> Html { + let table_state = match &self.table_state { + Some(x) => x, + None => return loading(), + }; - let center = match &*table_state { - Some(GameStatePlayerView::Bidding(bidding)) => - bidding_view(bidding, on_bid), - Some(GameStatePlayerView::Playing(playing)) => - playing_view(playing), - None => html! { <p>{"Loading table"}</p> }, - }; + let center = match table_state { + GameStatePlayerView::Bidding(bidding) => { + bidding_view(bidding, ctx.link().callback(Msg::Bid)) + } + GameStatePlayerView::Playing(playing) => { + playing_view(playing, ctx.link().callback(Msg::Play)) + } + }; - html! { - <> - <p>{ format!("This is table {}", props.table.id) }</p> - <button onclick={leave_table}> - { "Leave table" } - </button> - <div class="game-layout"> - { center } - </div> - </> + let leave_table = { + let ctx = ctx.props().app_ctx.clone(); + Callback::from(move |_| ctx.leave_table()) + }; + + html! { + <> + <div class="game-layout"> + {center} + </div> + <p>{format!("This is table {}", ctx.props().table.id)}</p> + <button onclick={leave_table}> + { "Leave table" } + </button> + </> + } } } -#[derive(PartialEq, Properties, Clone)] -pub struct OnlineTableProps { - pub table: protocol::Table, +fn loading() -> Html { + html! { <p>{"Loading table information"}</p> } } pub fn bidding_view( @@ -123,7 +155,7 @@ pub fn bidding_view( { format!("It is {:?} to bid", bidding.bidding.current_bidder()) } </div> <div class="hand south"> - <Hand cards={ bidding.hand.clone() } on_card_clicked={ Callback::from(|card| {}) } /> + <Hand cards={ bidding.hand.clone() } on_card_clicked={ Callback::from(|_| {}) } /> </div> <h2>{ "Table view" }</h2> <pre>{ format!("{:#?}", bidding) }</pre> @@ -132,16 +164,20 @@ pub fn bidding_view( } pub fn playing_view( - playing: &PlayStatePlayerView) - -> Html { - let on_card_clicked = Callback::from(|card| {}); - // Dummy is assumed to be north for now. + playing: &PlayStatePlayerView, + on_card_clicked: Callback<Card>, +) -> Html { + // Only one layout is currently supported. + assert_eq!(playing.player_position, Player::South); + assert_eq!(playing.contract.declarer, Player::South); + let dummy = match &playing.dummy { Some(hand) => html! { <Hand cards={hand.clone()} on_card_clicked={on_card_clicked.clone()}/> }, None => html! {<p>{"Dummy is not visible yet"}</p>}, }; + html! { <> <div class="center"> |