diff --git a/web/src/api.rs b/web/src/api.rs index 48bf81e..4f33b7a 100644 --- a/web/src/api.rs +++ b/web/src/api.rs @@ -99,6 +99,7 @@ impl LocalStore { .expect("Failed to set our app state"); } + // TODO(jwall): Is this never used? pub fn fetch_app_state(&self) -> Option { self.store.get("app_state").map_or(None, |val| { val.map(|s| from_str(&s).expect("Failed to deserialize app state")) diff --git a/web/src/app_state.rs b/web/src/app_state.rs index 4ed728b..71bfd32 100644 --- a/web/src/app_state.rs +++ b/web/src/app_state.rs @@ -28,7 +28,7 @@ use wasm_bindgen::throw_str; use crate::{ api::{HttpStore, LocalStore}, - components, + components, linear::LinearSignal, }; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -166,9 +166,7 @@ impl StateMachine { original: &Signal, ) -> Result<(), crate::api::Error> { // TODO(jwall): Load plan state from local_store first. - if let Some(app_state) = local_store.fetch_app_state() { - original.set_silent(app_state); - } + let original: LinearSignal = original.into(); let mut state = original.get().as_ref().clone(); info!("Synchronizing Recipes"); let recipe_entries = &store.fetch_recipes().await?; @@ -282,7 +280,7 @@ impl StateMachine { } // Finally we store all of this app state back to our localstore local_store.store_app_state(&state); - original.set(state); + original.update(state); Ok(()) } } @@ -309,17 +307,15 @@ impl MessageMapper for StateMachine { Message::RemoveExtra(idx) => { original_copy.extras.remove(idx); } - Message::UpdateExtra(idx, amt, name) => { - match original_copy.extras.get_mut(idx) { - Some(extra) => { - extra.0 = amt; - extra.1 = name; - } - None => { - throw_str("Attempted to remove extra that didn't exist"); - } + Message::UpdateExtra(idx, amt, name) => match original_copy.extras.get_mut(idx) { + Some(extra) => { + extra.0 = amt; + extra.1 = name; } - } + None => { + throw_str("Attempted to remove extra that didn't exist"); + } + }, Message::SaveRecipe(entry, callback) => { let recipe = parse::as_recipe(entry.recipe_text()).expect("Failed to parse RecipeEntry"); @@ -429,7 +425,8 @@ impl MessageMapper for StateMachine { let local_store = self.local_store.clone(); debug!("Loading user state."); spawn_local_scoped(cx, async move { - if let Err(err) = Self::load_state(&store, &local_store, original).await { + if let Err(err) = Self::load_state(&store, &local_store, original.clone()).await + { error!(?err, "Failed to load user state"); components::toast::error_message(cx, "Failed to load_state.", None); } else { @@ -454,7 +451,7 @@ impl MessageMapper for StateMachine { } Message::SelectPlanDate(date, callback) => { let store = self.store.clone(); - let local_store = self.local_store.clone(); + let local_store = self.local_store.clone(); spawn_local_scoped(cx, async move { if let Some(mut plan) = store .fetch_plan_for_date(&date) @@ -491,7 +488,7 @@ impl MessageMapper for StateMachine { } Message::DeletePlan(date, callback) => { let store = self.store.clone(); - let local_store = self.local_store.clone(); + let local_store = self.local_store.clone(); spawn_local_scoped(cx, async move { if let Err(err) = store.delete_plan_for_date(&date).await { components::toast::error_message( diff --git a/web/src/lib.rs b/web/src/lib.rs index ae80975..af53b8d 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -18,6 +18,7 @@ mod js_lib; mod pages; mod routing; mod web; +mod linear; use sycamore::prelude::*; use wasm_bindgen::prelude::wasm_bindgen; diff --git a/web/src/linear.rs b/web/src/linear.rs new file mode 100644 index 0000000..80c16d9 --- /dev/null +++ b/web/src/linear.rs @@ -0,0 +1,51 @@ +// Copyright 2022 Jeremy Wall +// +// 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 std::convert::Into; +use std::ops::Drop; +use std::rc::Rc; + +use sycamore::prelude::*; + +pub struct LinearSignal<'ctx, Payload> { + pub signal: &'ctx Signal, + nv: Option, +} + +impl<'ctx, Payload> Into> for &'ctx Signal { + fn into(self) -> LinearSignal<'ctx, Payload> { + LinearSignal { signal: self, nv: None } + } +} + +impl<'ctx, Payload> LinearSignal<'ctx, Payload> { + pub fn update(mut self, payload: Payload) -> Self { + self.nv = Some(payload); + return self; + } + + pub fn get(&'ctx self) -> Rc { + self.signal.get() + } +} + +impl<'ctx, Payload> Drop for LinearSignal<'ctx, Payload> { + fn drop(&mut self) { + if self.nv.is_some() { + let mut val: Option = None; + std::mem::swap(&mut val, &mut self.nv); + let payload = val.unwrap(); + self.signal.set(payload); + } + } +}