#![expect( clippy::needless_pass_by_value, reason = "It's soo much easier to just pass these values by value" )] use std::sync::Arc; use leptos::{ IntoView, component, prelude::{ClassAttribute, CollectView, ElementChild, Get, GlobalAttributes, IntoAny}, view, }; use leptos_router::hooks::use_params_map; use rocie_client::models::{self, Content, Ingredient, Item, Section, UnitAmount}; use crate::{ api::{ product_by_id_wrapped, recipe_by_id_wrapped, recipe_by_name_wrapped, unit_by_id_wrapped, }, components::{ async_fetch::{AsyncFetch, AsyncResource}, catch_errors::CatchErrors, login_wall::LoginWall, site_header::SiteHeader, }, }; #[component] pub fn Recipe() -> impl IntoView { let name = || { use_params_map() .get() .get("name") .expect("Should always have a name, because the router would otherwise not match") }; view! { { AsyncFetch! { @map_error_in_producer from_resource=AsyncResource! { (name: String = name().clone()) -> Result { recipe_by_name_wrapped(&name).await } }, producer=render_recipe } } } } fn render_recipe(recipe: models::recipe::Recipe) -> impl IntoView { let recipe_content = Arc::new(recipe.content.clone()); view! {

{recipe.name}

"Ingredients"

"Productions steps"

{ let local_recipe_content = Arc::clone(&recipe_content); recipe .content .sections .into_iter() .map(|section| { render_section(Arc::clone(&local_recipe_content), section) }) .collect_view() }
} } fn render_section(recipe: Arc, section: Section) -> impl IntoView { view! {
    {section .content .into_iter() .map(|content| { view! {
  1. {render_content(Arc::clone(&recipe), content)}
  2. } }) .collect_view()}
} } fn render_content(recipe: Arc, content: Content) -> impl IntoView { match content { Content::ContentOneOf(content_one_of) => { // Step let step = content_one_of.step; step.items .into_iter() .map(|item| render_item(Arc::clone(&recipe), item)) .collect_view() .into_any() } Content::ContentOneOf1(content_one_of1) => { // Text view! { {content_one_of1.text} }.into_any() } } } fn render_item(recipe: Arc, item: Item) -> impl IntoView { match item { Item::ItemOneOf(item_one_of) => { // text let text = item_one_of.text; view! { {text.value} }.into_any() } Item::ItemOneOf1(item_one_of1) => { // Ingredient let ingr = recipe .ingredients .get(item_one_of1.ingredient.index as usize) .expect("to be valid, as cooklang parser should have varified it") .to_owned(); render_ingredient_text(ingr).into_any() } Item::ItemOneOf2(item_one_of2) => { // Cookware index let cookware = recipe .cookware .get(item_one_of2.cookware.index as usize) .expect("to be valid, as cooklang parser should have varified it") .to_owned(); view! { {if let Some(qty) = cookware.quantity { format!("{} x {}", qty, cookware.name) } else { cookware.name }} } .into_any() } Item::ItemOneOf3(item_one_of3) => { // Timer index let timer = recipe .timers .get(item_one_of3.timer.index as usize) .expect("to be valid, as cooklang parser should have varified it") .to_owned(); let amount = timer.quantity.map_or(().into_any(), |amount| { render_unit_amount_text(amount).into_any() }); let name = timer.name.unwrap_or(String::from("")); view! { {name}{amount} }.into_any() } Item::ItemOneOf4(item_one_of4) => { // InlineQuantity todo!("Inline quantity not yet supported") } } } fn render_unit_amount_text(amount: UnitAmount) -> impl IntoView { AsyncFetch! { @map_error_in_producer fetcher = unit_by_id_wrapped(amount.unit), producer = |unit| { view! { { format!(" ({} {})", amount.value, unit.short_name) } } } } } fn render_ingredient_text(ingr: Ingredient) -> impl IntoView { match ingr.clone() { Ingredient::IngredientOneOf(ingredient_one_of) => { // Registered product AsyncFetch! { @map_error_in_producer fetcher = product_by_id_wrapped(ingredient_one_of.registered_product.id), producer =|product| { let amount = ingredient_one_of .registered_product .quantity .map_or(().into_any(), |amount| render_unit_amount_text(amount.amount).into_any()); view! { {product.name.clone()}{amount} } } } .into_any() } Ingredient::IngredientOneOf1(ingredient_one_of1) => { // Not registered product let amount = ingredient_one_of1 .not_registered_product .quantity .map_or(().into_any(), |amount| { render_unit_amount_text(amount).into_any() }); view! { {ingredient_one_of1.not_registered_product.name}{amount} } .into_any() } Ingredient::IngredientOneOf2(ingredient_one_of2) => { // Recipe reference let id = ingredient_one_of2.recipe_reference.id; AsyncFetch! { @map_error_in_producer fetcher=recipe_by_id_wrapped(id), producer=|recipe| { view! { {recipe.name.clone()} } }, } .into_any() } } } fn render_unit_amount_ingredient_list(amount: UnitAmount) -> impl IntoView { AsyncFetch! { @map_error_in_producer fetcher = unit_by_id_wrapped(amount.unit), producer = |unit| { view! { { format!( "{} {} of ", amount.value, if amount.value == 1 {unit.full_name_singular} else {unit.full_name_plural}, ) } } } } } fn render_ingredient_ingredient_list(ingr: Ingredient) -> impl IntoView { match ingr.clone() { Ingredient::IngredientOneOf(ingredient_one_of) => { // Registered product AsyncFetch! { @map_error_in_producer fetcher = product_by_id_wrapped(ingredient_one_of.registered_product.id), producer = |product| { let amount = ingredient_one_of .registered_product .quantity .map_or( ().into_any(), |amount| render_unit_amount_ingredient_list(amount.amount).into_any()); view! { {amount}{product.name.clone()} } } } .into_any() } Ingredient::IngredientOneOf1(ingredient_one_of1) => { // Not registered product let amount = ingredient_one_of1 .not_registered_product .quantity .map_or(().into_any(), |amount| { render_unit_amount_ingredient_list(amount).into_any() }); view! { {amount}{ingredient_one_of1.not_registered_product.name} } .into_any() } Ingredient::IngredientOneOf2(ingredient_one_of2) => { // Recipe reference let id = ingredient_one_of2.recipe_reference.id; AsyncFetch! { @map_error_in_producer fetcher=recipe_by_id_wrapped(id), producer=|recipe| { view! { {recipe.name.clone()} } }, } .into_any() } } }