mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
Implement save categories functionality
This commit is contained in:
parent
3094dee9f7
commit
481e44911f
68
Cargo.lock
generated
68
Cargo.lock
generated
@ -146,24 +146,6 @@ dependencies = [
|
||||
"event-listener",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-process"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02111fd8655a613c25069ea89fc8d9bb89331fa77486eb3bc059ee757cfa481c"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"autocfg",
|
||||
"blocking",
|
||||
"cfg-if 1.0.0",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"signal-hook",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-session"
|
||||
version = "3.0.0"
|
||||
@ -195,7 +177,6 @@ dependencies = [
|
||||
"async-global-executor",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-process",
|
||||
"crossbeam-utils",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@ -263,9 +244,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.5.15"
|
||||
version = "0.5.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9de18bc5f2e9df8f52da03856bf40e29b747de5a84e43aefff90e3dc4a21529b"
|
||||
checksum = "c9e3356844c4d6a6d6467b8da2cffb4a2820be256f50a3a386c9d152bab31043"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
@ -307,9 +288,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.2.7"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4f44a0e6200e9d11a1cdc989e4b358f6e3d354fbf48478f345a17f4e43f8635"
|
||||
checksum = "d9f0c0a60006f2a293d82d571f635042a72edf927539b7685bd62d361963839b"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
@ -317,6 +298,8 @@ dependencies = [
|
||||
"http",
|
||||
"http-body",
|
||||
"mime",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1894,25 +1877,6 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.7"
|
||||
@ -1955,9 +1919,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlformat"
|
||||
version = "0.1.8"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4"
|
||||
checksum = "f87e292b4291f154971a43c3774364e2cbcaec599d3f5bf6fa9d122885dbc38a"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"nom",
|
||||
@ -1966,9 +1930,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "788841def501aabde58d3666fcea11351ec3962e6ea75dbcd05c84a71d68bcd1"
|
||||
version = "0.6.2"
|
||||
dependencies = [
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
@ -1976,9 +1938,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-core"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c21d3b5e7cadfe9ba7cdc1295f72cc556c750b4419c27c219c0693198901f8e"
|
||||
version = "0.6.2"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"atoi",
|
||||
@ -2022,9 +1982,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4adfd2df3557bddd3b91377fc7893e8fa899e9b4061737cbade4e1bb85f1b45c"
|
||||
version = "0.6.2"
|
||||
dependencies = [
|
||||
"dotenvy",
|
||||
"either",
|
||||
@ -2044,9 +2002,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-rt"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7be52fc7c96c136cedea840ed54f7d446ff31ad670c9dea95ebcb998530971a3"
|
||||
version = "0.6.2"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"futures-rustls",
|
||||
|
@ -41,7 +41,7 @@ version = "1.0.144"
|
||||
features = ["serde", "v4"]
|
||||
|
||||
[dependencies.axum]
|
||||
version = "0.5.15"
|
||||
version = "0.5.16"
|
||||
features = ["headers", "http2"]
|
||||
|
||||
[dependencies.clap]
|
||||
@ -49,9 +49,10 @@ version = "3.2.16"
|
||||
features = [ "cargo" ]
|
||||
|
||||
[dependencies.async-std]
|
||||
version = "1.10.0"
|
||||
version = "1.12.0"
|
||||
features = ["tokio1"]
|
||||
|
||||
[dependencies.sqlx]
|
||||
version = "0.6.1"
|
||||
path = "../../sqlx"
|
||||
#version = "0.6.2"
|
||||
features = ["sqlite", "runtime-async-std-rustls", "offline"]
|
@ -70,7 +70,7 @@ async fn ui_static_assets(Path(path): Path<String>) -> impl IntoResponse {
|
||||
|
||||
let mut path = path.trim_start_matches("/");
|
||||
path = match path {
|
||||
"" | "inventory" | "plan" | "cook" | "login" => "index.html",
|
||||
"" | "inventory" | "plan" | "cook" | "categories" | "login" => "index.html",
|
||||
_ => {
|
||||
if path.starts_with("recipe") {
|
||||
"index.html"
|
||||
|
@ -18,6 +18,7 @@ pub enum AppRoutes {
|
||||
Inventory,
|
||||
Cook,
|
||||
Recipe(String),
|
||||
Categories,
|
||||
Login,
|
||||
Error(String),
|
||||
NotFound,
|
||||
|
107
web/src/components/categories.rs
Normal file
107
web/src/components/categories.rs
Normal file
@ -0,0 +1,107 @@
|
||||
// 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 serde_json::{from_str, to_string};
|
||||
use sycamore::{futures::spawn_local_in_scope, prelude::*};
|
||||
use tracing::{debug, error, instrument};
|
||||
use web_sys::HtmlDialogElement;
|
||||
|
||||
use recipes::parse;
|
||||
|
||||
use crate::{js_lib::get_element_by_id, service::get_appservice_from_context};
|
||||
|
||||
fn get_error_dialog() -> HtmlDialogElement {
|
||||
get_element_by_id::<HtmlDialogElement>("error-dialog")
|
||||
.expect("error-dialog isn't an html dialog element!")
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn check_category_text_parses(unparsed: &str, error_text: Signal<String>) -> bool {
|
||||
let el = get_error_dialog();
|
||||
if let Err(e) = parse::as_categories(unparsed) {
|
||||
error!(?e, "Error parsing categories");
|
||||
error_text.set(e);
|
||||
el.show();
|
||||
false
|
||||
} else {
|
||||
el.close();
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
#[component(Categories<G>)]
|
||||
pub fn categories() -> View<G> {
|
||||
let app_service = get_appservice_from_context();
|
||||
let save_signal = Signal::new(());
|
||||
let error_text = Signal::new(String::new());
|
||||
let category_text = Signal::new(
|
||||
match app_service
|
||||
.get_category_text()
|
||||
.expect("Failed to get categories.")
|
||||
{
|
||||
Some(js) => from_str::<String>(&js)
|
||||
.map_err(|e| format!("{}", e))
|
||||
.expect("Failed to parse categories as json"),
|
||||
None => String::new(),
|
||||
}, //.unwrap_or_else(|| String::new()),
|
||||
);
|
||||
|
||||
create_effect(
|
||||
cloned!((app_service, category_text, save_signal, error_text) => move || {
|
||||
// TODO(jwall): This is triggering on load which is not desired.
|
||||
save_signal.get();
|
||||
spawn_local_in_scope({
|
||||
cloned!((app_service, category_text, error_text) => async move {
|
||||
// TODO(jwall): Save the categories.
|
||||
if let Err(e) = app_service.save_categories(category_text.get_untracked().as_ref().clone()).await {
|
||||
error!(?e, "Failed to save categories");
|
||||
error_text.set(format!("{:?}", e));
|
||||
}
|
||||
})
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
let dialog_view = cloned!((error_text) => view! {
|
||||
dialog(id="error-dialog") {
|
||||
article{
|
||||
header {
|
||||
a(href="#", on:click=|_| {
|
||||
let el = get_error_dialog();
|
||||
el.close();
|
||||
}, class="close")
|
||||
"Invalid Categories"
|
||||
}
|
||||
p {
|
||||
(error_text.get().clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cloned!((category_text, error_text) => view! {
|
||||
(dialog_view)
|
||||
textarea(bind:value=category_text.clone(), rows=20)
|
||||
a(role="button", href="#", on:click=cloned!((category_text, error_text) => move |_| {
|
||||
check_category_text_parses(category_text.get().as_str(), error_text.clone());
|
||||
})) { "Check" } " "
|
||||
a(role="button", href="#", on:click=cloned!((category_text, error_text) => move |_| {
|
||||
// TODO(jwall): check and then save the categories.
|
||||
if check_category_text_parses(category_text.get().as_str(), error_text.clone()) {
|
||||
debug!("triggering category save");
|
||||
save_signal.trigger_subscribers();
|
||||
}
|
||||
})) { "Save" }
|
||||
})
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
// 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.
|
||||
pub mod categories;
|
||||
pub mod header;
|
||||
pub mod recipe;
|
||||
pub mod recipe_list;
|
||||
@ -19,6 +20,7 @@ pub mod recipe_selector;
|
||||
pub mod shopping_list;
|
||||
pub mod tabs;
|
||||
|
||||
pub use categories::*;
|
||||
pub use header::*;
|
||||
pub use recipe::*;
|
||||
pub use recipe_list::*;
|
||||
|
@ -50,6 +50,7 @@ fn editor(recipe: RecipeEntry) -> View<G> {
|
||||
|
||||
create_effect(
|
||||
cloned!((id, app_service, text, save_signal, error_text) => move || {
|
||||
// TODO(jwall): This is triggering on load which is not desired.
|
||||
save_signal.get();
|
||||
spawn_local_in_scope({
|
||||
cloned!((id, app_service, text, error_text) => async move {
|
||||
|
@ -16,12 +16,11 @@ use sycamore::{futures::spawn_local_in_scope, prelude::*};
|
||||
use tracing::{error, instrument};
|
||||
|
||||
use crate::components::recipe_selection::*;
|
||||
use crate::service::get_appservice_from_context;
|
||||
use crate::service::AppService;
|
||||
|
||||
#[instrument]
|
||||
#[component(RecipeSelector<G>)]
|
||||
pub fn recipe_selector() -> View<G> {
|
||||
let app_service = get_appservice_from_context();
|
||||
pub fn recipe_selector(app_service: AppService) -> View<G> {
|
||||
let rows = create_memo(cloned!(app_service => move || {
|
||||
let mut rows = Vec::new();
|
||||
for row in app_service.get_recipes().get().iter().map(|(k, v)| (k.clone(), v.clone())).collect::<Vec<(String, Signal<Recipe>)>>().chunks(4) {
|
||||
|
@ -29,6 +29,8 @@ pub fn tabbed_view(state: TabState<G>) -> View<G> {
|
||||
li { a(href="/ui/inventory", class="no-print") { "Inventory" } " > "
|
||||
}
|
||||
li { a(href="/ui/cook", class="no-print") { "Cook" }
|
||||
} " | "
|
||||
li { a(href="/ui/categories", class="no-print") { "Categories" }
|
||||
}
|
||||
}
|
||||
ul {
|
||||
|
30
web/src/pages/categories.rs
Normal file
30
web/src/pages/categories.rs
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2022 Jeremy Wall (jeremy@marzhillstudios.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 crate::components::categories::*;
|
||||
use crate::components::tabs::*;
|
||||
|
||||
use sycamore::prelude::*;
|
||||
use tracing::instrument;
|
||||
|
||||
#[instrument]
|
||||
#[component(CategoryPage<G>)]
|
||||
pub fn category_page() -> View<G> {
|
||||
view! {
|
||||
TabbedView(TabState {
|
||||
inner: view! {
|
||||
Categories()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -11,14 +11,20 @@
|
||||
// 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.
|
||||
mod categories;
|
||||
mod cook;
|
||||
mod inventory;
|
||||
mod login;
|
||||
mod plan;
|
||||
mod recipe;
|
||||
|
||||
pub use categories::*;
|
||||
pub use cook::*;
|
||||
pub use inventory::*;
|
||||
pub use login::*;
|
||||
pub use plan::*;
|
||||
pub use recipe::*;
|
||||
|
||||
pub struct PageProps {
|
||||
service: crate::service::AppService,
|
||||
}
|
||||
|
@ -15,12 +15,14 @@ use crate::components::{recipe_selector::*, tabs::*};
|
||||
|
||||
use sycamore::prelude::*;
|
||||
|
||||
use super::PageProps;
|
||||
|
||||
#[component(PlanPage<G>)]
|
||||
pub fn plan_page() -> View<G> {
|
||||
pub fn plan_page(props: PageProps) -> View<G> {
|
||||
view! {
|
||||
TabbedView(TabState {
|
||||
inner: view! {
|
||||
RecipeSelector()
|
||||
RecipeSelector(props.service.clone())
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -197,6 +197,7 @@ impl DeriveRoute for AppRoutes {
|
||||
"/ui/plan" => AppRoutes::Plan,
|
||||
"/ui/cook" => AppRoutes::Cook,
|
||||
"/ui/inventory" => AppRoutes::Inventory,
|
||||
"/ui/categories" => AppRoutes::Categories,
|
||||
h => {
|
||||
if h.starts_with("/ui/recipe/") {
|
||||
let parts: Vec<&str> = h.split("/").collect();
|
||||
|
@ -86,15 +86,18 @@ impl AppService {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_category_text(&self) -> Result<Option<String>, String> {
|
||||
let storage = self.get_storage()?.unwrap();
|
||||
storage
|
||||
.get_item("categories")
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
pub fn fetch_categories_from_storage(
|
||||
&self,
|
||||
) -> Result<Option<BTreeMap<String, String>>, String> {
|
||||
let storage = self.get_storage()?.unwrap();
|
||||
match storage
|
||||
.get_item("categories")
|
||||
.map_err(|e| format!("{:?}", e))?
|
||||
{
|
||||
match self.get_category_text()? {
|
||||
Some(s) => {
|
||||
let parsed = from_str::<String>(&s).map_err(|e| format!("{}", e))?;
|
||||
if parsed.is_empty() {
|
||||
@ -224,7 +227,7 @@ impl AppService {
|
||||
.push((i.clone(), recipes.clone()));
|
||||
}
|
||||
debug!(?self.category_map);
|
||||
// FIXM(jwall): Sort by categories and names.
|
||||
// FIXME(jwall): Sort by categories and names.
|
||||
groups
|
||||
}
|
||||
|
||||
@ -369,7 +372,7 @@ impl HttpStore {
|
||||
#[instrument(skip(categories))]
|
||||
async fn save_categories(&self, categories: String) -> Result<(), Error> {
|
||||
let mut path = self.root.clone();
|
||||
path.push_str("/recipes");
|
||||
path.push_str("/categories");
|
||||
let resp = reqwasm::http::Request::post(&path)
|
||||
.body(to_string(&categories).expect("Unable to encode categories as json"))
|
||||
.header("content-type", "application/json")
|
||||
|
@ -47,6 +47,9 @@ fn route_switch<G: Html>(route: ReadSignal<AppRoutes>) -> View<G> {
|
||||
AppRoutes::Recipe(idx) => view! {
|
||||
RecipePage(RecipePageProps { recipe: Signal::new(idx.clone()) })
|
||||
},
|
||||
AppRoutes::Categories => view ! {
|
||||
CategoryPage()
|
||||
},
|
||||
AppRoutes::NotFound => view! {
|
||||
// TODO(Create a real one)
|
||||
PlanPage()
|
||||
|
Loading…
x
Reference in New Issue
Block a user