diff --git a/web/src/app_state.rs b/web/src/app_state.rs index eed2541..6fed7d5 100644 --- a/web/src/app_state.rs +++ b/web/src/app_state.rs @@ -185,6 +185,7 @@ impl MessageMapper for StateMachine { #[instrument(skip_all, fields(?msg))] fn map<'ctx>(&self, cx: Scope<'ctx>, msg: Message, original: &'ctx Signal) { let mut original_copy = original.get().as_ref().clone(); + debug!("handling state message"); match msg { Message::ResetRecipeCounts => { let mut map = BTreeMap::new(); diff --git a/web/src/components/add_recipe.rs b/web/src/components/add_recipe.rs index e47551f..a93ff50 100644 --- a/web/src/components/add_recipe.rs +++ b/web/src/components/add_recipe.rs @@ -48,6 +48,7 @@ pub fn AddRecipe<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View ) }); + // TODO(jwall): This create effect should no longer be necessary; create_effect(cx, move || { create_recipe_signal.track(); if !*dirty.get_untracked() { diff --git a/web/src/components/categories.rs b/web/src/components/categories.rs index 935f468..3e4c527 100644 --- a/web/src/components/categories.rs +++ b/web/src/components/categories.rs @@ -43,7 +43,6 @@ fn check_category_text_parses(unparsed: &str, error_text: &Signal) -> bo #[instrument(skip_all)] #[component] pub fn Categories<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { - let save_signal = create_signal(cx, ()); let error_text = create_signal(cx, String::new()); let category_text: &Signal = create_signal(cx, String::new()); let dirty = create_signal(cx, false); @@ -61,21 +60,6 @@ pub fn Categories<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> Vie } }); - create_effect(cx, move || { - save_signal.track(); - if !*dirty.get() { - return; - } - spawn_local_scoped(cx, { - async move { - sh.dispatch( - cx, - Message::SetCategoryMap(category_text.get_untracked().as_ref().clone()), - ); - } - }); - }); - let dialog_view = view! {cx, dialog(id="error-dialog") { article{ @@ -102,10 +86,15 @@ pub fn Categories<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> Vie check_category_text_parses(category_text.get().as_str(), error_text); }) { "Check" } " " span(role="button", on:click=move |_| { - // TODO(jwall): check and then save the categories. + if !*dirty.get() { + return; + } if check_category_text_parses(category_text.get().as_str(), error_text) { debug!("triggering category save"); - save_signal.trigger_subscribers(); + sh.dispatch( + cx, + Message::SetCategoryMap(category_text.get_untracked().as_ref().clone()), + ); } }) { "Save" } } diff --git a/web/src/components/recipe.rs b/web/src/components/recipe.rs index cb1c951..0677ef0 100644 --- a/web/src/components/recipe.rs +++ b/web/src/components/recipe.rs @@ -67,44 +67,8 @@ pub fn Editor<'ctx, G: Html>(cx: Scope<'ctx>, props: RecipeComponentProps<'ctx>) }); let id = create_memo(cx, || recipe.get().recipe_id().to_owned()); - let save_signal = create_signal(cx, ()); let dirty = create_signal(cx, false); - debug!("Creating effect"); - create_effect(cx, move || { - save_signal.track(); - if !*dirty.get_untracked() { - debug!("Recipe text is unchanged"); - return; - } - debug!("Recipe text is changed"); - spawn_local_scoped(cx, { - let store = crate::api::HttpStore::get_from_context(cx); - async move { - debug!("Attempting to save recipe"); - if let Err(e) = store - .save_recipes(vec![RecipeEntry( - id.get_untracked().as_ref().clone(), - text.get_untracked().as_ref().clone(), - )]) - .await - { - error!(?e, "Failed to save recipe"); - error_text.set(format!("{:?}", e)); - } else { - // We also need to set recipe in our state - dirty.set(false); - if let Ok(recipe) = recipes::parse::as_recipe(text.get_untracked().as_ref()) { - sh.dispatch( - cx, - Message::SetRecipe(id.get_untracked().as_ref().to_owned(), recipe), - ); - } - }; - } - }); - }); - debug!("creating editor view"); view! {cx, div(class="grid") { @@ -121,7 +85,36 @@ pub fn Editor<'ctx, G: Html>(cx: Scope<'ctx>, props: RecipeComponentProps<'ctx>) let unparsed = text.get(); if check_recipe_parses(unparsed.as_str(), error_text, aria_hint) { debug!("triggering a save"); - save_signal.trigger_subscribers(); + if !*dirty.get_untracked() { + debug!("Recipe text is unchanged"); + return; + } + debug!("Recipe text is changed"); + spawn_local_scoped(cx, { + let store = crate::api::HttpStore::get_from_context(cx); + async move { + debug!("Attempting to save recipe"); + if let Err(e) = store + .save_recipes(vec![RecipeEntry( + id.get_untracked().as_ref().clone(), + text.get_untracked().as_ref().clone(), + )]) + .await + { + error!(?e, "Failed to save recipe"); + error_text.set(format!("{:?}", e)); + } else { + // We also need to set recipe in our state + dirty.set(false); + if let Ok(recipe) = recipes::parse::as_recipe(text.get_untracked().as_ref()) { + sh.dispatch( + cx, + Message::SetRecipe(id.get_untracked().as_ref().to_owned(), recipe), + ); + } + }; + } + }); } else { } }) { "Save" } diff --git a/web/src/components/recipe_plan.rs b/web/src/components/recipe_plan.rs index 2d8c4b6..6c0c4ae 100644 --- a/web/src/components/recipe_plan.rs +++ b/web/src/components/recipe_plan.rs @@ -35,17 +35,6 @@ pub fn RecipePlan<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> Vie } rows }); - let refresh_click = create_signal(cx, false); - let save_click = create_signal(cx, false); - // FIXME(jwall): We should probably make this a dispatch method instead. - create_effect(cx, move || { - refresh_click.track(); - sh.dispatch(cx, Message::LoadState); - }); - create_effect(cx, move || { - save_click.track(); - sh.dispatch(cx, Message::SaveState); - }); view! {cx, table(class="recipe_selector no-print") { (View::new_fragment( @@ -66,17 +55,14 @@ pub fn RecipePlan<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> Vie )) } input(type="button", value="Reset", on:click=move |_| { - // Poor man's click event signaling. - let toggle = !*refresh_click.get(); - refresh_click.set(toggle); + sh.dispatch(cx, Message::LoadState); }) input(type="button", value="Clear All", on:click=move |_| { sh.dispatch(cx, Message::ResetRecipeCounts); }) input(type="button", value="Save Plan", on:click=move |_| { // Poor man's click event signaling. - let toggle = !*save_click.get(); - save_click.set(toggle); + sh.dispatch(cx, Message::SaveState); }) } } diff --git a/web/src/components/recipe_selection.rs b/web/src/components/recipe_selection.rs index b758161..e3a09f8 100644 --- a/web/src/components/recipe_selection.rs +++ b/web/src/components/recipe_selection.rs @@ -53,7 +53,7 @@ pub fn RecipeSelection<'ctx, G: Html>( view! {cx, div() { label(for=for_id) { a(href=href) { (*title) } } - input(type="number", class="item-count-sel", min="0", value=count, name=name, on:change=move |_| { + input(type="number", class="item-count-sel", min="0", bind:value=count, name=name, on:change=move |_| { debug!(idx=%id, count=%(*count.get()), "setting recipe count"); sh.dispatch(cx, Message::UpdateRecipeCount(id.as_ref().clone(), count.get().parse().expect("Count is not a valid usize"))); }) diff --git a/web/src/components/shopping_list.rs b/web/src/components/shopping_list.rs index b2289f5..c695e06 100644 --- a/web/src/components/shopping_list.rs +++ b/web/src/components/shopping_list.rs @@ -192,12 +192,6 @@ fn make_shopping_table<'ctx, G: Html>( #[component] pub fn ShoppingList<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { let show_staples = create_signal(cx, true); - let save_click = create_signal(cx, ()); - create_effect(cx, move || { - save_click.track(); - info!("Registering save request for inventory"); - sh.dispatch(cx, Message::SaveState); - }); view! {cx, h1 { "Shopping List " } label(for="show_staples_cb") { "Show staples" } @@ -209,8 +203,9 @@ pub fn ShoppingList<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> V input(type="button", value="Reset", class="no-print", on:click=move |_| { sh.dispatch(cx, Message::ResetInventory); }) - input(type="button", value="Save", class="no-print", on:click=|_| { - save_click.trigger_subscribers(); + input(type="button", value="Save", class="no-print", on:click=move |_| { + info!("Registering save request for inventory"); + sh.dispatch(cx, Message::SaveState); }) } } diff --git a/web/src/pages/login.rs b/web/src/pages/login.rs index 560a162..90ec765 100644 --- a/web/src/pages/login.rs +++ b/web/src/pages/login.rs @@ -11,7 +11,8 @@ // 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 sycamore::futures::spawn_local_scoped; +use sycamore::prelude::*; use tracing::{debug, info}; use crate::app_state::{Message, StateHandler}; @@ -20,20 +21,6 @@ use crate::app_state::{Message, StateHandler}; pub fn LoginForm<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { let username = create_signal(cx, "".to_owned()); let password = create_signal(cx, "".to_owned()); - let clicked = create_signal(cx, ("".to_owned(), "".to_owned())); - create_effect(cx, move || { - let (username, password) = (*clicked.get()).clone(); - if username != "" && password != "" { - spawn_local_scoped(cx, async move { - let store = crate::api::HttpStore::get_from_context(cx); - debug!("authenticating against ui"); - // TODO(jwall): Navigate to plan if the below is successful. - if let Some(user_data) = store.authenticate(username, password).await { - sh.dispatch(cx, Message::SetUserData(user_data)); - } - }); - } - }); view! {cx, form() { label(for="username") { "Username" } @@ -42,9 +29,18 @@ pub fn LoginForm<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View input(type="password", bind:value=password) input(type="button", value="Login", on:click=move |_| { info!("Attempting login request"); - clicked.set(((*username.get_untracked()).clone(), (*password.get_untracked()).clone())); + let (username, password) = ((*username.get_untracked()).clone(), (*password.get_untracked()).clone()); + if username != "" && password != "" { + spawn_local_scoped(cx, async move { + let store = crate::api::HttpStore::get_from_context(cx); + debug!("authenticating against ui"); + // TODO(jwall): Navigate to plan if the below is successful. + if let Some(user_data) = store.authenticate(username, password).await { + sh.dispatch(cx, Message::SetUserData(user_data)); + } + }); + } debug!("triggering login click subscribers"); - clicked.trigger_subscribers(); }) { } } } diff --git a/web/src/web.rs b/web/src/web.rs index d217ba9..9a6fc1e 100644 --- a/web/src/web.rs +++ b/web/src/web.rs @@ -28,8 +28,6 @@ pub fn UI(cx: Scope) -> View { let app_state = crate::app_state::AppState::new(); let sh = crate::app_state::get_state_handler(cx, app_state, store); let view = create_signal(cx, View::empty()); - // FIXME(jwall): We need a way to trigger refreshes when required. Turn this - // into a create_effect with a refresh signal stored as a context. spawn_local_scoped(cx, { async move { sh.dispatch(cx, Message::LoadState);