app: Isolate RecipeInfoView in a separate component
This commit is contained in:
parent
5b44cc7dde
commit
8cf5803c1e
1 changed files with 124 additions and 99 deletions
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use api::{
|
use api::{
|
||||||
AddRecipeIngredientRequest, IngredientInfo, RecipeEditRating, RecipeEditStepsRequest,
|
AddRecipeIngredientRequest, IngredientInfo, RecipeEditRating, RecipeEditStepsRequest,
|
||||||
RecipeInfo, RecipeIngredientEditRequest, RecipeRenameRequest,
|
RecipeInfo, RecipeIngredientEditRequest, RecipeRenameRequest,
|
||||||
|
|
@ -128,7 +130,7 @@ struct RecipeViewerInnerProps {
|
||||||
household: Uuid,
|
household: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_recipe(token: String, household: Uuid, id: i64) -> anyhow::Result<RecipeInfo> {
|
async fn fetch_recipe(token: String, household: Uuid, id: i64) -> anyhow::Result<Rc<RecipeInfo>> {
|
||||||
let rsp = gloo_net::http::Request::get(api!("household/{household}/recipe/{id}"))
|
let rsp = gloo_net::http::Request::get(api!("household/{household}/recipe/{id}"))
|
||||||
.header("Authorization", &format!("Bearer {token}"))
|
.header("Authorization", &format!("Bearer {token}"))
|
||||||
.send()
|
.send()
|
||||||
|
|
@ -139,7 +141,7 @@ async fn fetch_recipe(token: String, household: Uuid, id: i64) -> anyhow::Result
|
||||||
anyhow::bail!("Could not get recipe (code={}): {body}", rsp.status());
|
anyhow::bail!("Could not get recipe (code={}): {body}", rsp.status());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(rsp.json().await?)
|
Ok(Rc::new(rsp.json().await?))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Properties)]
|
#[derive(Clone, PartialEq, Properties)]
|
||||||
|
|
@ -756,25 +758,23 @@ fn EditRating(props: &EditRatingProps) -> Html {
|
||||||
</>}
|
</>}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq, Clone)]
|
||||||
|
struct RecipeInfoProps {
|
||||||
|
token: String,
|
||||||
|
household: Uuid,
|
||||||
|
update: Callback<()>,
|
||||||
|
recipe_id: i64,
|
||||||
|
info: Rc<RecipeInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
#[function_component]
|
#[function_component]
|
||||||
fn RecipeViewerInner(props: &RecipeViewerInnerProps) -> HtmlResult {
|
fn RecipeInfoView(props: &RecipeInfoProps) -> Html {
|
||||||
let recipe_render = use_state(|| 0u64);
|
|
||||||
let recipe = use_future_with_deps(
|
|
||||||
|_| fetch_recipe(props.token.clone(), props.household, props.id),
|
|
||||||
*recipe_render,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let update = Callback::from(move |_| {
|
|
||||||
recipe_render.set((*recipe_render).wrapping_add(1));
|
|
||||||
});
|
|
||||||
|
|
||||||
let error = use_state(|| None::<String>);
|
let error = use_state(|| None::<String>);
|
||||||
|
|
||||||
let mk_del_ig = |&id| {
|
let mk_del_ig = |&id| {
|
||||||
let update = update.clone();
|
let update = props.update.clone();
|
||||||
let token = props.token.clone();
|
let token = props.token.clone();
|
||||||
let household = props.household;
|
let household = props.household;
|
||||||
let recipe = props.id;
|
let recipe = props.recipe_id;
|
||||||
let err = error.clone();
|
let err = error.clone();
|
||||||
Callback::from(move |_| {
|
Callback::from(move |_| {
|
||||||
let update = update.clone();
|
let update = update.clone();
|
||||||
|
|
@ -794,94 +794,119 @@ fn RecipeViewerInner(props: &RecipeViewerInnerProps) -> HtmlResult {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
html! {<>
|
||||||
|
<h1>{&props.info.name} <RecipeRating rating={props.info.rating} /> </h1>
|
||||||
|
<div class="mt-2">
|
||||||
|
<EditName
|
||||||
|
token={props.token.clone()}
|
||||||
|
id={props.recipe_id}
|
||||||
|
household={props.household}
|
||||||
|
name={props.info.name.clone()}
|
||||||
|
update={props.update.clone()}
|
||||||
|
/>
|
||||||
|
<EditRating
|
||||||
|
token={props.token.clone()}
|
||||||
|
recipe={props.recipe_id}
|
||||||
|
household={props.household}
|
||||||
|
rating={props.info.rating + 1}
|
||||||
|
update={props.update.clone()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
if let Some(e) = &*error {
|
||||||
|
<div class={classes!("alert", "alert-danger")} role="alert">
|
||||||
|
{e}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<hr />
|
||||||
|
<div class="text-start">
|
||||||
|
<h2>{"Ingredients"}</h2>
|
||||||
|
<ul class="list-group mb-2">
|
||||||
|
{for props.info.ingredients.iter().map(|(id, info, amount)| {
|
||||||
|
let delete_modal_id = format!("rcpRmIg{id}");
|
||||||
|
let amount_rounded = amount.round();
|
||||||
|
html!{
|
||||||
|
<li
|
||||||
|
key={*id}
|
||||||
|
class="list-group-item d-flex justify-content-between align-items-center"
|
||||||
|
>
|
||||||
|
{format!("{amount_rounded}{} {}", info.unit.as_deref().unwrap_or(""), info.name)}
|
||||||
|
<div>
|
||||||
|
<ConfirmDangerModal
|
||||||
|
id={delete_modal_id.clone()}
|
||||||
|
title="Remove ingredient"
|
||||||
|
centered=true
|
||||||
|
on_confirm={mk_del_ig(id)}
|
||||||
|
>
|
||||||
|
{format!("Are you sure you to delete '{}'", info.name)}
|
||||||
|
</ConfirmDangerModal>
|
||||||
|
<ModalToggleButton
|
||||||
|
modal_id={delete_modal_id}
|
||||||
|
classes={classes!("btn", "btn-danger", "me-1")}
|
||||||
|
>
|
||||||
|
<i class="bi-trash3" />
|
||||||
|
</ModalToggleButton>
|
||||||
|
<EditIngredient
|
||||||
|
token={props.token.clone()}
|
||||||
|
recipe={props.recipe_id}
|
||||||
|
ingredient={*id}
|
||||||
|
household={props.household}
|
||||||
|
amount={*amount}
|
||||||
|
update={props.update.clone()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
}})}
|
||||||
|
</ul>
|
||||||
|
<AddIngredient
|
||||||
|
token={props.token.clone()}
|
||||||
|
household={props.household}
|
||||||
|
recipe={props.recipe_id}
|
||||||
|
update={props.update.clone()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="text-start">
|
||||||
|
<h2>{"Steps"}</h2>
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
{for props.info.steps.split('\n').map(|text| html!{
|
||||||
|
<li class="list-group-item">{text}</li>
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
<EditSteps
|
||||||
|
token={props.token.clone()}
|
||||||
|
household={props.household}
|
||||||
|
recipe={props.recipe_id}
|
||||||
|
steps={props.info.steps.clone()}
|
||||||
|
update={props.update.clone()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
fn RecipeViewerInner(props: &RecipeViewerInnerProps) -> HtmlResult {
|
||||||
|
let recipe_render = use_state(|| 0u64);
|
||||||
|
let recipe = use_future_with_deps(
|
||||||
|
|_| fetch_recipe(props.token.clone(), props.household, props.id),
|
||||||
|
*recipe_render,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let update = Callback::from(move |_| {
|
||||||
|
recipe_render.set((*recipe_render).wrapping_add(1));
|
||||||
|
});
|
||||||
|
|
||||||
Ok(match &*recipe {
|
Ok(match &*recipe {
|
||||||
Ok(r) => html! {<>
|
Ok(r) => {
|
||||||
<h1>{&r.name} <RecipeRating rating={r.rating} /> </h1>
|
html! {
|
||||||
<div class="mt-2">
|
<RecipeInfoView
|
||||||
<EditName
|
|
||||||
token={props.token.clone()}
|
token={props.token.clone()}
|
||||||
id={props.id}
|
recipe_id={props.id}
|
||||||
|
info={r.clone()}
|
||||||
household={props.household}
|
household={props.household}
|
||||||
name={r.name.clone()}
|
{update}
|
||||||
update={update.clone()}
|
|
||||||
/>
|
/>
|
||||||
<EditRating
|
|
||||||
token={props.token.clone()}
|
|
||||||
recipe={props.id}
|
|
||||||
household={props.household}
|
|
||||||
rating={r.rating + 1}
|
|
||||||
update={update.clone()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
if let Some(e) = &*error {
|
|
||||||
<div class={classes!("alert", "alert-danger")} role="alert">
|
|
||||||
{e}
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
<hr />
|
}
|
||||||
<div class="text-start">
|
|
||||||
<h2>{"Ingredients"}</h2>
|
|
||||||
<ul class="list-group mb-2">
|
|
||||||
{for r.ingredients.iter().map(|(id, info, amount)| {
|
|
||||||
let delete_modal_id = format!("rcpRmIg{id}");
|
|
||||||
let amount_rounded = amount.round();
|
|
||||||
html!{
|
|
||||||
<li
|
|
||||||
key={*id}
|
|
||||||
class="list-group-item d-flex justify-content-between align-items-center"
|
|
||||||
>
|
|
||||||
{format!("{amount_rounded}{} {}", info.unit.as_deref().unwrap_or(""), info.name)}
|
|
||||||
<div>
|
|
||||||
<ConfirmDangerModal
|
|
||||||
id={delete_modal_id.clone()}
|
|
||||||
title="Remove ingredient"
|
|
||||||
centered=true
|
|
||||||
on_confirm={mk_del_ig(id)}
|
|
||||||
>
|
|
||||||
{format!("Are you sure you to delete '{}'", info.name)}
|
|
||||||
</ConfirmDangerModal>
|
|
||||||
<ModalToggleButton
|
|
||||||
modal_id={delete_modal_id}
|
|
||||||
classes={classes!("btn", "btn-danger", "me-1")}
|
|
||||||
>
|
|
||||||
<i class="bi-trash3" />
|
|
||||||
</ModalToggleButton>
|
|
||||||
<EditIngredient
|
|
||||||
token={props.token.clone()}
|
|
||||||
recipe={props.id}
|
|
||||||
ingredient={*id}
|
|
||||||
household={props.household}
|
|
||||||
amount={*amount}
|
|
||||||
update={update.clone()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
}})}
|
|
||||||
</ul>
|
|
||||||
<AddIngredient
|
|
||||||
token={props.token.clone()}
|
|
||||||
household={props.household}
|
|
||||||
recipe={props.id}
|
|
||||||
update={update.clone()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<div class="text-start">
|
|
||||||
<h2>{"Steps"}</h2>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
{for r.steps.split('\n').map(|text| html!{
|
|
||||||
<li class="list-group-item">{text}</li>
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
<EditSteps
|
|
||||||
token={props.token.clone()}
|
|
||||||
household={props.household}
|
|
||||||
recipe={props.id}
|
|
||||||
steps={r.steps.clone()}
|
|
||||||
update={update}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>},
|
|
||||||
Err(e) => html! {
|
Err(e) => html! {
|
||||||
<div class={classes!("alert", "alert-danger")} role="alert">
|
<div class={classes!("alert", "alert-danger")} role="alert">
|
||||||
{format!("Error fetching recipe: {e}")}
|
{format!("Error fetching recipe: {e}")}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue