Compare commits

...

7 Commits

Author SHA1 Message Date
Greg Johnston
ebea3ec14d continue trying to remove EitherOf instances 2024-10-20 15:57:23 -04:00
Greg Johnston
ed3ca9eae1 clippy 2024-10-20 14:18:24 -04:00
Greg Johnston
ed8abcfbcd update to main 2024-10-20 14:18:24 -04:00
Greg Johnston
29a56af38c use .into_any() throughout the router 2024-10-20 14:18:24 -04:00
Greg Johnston
f9d6f7fd64 manually add .into_any() on pages 2024-10-20 14:18:24 -04:00
Greg Johnston
251a472cd5 erase types everywhere in route matching 2024-10-20 14:18:24 -04:00
Greg Johnston
3c76cb24d3 erase ChooseView only 2024-10-20 14:18:02 -04:00
10 changed files with 256 additions and 220 deletions

View File

@@ -21,10 +21,16 @@ pub fn Nav() -> impl IntoView {
<A href="/job">
<strong>"Jobs"</strong>
</A>
<a class="github" href="http://github.com/leptos-rs/leptos" target="_blank" rel="noreferrer">
<a
class="github"
href="http://github.com/leptos-rs/leptos"
target="_blank"
rel="noreferrer"
>
"Built with Leptos"
</a>
</nav>
</header>
}
.into_any()
}

View File

@@ -50,30 +50,42 @@ pub fn Stories() -> impl IntoView {
<div class="news-view">
<div class="news-list-nav">
<span>
{move || if page() > 1 {
Either::Left(view! {
<a class="page-link"
href=move || format!("/{}?page={}", story_type(), page() - 1)
aria-label="Previous Page"
>
"< prev"
</a>
})
} else {
Either::Right(view! {
<span class="page-link disabled" aria-hidden="true">
"< prev"
</span>
})
{move || {
if page() > 1 {
Either::Left(
view! {
<a
class="page-link"
href=move || {
format!("/{}?page={}", story_type(), page() - 1)
}
aria-label="Previous Page"
>
"< prev"
</a>
},
)
} else {
Either::Right(
view! {
<span class="page-link disabled" aria-hidden="true">
"< prev"
</span>
},
)
}
}}
</span>
<span>"page " {page}</span>
<Suspense>
<span class="page-link"
<span
class="page-link"
class:disabled=hide_more_link
aria-hidden=hide_more_link
>
<a href=move || format!("/{}?page={}", story_type(), page() + 1)
<a
href=move || format!("/{}?page={}", story_type(), page() + 1)
aria-label="Next Page"
>
"more >"
@@ -83,14 +95,10 @@ pub fn Stories() -> impl IntoView {
</div>
<main class="news-list">
<div>
<Transition
fallback=move || view! { <p>"Loading..."</p> }
set_pending
>
<Show when=move || stories.read().as_ref().map(Option::is_none).unwrap_or(false)>
>
<p>"Error loading stories."</p>
</Show>
<Transition fallback=move || view! { <p>"Loading..."</p> } set_pending>
<Show when=move || {
stories.read().as_ref().map(Option::is_none).unwrap_or(false)
}>> <p>"Error loading stories."</p></Show>
<ul>
<For
each=move || stories.get().unwrap_or_default().unwrap_or_default()
@@ -105,54 +113,78 @@ pub fn Stories() -> impl IntoView {
</main>
</div>
}
.into_any()
}
#[component]
fn Story(story: api::Story) -> impl IntoView {
view! {
<li class="news-item">
<li class="news-item">
<span class="score">{story.points}</span>
<span class="title">
{if !story.url.starts_with("item?id=") {
Either::Left(view! {
<span>
<a href=story.url target="_blank" rel="noreferrer">
{story.title.clone()}
</a>
<span class="host">"("{story.domain}")"</span>
</span>
})
Either::Left(
view! {
<span>
<a href=story.url target="_blank" rel="noreferrer">
{story.title.clone()}
</a>
<span class="host">"(" {story.domain} ")"</span>
</span>
},
)
} else {
let title = story.title.clone();
Either::Right(view! { <A href=format!("/stories/{}", story.id)>{title}</A> })
}}
</span>
<br />
<br/>
<span class="meta">
{if story.story_type != "job" {
Either::Left(view! {
<span>
{"by "}
{story.user.map(|user| view ! { <A href=format!("/users/{user}")>{user.clone()}</A>})}
{format!(" {} | ", story.time_ago)}
<A href=format!("/stories/{}", story.id)>
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"discuss".into()
}}
</A>
</span>
})
Either::Left(
view! {
<span>
{"by "}
{story
.user
.map(|user| {
view! {
<A href=format!("/users/{user}")>{user.clone()}</A>
}
})} {format!(" {} | ", story.time_ago)}
<A href=format!(
"/stories/{}",
story.id,
)>
{if story.comments_count.unwrap_or_default() > 0 {
format!(
"{} comments",
story.comments_count.unwrap_or_default(),
)
} else {
"discuss".into()
}}
</A>
</span>
},
)
} else {
let title = story.title.clone();
Either::Right(view! { <A href=format!("/item/{}", story.id)>{title}</A> })
}}
</span>
{(story.story_type != "link").then(|| view! {
" "
<span class="label">{story.story_type}</span>
})}
{(story.story_type != "link")
.then(|| {
view! {
" "
<span class="label">{story.story_type}</span>
}
})}
</li>
}
.into_any()
}

View File

@@ -28,18 +28,21 @@ pub fn Story() -> impl IntoView {
<Meta name="description" content=story.title.clone()/>
<div class="item-view">
<div class="item-view-header">
<a href=story.url target="_blank">
<h1>{story.title}</h1>
</a>
<span class="host">
"("{story.domain}")"
</span>
{story.user.map(|user| view! { <p class="meta">
{story.points}
" points | by "
<A href=format!("/users/{user}")>{user.clone()}</A>
{format!(" {}", story.time_ago)}
</p>})}
<a href=story.url target="_blank">
<h1>{story.title}</h1>
</a>
<span class="host">"(" {story.domain} ")"</span>
{story
.user
.map(|user| {
view! {
<p class="meta">
{story.points} " points | by "
<A href=format!("/users/{user}")>{user.clone()}</A>
{format!(" {}", story.time_ago)}
</p>
}
})}
</div>
<div class="item-view-comments">
<p class="item-view-comments-header">
@@ -48,6 +51,7 @@ pub fn Story() -> impl IntoView {
} else {
"No comments yet.".into()
}}
</p>
<ul class="comment-children">
<For
@@ -55,7 +59,7 @@ pub fn Story() -> impl IntoView {
key=|comment| comment.id
let:comment
>
<Comment comment />
<Comment comment/>
</For>
</ul>
</div>
@@ -64,6 +68,7 @@ pub fn Story() -> impl IntoView {
}
}
}))).build())
.into_any()
}
#[component]
@@ -72,43 +77,65 @@ pub fn Comment(comment: api::Comment) -> impl IntoView {
view! {
<li class="comment">
<div class="by">
<A href=format!("/users/{}", comment.user.clone().unwrap_or_default())>{comment.user.clone()}</A>
{format!(" {}", comment.time_ago)}
</div>
<div class="text" inner_html=comment.content></div>
{(!comment.comments.is_empty()).then(|| {
view! {
<div>
<div class="toggle" class:open=open>
<a on:click=move |_| set_open.update(|n| *n = !*n)>
{
let comments_len = comment.comments.len();
move || if open.get() {
"[-]".into()
} else {
format!("[+] {}{} collapsed", comments_len, pluralize(comments_len))
}
}
</a>
</div>
{move || open.get().then({
let comments = comment.comments.clone();
move || view! {
<ul class="comment-children">
<For
each=move || comments.clone()
key=|comment| comment.id
let:comment
>
<Comment comment />
</For>
</ul>
}
})}
</div>
}
})}
<div class="by">
<A href=format!(
"/users/{}",
comment.user.clone().unwrap_or_default(),
)>{comment.user.clone()}</A>
{format!(" {}", comment.time_ago)}
</div>
<div class="text" inner_html=comment.content></div>
{(!comment.comments.is_empty())
.then(|| {
view! {
<div>
<div class="toggle" class:open=open>
<a on:click=move |_| {
set_open.update(|n| *n = !*n)
}>
{
let comments_len = comment.comments.len();
move || {
if open.get() {
"[-]".into()
} else {
format!(
"[+] {}{} collapsed",
comments_len,
pluralize(comments_len),
)
}
}
}
</a>
</div>
{move || {
open
.get()
.then({
let comments = comment.comments.clone();
move || {
view! {
<ul class="comment-children">
<For
each=move || comments.clone()
key=|comment| comment.id
let:comment
>
<Comment comment/>
</For>
</ul>
}
}
})
}}
</div>
}
})}
</li>
}.into_any()
}

