use std::rc::Rc;
use yew::prelude::*;
use yew::virtual_dom::AttrValue;
use crate::history::{AnyHistory, BrowserHistory, HashHistory, History, Location};
use crate::navigator::Navigator;
use crate::utils::{base_url, strip_slash_suffix};
#[derive(Properties, PartialEq, Clone)]
pub struct RouterProps {
#[prop_or_default]
pub children: Html,
pub history: AnyHistory,
#[prop_or_default]
pub basename: Option<AttrValue>,
}
#[derive(Clone)]
pub(crate) struct LocationContext {
location: Location,
ctr: u32,
}
impl LocationContext {
pub fn location(&self) -> Location {
self.location.clone()
}
}
impl PartialEq for LocationContext {
fn eq(&self, rhs: &Self) -> bool {
self.ctr == rhs.ctr
}
}
impl Reducible for LocationContext {
type Action = Location;
fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
Self {
location: action,
ctr: self.ctr + 1,
}
.into()
}
}
#[derive(Clone, PartialEq)]
pub(crate) struct NavigatorContext {
navigator: Navigator,
}
impl NavigatorContext {
pub fn navigator(&self) -> Navigator {
self.navigator.clone()
}
}
#[function_component(BaseRouter)]
fn base_router(props: &RouterProps) -> Html {
let RouterProps {
history,
children,
basename,
} = props.clone();
let loc_ctx = use_reducer(|| LocationContext {
location: history.location(),
ctr: 0,
});
let basename = basename.map(|m| strip_slash_suffix(&m).to_string());
let navi_ctx = NavigatorContext {
navigator: Navigator::new(history.clone(), basename),
};
{
let loc_ctx_dispatcher = loc_ctx.dispatcher();
use_effect_with(history, move |history| {
let history = history.clone();
loc_ctx_dispatcher.dispatch(history.location());
let history_cb = {
let history = history.clone();
move || loc_ctx_dispatcher.dispatch(history.location())
};
let listener = history.listen(history_cb);
move || {
std::mem::drop(listener);
}
});
}
html! {
<ContextProvider<NavigatorContext> context={navi_ctx}>
<ContextProvider<LocationContext> context={(*loc_ctx).clone()}>
{children}
</ContextProvider<LocationContext>>
</ContextProvider<NavigatorContext>>
}
}
#[function_component(Router)]
pub fn router(props: &RouterProps) -> Html {
html! {
<BaseRouter ..{props.clone()} />
}
}
#[derive(Properties, PartialEq, Clone)]
pub struct ConcreteRouterProps {
pub children: Html,
#[prop_or_default]
pub basename: Option<AttrValue>,
}
#[function_component(BrowserRouter)]
pub fn browser_router(props: &ConcreteRouterProps) -> Html {
let ConcreteRouterProps { children, basename } = props.clone();
let history = use_state(|| AnyHistory::from(BrowserHistory::new()));
let basename = basename.map(|m| m.to_string()).or_else(base_url);
html! {
<BaseRouter history={(*history).clone()} {basename}>
{children}
</BaseRouter>
}
}
#[function_component(HashRouter)]
pub fn hash_router(props: &ConcreteRouterProps) -> Html {
let ConcreteRouterProps { children, basename } = props.clone();
let history = use_state(|| AnyHistory::from(HashHistory::new()));
html! {
<BaseRouter history={(*history).clone()} {basename}>
{children}
</BaseRouter>
}
}