From 6c9651194fda7a9167157e835fbe9fd691e9a1a9 Mon Sep 17 00:00:00 2001 From: Kjetil Orbekk Date: Fri, 23 Dec 2022 11:29:37 -0500 Subject: Replace table with a struct component This makes async & state handling much easier --- webapp/src/components/table.rs | 228 ++++++++++++++++++++++++----------------- 1 file changed, 132 insertions(+), 96 deletions(-) (limited to 'webapp/src/components/table.rs') 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! { + + } +} - let table_state: UseStateHandle> = - 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>>> - } - }; +#[derive(PartialEq, Properties, Clone)] +pub struct OnlineTableInnerProps { + pub table: protocol::Table, + pub app_ctx: AppContext, +} + +struct OnlineTableInner { + table_state: Option, +} + +pub enum Msg { + TableStateUpdated(Result), + Bid(Bid), + Play(Card), +} + +impl OnlineTableInner { + fn play(&mut self, ctx: &yew::Context, 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 { + 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, 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) -> 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! {

{"Loading table"}

}, - }; + 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! { - <> -

{ format!("This is table {}", props.table.id) }

- -
- { center } -
- + let leave_table = { + let ctx = ctx.props().app_ctx.clone(); + Callback::from(move |_| ctx.leave_table()) + }; + + html! { + <> +
+ {center} +
+

{format!("This is table {}", ctx.props().table.id)}

+ + + } } } -#[derive(PartialEq, Properties, Clone)] -pub struct OnlineTableProps { - pub table: protocol::Table, +fn loading() -> Html { + html! {

{"Loading table information"}

} } pub fn bidding_view( @@ -123,7 +155,7 @@ pub fn bidding_view( { format!("It is {:?} to bid", bidding.bidding.current_bidder()) }
- +

{ "Table view" }

{ format!("{:#?}", bidding) }
@@ -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, +) -> 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! { }, None => html! {

{"Dummy is not visible yet"}

}, }; + html! { <>
-- cgit v1.2.3