#![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"
{recipe
.content
.ingredients
.into_iter()
.map(|ingredient| {
view! { - {render_ingredient_ingredient_list(ingredient)}
}
})
.collect_view()}
"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! { - {render_content(Arc::clone(&recipe), content)}
}
})
.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()
}
}
}