View File

@@ -18,30 +18,48 @@ pub fn User() -> impl IntoView {
);
view! {
<div class="user-view">
<Suspense fallback=|| view! { "Loading..." }>
{move || Suspend::new(async move { match user.await.clone() {
None => Either::Left(view! { <h1>"User not found."</h1> }),
Some(user) => Either::Right(view! {
<div>
<h1>"User: " {user.id.clone()}</h1>
<ul class="meta">
<li>
<span class="label">"Created: "</span> {user.created}
</li>
<li>
<span class="label">"Karma: "</span> {user.karma}
</li>
<li inner_html={user.about} class="about"></li>
</ul>
<p class="links">
<a href=format!("https://news.ycombinator.com/submitted?id={}", user.id)>"submissions"</a>
" | "
<a href=format!("https://news.ycombinator.com/threads?id={}", user.id)>"comments"</a>
</p>
</div>
})
}})}
<Suspense fallback=|| {
view! { "Loading..." }
}>
{move || Suspend::new(async move {
match user.await.clone() {
None => Either::Left(view! { <h1>"User not found."</h1> }),
Some(user) => {
Either::Right(
view! {
<div>
<h1>"User: " {user.id.clone()}</h1>
<ul class="meta">
<li>
<span class="label">"Created: "</span>
{user.created}
</li>
<li>
<span class="label">"Karma: "</span>
{user.karma}
</li>
<li inner_html=user.about class="about"></li>
</ul>
<p class="links">
<a href=format!(
"https://news.ycombinator.com/submitted?id={}",
user.id,
)>"submissions"</a>
" | "
<a href=format!(
"https://news.ycombinator.com/threads?id={}",
user.id,
)>"comments"</a>
</p>
</div>
},
)
}
}
})}
</Suspense>
</div>
}
.into_any()
}

View File

