use std::borrow::Cow;
use std::rc::Rc;
use gloo::history::query::Raw;
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 basename = basename.map(|m| strip_slash_suffix(&m).to_owned());
let navigator = Navigator::new(history.clone(), basename.clone());
let old_basename = use_mut_ref(|| Option::<String>::None);
let mut old_basename = old_basename.borrow_mut();
if basename != *old_basename {
let old_navigator = Navigator::new(
history.clone(),
old_basename.as_ref().or(basename.as_ref()).cloned(),
);
*old_basename = basename.clone();
let location = history.location();
let stripped = old_navigator.strip_basename(Cow::from(location.path()));
let prefixed = navigator.prefix_basename(&stripped);
if prefixed != location.path() {
history
.replace_with_query(prefixed, Raw(location.query_str()))
.unwrap_or_else(|never| match never {});
} else {
}
}
let navi_ctx = NavigatorContext { navigator };
let loc_ctx = use_reducer(|| LocationContext {
location: history.location(),
ctr: 0,
});
{
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>
}
}