mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-21 19:29:49 -04:00
Introduce a save state message
This commit is contained in:
parent
fbb4e4ceeb
commit
e77fe40d75
@ -58,11 +58,10 @@ fn filter_recipes(
|
||||
}
|
||||
|
||||
pub async fn init_app_state<'ctx>(
|
||||
cx: Scope<'ctx>,
|
||||
store: &HttpStore,
|
||||
h: &'ctx Handler<'ctx, StateMachine, AppState, Message>,
|
||||
) {
|
||||
info!("Synchronizing Recipes");
|
||||
let store = HttpStore::get_from_context(cx);
|
||||
// TODO(jwall): Make our caching logic using storage more robust.
|
||||
let recipe_entries = match store.get_recipes().await {
|
||||
Ok(recipe_entries) => {
|
||||
@ -270,7 +269,7 @@ pub struct HttpStore {
|
||||
}
|
||||
|
||||
impl HttpStore {
|
||||
fn new(root: String) -> Self {
|
||||
pub fn new(root: String) -> Self {
|
||||
Self { root }
|
||||
}
|
||||
|
||||
@ -493,6 +492,27 @@ impl HttpStore {
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn save_app_state(&self, state: AppState) -> Result<(), Error> {
|
||||
let mut plan = Vec::new();
|
||||
for (key, count) in state.recipe_counts.iter() {
|
||||
plan.push((key.clone(), *count as i32));
|
||||
}
|
||||
debug!("Saving plan data");
|
||||
self.save_plan(plan).await?;
|
||||
debug!("Saving inventory data");
|
||||
self.save_inventory_data(
|
||||
state.filtered_ingredients,
|
||||
state.modified_amts,
|
||||
state
|
||||
.extras
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<(String, String)>>(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn save_state(&self, state: std::rc::Rc<app_state::State>) -> Result<(), Error> {
|
||||
let mut plan = Vec::new();
|
||||
|
@ -13,14 +13,16 @@
|
||||
// limitations under the License.
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use sycamore::prelude::*;
|
||||
use tracing::{debug, instrument, warn};
|
||||
use sycamore::{futures::spawn_local, prelude::*};
|
||||
use tracing::{debug, error, instrument, warn};
|
||||
|
||||
use client_api::UserData;
|
||||
use recipes::{Ingredient, IngredientAccumulator, IngredientKey, Recipe};
|
||||
|
||||
use sycamore_state::{Handler, MessageMapper};
|
||||
|
||||
use crate::api::HttpStore;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AppState {
|
||||
pub recipe_counts: BTreeMap<String, usize>,
|
||||
@ -48,6 +50,7 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Message {
|
||||
InitRecipeCounts(BTreeMap<String, usize>),
|
||||
UpdateRecipeCount(String, usize),
|
||||
@ -66,12 +69,13 @@ pub enum Message {
|
||||
UpdateAmt(IngredientKey, String),
|
||||
SetUserData(UserData),
|
||||
UnsetUserData,
|
||||
SaveState,
|
||||
}
|
||||
|
||||
// TODO(jwall): Add HttpStore here and do the proper things for each event.
|
||||
pub struct StateMachine();
|
||||
pub struct StateMachine(HttpStore);
|
||||
|
||||
impl MessageMapper<Message, AppState> for StateMachine {
|
||||
#[instrument(skip_all, fields(?msg))]
|
||||
fn map(&self, msg: Message, original: &ReadSignal<AppState>) -> AppState {
|
||||
let mut original_copy = original.get().as_ref().clone();
|
||||
match msg {
|
||||
@ -126,6 +130,15 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
||||
Message::UnsetUserData => {
|
||||
original_copy.auth = None;
|
||||
}
|
||||
Message::SaveState => {
|
||||
let store = self.0.clone();
|
||||
let original_copy = original_copy.clone();
|
||||
spawn_local(async move {
|
||||
if let Err(e) = store.save_app_state(original_copy).await {
|
||||
error!(err=?e, "Error saving app state")
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
original_copy
|
||||
}
|
||||
@ -133,9 +146,14 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
||||
|
||||
pub type StateHandler<'ctx> = &'ctx Handler<'ctx, StateMachine, AppState, Message>;
|
||||
|
||||
pub fn get_state_handler<'ctx>(cx: Scope<'ctx>, initial: AppState) -> StateHandler<'ctx> {
|
||||
Handler::new(cx, initial, StateMachine())
|
||||
pub fn get_state_handler<'ctx>(
|
||||
cx: Scope<'ctx>,
|
||||
initial: AppState,
|
||||
store: HttpStore,
|
||||
) -> StateHandler<'ctx> {
|
||||
Handler::new(cx, initial, StateMachine(store))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct State {
|
||||
pub recipe_counts: RcSignal<BTreeMap<String, RcSignal<usize>>>,
|
||||
|
@ -13,21 +13,20 @@
|
||||
// limitations under the License.
|
||||
use recipes::Recipe;
|
||||
use sycamore::{futures::spawn_local_scoped, prelude::*};
|
||||
use tracing::{error, instrument};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::app_state::{Message, StateHandler};
|
||||
use crate::components::recipe_selection::*;
|
||||
use crate::{api::*, app_state};
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[instrument]
|
||||
pub fn RecipePlan<G: Html>(cx: Scope) -> View<G> {
|
||||
let rows = create_memo(cx, move || {
|
||||
let state = app_state::State::get_from_context(cx);
|
||||
#[instrument(skip_all)]
|
||||
pub fn RecipePlan<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
|
||||
let rows = sh.get_selector(cx, move |state| {
|
||||
let mut rows = Vec::new();
|
||||
for row in state
|
||||
.recipes
|
||||
.get()
|
||||
.as_ref()
|
||||
.recipes
|
||||
.iter()
|
||||
.map(|(k, v)| create_signal(cx, (k.clone(), v.clone())))
|
||||
.collect::<Vec<&Signal<(String, Recipe)>>>()
|
||||
@ -39,27 +38,15 @@ pub fn RecipePlan<G: Html>(cx: Scope) -> View<G> {
|
||||
});
|
||||
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();
|
||||
let store = HttpStore::get_from_context(cx);
|
||||
let state = app_state::State::get_from_context(cx);
|
||||
spawn_local_scoped(cx, {
|
||||
async move {
|
||||
if let Err(err) = init_page_state(store.as_ref(), state.as_ref()).await {
|
||||
error!(?err);
|
||||
};
|
||||
}
|
||||
});
|
||||
spawn_local_scoped(cx, async move { init_app_state(store.as_ref(), sh).await });
|
||||
});
|
||||
create_effect(cx, move || {
|
||||
save_click.track();
|
||||
let store = HttpStore::get_from_context(cx);
|
||||
let state = app_state::State::get_from_context(cx);
|
||||
spawn_local_scoped(cx, {
|
||||
async move {
|
||||
store.save_state(state).await.expect("Failed to save plan");
|
||||
}
|
||||
})
|
||||
sh.dispatch(Message::SaveState);
|
||||
});
|
||||
view! {cx,
|
||||
table(class="recipe_selector no-print") {
|
||||
|
@ -21,6 +21,6 @@ pub fn PlanPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<
|
||||
view! {cx,
|
||||
PlanningPage(
|
||||
selected=Some("Plan".to_owned()),
|
||||
) { RecipePlan() }
|
||||
) { RecipePlan(sh) }
|
||||
}
|
||||
}
|
||||
|
@ -21,15 +21,18 @@ use crate::{api, routing::Handler as RouteHandler};
|
||||
#[component]
|
||||
pub fn UI<G: Html>(cx: Scope) -> View<G> {
|
||||
api::HttpStore::provide_context(cx, "/api".to_owned());
|
||||
// FIXME(jwall): We shouldn't need to get the store from a context anymore.
|
||||
let store = api::HttpStore::get_from_context(cx).as_ref().clone();
|
||||
info!("Starting UI");
|
||||
let app_state = crate::app_state::AppState::new();
|
||||
let handler = crate::app_state::get_state_handler(cx, app_state);
|
||||
let handler = 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, {
|
||||
let store = api::HttpStore::get_from_context(cx);
|
||||
async move {
|
||||
api::init_app_state(cx, handler).await;
|
||||
api::init_app_state(store.as_ref(), handler).await;
|
||||
// TODO(jwall): This needs to be moved into the RouteHandler
|
||||
view.set(view! { cx,
|
||||
div(class="app") {
|
||||
|
Loading…
x
Reference in New Issue
Block a user