From 1888e5328f93f891fc683082da215e9089755a35 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Wed, 28 Dec 2022 14:27:45 -0600 Subject: [PATCH] Use StateHandler in AddRecipe --- web/src/app_state.rs | 17 ++++++ web/src/components/add_recipe.rs | 90 ++++++++++++++++++++++++++++++ web/src/pages/manage/add_recipe.rs | 26 +++++++++ 3 files changed, 133 insertions(+) create mode 100644 web/src/components/add_recipe.rs create mode 100644 web/src/pages/manage/add_recipe.rs diff --git a/web/src/app_state.rs b/web/src/app_state.rs index d91399b..0e0479f 100644 --- a/web/src/app_state.rs +++ b/web/src/app_state.rs @@ -59,6 +59,7 @@ pub enum Message { AddExtra(String, String), RemoveExtra(String, String), InitRecipes(BTreeMap), + SaveRecipe(RecipeEntry), SetRecipe(String, Recipe), RemoveRecipe(String), SetStaples(Option), @@ -213,6 +214,22 @@ impl MessageMapper for StateMachine { Message::SetRecipe(id, recipe) => { original_copy.recipes.insert(id, recipe); } + Message::SaveRecipe(entry) => { + let recipe = + parse::as_recipe(entry.recipe_text()).expect("Failed to parse RecipeEntry"); + original_copy + .recipes + .insert(entry.recipe_id().to_owned(), recipe); + let store = self.0.clone(); + original_copy + .recipe_counts + .insert(entry.recipe_id().to_owned(), 0); + spawn_local_scoped(cx, async move { + if let Err(e) = store.save_recipes(vec![entry]).await { + error!(err=?e, "Unable to save Recipe"); + } + }); + } Message::RemoveRecipe(id) => { original_copy.recipes.remove(&id); } diff --git a/web/src/components/add_recipe.rs b/web/src/components/add_recipe.rs new file mode 100644 index 0000000..e47551f --- /dev/null +++ b/web/src/components/add_recipe.rs @@ -0,0 +1,90 @@ +// Copyright 2022 Jeremy Wall (Jeremy@marzhilsltudios.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use sycamore::{futures::spawn_local_scoped, prelude::*}; +use tracing::{error, info}; + +use crate::app_state::{Message, StateHandler}; +use recipes::RecipeEntry; + +const STARTER_RECIPE: &'static str = "title: TITLE_PLACEHOLDER + +Description here. + +step: + +1 ingredient + +Instructions here +"; + +#[component] +pub fn AddRecipe<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { + let recipe_title = create_signal(cx, String::new()); + let create_recipe_signal = create_signal(cx, ()); + let dirty = create_signal(cx, false); + + let entry = create_memo(cx, || { + RecipeEntry( + recipe_title + .get() + .as_ref() + .to_lowercase() + .replace(" ", "_") + .replace("\n", ""), + STARTER_RECIPE + .replace("TITLE_PLACEHOLDER", recipe_title.get().as_str()) + .replace("\r", ""), + ) + }); + + create_effect(cx, move || { + create_recipe_signal.track(); + if !*dirty.get_untracked() { + return; + } + spawn_local_scoped(cx, { + let store = crate::api::HttpStore::get_from_context(cx); + async move { + let entry = entry.get_untracked(); + // TODO(jwall): Better error reporting here. + match store.get_recipe_text(entry.recipe_id()).await { + Ok(Some(_)) => { + // TODO(jwall): We should tell the user that this id already exists + info!(recipe_id = entry.recipe_id(), "Recipe already exists"); + return; + } + Ok(None) => { + // noop + } + Err(err) => { + // TODO(jwall): We should tell the user that this is failing + error!(?err) + } + } + sh.dispatch(cx, Message::SaveRecipe((*entry).clone())); + crate::js_lib::navigate_to_path(&format!("/ui/recipe/{}", entry.recipe_id())) + .expect("Unable to navigate to recipe"); + } + }); + }); + view! {cx, + label(for="recipe_title") { "Recipe Title" } + input(bind:value=recipe_title, type="text", name="recipe_title", id="recipe_title", on:change=move |_| { + dirty.set(true); + }) + button(on:click=move |_| { + create_recipe_signal.trigger_subscribers(); + }) { "Create" } + } +} diff --git a/web/src/pages/manage/add_recipe.rs b/web/src/pages/manage/add_recipe.rs new file mode 100644 index 0000000..a84dc9f --- /dev/null +++ b/web/src/pages/manage/add_recipe.rs @@ -0,0 +1,26 @@ +// Copyright 2022 Jeremy Wall (Jeremy@marzhilsltudios.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use super::ManagePage; +use crate::{app_state::StateHandler, components::add_recipe::AddRecipe}; + +use sycamore::prelude::*; + +#[component] +pub fn AddRecipePage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { + view! {cx, + ManagePage( + selected=Some("New Recipe".to_owned()), + ) { AddRecipe(sh) } + } +}