use crate::actions::Action;
use crate::components::id::PageId;
use crate::components::PageComponent;
use crate::routing::{ClientPageRoute, Route};
use serde::de::DeserializeOwned;
use std::{collections::HashMap, marker::PhantomData};
use yew::html::ChildrenProps;
use yew::BaseComponent;
#[allow(dead_code)]
pub(crate) struct InsertInRootRoute;
#[derive(Default)]
pub struct AppNested<BASE> {
#[cfg(not(feature = "client"))]
pub(crate) server_router: HashMap<String, Route>,
pub(crate) page_router: HashMap<String, ClientPageRoute>,
_marker: PhantomData<BASE>,
}
impl<BASE> AppNested<BASE>
where
BASE: BaseComponent<Properties = ChildrenProps> + 'static,
{
pub fn new() -> Self {
AppNested {
#[cfg(not(feature = "client"))]
server_router: HashMap::new(),
page_router: HashMap::new(),
_marker: PhantomData,
}
}
#[cfg_attr(feature = "client", allow(unused_mut, unused_variables))]
pub fn route(mut self, route: Route) -> Self {
#[cfg(not(feature = "client"))]
{
let path = route.path().to_owned(); self.server_router.insert(path, route);
}
self
}
#[cfg_attr(feature = "client", allow(unused_variables))]
pub fn page<COMP>(mut self) -> Self
where
COMP: PageComponent,
COMP::Properties: DeserializeOwned,
{
let route = COMP::route().unwrap_or_else(|| {
panic!(
"`{}` is not declaring a route",
std::any::type_name::<COMP>()
)
});
self.add_component::<COMP>(route);
#[cfg(not(feature = "client"))]
{
use crate::app::{RenderContext, RenderLayout, RequestContext};
use crate::routing::HandlerKind;
let mut route = Route::get(route, move |ctx: RequestContext, body| {
let head = super::page_head::PageHead::new();
let render_layout = ctx.app_data::<RenderLayout>().cloned().unwrap();
let render_ctx = RenderContext::new(ctx, head, render_layout);
COMP::render::<BASE>(render_ctx, body)
});
route.extensions_mut().insert(HandlerKind::Page);
self.route(route)
}
#[cfg(feature = "client")]
self
}
pub fn action<A>(self) -> Self
where
A: Action,
{
#[cfg(not(feature = "client"))]
{
use crate::app::RequestContext;
use crate::web::{Body, IntoJsonResponse, Response};
use crate::routing::HandlerKind;
let route = A::route().to_string();
let method = A::method();
let mut route = Route::new(&route, method, |ctx: RequestContext, body: Body| async move {
let output = crate::try_response!(A::call(ctx, body).await);
let json_res = crate::try_response!(output.into_json_response());
let (parts, body) = json_res.into_parts();
let bytes = crate::try_response!(serde_json::to_vec(&body));
let body = Body::from(bytes);
Response::from_parts(parts, body)
});
route.extensions_mut().insert(InsertInRootRoute);
route.extensions_mut().insert(HandlerKind::Action);
self.route(route)
}
#[cfg(feature = "client")]
self
}
fn add_component<COMP>(&mut self, path: &str)
where
COMP: PageComponent,
COMP::Properties: DeserializeOwned,
{
use crate::components::AnyComponent;
log::debug!(
"Registering component `{}` on path: {path}",
std::any::type_name::<COMP>()
);
self.page_router.insert(
path.to_owned(),
ClientPageRoute {
path: path.to_owned(),
page_id: PageId::of::<COMP>(),
component: AnyComponent::<serde_json::Value>::new(|props_json| {
let props = serde_json::from_value(props_json).unwrap_or_else(|err| {
panic!(
"Failed to deserialize `{}` component props. {err}",
std::any::type_name::<COMP>()
)
});
yew::html! {
<COMP ..props/>
}
}),
},
);
}
}
pub fn nested<BASE>() -> AppNested<BASE>
where
BASE: BaseComponent<Properties = ChildrenProps> + 'static,
{
AppNested::new()
}