hashira/app/
app_nested.rs1use crate::actions::Action;
2use crate::components::id::PageId;
3use crate::components::PageComponent;
4use crate::routing::{ClientPageRoute, Route};
5use serde::de::DeserializeOwned;
6use std::{collections::HashMap, marker::PhantomData};
7use yew::html::ChildrenProps;
8use yew::BaseComponent;
9
10#[allow(dead_code)]
15pub(crate) struct InsertInRootRoute;
16
17#[derive(Default)]
19pub struct AppNested<BASE> {
20 #[cfg(not(feature = "client"))]
22 pub(crate) server_router: HashMap<String, Route>,
23
24 pub(crate) page_router: HashMap<String, ClientPageRoute>,
26
27 _marker: PhantomData<BASE>,
29}
30
31impl<BASE> AppNested<BASE>
32where
33 BASE: BaseComponent<Properties = ChildrenProps> + 'static,
34{
35 pub fn new() -> Self {
37 AppNested {
38 #[cfg(not(feature = "client"))]
39 server_router: HashMap::new(),
40 page_router: HashMap::new(),
41 _marker: PhantomData,
42 }
43 }
44
45 #[cfg_attr(feature = "client", allow(unused_mut, unused_variables))]
47 pub fn route(mut self, route: Route) -> Self {
48 #[cfg(not(feature = "client"))]
49 {
50 let path = route.path().to_owned(); self.server_router.insert(path, route);
52 }
53
54 self
55 }
56
57 #[cfg_attr(feature = "client", allow(unused_variables))]
59 pub fn page<COMP>(mut self) -> Self
60 where
61 COMP: PageComponent,
62 COMP::Properties: DeserializeOwned,
63 {
64 let route = COMP::route().unwrap_or_else(|| {
66 panic!(
67 "`{}` is not declaring a route",
68 std::any::type_name::<COMP>()
69 )
70 });
71
72 self.add_component::<COMP>(route);
73
74 #[cfg(not(feature = "client"))]
75 {
76 use crate::app::{RenderContext, RenderLayout, RequestContext};
77 use crate::routing::HandlerKind;
78
79 let mut route = Route::get(route, move |ctx: RequestContext, body| {
80 let head = super::page_head::PageHead::new();
81 let render_layout = ctx.app_data::<RenderLayout>().cloned().unwrap();
82 let render_ctx = RenderContext::new(ctx, head, render_layout);
83
84 COMP::render::<BASE>(render_ctx, body)
86 });
87
88 route.extensions_mut().insert(HandlerKind::Page);
89 self.route(route)
90 }
91
92 #[cfg(feature = "client")]
94 self
95 }
96
97 pub fn action<A>(self) -> Self
99 where
100 A: Action,
101 {
102 #[cfg(not(feature = "client"))]
103 {
104 use crate::app::RequestContext;
105 use crate::web::{Body, IntoJsonResponse, Response};
106 use crate::routing::HandlerKind;
107
108 let route = A::route().to_string();
109 let method = A::method();
110 let mut route = Route::new(&route, method, |ctx: RequestContext, body: Body| async move {
111 let output = crate::try_response!(A::call(ctx, body).await);
112 let json_res = crate::try_response!(output.into_json_response());
113 let (parts, body) = json_res.into_parts();
114 let bytes = crate::try_response!(serde_json::to_vec(&body));
115 let body = Body::from(bytes);
116 Response::from_parts(parts, body)
117 });
118
119 route.extensions_mut().insert(InsertInRootRoute);
120 route.extensions_mut().insert(HandlerKind::Action);
121 self.route(route)
122 }
123
124 #[cfg(feature = "client")]
125 self
126 }
127
128 fn add_component<COMP>(&mut self, path: &str)
129 where
130 COMP: PageComponent,
131 COMP::Properties: DeserializeOwned,
132 {
133 use crate::components::AnyComponent;
134
135 log::debug!(
136 "Registering component `{}` on path: {path}",
137 std::any::type_name::<COMP>()
138 );
139
140 self.page_router.insert(
141 path.to_owned(),
142 ClientPageRoute {
143 path: path.to_owned(),
144 page_id: PageId::of::<COMP>(),
145 component: AnyComponent::<serde_json::Value>::new(|props_json| {
146 let props = serde_json::from_value(props_json).unwrap_or_else(|err| {
147 panic!(
148 "Failed to deserialize `{}` component props. {err}",
149 std::any::type_name::<COMP>()
150 )
151 });
152
153 yew::html! {
154 <COMP ..props/>
155 }
156 }),
157 },
158 );
159 }
160}
161
162pub fn nested<BASE>() -> AppNested<BASE>
164where
165 BASE: BaseComponent<Properties = ChildrenProps> + 'static,
166{
167 AppNested::new()
168}