@@ -8,7 +8,7 @@ use crate::{
RouteList, RouteListing, RouteMatchId,
};
use any_spawner::Executor;
use either_of::{Either, EitherOf3};
use either_of::Either;
use futures::FutureExt;
use reactive_graph::{
computed::{ArcMemo, ScopedFuture},
@@ -24,8 +24,9 @@ use tachys::{
reactive_graph::OwnedView,
ssr::StreamBuilder,
view::{
add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
RenderHtml,
add_attr::AddAnyAttr,
any_view::{AnyView, AnyViewState, IntoAny},
Mountable, Position, PositionState, Render, RenderHtml,
},
};
@@ -39,26 +40,18 @@ pub(crate) struct FlatRoutesView<Loc, Defs, FalFn> {
pub transition: bool,
}
pub struct FlatRoutesViewState<Defs, Fal>
where
Defs: MatchNestedRoutes + 'static,
Fal: Render + 'static,
{
pub struct FlatRoutesViewState {
#[allow(clippy::type_complexity)]
view: <EitherOf3<(), Fal, OwnedView<<Defs::Match as MatchInterface>::View>> as Render>::State,
view: AnyViewState,
id: Option<RouteMatchId>,
owner: Owner,
params: ArcRwSignal<ParamsMap>,
path: String,
url: ArcRwSignal<Url>,
matched: ArcRwSignal<String>
matched: ArcRwSignal<String>,
}
impl<Defs, Fal> Mountable for FlatRoutesViewState<Defs, Fal>
where
Defs: MatchNestedRoutes + 'static,
Fal: Render + 'static,
{
impl Mountable for FlatRoutesViewState {
fn unmount(&mut self) {
self.view.unmount();
}
@@ -81,9 +74,9 @@ where
Loc: LocationProvider,
Defs: MatchNestedRoutes + 'static,
FalFn: FnOnce() -> Fal + Send,
Fal: Render + 'static,
Fal: IntoAny,
{
type State = Rc<RefCell<FlatRoutesViewState<Defs, Fal>>>;
type State = Rc<RefCell<FlatRoutesViewState>>;
fn build(self) -> Self::State {
let FlatRoutesView {
@@ -123,7 +116,7 @@ where
match new_match {
None => Rc::new(RefCell::new(FlatRoutesViewState {
view: EitherOf3::B(fallback()).build(),
view: fallback().into_any().build(),
id,
owner,
params,
@@ -156,7 +149,7 @@ where
match view.as_mut().now_or_never() {
Some(view) => Rc::new(RefCell::new(FlatRoutesViewState {
view: EitherOf3::C(view).build(),
view: view.into_any().build(),
id,
owner,
params,
@@ -167,7 +160,7 @@ where
None => {
let state =
Rc::new(RefCell::new(FlatRoutesViewState {
view: EitherOf3::A(()).build(),
view: ().into_any().build(),
id,
owner,
params,
@@ -180,7 +173,7 @@ where
let state = Rc::clone(&state);
async move {
let view = view.await;
EitherOf3::C(view)
view.into_any()
.rebuild(&mut state.borrow_mut().view);
}
});
@@ -270,8 +263,7 @@ where
provide_context(url);
provide_context(params_memo);
provide_context(Matched(ArcMemo::from(new_matched)));
EitherOf3::B(fallback())
.rebuild(&mut state.borrow_mut().view)
fallback().into_any().rebuild(&mut state.borrow_mut().view)
});
}
Some(new_match) => {
@@ -318,7 +310,7 @@ where
== spawned_path
{
let rebuild = move || {
EitherOf3::C(view)
view.into_any()
.rebuild(&mut state.borrow_mut().view);
};
if transition {
@@ -371,9 +363,7 @@ where
FalFn: FnOnce() -> Fal + Send,
Fal: RenderHtml + 'static,
{
fn choose_ssr(
self,
) -> OwnedView<Either<Fal, <Defs::Match as MatchInterface>::View>> {
fn choose_ssr(self) -> OwnedView<AnyView> {
let current_url = self.current_url.read_untracked();
let new_match = self.routes.match_route(current_url.path());
let owner = self.outer_owner.child();
@@ -396,7 +386,7 @@ where
drop(current_url);
let view = match new_match {
None => Either::Left((self.fallback)()),
None => (self.fallback)().into_any(),
Some(new_match) => {
let (view, _) = new_match.into_view_and_child();
let view = owner
@@ -410,7 +400,7 @@ where
})
.now_or_never()
.expect("async route used in SSR");
Either::Right(view)
view.into_any()
}
};
@@ -427,10 +417,7 @@ where
{
type AsyncOutput = Self;
const MIN_LENGTH: usize = <Either<
Fal,
<Defs::Match as MatchInterface>::View,
> as RenderHtml>::MIN_LENGTH;
const MIN_LENGTH: usize = <Either<Fal, AnyView> as RenderHtml>::MIN_LENGTH;
fn dry_resolve(&mut self) {}
@@ -560,7 +547,8 @@ where
match new_match {
None => Rc::new(RefCell::new(FlatRoutesViewState {
view: EitherOf3::B(fallback())
view: fallback()
.into_any()
.hydrate::<FROM_SERVER>(cursor, position),
id,
owner,
@@ -594,7 +582,8 @@ where
match view.as_mut().now_or_never() {
Some(view) => Rc::new(RefCell::new(FlatRoutesViewState {
view: EitherOf3::C(view)
view: view
.into_any()
.hydrate::<FROM_SERVER>(cursor, position),
id,
owner,

View File

@@ -1,14 +1,12 @@
use either_of::*;
use std::{future::Future, marker::PhantomData};
use tachys::view::{any_view::AnyView, Render};
use tachys::view::any_view::{AnyView, IntoAny};
pub trait ChooseView
where
Self: Send + Clone + 'static,
{
type Output;
fn choose(self) -> impl Future<Output = Self::Output>;
fn choose(self) -> impl Future<Output = AnyView>;
fn preload(&self) -> impl Future<Output = ()>;
}
@@ -16,12 +14,10 @@ where
impl<F, View> ChooseView for F
where
F: Fn() -> View + Send + Clone + 'static,
View: Render + Send,
View: IntoAny,
{
type Output = View;
async fn choose(self) -> Self::Output {
self()
async fn choose(self) -> AnyView {
self().into_any()
}
async fn preload(&self) {}
@@ -31,10 +27,8 @@ impl<T> ChooseView for Lazy<T>
where
T: LazyRoute,
{
type Output = AnyView;
async fn choose(self) -> Self::Output {
T::data().view().await
async fn choose(self) -> AnyView {
T::data().view().await.into_any()
}
async fn preload(&self) {
@@ -74,9 +68,9 @@ impl<T> Default for Lazy<T> {
}
impl ChooseView for () {
type Output = ();
async fn choose(self) -> Self::Output {}
async fn choose(self) -> AnyView {
().into_any()
}
async fn preload(&self) {}
}
@@ -86,12 +80,10 @@ where
A: ChooseView,
B: ChooseView,
{
type Output = Either<A::Output, B::Output>;
async fn choose(self) -> Self::Output {
async fn choose(self) -> AnyView {
match self {
Either::Left(f) => Either::Left(f.choose().await),
Either::Right(f) => Either::Right(f.choose().await),
Either::Left(f) => f.choose().await.into_any(),
Either::Right(f) => f.choose().await.into_any(),
}
}
@@ -109,11 +101,9 @@ macro_rules! tuples {
where
$($ty: ChooseView,)*
{
type Output = $either<$($ty::Output,)*>;
async fn choose(self ) -> Self::Output {
async fn choose(self ) -> AnyView {
match self {
$($either::$ty(f) => $either::$ty(f.choose().await),)*
$($either::$ty(f) => f.choose().await.into_any(),)*
}
}

View File

@@ -10,7 +10,6 @@ use crate::{static_routes::RegenerationFn, Method, SsrMode};
pub use horizontal::*;
pub use nested::*;
use std::{borrow::Cow, collections::HashSet};
use tachys::view::{Render, RenderHtml};
pub use vertical::*;
#[derive(Debug)]
@@ -95,15 +94,12 @@ pub struct RouteMatchId(pub(crate) u16);
pub trait MatchInterface {
type Child: MatchInterface + MatchParams + 'static;
type View: Render + RenderHtml + Send + 'static;
fn as_id(&self) -> RouteMatchId;
fn as_matched(&self) -> &str;
fn into_view_and_child(
self,
) -> (impl ChooseView<Output = Self::View>, Option<Self::Child>);
fn into_view_and_child(self) -> (impl ChooseView, Option<Self::Child>);
}
pub trait MatchParams {
@@ -114,7 +110,6 @@ pub trait MatchParams {
pub trait MatchNestedRoutes {
type Data;
type View;
type Match: MatchInterface + MatchParams;
fn match_nested<'a>(

View File

@@ -10,7 +10,6 @@ use std::{
collections::HashSet,
sync::atomic::{AtomicU16, Ordering},
};
use tachys::view::{Render, RenderHtml};
mod tuples;
@@ -141,10 +140,8 @@ impl<ParamsIter, Child, View> MatchInterface
where
Child: MatchInterface + MatchParams + 'static,
View: ChooseView,
View::Output: Render + RenderHtml + Send + 'static,
{
type Child = Child;
type View = View::Output;
fn as_id(&self) -> RouteMatchId {
self.id
@@ -154,9 +151,7 @@ where
&self.matched
}
fn into_view_and_child(
self,
) -> (impl ChooseView<Output = Self::View>, Option<Self::Child>) {
fn into_view_and_child(self) -> (impl ChooseView, Option<Self::Child>) {
(self.view_fn, self.child)
}
}
@@ -173,10 +168,8 @@ where
Children: 'static,
<Children::Match as MatchParams>::Params: Clone,
View: ChooseView + Clone,
View::Output: Render + RenderHtml + Send + 'static,
{
type Data = Data;
type View = View::Output;
type Match = NestedMatch<iter::Chain<
<Segments::ParamsIter as IntoIterator>::IntoIter,
Either<iter::Empty::<

View File

@@ -14,7 +14,6 @@ impl MatchParams for () {
impl MatchInterface for () {
type Child = ();
type View = ();
fn as_id(&self) -> RouteMatchId {
RouteMatchId(0)
@@ -24,16 +23,13 @@ impl MatchInterface for () {
""
}
fn into_view_and_child(
self,
) -> (impl ChooseView<Output = Self::View>, Option<Self::Child>) {
fn into_view_and_child(self) -> (impl ChooseView, Option<Self::Child>) {
((), None)
}
}
impl MatchNestedRoutes for () {
type Data = ();
type View = ();
type Match = ();
fn match_nested<'a>(
@@ -69,7 +65,6 @@ where
A: MatchInterface + 'static,
{
type Child = A::Child;
type View = A::View;
fn as_id(&self) -> RouteMatchId {
self.0.as_id()
@@ -79,9 +74,7 @@ where
self.0.as_matched()
}
fn into_view_and_child(
self,
) -> (impl ChooseView<Output = Self::View>, Option<Self::Child>) {
fn into_view_and_child(self) -> (impl ChooseView, Option<Self::Child>) {
self.0.into_view_and_child()
}
}
@@ -91,7 +84,6 @@ where
A: MatchNestedRoutes + 'static,
{
type Data = A::Data;
type View = A::View;
type Match = A::Match;
fn match_nested<'a>(
@@ -132,7 +124,6 @@ where
B: MatchInterface,
{
type Child = Either<A::Child, B::Child>;
type View = Either<A::View, B::View>;
fn as_id(&self) -> RouteMatchId {
match self {
@@ -148,9 +139,7 @@ where
}
}
fn into_view_and_child(
self,
) -> (impl ChooseView<Output = Self::View>, Option<Self::Child>) {
fn into_view_and_child(self) -> (impl ChooseView, Option<Self::Child>) {
match self {
Either::Left(i) => {
let (view, child) = i.into_view_and_child();
@@ -170,7 +159,6 @@ where
B: MatchNestedRoutes,
{
type Data = (A::Data, B::Data);
type View = Either<A::View, B::View>;
type Match = Either<A::Match, B::Match>;
fn match_nested<'a>(
@@ -236,7 +224,6 @@ macro_rules! tuples {
$($ty: MatchInterface + 'static),*,
{
type Child = $either<$($ty::Child,)*>;
type View = $either<$($ty::View,)*>;
fn as_id(&self) -> RouteMatchId {
match self {
@@ -253,7 +240,7 @@ macro_rules! tuples {
fn into_view_and_child(
self,
) -> (
impl ChooseView<Output = Self::View>,
impl ChooseView,
Option<Self::Child>,
) {
match self {
@@ -270,7 +257,6 @@ macro_rules! tuples {
$($ty: MatchNestedRoutes + 'static),*,
{
type Data = ($($ty::Data,)*);
type View = $either<$($ty::View,)*>;
type Match = $either<$($ty::Match,)*>;
fn match_nested<'a>(&'a self, path: &'a str) -> (Option<(RouteMatchId, Self::Match)>, &'a str) {

View File

@@ -353,7 +353,7 @@ impl ResolvedStaticPath {
eprintln!("{e}");
}
}
drop(owner);
owner.unset();
}
}
});