mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 14:54:16 +00:00
get basic routing working
This commit is contained in:
parent
9f02cc8cc1
commit
84ebdc1b92
9 changed files with 408 additions and 85 deletions
|
@ -12,7 +12,7 @@ use leptos::{
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use routing::{
|
use routing::{
|
||||||
location::{BrowserUrl, Location},
|
location::{BrowserUrl, Location},
|
||||||
NestedRoute, Router, Routes, StaticSegment,
|
NestedRoute, ParamSegment, Router, Routes, StaticSegment,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -28,6 +28,25 @@ pub fn RouterExample() -> impl IntoView {
|
||||||
let router = Router::new(
|
let router = Router::new(
|
||||||
BrowserUrl::new().unwrap(),
|
BrowserUrl::new().unwrap(),
|
||||||
Routes::new((
|
Routes::new((
|
||||||
|
NestedRoute {
|
||||||
|
segments: StaticSegment(""),
|
||||||
|
children: (
|
||||||
|
NestedRoute {
|
||||||
|
segments: ParamSegment("id"),
|
||||||
|
children: (),
|
||||||
|
data: (),
|
||||||
|
view: Contact,
|
||||||
|
},
|
||||||
|
NestedRoute {
|
||||||
|
segments: StaticSegment(""),
|
||||||
|
children: (),
|
||||||
|
data: (),
|
||||||
|
view: || "Select a contact.",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
data: (),
|
||||||
|
view: ContactList,
|
||||||
|
},
|
||||||
NestedRoute {
|
NestedRoute {
|
||||||
segments: StaticSegment("settings"),
|
segments: StaticSegment("settings"),
|
||||||
children: (),
|
children: (),
|
||||||
|
@ -122,19 +141,28 @@ pub fn ContactRoutes() -> impl IntoView {
|
||||||
</Route>
|
</Route>
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
/*
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ContactList() -> impl IntoView {
|
pub fn ContactList() -> impl IntoView {
|
||||||
log::debug!("rendering <ContactList/>");
|
info!("rendering <ContactList/>");
|
||||||
|
|
||||||
// contexts are passed down through the route tree
|
// contexts are passed down through the route tree
|
||||||
provide_context(ExampleContext(42));
|
provide_context(ExampleContext(42));
|
||||||
|
|
||||||
on_cleanup(|| {
|
Owner::on_cleanup(|| {
|
||||||
info!("cleaning up <ContactList/>");
|
info!("cleaning up <ContactList/>");
|
||||||
});
|
});
|
||||||
|
|
||||||
let location = use_location();
|
view! {
|
||||||
|
<div class="contact-list">
|
||||||
|
<h1>"Contacts"</h1>
|
||||||
|
<li><a href="/1">1</a></li>
|
||||||
|
<li><a href="/2">2</a></li>
|
||||||
|
<li><a href="/3">3</a></li>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
/*let location = use_location();
|
||||||
let contacts = create_resource(move || location.search.get(), get_contacts);
|
let contacts = create_resource(move || location.search.get(), get_contacts);
|
||||||
let contacts = move || {
|
let contacts = move || {
|
||||||
contacts.get().map(|contacts| {
|
contacts.get().map(|contacts| {
|
||||||
|
@ -162,14 +190,14 @@ pub fn ContactList() -> impl IntoView {
|
||||||
intro="fadeIn"
|
intro="fadeIn"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}*/
|
||||||
}*/
|
}
|
||||||
/*
|
/*
|
||||||
#[derive(Params, PartialEq, Clone, Debug)]
|
#[derive(Params, PartialEq, Clone, Debug)]
|
||||||
pub struct ContactParams {
|
pub struct ContactParams {
|
||||||
// Params isn't implemented for usize, only Option<usize>
|
// Params isn't implemented for usize, only Option<usize>
|
||||||
id: Option<usize>,
|
id: Option<usize>,
|
||||||
}
|
}*/
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Contact() -> impl IntoView {
|
pub fn Contact() -> impl IntoView {
|
||||||
|
@ -180,11 +208,17 @@ pub fn Contact() -> impl IntoView {
|
||||||
use_context::<ExampleContext>()
|
use_context::<ExampleContext>()
|
||||||
);
|
);
|
||||||
|
|
||||||
on_cleanup(|| {
|
Owner::on_cleanup(|| {
|
||||||
info!("cleaning up <Contact/>");
|
info!("cleaning up <Contact/>");
|
||||||
});
|
});
|
||||||
|
|
||||||
let params = use_params::<ContactParams>();
|
view! {
|
||||||
|
<div class="contact">
|
||||||
|
<h2>"Contact"</h2>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
/* let params = use_params::<ContactParams>();
|
||||||
let contact = create_resource(
|
let contact = create_resource(
|
||||||
move || {
|
move || {
|
||||||
params
|
params
|
||||||
|
@ -229,8 +263,8 @@ pub fn Contact() -> impl IntoView {
|
||||||
{contact_display}
|
{contact_display}
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
}
|
}*/
|
||||||
}*/
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn About() -> impl IntoView {
|
pub fn About() -> impl IntoView {
|
||||||
|
@ -271,16 +305,14 @@ pub fn Settings() -> impl IntoView {
|
||||||
});
|
});
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<>
|
<h1>"Settings"</h1>
|
||||||
<h1>"Settings"</h1>
|
<form>
|
||||||
<form>
|
<fieldset>
|
||||||
<fieldset>
|
<legend>"Name"</legend>
|
||||||
<legend>"Name"</legend>
|
<input type="text" name="first_name" placeholder="First"/>
|
||||||
<input type="text" name="first_name" placeholder="First"/>
|
<input type="text" name="last_name" placeholder="Last"/>
|
||||||
<input type="text" name="last_name" placeholder="Last"/>
|
</fieldset>
|
||||||
</fieldset>
|
<pre>"This page is just a placeholder."</pre>
|
||||||
<pre>"This page is just a placeholder."</pre>
|
</form>
|
||||||
</form>
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,9 @@ use tracing_subscriber_wasm::MakeConsoleWriter;
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
fmt()
|
fmt()
|
||||||
.with_writer(
|
.with_writer(
|
||||||
// To avoide trace events in the browser from showing their
|
|
||||||
// JS backtrace, which is very annoying, in my opinion
|
|
||||||
MakeConsoleWriter::default()
|
MakeConsoleWriter::default()
|
||||||
.map_trace_level_to(tracing::Level::DEBUG),
|
.map_trace_level_to(tracing::Level::DEBUG),
|
||||||
)
|
)
|
||||||
// For some reason, if we don't do this in the browser, we get
|
|
||||||
// a runtime error.
|
|
||||||
.without_time()
|
.without_time()
|
||||||
.init();
|
.init();
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
|
@ -38,6 +38,18 @@ impl Owner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn child(&self) -> Self {
|
||||||
|
let parent = Some(Arc::downgrade(&self.inner));
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(RwLock::new(OwnerInner {
|
||||||
|
parent,
|
||||||
|
nodes: Default::default(),
|
||||||
|
contexts: Default::default(),
|
||||||
|
cleanups: Default::default(),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with<T>(&self, fun: impl FnOnce() -> T) -> T {
|
pub fn with<T>(&self, fun: impl FnOnce() -> T) -> T {
|
||||||
let prev = {
|
let prev = {
|
||||||
OWNER.with(|o| {
|
OWNER.with(|o| {
|
||||||
|
|
|
@ -13,6 +13,7 @@ url = "2"
|
||||||
js-sys = { version = "0.3" }
|
js-sys = { version = "0.3" }
|
||||||
wasm-bindgen = { version = "0.2" }
|
wasm-bindgen = { version = "0.2" }
|
||||||
tracing = { version = "0.1", optional = true }
|
tracing = { version = "0.1", optional = true }
|
||||||
|
paste = "1.0.14"
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
|
@ -41,3 +42,6 @@ features = [
|
||||||
"RequestMode",
|
"RequestMode",
|
||||||
"Response",
|
"Response",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
tracing = ["dep:tracing", "routing_utils/tracing"]
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
use crate::{generate_route_list::RouteList, location::Location};
|
use crate::{generate_route_list::RouteList, location::Location, Params};
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
use either_of::Either;
|
use either_of::*;
|
||||||
use reactive_graph::{
|
use reactive_graph::{
|
||||||
computed::ArcMemo,
|
computed::ArcMemo,
|
||||||
effect::RenderEffect,
|
effect::RenderEffect,
|
||||||
|
owner::Owner,
|
||||||
traits::{Read, Track},
|
traits::{Read, Track},
|
||||||
};
|
};
|
||||||
use routing_utils::{
|
use routing_utils::{
|
||||||
MatchInterface, MatchNestedRoutes, PossibleRouteMatch, Routes,
|
MatchInterface, MatchNestedRoutes, PossibleRouteMatch, RouteMatchId, Routes,
|
||||||
};
|
};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use tachys::{
|
use tachys::{
|
||||||
|
@ -16,8 +17,8 @@ use tachys::{
|
||||||
renderer::Renderer,
|
renderer::Renderer,
|
||||||
ssr::StreamBuilder,
|
ssr::StreamBuilder,
|
||||||
view::{
|
view::{
|
||||||
add_attr::AddAnyAttr, either::EitherState, Position, PositionState,
|
add_attr::AddAnyAttr, either::EitherState, Mountable, Position,
|
||||||
Render, RenderHtml,
|
PositionState, Render, RenderHtml,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -125,7 +126,11 @@ where
|
||||||
Rndr: Renderer + 'static,
|
Rndr: Renderer + 'static,
|
||||||
{
|
{
|
||||||
type State = RenderEffect<
|
type State = RenderEffect<
|
||||||
EitherState<View::State, <Fallback as Render<Rndr>>::State, Rndr>,
|
EitherState<
|
||||||
|
NestedRouteState<View::State>,
|
||||||
|
<Fallback as Render<Rndr>>::State,
|
||||||
|
Rndr,
|
||||||
|
>,
|
||||||
>;
|
>;
|
||||||
type FallibleState = ();
|
type FallibleState = ();
|
||||||
|
|
||||||
|
@ -136,27 +141,47 @@ where
|
||||||
let url = url.clone();
|
let url = url.clone();
|
||||||
move |_| url.read().path().to_string()
|
move |_| url.read().path().to_string()
|
||||||
});
|
});
|
||||||
let search_parans = ArcMemo::new({
|
let search_params = ArcMemo::new({
|
||||||
let url = url.clone();
|
let url = url.clone();
|
||||||
move |_| url.read().search_params().clone()
|
move |_| url.read().search_params().clone()
|
||||||
});
|
});
|
||||||
RenderEffect::new(move |prev| {
|
let outer_owner =
|
||||||
tachys::dom::log(&format!("recalculating route"));
|
Owner::current().expect("creating Router, but no Owner was found");
|
||||||
|
|
||||||
|
RenderEffect::new(move |prev: Option<EitherState<_, _, _>>| {
|
||||||
let path = path.read();
|
let path = path.read();
|
||||||
let new_view = match self.routes.match_route(&*path) {
|
let new_match = self.routes.match_route(&path);
|
||||||
Some(matched) => {
|
|
||||||
let view = matched.to_view();
|
|
||||||
let view = view.choose();
|
|
||||||
Either::Left(view)
|
|
||||||
}
|
|
||||||
_ => Either::Right((self.fallback)()),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(mut prev) = prev {
|
if let Some(mut prev) = prev {
|
||||||
new_view.rebuild(&mut prev);
|
if let Some(new_match) = new_match {
|
||||||
|
match &mut prev.state {
|
||||||
|
Either::Left(prev) => {
|
||||||
|
nested_rebuild(&outer_owner, prev, new_match);
|
||||||
|
}
|
||||||
|
Either::Right(_) => {
|
||||||
|
Either::<_, Fallback>::Left(NestedRouteView::new(
|
||||||
|
&outer_owner,
|
||||||
|
new_match,
|
||||||
|
))
|
||||||
|
.rebuild(&mut prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Either::<NestedRouteView<View>, _>::Right((self
|
||||||
|
.fallback)(
|
||||||
|
))
|
||||||
|
.rebuild(&mut prev);
|
||||||
|
}
|
||||||
prev
|
prev
|
||||||
} else {
|
} else {
|
||||||
new_view.build()
|
match new_match {
|
||||||
|
Some(matched) => Either::Left(NestedRouteView::new(
|
||||||
|
&outer_owner,
|
||||||
|
matched,
|
||||||
|
)),
|
||||||
|
_ => Either::Right((self.fallback)()),
|
||||||
|
}
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -175,6 +200,142 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn nested_rebuild<'a, NewMatch, R>(
|
||||||
|
outer_owner: &Owner,
|
||||||
|
current: &mut NestedRouteState<
|
||||||
|
<<NewMatch::View as ChooseView>::Output as Render<R>>::State,
|
||||||
|
>,
|
||||||
|
new: NewMatch,
|
||||||
|
) where
|
||||||
|
NewMatch: MatchInterface<'a>,
|
||||||
|
NewMatch::View: ChooseView,
|
||||||
|
<NewMatch::View as ChooseView>::Output: Render<R>,
|
||||||
|
R: Renderer,
|
||||||
|
{
|
||||||
|
// if the new match is a different branch of the nested route tree from the current one, we can
|
||||||
|
// just rebuild the view starting here: everything underneath it will change
|
||||||
|
if new.as_id() != current.id {
|
||||||
|
// TODO provide params + matched via context?
|
||||||
|
let new_view = NestedRouteView::new(&outer_owner, new);
|
||||||
|
let prev_owner = std::mem::replace(&mut current.owner, new_view.owner);
|
||||||
|
current.id = new_view.id;
|
||||||
|
current.params = new_view.params;
|
||||||
|
current.matched = new_view.matched;
|
||||||
|
current
|
||||||
|
.owner
|
||||||
|
.with(|| new_view.view.rebuild(&mut current.view));
|
||||||
|
|
||||||
|
// TODO is this the right place to drop the old Owner?
|
||||||
|
drop(prev_owner);
|
||||||
|
} else {
|
||||||
|
tracing::warn!("TODO: replace");
|
||||||
|
// otherwise, we should recurse to the children of the current view, and the new match
|
||||||
|
//nested_rebuild(current.as_child_mut(), new.as_child())
|
||||||
|
}
|
||||||
|
|
||||||
|
// update params, in case they're different
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NestedRouteView<View> {
|
||||||
|
id: RouteMatchId,
|
||||||
|
owner: Owner,
|
||||||
|
params: Params,
|
||||||
|
matched: String,
|
||||||
|
view: View,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<View> NestedRouteView<View> {
|
||||||
|
pub fn new<'a, Matcher>(outer_owner: &Owner, matched: Matcher) -> Self
|
||||||
|
where
|
||||||
|
Matcher: MatchInterface<'a>,
|
||||||
|
Matcher::View: ChooseView<Output = View>,
|
||||||
|
{
|
||||||
|
NestedRouteView {
|
||||||
|
id: matched.as_id(),
|
||||||
|
owner: outer_owner.child(),
|
||||||
|
params: matched
|
||||||
|
.to_params()
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||||
|
.collect(),
|
||||||
|
matched: matched.as_matched().to_string(),
|
||||||
|
view: matched.to_view().choose(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R, View> Render<R> for NestedRouteView<View>
|
||||||
|
where
|
||||||
|
View: Render<R>,
|
||||||
|
R: Renderer,
|
||||||
|
{
|
||||||
|
type State = NestedRouteState<View::State>;
|
||||||
|
type FallibleState = ();
|
||||||
|
|
||||||
|
fn build(self) -> Self::State {
|
||||||
|
let NestedRouteView {
|
||||||
|
id,
|
||||||
|
owner,
|
||||||
|
params,
|
||||||
|
matched,
|
||||||
|
view,
|
||||||
|
} = self;
|
||||||
|
NestedRouteState {
|
||||||
|
id,
|
||||||
|
owner,
|
||||||
|
params,
|
||||||
|
matched,
|
||||||
|
view: view.build(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebuild(self, state: &mut Self::State) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_build(self) -> tachys::error::Result<Self::FallibleState> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_rebuild(
|
||||||
|
self,
|
||||||
|
state: &mut Self::FallibleState,
|
||||||
|
) -> tachys::error::Result<()> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NestedRouteState<ViewState> {
|
||||||
|
id: RouteMatchId,
|
||||||
|
owner: Owner,
|
||||||
|
params: Params,
|
||||||
|
matched: String,
|
||||||
|
view: ViewState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ViewState, R> Mountable<R> for NestedRouteState<ViewState>
|
||||||
|
where
|
||||||
|
ViewState: Mountable<R>,
|
||||||
|
R: Renderer,
|
||||||
|
{
|
||||||
|
fn unmount(&mut self) {
|
||||||
|
self.view.unmount();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mount(&mut self, parent: &R::Element, marker: Option<&R::Node>) {
|
||||||
|
self.view.mount(parent, marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_before_this(
|
||||||
|
&self,
|
||||||
|
parent: &R::Element,
|
||||||
|
child: &mut dyn Mountable<R>,
|
||||||
|
) -> bool {
|
||||||
|
self.view.insert_before_this(parent, child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<Rndr, Loc, FallbackFn, Fallback, Children, View> RenderHtml<Rndr>
|
impl<Rndr, Loc, FallbackFn, Fallback, Children, View> RenderHtml<Rndr>
|
||||||
for Router<Rndr, Loc, Children, FallbackFn>
|
for Router<Rndr, Loc, Children, FallbackFn>
|
||||||
where
|
where
|
||||||
|
@ -239,6 +400,40 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! tuples {
|
||||||
|
($either:ident => $($ty:ident),*) => {
|
||||||
|
paste::paste! {
|
||||||
|
impl<$($ty, [<Fn $ty>],)*> ChooseView for $either<$([<Fn $ty>],)*>
|
||||||
|
where
|
||||||
|
$([<Fn $ty>]: Fn() -> $ty,)*
|
||||||
|
{
|
||||||
|
type Output = $either<$($ty,)*>;
|
||||||
|
|
||||||
|
fn choose(self) -> Self::Output {
|
||||||
|
match self {
|
||||||
|
$($either::$ty(f) => $either::$ty(f()),)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tuples!(EitherOf3 => A, B, C);
|
||||||
|
tuples!(EitherOf4 => A, B, C, D);
|
||||||
|
tuples!(EitherOf5 => A, B, C, D, E);
|
||||||
|
tuples!(EitherOf6 => A, B, C, D, E, F);
|
||||||
|
tuples!(EitherOf7 => A, B, C, D, E, F, G);
|
||||||
|
tuples!(EitherOf8 => A, B, C, D, E, F, G, H);
|
||||||
|
tuples!(EitherOf9 => A, B, C, D, E, F, G, H, I);
|
||||||
|
tuples!(EitherOf10 => A, B, C, D, E, F, G, H, I, J);
|
||||||
|
tuples!(EitherOf11 => A, B, C, D, E, F, G, H, I, J, K);
|
||||||
|
tuples!(EitherOf12 => A, B, C, D, E, F, G, H, I, J, K, L);
|
||||||
|
tuples!(EitherOf13 => A, B, C, D, E, F, G, H, I, J, K, L, M);
|
||||||
|
tuples!(EitherOf14 => A, B, C, D, E, F, G, H, I, J, K, L, M, N);
|
||||||
|
tuples!(EitherOf15 => A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
|
||||||
|
tuples!(EitherOf16 => A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
|
||||||
/*
|
/*
|
||||||
impl<Rndr, Loc, Fal, Children> RenderHtml<Rndr>
|
impl<Rndr, Loc, Fal, Children> RenderHtml<Rndr>
|
||||||
for Router<Rndr, Loc, Children, Fal>
|
for Router<Rndr, Loc, Children, Fal>
|
||||||
|
|
|
@ -60,12 +60,16 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
let (matched, remaining) = self.children.match_nested(path);
|
let (matched, remaining) = self.children.match_nested(path);
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::info!("matched = {:?}", matched.is_some());
|
||||||
let matched = matched?;
|
let matched = matched?;
|
||||||
|
|
||||||
if !remaining.is_empty() {
|
if !remaining.is_empty() {
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::info!("did not match because remaining was {remaining:?}");
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(matched)
|
Some(matched.1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,11 +83,18 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct RouteMatchId(pub(crate) u8);
|
||||||
|
|
||||||
pub trait MatchInterface<'a> {
|
pub trait MatchInterface<'a> {
|
||||||
type Params: IntoIterator<Item = (&'a str, &'a str)>;
|
type Params: IntoIterator<Item = (&'a str, &'a str)>;
|
||||||
type Child;
|
type Child;
|
||||||
type View;
|
type View;
|
||||||
|
|
||||||
|
fn as_id(&self) -> RouteMatchId;
|
||||||
|
|
||||||
|
fn as_matched(&self) -> &str;
|
||||||
|
|
||||||
fn to_params(&self) -> Self::Params;
|
fn to_params(&self) -> Self::Params;
|
||||||
|
|
||||||
fn to_child(&'a self) -> Self::Child;
|
fn to_child(&'a self) -> Self::Child;
|
||||||
|
@ -95,7 +106,10 @@ pub trait MatchNestedRoutes<'a> {
|
||||||
type Data;
|
type Data;
|
||||||
type Match: MatchInterface<'a>;
|
type Match: MatchInterface<'a>;
|
||||||
|
|
||||||
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str);
|
fn match_nested(
|
||||||
|
&'a self,
|
||||||
|
path: &'a str,
|
||||||
|
) -> (Option<(RouteMatchId, Self::Match)>, &'a str);
|
||||||
|
|
||||||
fn generate_routes(
|
fn generate_routes(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use super::{
|
use super::{
|
||||||
MatchInterface, MatchNestedRoutes, PartialPathMatch, PossibleRouteMatch,
|
MatchInterface, MatchNestedRoutes, PartialPathMatch, PossibleRouteMatch,
|
||||||
};
|
};
|
||||||
use crate::PathSegment;
|
use crate::{PathSegment, RouteMatchId};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::{fmt, iter};
|
use core::{fmt, iter};
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ pub struct NestedRoute<Segments, Children, Data, View> {
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
pub struct NestedMatch<'a, ParamsIter, Child, View> {
|
pub struct NestedMatch<'a, ParamsIter, Child, View> {
|
||||||
|
id: RouteMatchId,
|
||||||
/// The portion of the full path matched only by this nested route.
|
/// The portion of the full path matched only by this nested route.
|
||||||
matched: &'a str,
|
matched: &'a str,
|
||||||
/// The map of params matched only by this nested route.
|
/// The map of params matched only by this nested route.
|
||||||
|
@ -51,6 +52,14 @@ where
|
||||||
type Child = &'a Child;
|
type Child = &'a Child;
|
||||||
type View = &'a View;
|
type View = &'a View;
|
||||||
|
|
||||||
|
fn as_id(&self) -> RouteMatchId {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_matched(&self) -> &str {
|
||||||
|
self.matched
|
||||||
|
}
|
||||||
|
|
||||||
fn to_params(&self) -> Self::Params {
|
fn to_params(&self) -> Self::Params {
|
||||||
self.params.clone()
|
self.params.clone()
|
||||||
}
|
}
|
||||||
|
@ -87,7 +96,10 @@ where
|
||||||
<<Children::Match as MatchInterface<'a>>::Params as IntoIterator>::IntoIter,
|
<<Children::Match as MatchInterface<'a>>::Params as IntoIterator>::IntoIter,
|
||||||
>, Children::Match, View>;
|
>, Children::Match, View>;
|
||||||
|
|
||||||
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str) {
|
fn match_nested(
|
||||||
|
&'a self,
|
||||||
|
path: &'a str,
|
||||||
|
) -> (Option<(RouteMatchId, Self::Match)>, &'a str) {
|
||||||
self.segments
|
self.segments
|
||||||
.test(path)
|
.test(path)
|
||||||
.and_then(
|
.and_then(
|
||||||
|
@ -98,17 +110,21 @@ where
|
||||||
}| {
|
}| {
|
||||||
let (inner, remaining) =
|
let (inner, remaining) =
|
||||||
self.children.match_nested(remaining);
|
self.children.match_nested(remaining);
|
||||||
let inner = inner?;
|
let (id, inner) = inner?;
|
||||||
let params = params.into_iter();
|
let params = params.into_iter();
|
||||||
|
|
||||||
if remaining.is_empty() {
|
if remaining.is_empty() {
|
||||||
Some((
|
Some((
|
||||||
Some(NestedMatch {
|
Some((
|
||||||
matched,
|
id,
|
||||||
params: params.chain(inner.to_params()),
|
NestedMatch {
|
||||||
child: inner,
|
id,
|
||||||
view: &self.view,
|
matched,
|
||||||
}),
|
params: params.chain(inner.to_params()),
|
||||||
|
child: inner,
|
||||||
|
view: &self.view,
|
||||||
|
},
|
||||||
|
)),
|
||||||
remaining,
|
remaining,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{MatchInterface, MatchNestedRoutes, PathSegment};
|
use crate::{MatchInterface, MatchNestedRoutes, PathSegment, RouteMatchId};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::iter;
|
use core::iter;
|
||||||
use either_of::*;
|
use either_of::*;
|
||||||
|
@ -8,6 +8,14 @@ impl<'a> MatchInterface<'a> for () {
|
||||||
type Child = ();
|
type Child = ();
|
||||||
type View = ();
|
type View = ();
|
||||||
|
|
||||||
|
fn as_id(&self) -> RouteMatchId {
|
||||||
|
RouteMatchId(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_matched(&self) -> &str {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
fn to_params(&self) -> Self::Params {
|
fn to_params(&self) -> Self::Params {
|
||||||
iter::empty()
|
iter::empty()
|
||||||
}
|
}
|
||||||
|
@ -21,8 +29,11 @@ impl<'a> MatchNestedRoutes<'a> for () {
|
||||||
type Data = ();
|
type Data = ();
|
||||||
type Match = ();
|
type Match = ();
|
||||||
|
|
||||||
fn match_nested(&self, path: &'a str) -> (Option<Self::Match>, &'a str) {
|
fn match_nested(
|
||||||
(Some(()), path)
|
&self,
|
||||||
|
path: &'a str,
|
||||||
|
) -> (Option<(RouteMatchId, Self::Match)>, &'a str) {
|
||||||
|
(Some((RouteMatchId(0), ())), path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_routes(
|
fn generate_routes(
|
||||||
|
@ -40,6 +51,14 @@ where
|
||||||
type Child = A::Child;
|
type Child = A::Child;
|
||||||
type View = A::View;
|
type View = A::View;
|
||||||
|
|
||||||
|
fn as_id(&self) -> RouteMatchId {
|
||||||
|
RouteMatchId(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_matched(&self) -> &str {
|
||||||
|
self.0.as_matched()
|
||||||
|
}
|
||||||
|
|
||||||
fn to_params(&self) -> Self::Params {
|
fn to_params(&self) -> Self::Params {
|
||||||
self.0.to_params()
|
self.0.to_params()
|
||||||
}
|
}
|
||||||
|
@ -60,7 +79,10 @@ where
|
||||||
type Data = A::Data;
|
type Data = A::Data;
|
||||||
type Match = A::Match;
|
type Match = A::Match;
|
||||||
|
|
||||||
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str) {
|
fn match_nested(
|
||||||
|
&'a self,
|
||||||
|
path: &'a str,
|
||||||
|
) -> (Option<(RouteMatchId, Self::Match)>, &'a str) {
|
||||||
self.0.match_nested(path)
|
self.0.match_nested(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +105,20 @@ where
|
||||||
type Child = Either<A::Child, B::Child>;
|
type Child = Either<A::Child, B::Child>;
|
||||||
type View = Either<A::View, B::View>;
|
type View = Either<A::View, B::View>;
|
||||||
|
|
||||||
|
fn as_id(&self) -> RouteMatchId {
|
||||||
|
match self {
|
||||||
|
Either::Left(_) => RouteMatchId(0),
|
||||||
|
Either::Right(_) => RouteMatchId(1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_matched(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Either::Left(i) => i.as_matched(),
|
||||||
|
Either::Right(i) => i.as_matched(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn to_params(&self) -> Self::Params {
|
fn to_params(&self) -> Self::Params {
|
||||||
match self {
|
match self {
|
||||||
Either::Left(i) => Either::Left(i.to_params().into_iter()),
|
Either::Left(i) => Either::Left(i.to_params().into_iter()),
|
||||||
|
@ -113,14 +149,17 @@ where
|
||||||
type Data = (A::Data, B::Data);
|
type Data = (A::Data, B::Data);
|
||||||
type Match = Either<A::Match, B::Match>;
|
type Match = Either<A::Match, B::Match>;
|
||||||
|
|
||||||
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str) {
|
fn match_nested(
|
||||||
|
&'a self,
|
||||||
|
path: &'a str,
|
||||||
|
) -> (Option<(RouteMatchId, Self::Match)>, &'a str) {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
let (A, B) = &self;
|
let (A, B) = &self;
|
||||||
if let (Some(matched), remaining) = A.match_nested(path) {
|
if let (Some((id, matched)), remaining) = A.match_nested(path) {
|
||||||
return (Some(Either::Left(matched)), remaining);
|
return (Some((id, Either::Left(matched))), remaining);
|
||||||
}
|
}
|
||||||
if let (Some(matched), remaining) = B.match_nested(path) {
|
if let (Some((id, matched)), remaining) = B.match_nested(path) {
|
||||||
return (Some(Either::Right(matched)), remaining);
|
return (Some((id, Either::Right(matched))), remaining);
|
||||||
}
|
}
|
||||||
(None, path)
|
(None, path)
|
||||||
}
|
}
|
||||||
|
@ -152,7 +191,7 @@ macro_rules! chain_generated {
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! tuples {
|
macro_rules! tuples {
|
||||||
($either:ident => $($ty:ident),*) => {
|
($either:ident => $($ty:ident = $count:expr),*) => {
|
||||||
impl<'a, $($ty,)*> MatchInterface<'a> for $either <$($ty,)*>
|
impl<'a, $($ty,)*> MatchInterface<'a> for $either <$($ty,)*>
|
||||||
where
|
where
|
||||||
$($ty: MatchInterface<'a>),*,
|
$($ty: MatchInterface<'a>),*,
|
||||||
|
@ -165,6 +204,18 @@ macro_rules! tuples {
|
||||||
type Child = $either<$($ty::Child,)*>;
|
type Child = $either<$($ty::Child,)*>;
|
||||||
type View = $either<$($ty::View,)*>;
|
type View = $either<$($ty::View,)*>;
|
||||||
|
|
||||||
|
fn as_id(&self) -> RouteMatchId {
|
||||||
|
match self {
|
||||||
|
$($either::$ty(_) => RouteMatchId($count),)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_matched(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
$($either::$ty(i) => i.as_matched(),)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn to_params(&self) -> Self::Params {
|
fn to_params(&self) -> Self::Params {
|
||||||
match self {
|
match self {
|
||||||
$($either::$ty(i) => $either::$ty(i.to_params().into_iter()),)*
|
$($either::$ty(i) => $either::$ty(i.to_params().into_iter()),)*
|
||||||
|
@ -192,12 +243,15 @@ macro_rules! tuples {
|
||||||
type Data = ($($ty::Data,)*);
|
type Data = ($($ty::Data,)*);
|
||||||
type Match = $either<$($ty::Match,)*>;
|
type Match = $either<$($ty::Match,)*>;
|
||||||
|
|
||||||
fn match_nested(&'a self, path: &'a str) -> (Option<Self::Match>, &'a str) {
|
fn match_nested(&'a self, path: &'a str) -> (Option<(RouteMatchId, Self::Match)>, &'a str) {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
|
||||||
let ($($ty,)*) = &self;
|
let ($($ty,)*) = &self;
|
||||||
$(if let (Some(matched), remaining) = $ty.match_nested(path) {
|
let mut id = 0;
|
||||||
return (Some($either::$ty(matched)), remaining);
|
$(if let (Some((_, matched)), remaining) = $ty.match_nested(path) {
|
||||||
|
return (Some((RouteMatchId(id), $either::$ty(matched))), remaining);
|
||||||
|
} else {
|
||||||
|
id += 1;
|
||||||
})*
|
})*
|
||||||
(None, path)
|
(None, path)
|
||||||
}
|
}
|
||||||
|
@ -215,17 +269,17 @@ macro_rules! tuples {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tuples!(EitherOf3 => A, B, C);
|
tuples!(EitherOf3 => A = 0, B = 1, C = 2);
|
||||||
tuples!(EitherOf4 => A, B, C, D);
|
tuples!(EitherOf4 => A = 0, B = 1, C = 2, D = 3);
|
||||||
tuples!(EitherOf5 => A, B, C, D, E);
|
tuples!(EitherOf5 => A = 0, B = 1, C = 2, D = 3, E = 4);
|
||||||
tuples!(EitherOf6 => A, B, C, D, E, F);
|
tuples!(EitherOf6 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5);
|
||||||
tuples!(EitherOf7 => A, B, C, D, E, F, G);
|
tuples!(EitherOf7 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6);
|
||||||
tuples!(EitherOf8 => A, B, C, D, E, F, G, H);
|
tuples!(EitherOf8 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7);
|
||||||
tuples!(EitherOf9 => A, B, C, D, E, F, G, H, I);
|
tuples!(EitherOf9 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7, I = 8);
|
||||||
tuples!(EitherOf10 => A, B, C, D, E, F, G, H, I, J);
|
tuples!(EitherOf10 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7, I = 8, J = 9);
|
||||||
tuples!(EitherOf11 => A, B, C, D, E, F, G, H, I, J, K);
|
tuples!(EitherOf11 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7, I = 8, J = 9, K = 10);
|
||||||
tuples!(EitherOf12 => A, B, C, D, E, F, G, H, I, J, K, L);
|
tuples!(EitherOf12 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7, I = 8, J = 9, K = 10, L = 11);
|
||||||
tuples!(EitherOf13 => A, B, C, D, E, F, G, H, I, J, K, L, M);
|
tuples!(EitherOf13 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7, I = 8, J = 9, K = 10, L = 11, M = 12);
|
||||||
tuples!(EitherOf14 => A, B, C, D, E, F, G, H, I, J, K, L, M, N);
|
tuples!(EitherOf14 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7, I = 8, J = 9, K = 10, L = 11, M = 12, N = 13);
|
||||||
tuples!(EitherOf15 => A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
|
tuples!(EitherOf15 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7, I = 8, J = 9, K = 10, L = 11, M = 12, N = 13, O = 14);
|
||||||
tuples!(EitherOf16 => A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
|
tuples!(EitherOf16 => A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7, I = 8, J = 9, K = 10, L = 11, M = 12, N = 13, O = 14, P = 15);
|
||||||
|
|
|
@ -16,7 +16,7 @@ where
|
||||||
B: Mountable<Rndr>,
|
B: Mountable<Rndr>,
|
||||||
Rndr: Renderer,
|
Rndr: Renderer,
|
||||||
{
|
{
|
||||||
state: Either<A, B>,
|
pub state: Either<A, B>,
|
||||||
marker: Rndr::Placeholder,
|
marker: Rndr::Placeholder,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue