mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
Use nested routing and expand the recipe page into two
This commit is contained in:
parent
efbd5140a8
commit
2058e047eb
@ -53,6 +53,10 @@ impl Mealplan {
|
|||||||
pub struct RecipeEntry(pub String, pub String);
|
pub struct RecipeEntry(pub String, pub String);
|
||||||
|
|
||||||
impl RecipeEntry {
|
impl RecipeEntry {
|
||||||
|
pub fn new<IS: Into<String>, TS: Into<String>>(recipe_id: IS, text: TS) -> Self {
|
||||||
|
Self(recipe_id.into(), text.into())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_recipe_id<S: Into<String>>(&mut self, id: S) {
|
pub fn set_recipe_id<S: Into<String>>(&mut self, id: S) {
|
||||||
self.0 = id.into();
|
self.0 = id.into();
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,8 @@ pub fn Header<G: Html>(cx: Scope) -> View<G> {
|
|||||||
nav(class="no-print") {
|
nav(class="no-print") {
|
||||||
h1(class="title") { "Kitchen" }
|
h1(class="title") { "Kitchen" }
|
||||||
ul {
|
ul {
|
||||||
li { a(href="/ui/plan") { "MealPlan" } }
|
li { a(href="/ui/planning/plan") { "MealPlan" } }
|
||||||
li { a(href="/ui/categories") { "Manage" } }
|
li { a(href="/ui/manage/categories") { "Manage" } }
|
||||||
li { a(href="/ui/login") { "Login" } }
|
li { a(href="/ui/login") { "Login" } }
|
||||||
li { a(href="https://github.com/zaphar/kitchen") { "Github" } }
|
li { a(href="https://github.com/zaphar/kitchen") { "Github" } }
|
||||||
}
|
}
|
||||||
|
@ -40,9 +40,28 @@ fn check_recipe_parses(text: &str, error_text: &Signal<String>) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Editor<G: Html>(cx: Scope, recipe: RecipeEntry) -> View<G> {
|
pub fn Editor<G: Html>(cx: Scope, recipe_id: String) -> View<G> {
|
||||||
let id = create_signal(cx, recipe.recipe_id().to_owned());
|
let store = crate::api::HttpStore::get_from_context(cx);
|
||||||
let text = create_signal(cx, recipe.recipe_text().to_owned());
|
let recipe: &Signal<RecipeEntry> =
|
||||||
|
create_signal(cx, RecipeEntry::new(&recipe_id, String::new()));
|
||||||
|
let text = create_signal(cx, String::new());
|
||||||
|
spawn_local_scoped(cx, {
|
||||||
|
let store = store.clone();
|
||||||
|
async move {
|
||||||
|
let entry = store
|
||||||
|
.get_recipe_text(recipe_id.as_str())
|
||||||
|
.await
|
||||||
|
.expect("Failure getting recipe");
|
||||||
|
if let Some(entry) = entry {
|
||||||
|
text.set(entry.recipe_text().to_owned());
|
||||||
|
recipe.set(entry);
|
||||||
|
} else {
|
||||||
|
// FIXME(jwall): Show error message for missing recipe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let id = create_memo(cx, || recipe.get().recipe_id().to_owned());
|
||||||
let error_text = create_signal(cx, String::new());
|
let error_text = create_signal(cx, String::new());
|
||||||
let save_signal = create_signal(cx, ());
|
let save_signal = create_signal(cx, ());
|
||||||
let dirty = create_signal(cx, false);
|
let dirty = create_signal(cx, false);
|
||||||
@ -57,6 +76,7 @@ pub fn Editor<G: Html>(cx: Scope, recipe: RecipeEntry) -> View<G> {
|
|||||||
debug!("Recipe text is changed");
|
debug!("Recipe text is changed");
|
||||||
spawn_local_scoped(cx, {
|
spawn_local_scoped(cx, {
|
||||||
let store = crate::api::HttpStore::get_from_context(cx);
|
let store = crate::api::HttpStore::get_from_context(cx);
|
||||||
|
let state = app_state::State::get_from_context(cx);
|
||||||
async move {
|
async move {
|
||||||
debug!("Attempting to save recipe");
|
debug!("Attempting to save recipe");
|
||||||
if let Err(e) = store
|
if let Err(e) = store
|
||||||
@ -69,7 +89,14 @@ pub fn Editor<G: Html>(cx: Scope, recipe: RecipeEntry) -> View<G> {
|
|||||||
error!(?e, "Failed to save recipe");
|
error!(?e, "Failed to save recipe");
|
||||||
error_text.set(format!("{:?}", e));
|
error_text.set(format!("{:?}", e));
|
||||||
} else {
|
} else {
|
||||||
|
// We also need to set recipe in our state
|
||||||
dirty.set(false);
|
dirty.set(false);
|
||||||
|
if let Ok(recipe) = recipes::parse::as_recipe(text.get_untracked().as_ref()) {
|
||||||
|
state
|
||||||
|
.recipes
|
||||||
|
.modify()
|
||||||
|
.insert(id.get_untracked().as_ref().to_owned(), recipe);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -114,91 +141,114 @@ pub fn Editor<G: Html>(cx: Scope, recipe: RecipeEntry) -> View<G> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn Steps<'ctx, G: Html>(cx: Scope<'ctx>, steps: &'ctx ReadSignal<Vec<recipes::Step>>) -> View<G> {
|
fn Steps<G: Html>(cx: Scope, steps: Vec<recipes::Step>) -> View<G> {
|
||||||
|
let step_fragments = View::new_fragment(steps.iter().map(|step| {
|
||||||
|
let mut step = step.clone();
|
||||||
|
let ingredient_fragments = View::new_fragment(step.ingredients.drain(0..).map(|i| {
|
||||||
|
view! {cx,
|
||||||
|
li {
|
||||||
|
(i.amt) " " (i.name) " " (i.form.as_ref().map(|f| format!("({})", f)).unwrap_or(String::new()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).collect());
|
||||||
|
view! {cx,
|
||||||
|
div {
|
||||||
|
h3 { "Instructions" }
|
||||||
|
ul(class="ingredients") {
|
||||||
|
(ingredient_fragments)
|
||||||
|
}
|
||||||
|
div(class="instructions") {
|
||||||
|
(step.instructions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).collect());
|
||||||
view! {cx,
|
view! {cx,
|
||||||
h2 { "Steps: " }
|
h2 { "Steps: " }
|
||||||
div(class="recipe_steps") {
|
div(class="recipe_steps") {
|
||||||
Indexed(
|
(step_fragments)
|
||||||
iterable=steps,
|
|
||||||
view = |cx, step: recipes::Step| { view! {cx,
|
|
||||||
div {
|
|
||||||
h3 { "Instructions" }
|
|
||||||
ul(class="ingredients") {
|
|
||||||
Indexed(
|
|
||||||
iterable = create_signal(cx, step.ingredients),
|
|
||||||
view = |cx, i| { view! {cx,
|
|
||||||
li {
|
|
||||||
(i.amt) " " (i.name) " " (i.form.as_ref().map(|f| format!("({})", f)).unwrap_or(String::new()))
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
div(class="instructions") {
|
|
||||||
(step.instructions)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Recipe<'ctx, G: Html>(cx: Scope<'ctx>, recipe_id: String) -> View<G> {
|
pub fn Viewer<G: Html>(cx: Scope, recipe_id: String) -> View<G> {
|
||||||
let state = app_state::State::get_from_context(cx);
|
let state = app_state::State::get_from_context(cx);
|
||||||
let store = crate::api::HttpStore::get_from_context(cx);
|
|
||||||
let view = create_signal(cx, View::empty());
|
let view = create_signal(cx, View::empty());
|
||||||
let show_edit = create_signal(cx, false);
|
|
||||||
if let Some(recipe) = state.recipes.get_untracked().get(&recipe_id) {
|
if let Some(recipe) = state.recipes.get_untracked().get(&recipe_id) {
|
||||||
// FIXME(jwall): This should be create_effect rather than create_signal
|
let title = recipe.title.clone();
|
||||||
let recipe_text: &Signal<Option<RecipeEntry>> = create_signal(cx, None);
|
let desc = recipe.desc.clone().unwrap_or_else(|| String::new());
|
||||||
spawn_local_scoped(cx, {
|
let steps = recipe.steps.clone();
|
||||||
let store = store.clone();
|
debug!("Viewing recipe.");
|
||||||
async move {
|
view.set(view! {cx,
|
||||||
let entry = store
|
div(class="recipe") {
|
||||||
.get_recipe_text(recipe_id.as_str())
|
h1(class="recipe_title") { (title) }
|
||||||
.await
|
div(class="recipe_description") {
|
||||||
.expect("Failure getting recipe");
|
(desc)
|
||||||
recipe_text.set(entry);
|
}
|
||||||
}
|
Steps(steps)
|
||||||
});
|
|
||||||
let recipe = create_signal(cx, recipe.clone());
|
|
||||||
let title = create_memo(cx, move || recipe.get().title.clone());
|
|
||||||
let desc = create_memo(cx, move || {
|
|
||||||
recipe
|
|
||||||
.clone()
|
|
||||||
.get()
|
|
||||||
.desc
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| String::new())
|
|
||||||
});
|
|
||||||
let steps = create_memo(cx, move || recipe.get().steps.clone());
|
|
||||||
create_effect(cx, move || {
|
|
||||||
debug!("Choosing edit or view for recipe.");
|
|
||||||
if *show_edit.get() {
|
|
||||||
{
|
|
||||||
debug!("Showing editor for recipe.");
|
|
||||||
view.set(view! {cx,
|
|
||||||
Editor(recipe_text.get().as_ref().clone().unwrap())
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debug!("Showing text for recipe.");
|
|
||||||
view.set(view! {cx,
|
|
||||||
div(class="recipe") {
|
|
||||||
h1(class="recipe_title") { (title.get()) }
|
|
||||||
div(class="recipe_description") {
|
|
||||||
(desc.get())
|
|
||||||
}
|
|
||||||
Steps(steps)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
view! {cx,
|
view! {cx, (view.get().as_ref()) }
|
||||||
span(role="button", on:click=move |_| { show_edit.set(true); }) { "Edit" } " "
|
|
||||||
span(role="button", on:click=move |_| { show_edit.set(false); }) { "View" }
|
|
||||||
(view.get().as_ref())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#[component]
|
||||||
|
//pub fn Recipe<'ctx, G: Html>(cx: Scope<'ctx>, recipe_id: String) -> View<G> {
|
||||||
|
// let state = app_state::State::get_from_context(cx);
|
||||||
|
// let store = crate::api::HttpStore::get_from_context(cx);
|
||||||
|
// let view = create_signal(cx, View::empty());
|
||||||
|
// let show_edit = create_signal(cx, false);
|
||||||
|
// if let Some(recipe) = state.recipes.get_untracked().get(&recipe_id) {
|
||||||
|
// // FIXME(jwall): This should be create_effect rather than create_signal
|
||||||
|
// let recipe_text: &Signal<Option<RecipeEntry>> = create_signal(cx, None);
|
||||||
|
// spawn_local_scoped(cx, {
|
||||||
|
// let store = store.clone();
|
||||||
|
// async move {
|
||||||
|
// let entry = store
|
||||||
|
// .get_recipe_text(recipe_id.as_str())
|
||||||
|
// .await
|
||||||
|
// .expect("Failure getting recipe");
|
||||||
|
// recipe_text.set(entry);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// let recipe = create_signal(cx, recipe.clone());
|
||||||
|
// let title = create_memo(cx, move || recipe.get().title.clone());
|
||||||
|
// let desc = create_memo(cx, move || {
|
||||||
|
// recipe
|
||||||
|
// .clone()
|
||||||
|
// .get()
|
||||||
|
// .desc
|
||||||
|
// .clone()
|
||||||
|
// .unwrap_or_else(|| String::new())
|
||||||
|
// });
|
||||||
|
// let steps = create_memo(cx, move || recipe.get().steps.clone());
|
||||||
|
// create_effect(cx, move || {
|
||||||
|
// debug!("Choosing edit or view for recipe.");
|
||||||
|
// if *show_edit.get() {
|
||||||
|
// {
|
||||||
|
// debug!("Showing editor for recipe.");
|
||||||
|
// view.set(view! {cx,
|
||||||
|
// Editor(recipe_text.get().as_ref().clone().unwrap())
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// debug!("Showing text for recipe.");
|
||||||
|
// view.set(view! {cx,
|
||||||
|
// div(class="recipe") {
|
||||||
|
// h1(class="recipe_title") { (title.get()) }
|
||||||
|
// div(class="recipe_description") {
|
||||||
|
// (desc.get())
|
||||||
|
// }
|
||||||
|
// Steps(steps)
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// view! {cx,
|
||||||
|
// span(role="button", on:click=move |_| { show_edit.set(true); }) { "Edit" } " "
|
||||||
|
// span(role="button", on:click=move |_| { show_edit.set(false); }) { "View" }
|
||||||
|
// (view.get().as_ref())
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
use crate::{app_state, components::Recipe};
|
use crate::{app_state, components::recipe::Viewer};
|
||||||
|
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
use tracing::{debug, instrument};
|
use tracing::{debug, instrument};
|
||||||
@ -26,10 +26,10 @@ pub fn RecipeList<G: Html>(cx: Scope) -> View<G> {
|
|||||||
div() {
|
div() {
|
||||||
Indexed(
|
Indexed(
|
||||||
iterable=menu_list,
|
iterable=menu_list,
|
||||||
view= |cx, (idx, _count)| {
|
view= |cx, (id, _count)| {
|
||||||
debug!(idx=%idx, "Rendering recipe");
|
debug!(id=%id, "Rendering recipe");
|
||||||
view ! {cx,
|
view ! {cx,
|
||||||
Recipe(idx)
|
Viewer(id)
|
||||||
hr()
|
hr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ pub fn RecipeSelection<G: Html>(cx: Scope, props: RecipeCheckBoxProps) -> View<G
|
|||||||
);
|
);
|
||||||
let title = props.title.get().clone();
|
let title = props.title.get().clone();
|
||||||
let for_id = id.clone();
|
let for_id = id.clone();
|
||||||
let href = format!("/ui/recipe/{}", id);
|
let href = format!("/ui/recipe/view/{}", id);
|
||||||
let name = format!("recipe_id:{}", id);
|
let name = format!("recipe_id:{}", id);
|
||||||
view! {cx,
|
view! {cx,
|
||||||
div() {
|
div() {
|
||||||
|
@ -18,7 +18,7 @@ use tracing::debug;
|
|||||||
pub struct TabState<'a, G: Html> {
|
pub struct TabState<'a, G: Html> {
|
||||||
pub children: Children<'a, G>,
|
pub children: Children<'a, G>,
|
||||||
pub selected: Option<String>,
|
pub selected: Option<String>,
|
||||||
tablist: Vec<(&'static str, &'static str)>,
|
tablist: Vec<(String, &'static str)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
@ -32,7 +32,8 @@ pub fn TabbedView<'a, G: Html>(cx: Scope<'a>, state: TabState<'a, G>) -> View<G>
|
|||||||
let menu = View::new_fragment(
|
let menu = View::new_fragment(
|
||||||
tablist
|
tablist
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&(href, show)| {
|
.map(|&(ref href, show)| {
|
||||||
|
let href = href.clone();
|
||||||
debug!(?selected, show, "identifying tab");
|
debug!(?selected, show, "identifying tab");
|
||||||
let class = if selected.as_ref().map_or(false, |selected| selected == show) {
|
let class = if selected.as_ref().map_or(false, |selected| selected == show) {
|
||||||
"no-print selected"
|
"no-print selected"
|
||||||
|
@ -27,9 +27,9 @@ pub struct PageState<'a, G: Html> {
|
|||||||
pub fn ManagePage<'a, G: Html>(cx: Scope<'a>, state: PageState<'a, G>) -> View<G> {
|
pub fn ManagePage<'a, G: Html>(cx: Scope<'a>, state: PageState<'a, G>) -> View<G> {
|
||||||
let PageState { children, selected } = state;
|
let PageState { children, selected } = state;
|
||||||
let children = children.call(cx);
|
let children = children.call(cx);
|
||||||
let manage_tabs: Vec<(&'static str, &'static str)> = vec![
|
let manage_tabs: Vec<(String, &'static str)> = vec![
|
||||||
("/ui/categories", "Categories"),
|
("/ui/manage/categories".to_owned(), "Categories"),
|
||||||
("/ui/new_recipe", "New Recipe"),
|
("/ui/manage/new_recipe".to_owned(), "New Recipe"),
|
||||||
];
|
];
|
||||||
|
|
||||||
view! {cx,
|
view! {cx,
|
||||||
|
@ -28,10 +28,10 @@ pub struct PageState<'a, G: Html> {
|
|||||||
pub fn PlanningPage<'a, G: Html>(cx: Scope<'a>, state: PageState<'a, G>) -> View<G> {
|
pub fn PlanningPage<'a, G: Html>(cx: Scope<'a>, state: PageState<'a, G>) -> View<G> {
|
||||||
let PageState { children, selected } = state;
|
let PageState { children, selected } = state;
|
||||||
let children = children.call(cx);
|
let children = children.call(cx);
|
||||||
let planning_tabs: Vec<(&'static str, &'static str)> = vec![
|
let planning_tabs: Vec<(String, &'static str)> = vec![
|
||||||
("/ui/plan", "Plan"),
|
("/ui/planning/plan".to_owned(), "Plan"),
|
||||||
("/ui/inventory", "Inventory"),
|
("/ui/planning/inventory".to_owned(), "Inventory"),
|
||||||
("/ui/cook", "Cook"),
|
("/ui/planning/cook".to_owned(), "Cook"),
|
||||||
];
|
];
|
||||||
|
|
||||||
view! {cx,
|
view! {cx,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Jeremy Wall (jeremy@marzhillstudios.com)
|
// Copyright 2022 Jeremy Wall (Jeremy@marzhilsltudios.com)
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -11,20 +11,20 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
use crate::components::recipe::Recipe;
|
use crate::components::recipe::Editor;
|
||||||
|
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
#[derive(Debug, Props)]
|
use super::{RecipePage, RecipePageProps};
|
||||||
pub struct RecipePageProps {
|
|
||||||
pub recipe: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
#[component()]
|
#[component()]
|
||||||
pub fn RecipePage<G: Html>(cx: Scope, props: RecipePageProps) -> View<G> {
|
pub fn RecipeEditPage<G: Html>(cx: Scope, props: RecipePageProps) -> View<G> {
|
||||||
view! {cx,
|
view! {cx,
|
||||||
Recipe(props.recipe)
|
RecipePage(
|
||||||
|
selected=Some("Edit".to_owned()),
|
||||||
|
recipe=props.recipe.clone(),
|
||||||
|
) { Editor(props.recipe) }
|
||||||
}
|
}
|
||||||
}
|
}
|
53
web/src/pages/recipe/mod.rs
Normal file
53
web/src/pages/recipe/mod.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// 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 sycamore::prelude::*;
|
||||||
|
|
||||||
|
use crate::components::tabs::*;
|
||||||
|
|
||||||
|
mod edit;
|
||||||
|
mod view;
|
||||||
|
pub use edit::*;
|
||||||
|
pub use view::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Props)]
|
||||||
|
pub struct RecipePageProps {
|
||||||
|
pub recipe: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Props)]
|
||||||
|
pub struct PageState<'a, G: Html> {
|
||||||
|
pub recipe: String,
|
||||||
|
pub children: Children<'a, G>,
|
||||||
|
pub selected: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn RecipePage<'ctx, G: Html>(cx: Scope<'ctx>, state: PageState<'ctx, G>) -> View<G> {
|
||||||
|
let PageState {
|
||||||
|
children,
|
||||||
|
selected,
|
||||||
|
recipe,
|
||||||
|
} = state;
|
||||||
|
let children = children.call(cx);
|
||||||
|
let recipe_tabs: Vec<(String, &'static str)> = vec![
|
||||||
|
(format!("/ui/recipe/view/{}", recipe), "View"),
|
||||||
|
(format!("/ui/recipe/edit/{}", recipe), "Edit"),
|
||||||
|
];
|
||||||
|
view! {cx,
|
||||||
|
TabbedView(
|
||||||
|
selected= selected,
|
||||||
|
tablist=recipe_tabs,
|
||||||
|
) { (children) }
|
||||||
|
}
|
||||||
|
}
|
30
web/src/pages/recipe/view.rs
Normal file
30
web/src/pages/recipe/view.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// 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 crate::components::recipe::Viewer;
|
||||||
|
|
||||||
|
use sycamore::prelude::*;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use super::{RecipePage, RecipePageProps};
|
||||||
|
|
||||||
|
#[instrument]
|
||||||
|
#[component()]
|
||||||
|
pub fn RecipeViewPage<G: Html>(cx: Scope, props: RecipePageProps) -> View<G> {
|
||||||
|
view! {cx,
|
||||||
|
RecipePage(
|
||||||
|
selected=Some("View".to_owned()),
|
||||||
|
recipe=props.recipe.clone(),
|
||||||
|
) { Viewer(props.recipe) }
|
||||||
|
}
|
||||||
|
}
|
@ -13,71 +13,105 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
//use sycamore_router::{HistoryIntegration, Route, Router};
|
|
||||||
use sycamore_router::{HistoryIntegration, Route, Router};
|
use sycamore_router::{HistoryIntegration, Route, Router};
|
||||||
use tracing::instrument;
|
use tracing::{debug, instrument};
|
||||||
|
|
||||||
use crate::pages::*;
|
use crate::pages::*;
|
||||||
|
|
||||||
//mod router;
|
|
||||||
//use router::{HistoryIntegration, Router};
|
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
fn route_switch<'a, G: Html>(cx: Scope<'a>, route: &'a ReadSignal<Routes>) -> View<G> {
|
fn route_switch<'a, G: Html>(cx: Scope<'a>, route: &'a ReadSignal<Routes>) -> View<G> {
|
||||||
// NOTE(jwall): This needs to not be a dynamic node. The rules around
|
// NOTE(jwall): This needs to not be a dynamic node. The rules around
|
||||||
// this are somewhat unclear and underdocumented for Sycamore. But basically
|
// this are somewhat unclear and underdocumented for Sycamore. But basically
|
||||||
// avoid conditionals in the `view!` macro calls here.
|
// avoid conditionals in the `view!` macro calls here.
|
||||||
view! {cx,
|
|
||||||
(match route.get().as_ref() {
|
let switcher = |cx: Scope, route: &Routes| {
|
||||||
Routes::Plan => view! {cx,
|
debug!(?route, "Dispatching for route");
|
||||||
|
match route {
|
||||||
|
Routes::Planning(Plan) => view! {cx,
|
||||||
PlanPage()
|
PlanPage()
|
||||||
},
|
},
|
||||||
Routes::Inventory => view! {cx,
|
Routes::Planning(Inventory) => view! {cx,
|
||||||
InventoryPage()
|
InventoryPage()
|
||||||
},
|
},
|
||||||
|
Routes::Planning(Cook) => view! {cx,
|
||||||
|
CookPage()
|
||||||
|
},
|
||||||
Routes::Login => view! {cx,
|
Routes::Login => view! {cx,
|
||||||
LoginPage()
|
LoginPage()
|
||||||
},
|
},
|
||||||
Routes::Cook => view! {cx,
|
Routes::Recipe(RecipeRoutes::View(id)) => view! {cx,
|
||||||
CookPage()
|
RecipeViewPage(recipe=id.clone())
|
||||||
},
|
},
|
||||||
Routes::Recipe(idx) => view! {cx,
|
Routes::Recipe(RecipeRoutes::Edit(id)) => view! {cx,
|
||||||
RecipePage(recipe=idx.clone())
|
RecipeEditPage(recipe=id.clone())
|
||||||
},
|
},
|
||||||
Routes::Categories => view! {cx,
|
Routes::Manage(ManageRoutes::Categories) => view! {cx,
|
||||||
CategoryPage()
|
CategoryPage()
|
||||||
},
|
},
|
||||||
Routes::NewRecipe => view! {cx,
|
Routes::Manage(ManageRoutes::NewRecipe) => view! {cx,
|
||||||
AddRecipePage()
|
AddRecipePage()
|
||||||
},
|
},
|
||||||
Routes::NotFound => view! {cx,
|
Routes::NotFound
|
||||||
|
| Routes::Manage(ManageRoutes::NotFound)
|
||||||
|
| Routes::Planning(PlanningRoutes::NotFound)
|
||||||
|
| Routes::Recipe(RecipeRoutes::NotFound) => view! {cx,
|
||||||
// TODO(Create a real one)
|
// TODO(Create a real one)
|
||||||
PlanPage()
|
PlanPage()
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
};
|
||||||
|
use PlanningRoutes::*;
|
||||||
|
view! {cx,
|
||||||
|
(switcher(cx, route.get().as_ref()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Route, Debug)]
|
#[derive(Route, Debug)]
|
||||||
pub enum Routes {
|
pub enum Routes {
|
||||||
#[to("/ui/plan")]
|
#[to("/ui/planning/<_..>")]
|
||||||
Plan,
|
Planning(PlanningRoutes),
|
||||||
#[to("/ui/inventory")]
|
#[to("/ui/recipe/<_..>")]
|
||||||
Inventory,
|
Recipe(RecipeRoutes),
|
||||||
#[to("/ui/cook")]
|
#[to("/ui/manage/<_..>")]
|
||||||
Cook,
|
Manage(ManageRoutes),
|
||||||
#[to("/ui/recipe/<id>")]
|
|
||||||
Recipe(String),
|
|
||||||
#[to("/ui/new_recipe")]
|
|
||||||
NewRecipe,
|
|
||||||
#[to("/ui/categories")]
|
|
||||||
Categories,
|
|
||||||
#[to("/ui/login")]
|
#[to("/ui/login")]
|
||||||
Login,
|
Login,
|
||||||
#[not_found]
|
#[not_found]
|
||||||
NotFound,
|
NotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Route, Debug)]
|
||||||
|
pub enum RecipeRoutes {
|
||||||
|
#[to("/edit/<id>")]
|
||||||
|
Edit(String),
|
||||||
|
#[to("/view/<id>")]
|
||||||
|
View(String),
|
||||||
|
#[not_found]
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Route, Debug)]
|
||||||
|
pub enum ManageRoutes {
|
||||||
|
#[to("/new_recipe")]
|
||||||
|
NewRecipe,
|
||||||
|
#[to("/categories")]
|
||||||
|
Categories,
|
||||||
|
#[not_found]
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Route, Debug)]
|
||||||
|
pub enum PlanningRoutes {
|
||||||
|
#[to("/plan")]
|
||||||
|
Plan,
|
||||||
|
#[to("/inventory")]
|
||||||
|
Inventory,
|
||||||
|
#[to("/cook")]
|
||||||
|
Cook,
|
||||||
|
#[not_found]
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Handler<G: Html>(cx: Scope) -> View<G> {
|
pub fn Handler<G: Html>(cx: Scope) -> View<G> {
|
||||||
view! {cx,
|
view! {cx,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user