mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -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>(
|
pub async fn init_app_state<'ctx>(
|
||||||
cx: Scope<'ctx>,
|
store: &HttpStore,
|
||||||
h: &'ctx Handler<'ctx, StateMachine, AppState, Message>,
|
h: &'ctx Handler<'ctx, StateMachine, AppState, Message>,
|
||||||
) {
|
) {
|
||||||
info!("Synchronizing Recipes");
|
info!("Synchronizing Recipes");
|
||||||
let store = HttpStore::get_from_context(cx);
|
|
||||||
// TODO(jwall): Make our caching logic using storage more robust.
|
// TODO(jwall): Make our caching logic using storage more robust.
|
||||||
let recipe_entries = match store.get_recipes().await {
|
let recipe_entries = match store.get_recipes().await {
|
||||||
Ok(recipe_entries) => {
|
Ok(recipe_entries) => {
|
||||||
@ -270,7 +269,7 @@ pub struct HttpStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl HttpStore {
|
impl HttpStore {
|
||||||
fn new(root: String) -> Self {
|
pub fn new(root: String) -> Self {
|
||||||
Self { root }
|
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]
|
#[instrument]
|
||||||
pub async fn save_state(&self, state: std::rc::Rc<app_state::State>) -> Result<(), Error> {
|
pub async fn save_state(&self, state: std::rc::Rc<app_state::State>) -> Result<(), Error> {
|
||||||
let mut plan = Vec::new();
|
let mut plan = Vec::new();
|
||||||
|
@ -13,14 +13,16 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
use sycamore::prelude::*;
|
use sycamore::{futures::spawn_local, prelude::*};
|
||||||
use tracing::{debug, instrument, warn};
|
use tracing::{debug, error, instrument, warn};
|
||||||
|
|
||||||
use client_api::UserData;
|
use client_api::UserData;
|
||||||
use recipes::{Ingredient, IngredientAccumulator, IngredientKey, Recipe};
|
use recipes::{Ingredient, IngredientAccumulator, IngredientKey, Recipe};
|
||||||
|
|
||||||
use sycamore_state::{Handler, MessageMapper};
|
use sycamore_state::{Handler, MessageMapper};
|
||||||
|
|
||||||
|
use crate::api::HttpStore;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub recipe_counts: BTreeMap<String, usize>,
|
pub recipe_counts: BTreeMap<String, usize>,
|
||||||
@ -48,6 +50,7 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
InitRecipeCounts(BTreeMap<String, usize>),
|
InitRecipeCounts(BTreeMap<String, usize>),
|
||||||
UpdateRecipeCount(String, usize),
|
UpdateRecipeCount(String, usize),
|
||||||
@ -66,12 +69,13 @@ pub enum Message {
|
|||||||
UpdateAmt(IngredientKey, String),
|
UpdateAmt(IngredientKey, String),
|
||||||
SetUserData(UserData),
|
SetUserData(UserData),
|
||||||
UnsetUserData,
|
UnsetUserData,
|
||||||
|
SaveState,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(jwall): Add HttpStore here and do the proper things for each event.
|
pub struct StateMachine(HttpStore);
|
||||||
pub struct StateMachine();
|
|
||||||
|
|
||||||
impl MessageMapper<Message, AppState> for StateMachine {
|
impl MessageMapper<Message, AppState> for StateMachine {
|
||||||
|
#[instrument(skip_all, fields(?msg))]
|
||||||
fn map(&self, msg: Message, original: &ReadSignal<AppState>) -> AppState {
|
fn map(&self, msg: Message, original: &ReadSignal<AppState>) -> AppState {
|
||||||
let mut original_copy = original.get().as_ref().clone();
|
let mut original_copy = original.get().as_ref().clone();
|
||||||
match msg {
|
match msg {
|
||||||
@ -126,6 +130,15 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
|||||||
Message::UnsetUserData => {
|
Message::UnsetUserData => {
|
||||||
original_copy.auth = None;
|
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
|
original_copy
|
||||||
}
|
}
|
||||||
@ -133,9 +146,14 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
|||||||
|
|
||||||
pub type StateHandler<'ctx> = &'ctx Handler<'ctx, StateMachine, AppState, Message>;
|
pub type StateHandler<'ctx> = &'ctx Handler<'ctx, StateMachine, AppState, Message>;
|
||||||
|
|
||||||
pub fn get_state_handler<'ctx>(cx: Scope<'ctx>, initial: AppState) -> StateHandler<'ctx> {
|
pub fn get_state_handler<'ctx>(
|
||||||
Handler::new(cx, initial, StateMachine())
|
cx: Scope<'ctx>,
|
||||||
|
initial: AppState,
|
||||||
|
store: HttpStore,
|
||||||
|
) -> StateHandler<'ctx> {
|
||||||
|
Handler::new(cx, initial, StateMachine(store))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub recipe_counts: RcSignal<BTreeMap<String, RcSignal<usize>>>,
|
pub recipe_counts: RcSignal<BTreeMap<String, RcSignal<usize>>>,
|
||||||
|
@ -13,21 +13,20 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
use recipes::Recipe;
|
use recipes::Recipe;
|
||||||
use sycamore::{futures::spawn_local_scoped, prelude::*};
|
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::components::recipe_selection::*;
|
||||||
use crate::{api::*, app_state};
|
use crate::{api::*, app_state};
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[instrument]
|
#[instrument(skip_all)]
|
||||||
pub fn RecipePlan<G: Html>(cx: Scope) -> View<G> {
|
pub fn RecipePlan<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
|
||||||
let rows = create_memo(cx, move || {
|
let rows = sh.get_selector(cx, move |state| {
|
||||||
let state = app_state::State::get_from_context(cx);
|
|
||||||
let mut rows = Vec::new();
|
let mut rows = Vec::new();
|
||||||
for row in state
|
for row in state
|
||||||
.recipes
|
|
||||||
.get()
|
.get()
|
||||||
.as_ref()
|
.recipes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| create_signal(cx, (k.clone(), v.clone())))
|
.map(|(k, v)| create_signal(cx, (k.clone(), v.clone())))
|
||||||
.collect::<Vec<&Signal<(String, Recipe)>>>()
|
.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 refresh_click = create_signal(cx, false);
|
||||||
let save_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 || {
|
create_effect(cx, move || {
|
||||||
refresh_click.track();
|
refresh_click.track();
|
||||||
let store = HttpStore::get_from_context(cx);
|
let store = HttpStore::get_from_context(cx);
|
||||||
let state = app_state::State::get_from_context(cx);
|
spawn_local_scoped(cx, async move { init_app_state(store.as_ref(), sh).await });
|
||||||
spawn_local_scoped(cx, {
|
|
||||||
async move {
|
|
||||||
if let Err(err) = init_page_state(store.as_ref(), state.as_ref()).await {
|
|
||||||
error!(?err);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
create_effect(cx, move || {
|
create_effect(cx, move || {
|
||||||
save_click.track();
|
save_click.track();
|
||||||
let store = HttpStore::get_from_context(cx);
|
sh.dispatch(Message::SaveState);
|
||||||
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");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
view! {cx,
|
view! {cx,
|
||||||
table(class="recipe_selector no-print") {
|
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,
|
view! {cx,
|
||||||
PlanningPage(
|
PlanningPage(
|
||||||
selected=Some("Plan".to_owned()),
|
selected=Some("Plan".to_owned()),
|
||||||
) { RecipePlan() }
|
) { RecipePlan(sh) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,15 +21,18 @@ use crate::{api, routing::Handler as RouteHandler};
|
|||||||
#[component]
|
#[component]
|
||||||
pub fn UI<G: Html>(cx: Scope) -> View<G> {
|
pub fn UI<G: Html>(cx: Scope) -> View<G> {
|
||||||
api::HttpStore::provide_context(cx, "/api".to_owned());
|
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");
|
info!("Starting UI");
|
||||||
let app_state = crate::app_state::AppState::new();
|
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());
|
let view = create_signal(cx, View::empty());
|
||||||
// FIXME(jwall): We need a way to trigger refreshes when required. Turn this
|
// 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.
|
// into a create_effect with a refresh signal stored as a context.
|
||||||
spawn_local_scoped(cx, {
|
spawn_local_scoped(cx, {
|
||||||
|
let store = api::HttpStore::get_from_context(cx);
|
||||||
async move {
|
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
|
// TODO(jwall): This needs to be moved into the RouteHandler
|
||||||
view.set(view! { cx,
|
view.set(view! { cx,
|
||||||
div(class="app") {
|
div(class="app") {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user