mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
Homegrown Router
This commit is contained in:
parent
24fd30af8b
commit
5ffe339626
43
Cargo.lock
generated
43
Cargo.lock
generated
@ -787,7 +787,6 @@ dependencies = [
|
|||||||
"reqwasm",
|
"reqwasm",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sycamore",
|
"sycamore",
|
||||||
"sycamore-router",
|
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
@ -924,12 +923,6 @@ dependencies = [
|
|||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "minimal-lexical"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@ -970,17 +963,6 @@ dependencies = [
|
|||||||
"twoway",
|
"twoway",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nom"
|
|
||||||
version = "7.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
"minimal-lexical",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ntapi"
|
name = "ntapi"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@ -1424,31 +1406,6 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sycamore-router"
|
|
||||||
version = "0.7.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cace57b69d923ef7ac5a1291bee73fa62e7d75b1f3a713db70d30ab0ee032185"
|
|
||||||
dependencies = [
|
|
||||||
"sycamore",
|
|
||||||
"sycamore-router-macro",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sycamore-router-macro"
|
|
||||||
version = "0.7.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0a1f83a4862484dba897a6dc64c4a72b5c808c9af05573f7a55133b4f110ac66"
|
|
||||||
dependencies = [
|
|
||||||
"nom",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"unicode-xid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.86"
|
version = "1.0.86"
|
||||||
|
@ -17,7 +17,6 @@ recipes = {path = "../recipes" }
|
|||||||
reqwasm = "0.5.0"
|
reqwasm = "0.5.0"
|
||||||
# This makes debugging panics more tractable.
|
# This makes debugging panics more tractable.
|
||||||
console_error_panic_hook = "0.1.7"
|
console_error_panic_hook = "0.1.7"
|
||||||
sycamore-router = "0.7.1"
|
|
||||||
serde_json = "1.0.79"
|
serde_json = "1.0.79"
|
||||||
|
|
||||||
[dependencies.wasm-bindgen]
|
[dependencies.wasm-bindgen]
|
||||||
@ -26,7 +25,19 @@ version = "0.2.78"
|
|||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
features = [ "Storage", "Window" ]
|
features = [
|
||||||
|
"Event",
|
||||||
|
"EventTarget",
|
||||||
|
"History",
|
||||||
|
"HtmlAnchorElement",
|
||||||
|
"HtmlBaseElement",
|
||||||
|
"KeyboardEvent",
|
||||||
|
"Location",
|
||||||
|
"PopStateEvent",
|
||||||
|
"Url",
|
||||||
|
"Window",
|
||||||
|
"Storage"
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies.sycamore]
|
[dependencies.sycamore]
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -12,10 +12,18 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum AppRoutes {
|
pub enum AppRoutes {
|
||||||
Plan,
|
Plan,
|
||||||
Inventory,
|
Inventory,
|
||||||
Cook,
|
Cook,
|
||||||
Recipe(usize),
|
Recipe(usize),
|
||||||
|
Error(String),
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AppRoutes {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Plan
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
use crate::app_state::AppRoutes;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TabState<G: GenericNode> {
|
pub struct TabState<G: GenericNode> {
|
||||||
pub route: Signal<AppRoutes>,
|
|
||||||
pub inner: View<G>,
|
pub inner: View<G>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,17 +24,11 @@ pub fn tabbed_view(state: TabState<G>) -> View<G> {
|
|||||||
header(class="no-print") {
|
header(class="no-print") {
|
||||||
nav {
|
nav {
|
||||||
ul {
|
ul {
|
||||||
li { a(href="#", class="no-print", on:click=cloned!((state) => move |_| {
|
li { a(href="#plan", class="no-print") { "Plan" } " > "
|
||||||
state.route.set(AppRoutes::Plan);
|
|
||||||
})) { "Plan" } " > "
|
|
||||||
}
|
}
|
||||||
li { a(href="#", class="no-print", on:click=cloned!((state) => move |_| {
|
li { a(href="#inventory", class="no-print") { "Inventory" } " > "
|
||||||
state.route.set(AppRoutes::Inventory);
|
|
||||||
})) { "Inventory" } " > "
|
|
||||||
}
|
}
|
||||||
li { a(href="#", class="no-print", on:click=cloned!((state) => move |_| {
|
li { a(href="#cook", class="no-print") { "Cook" }
|
||||||
state.route.set(AppRoutes::Cook);
|
|
||||||
})) { "Cook" }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ul {
|
ul {
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
mod app_state;
|
mod app_state;
|
||||||
mod components;
|
mod components;
|
||||||
mod pages;
|
mod pages;
|
||||||
|
mod router_integration;
|
||||||
mod service;
|
mod service;
|
||||||
mod typings;
|
mod typings;
|
||||||
mod web;
|
mod web;
|
||||||
|
@ -12,20 +12,13 @@
|
|||||||
// 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_list::*, tabs::*};
|
use crate::components::{recipe_list::*, tabs::*};
|
||||||
use crate::pages::PageState;
|
|
||||||
|
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct CookPageProps {
|
|
||||||
pub page_state: PageState,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component(CookPage<G>)]
|
#[component(CookPage<G>)]
|
||||||
pub fn cook_page(props: CookPageProps) -> View<G> {
|
pub fn cook_page() -> View<G> {
|
||||||
view! {
|
view! {
|
||||||
TabbedView(TabState {
|
TabbedView(TabState {
|
||||||
route: props.page_state.route.clone(),
|
|
||||||
inner: view! {
|
inner: view! {
|
||||||
RecipeList()
|
RecipeList()
|
||||||
},
|
},
|
||||||
|
@ -12,20 +12,13 @@
|
|||||||
// 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::{shopping_list::*, tabs::*};
|
use crate::components::{shopping_list::*, tabs::*};
|
||||||
use crate::pages::PageState;
|
|
||||||
|
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct InventoryPageProps {
|
|
||||||
pub page_state: PageState,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component(InventoryPage<G>)]
|
#[component(InventoryPage<G>)]
|
||||||
pub fn inventory_page(props: InventoryPageProps) -> View<G> {
|
pub fn inventory_page() -> View<G> {
|
||||||
view! {
|
view! {
|
||||||
TabbedView(TabState {
|
TabbedView(TabState {
|
||||||
route: props.page_state.route.clone(),
|
|
||||||
inner: view! {
|
inner: view! {
|
||||||
ShoppingList()
|
ShoppingList()
|
||||||
},
|
},
|
||||||
|
@ -11,10 +11,6 @@
|
|||||||
// 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 sycamore::prelude::*;
|
|
||||||
|
|
||||||
use crate::app_state::AppRoutes;
|
|
||||||
|
|
||||||
mod cook;
|
mod cook;
|
||||||
mod inventory;
|
mod inventory;
|
||||||
mod plan;
|
mod plan;
|
||||||
@ -24,8 +20,3 @@ pub use cook::*;
|
|||||||
pub use inventory::*;
|
pub use inventory::*;
|
||||||
pub use plan::*;
|
pub use plan::*;
|
||||||
pub use recipe::*;
|
pub use recipe::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct PageState {
|
|
||||||
pub route: Signal<AppRoutes>,
|
|
||||||
}
|
|
||||||
|
@ -12,20 +12,13 @@
|
|||||||
// 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_selector::*, tabs::*};
|
use crate::components::{recipe_selector::*, tabs::*};
|
||||||
use crate::pages::PageState;
|
|
||||||
|
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct PlanPageProps {
|
|
||||||
pub page_state: PageState,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component(PlanPage<G>)]
|
#[component(PlanPage<G>)]
|
||||||
pub fn plan_page(props: PlanPageProps) -> View<G> {
|
pub fn plan_page() -> View<G> {
|
||||||
view! {
|
view! {
|
||||||
TabbedView(TabState {
|
TabbedView(TabState {
|
||||||
route: props.page_state.route.clone(),
|
|
||||||
inner: view! {
|
inner: view! {
|
||||||
RecipeSelector()
|
RecipeSelector()
|
||||||
},
|
},
|
||||||
|
@ -12,12 +12,10 @@
|
|||||||
// 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, tabs::*};
|
use crate::components::{recipe::Recipe, tabs::*};
|
||||||
use crate::pages::PageState;
|
|
||||||
|
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
pub struct RecipePageProps {
|
pub struct RecipePageProps {
|
||||||
pub page_state: PageState,
|
|
||||||
pub recipe: Signal<usize>,
|
pub recipe: Signal<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +23,6 @@ pub struct RecipePageProps {
|
|||||||
pub fn recipe_page(props: RecipePageProps) -> View<G> {
|
pub fn recipe_page(props: RecipePageProps) -> View<G> {
|
||||||
view! {
|
view! {
|
||||||
TabbedView(TabState {
|
TabbedView(TabState {
|
||||||
route: props.page_state.route.clone(),
|
|
||||||
inner: view! {
|
inner: view! {
|
||||||
Recipe(props.recipe.handle())
|
Recipe(props.recipe.handle())
|
||||||
}
|
}
|
||||||
|
191
web/src/router_integration.rs
Normal file
191
web/src/router_integration.rs
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
// Copyright 2022 zaphar
|
||||||
|
//
|
||||||
|
// 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::fmt::Debug;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use sycamore::prelude::*;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
use web_sys::Event;
|
||||||
|
use web_sys::{Element, HtmlAnchorElement};
|
||||||
|
|
||||||
|
use crate::app_state::AppRoutes;
|
||||||
|
use crate::console_debug;
|
||||||
|
use crate::console_error;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BrowserIntegration(Signal<(String, String, String)>);
|
||||||
|
|
||||||
|
impl BrowserIntegration {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let location = web_sys::window().unwrap_throw().location();
|
||||||
|
Self(Signal::new((
|
||||||
|
location.origin().unwrap_or(String::new()),
|
||||||
|
location.pathname().unwrap_or(String::new()),
|
||||||
|
location.hash().unwrap_or(String::new()),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn click_handler(&self) -> Box<dyn Fn(web_sys::Event)> {
|
||||||
|
let route_signal = self.0.clone();
|
||||||
|
Box::new(move |ev| {
|
||||||
|
if let Some(tgt) = ev
|
||||||
|
.target()
|
||||||
|
.unwrap_throw()
|
||||||
|
.unchecked_into::<Element>()
|
||||||
|
.closest("a[href]")
|
||||||
|
.unwrap_throw()
|
||||||
|
.map(|e| e.unchecked_into::<HtmlAnchorElement>())
|
||||||
|
{
|
||||||
|
console_debug!("handling navigation event.");
|
||||||
|
let location = web_sys::window().unwrap_throw().location();
|
||||||
|
|
||||||
|
if tgt.rel() == "external" {
|
||||||
|
return;
|
||||||
|
console_debug!("External Link so ignoring.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let origin = tgt.origin();
|
||||||
|
let tgt_pathname = tgt.pathname();
|
||||||
|
let hash = tgt.hash();
|
||||||
|
match (location.origin().as_ref() == Ok(&origin), location.pathname().as_ref() == Ok(&tgt_pathname), location.hash().as_ref() == Ok(&hash)) {
|
||||||
|
(true, true, true) // Same location
|
||||||
|
| (false, _, _) /* different origin */ => {
|
||||||
|
// Do nothing this is the same location as we are already at.
|
||||||
|
}
|
||||||
|
(true, _, false) // different hash
|
||||||
|
| (true, false, _) /* different path */ => {
|
||||||
|
console_debug!("different path or hash");
|
||||||
|
ev.prevent_default();
|
||||||
|
// Signal the pathname change
|
||||||
|
let path = format!("{}{}{}", &origin, &tgt_pathname, &hash);
|
||||||
|
console_debug!("new route: ({}, {}, {})", origin, tgt_pathname, hash);
|
||||||
|
console_debug!("new path: ({})", &path);
|
||||||
|
route_signal.set((origin, tgt_pathname, hash));
|
||||||
|
// Update History API.
|
||||||
|
let window = web_sys::window().unwrap_throw();
|
||||||
|
let history = window.history().unwrap_throw();
|
||||||
|
history
|
||||||
|
.push_state_with_url(&JsValue::UNDEFINED, "", Some(&path))
|
||||||
|
.unwrap_throw();
|
||||||
|
window.scroll_to_with_x_and_y(0.0, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RouterProps<R, F, G>
|
||||||
|
where
|
||||||
|
G: GenericNode,
|
||||||
|
R: DeriveRoute + NotFound + Clone + Default + Debug + 'static,
|
||||||
|
F: Fn(ReadSignal<R>) -> View<G> + 'static,
|
||||||
|
{
|
||||||
|
pub route: R,
|
||||||
|
pub route_select: F,
|
||||||
|
pub browser_integration: BrowserIntegration,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component(Router<G>)]
|
||||||
|
pub fn router<R, F>(props: RouterProps<R, F, G>) -> View<G>
|
||||||
|
where
|
||||||
|
R: DeriveRoute + NotFound + Clone + Default + Debug + 'static,
|
||||||
|
F: Fn(ReadSignal<R>) -> View<G> + 'static,
|
||||||
|
{
|
||||||
|
console_debug!("Setting up router");
|
||||||
|
let integration = Rc::new(props.browser_integration);
|
||||||
|
let route_select = Rc::new(props.route_select);
|
||||||
|
|
||||||
|
let view_signal = Signal::new(View::empty());
|
||||||
|
create_effect(
|
||||||
|
cloned!((view_signal, integration, route_select) => move || {
|
||||||
|
let path_signal = integration.0.clone();
|
||||||
|
console_debug!("new path: {:?}", path_signal.get());
|
||||||
|
let path = path_signal.clone();
|
||||||
|
let route = R::from(path.get().as_ref());
|
||||||
|
console_debug!("new route: {:?}", &route);
|
||||||
|
// TODO(jwall): this is an unnecessary use of signal.
|
||||||
|
let view = route_select.as_ref()(Signal::new(route).handle());
|
||||||
|
register_click_handler(&view, integration.clone());
|
||||||
|
view_signal.set(view);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// NOTE(jwall): This needs to be a dynamic node so Sycamore knows to rerender it
|
||||||
|
// based on the results of the effect above.
|
||||||
|
view! {
|
||||||
|
(view_signal.get().as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_click_handler<G>(view: &View<G>, integration: Rc<BrowserIntegration>)
|
||||||
|
where
|
||||||
|
G: GenericNode<EventType = Event>,
|
||||||
|
{
|
||||||
|
console_debug!("Registring click handler on node(s)");
|
||||||
|
if let Some(node) = view.as_node() {
|
||||||
|
node.event("click", integration.click_handler());
|
||||||
|
} else if let Some(frag) = view.as_fragment() {
|
||||||
|
console_debug!("Fragment? {:?}", frag);
|
||||||
|
for n in frag {
|
||||||
|
register_click_handler(n, integration.clone());
|
||||||
|
}
|
||||||
|
} else if let Some(dyn_node) = view.as_dyn() {
|
||||||
|
console_debug!("Dynamic node? {:?}", dyn_node);
|
||||||
|
} else {
|
||||||
|
console_debug!("Unknown node? {:?}", view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait NotFound {
|
||||||
|
fn not_found() -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NotFound for AppRoutes {
|
||||||
|
fn not_found() -> Self {
|
||||||
|
AppRoutes::NotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DeriveRoute {
|
||||||
|
fn from(input: &(String, String, String)) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeriveRoute for AppRoutes {
|
||||||
|
fn from(input: &(String, String, String)) -> AppRoutes {
|
||||||
|
console_debug!("routing: {input:?}");
|
||||||
|
match input.2.as_str() {
|
||||||
|
"" => AppRoutes::default(),
|
||||||
|
"#plan" => AppRoutes::Plan,
|
||||||
|
"#cook" => AppRoutes::Cook,
|
||||||
|
"#inventory" => AppRoutes::Inventory,
|
||||||
|
h => {
|
||||||
|
// TODO(jwall): Parse the recipe hash
|
||||||
|
let parts: Vec<&str> = h.splitn(2, "/").collect();
|
||||||
|
if let Some(&"#recipe") = parts.get(0) {
|
||||||
|
if let Some(&idx) = parts.get(1) {
|
||||||
|
return match usize::from_str(idx) {
|
||||||
|
Ok(idx) => AppRoutes::Recipe(idx),
|
||||||
|
Err(e) => AppRoutes::Error(format!("{:?}", e)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console_error!("Path not found: [{:?}]", input);
|
||||||
|
AppRoutes::NotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,8 +11,9 @@
|
|||||||
// 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::*, service::AppService};
|
use crate::pages::*;
|
||||||
use crate::{console_debug, console_error, console_log};
|
use crate::{app_state::*, components::*, router_integration::*, service::AppService};
|
||||||
|
use crate::{console_error, console_log};
|
||||||
|
|
||||||
use sycamore::{
|
use sycamore::{
|
||||||
context::{ContextProvider, ContextProviderProps},
|
context::{ContextProvider, ContextProviderProps},
|
||||||
@ -20,25 +21,33 @@ use sycamore::{
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::pages::*;
|
fn route_switch<G: Html>(route: ReadSignal<AppRoutes>) -> View<G> {
|
||||||
|
// NOTE(jwall): This needs to not be a dynamic node. The rules around
|
||||||
fn route_switch<G: Html>(page_state: PageState) -> View<G> {
|
// this are somewhat unclear and underdocumented for Sycamore. But basically
|
||||||
let route = page_state.route.clone();
|
// avoid conditionals in the `view!` macro calls here.
|
||||||
cloned!((page_state, route) => view! {
|
cloned!((route) => match route.get().as_ref() {
|
||||||
(match route.get().as_ref() {
|
AppRoutes::Plan => view! {
|
||||||
AppRoutes::Plan => view! {
|
PlanPage()
|
||||||
PlanPage(PlanPageProps { page_state: page_state.clone() })
|
},
|
||||||
},
|
AppRoutes::Inventory => view! {
|
||||||
AppRoutes::Inventory => view! {
|
InventoryPage()
|
||||||
InventoryPage(InventoryPageProps { page_state: page_state.clone() })
|
},
|
||||||
},
|
AppRoutes::Cook => view! {
|
||||||
AppRoutes::Cook => view! {
|
CookPage()
|
||||||
CookPage(CookPageProps { page_state: page_state.clone() })
|
},
|
||||||
},
|
AppRoutes::Recipe(idx) => view! {
|
||||||
AppRoutes::Recipe(idx) => view! {
|
RecipePage(RecipePageProps { recipe: Signal::new(*idx) })
|
||||||
RecipePage(RecipePageProps { page_state: page_state.clone(), recipe: Signal::new(*idx) })
|
},
|
||||||
|
AppRoutes::NotFound => view! {
|
||||||
|
// TODO(Create a real one)
|
||||||
|
PlanPage()
|
||||||
|
},
|
||||||
|
AppRoutes::Error(ref e) => {
|
||||||
|
let e = e.clone();
|
||||||
|
view! {
|
||||||
|
"Error: " (e)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,11 +61,8 @@ pub fn ui() -> View<G> {
|
|||||||
ContextProvider(ContextProviderProps {
|
ContextProvider(ContextProviderProps {
|
||||||
value: app_service.clone(),
|
value: app_service.clone(),
|
||||||
children: || {
|
children: || {
|
||||||
let view = Signal::new(View::empty());
|
create_effect(move || {
|
||||||
let route = Signal::new(AppRoutes::Plan);
|
spawn_local_in_scope({
|
||||||
let page_state = PageState { route: route.clone() };
|
|
||||||
create_effect(cloned!((page_state, view) => move || {
|
|
||||||
spawn_local_in_scope(cloned!((page_state, view) => {
|
|
||||||
let mut app_service = app_service.clone();
|
let mut app_service = app_service.clone();
|
||||||
async move {
|
async move {
|
||||||
match AppService::fetch_recipes_from_storage() {
|
match AppService::fetch_recipes_from_storage() {
|
||||||
@ -68,18 +74,18 @@ pub fn ui() -> View<G> {
|
|||||||
}
|
}
|
||||||
Err(msg) => console_error!("Failed to get recipes {}", msg),
|
Err(msg) => console_error!("Failed to get recipes {}", msg),
|
||||||
}
|
}
|
||||||
console_debug!("Determining route.");
|
|
||||||
view.set(route_switch(page_state.clone()));
|
|
||||||
console_debug!("Created our route view effect.");
|
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
}));
|
});
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
// NOTE(jwall): The Router component *requires* there to be exactly one node as the root of this view.
|
|
||||||
// No fragments or missing nodes allowed or it will panic at runtime.
|
|
||||||
div(class="app") {
|
div(class="app") {
|
||||||
Header()
|
Header()
|
||||||
(view.get().as_ref().clone())
|
Router(RouterProps {
|
||||||
|
route: AppRoutes::Plan,
|
||||||
|
route_select: route_switch,
|
||||||
|
browser_integration: BrowserIntegration::new(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user