mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 12:31:55 -05:00
Compare commits
17 Commits
api-routes
...
nightly-fe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0821de8a3a | ||
|
|
a5f6e0bac4 | ||
|
|
2c9de79576 | ||
|
|
63dd00a050 | ||
|
|
99823a3d4f | ||
|
|
c0bdd464f6 | ||
|
|
7e7377f4f7 | ||
|
|
15448765dd | ||
|
|
f0f1c3144b | ||
|
|
630da4212d | ||
|
|
38bc24bb9e | ||
|
|
012285337b | ||
|
|
3ba4f62cef | ||
|
|
b4996769c1 | ||
|
|
9a6b1f53da | ||
|
|
ef45828ca7 | ||
|
|
5ab799bbf8 |
@@ -41,7 +41,7 @@ ssr = [
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
]
|
||||
stable = ["leptos/stable", "leptos_router/stable"]
|
||||
nightly = ["leptos/nightly", "leptos_router/nightly"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["actix-files", "actix-web", "leptos_actix", "stable"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use leptos::{For, ForProps, *};
|
||||
use leptos::{For, *};
|
||||
|
||||
const MANY_COUNTERS: usize = 1000;
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
extend = [{ path = "../cargo-make/common.toml" }]
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use crate::{
|
||||
error_template::{ErrorTemplate, ErrorTemplateProps},
|
||||
errors::AppError,
|
||||
};
|
||||
use crate::{error_template::ErrorTemplate, errors::AppError};
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
@@ -54,7 +51,8 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
|
||||
#[component]
|
||||
pub fn ExampleErrors(cx: Scope) -> impl IntoView {
|
||||
let generate_internal_error = create_server_action::<CauseInternalServerError>(cx);
|
||||
let generate_internal_error =
|
||||
create_server_action::<CauseInternalServerError>(cx);
|
||||
|
||||
view! { cx,
|
||||
<p>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
pub mod error_template;
|
||||
pub mod errors;
|
||||
pub mod fallback;
|
||||
@@ -8,6 +7,7 @@ pub mod landing;
|
||||
// Needs to be in lib.rs AFAIK because wasm-bindgen needs us to be compiling a lib. I may be wrong.
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "hydrate")] {
|
||||
use leptos::*;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use crate::landing::*;
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@ async fn custom_handler(
|
||||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
|
||||
simple_logger::init_with_level(log::Level::Debug)
|
||||
.expect("couldn't initialize logging");
|
||||
|
||||
crate::landing::register_server_functions();
|
||||
|
||||
@@ -51,7 +52,11 @@ async fn main() {
|
||||
let app = Router::new()
|
||||
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
|
||||
.route("/special/:id", get(custom_handler))
|
||||
.leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, <App/> })
|
||||
.leptos_routes(
|
||||
leptos_options.clone(),
|
||||
routes,
|
||||
|cx| view! { cx, <App/> },
|
||||
)
|
||||
.fallback(file_and_error_handler)
|
||||
.layer(Extension(Arc::new(leptos_options)));
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
extend = [{ path = "../cargo-make/common.toml" }]
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -14,7 +14,7 @@ pub enum FetchError {
|
||||
#[error("Error loading data from serving.")]
|
||||
Request,
|
||||
#[error("Error deserializaing cat data from request.")]
|
||||
Json
|
||||
Json,
|
||||
}
|
||||
|
||||
async fn fetch_cats(count: u32) -> Result<Vec<String>, FetchError> {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
extend = [{ path = "../cargo-make/common.toml" }]
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -4,10 +4,7 @@ use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
mod api;
|
||||
mod routes;
|
||||
use routes::nav::*;
|
||||
use routes::stories::*;
|
||||
use routes::story::*;
|
||||
use routes::users::*;
|
||||
use routes::{nav::*, stories::*, story::*, users::*};
|
||||
|
||||
#[component]
|
||||
pub fn App(cx: Scope) -> impl IntoView {
|
||||
|
||||
@@ -7,7 +7,7 @@ cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use actix_files::{Files};
|
||||
use actix_web::*;
|
||||
use hackernews::{App,AppProps};
|
||||
use hackernews::{App};
|
||||
use leptos_actix::{LeptosRoutes, generate_route_list};
|
||||
|
||||
#[get("/style.css")]
|
||||
@@ -46,7 +46,7 @@ cfg_if! {
|
||||
}
|
||||
} else {
|
||||
fn main() {
|
||||
use hackernews::{App, AppProps};
|
||||
use hackernews::{App};
|
||||
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::api;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
use crate::api;
|
||||
|
||||
fn category(from: &str) -> &'static str {
|
||||
match from {
|
||||
"new" => "newest",
|
||||
@@ -37,8 +36,10 @@ pub fn Stories(cx: Scope) -> impl IntoView {
|
||||
);
|
||||
let (pending, set_pending) = create_signal(cx, false);
|
||||
|
||||
let hide_more_link =
|
||||
move |cx| pending() || stories.read(cx).unwrap_or(None).unwrap_or_default().len() < 28;
|
||||
let hide_more_link = move |cx| {
|
||||
pending()
|
||||
|| stories.read(cx).unwrap_or(None).unwrap_or_default().len() < 28
|
||||
};
|
||||
|
||||
view! {
|
||||
cx,
|
||||
|
||||
@@ -13,11 +13,20 @@ pub fn Story(cx: Scope) -> impl IntoView {
|
||||
if id.is_empty() {
|
||||
None
|
||||
} else {
|
||||
api::fetch_api::<api::Story>(cx, &api::story(&format!("item/{id}"))).await
|
||||
api::fetch_api::<api::Story>(
|
||||
cx,
|
||||
&api::story(&format!("item/{id}")),
|
||||
)
|
||||
.await
|
||||
}
|
||||
},
|
||||
);
|
||||
let meta_description = move || story.read(cx).and_then(|story| story.map(|story| story.title)).unwrap_or_else(|| "Loading story...".to_string());
|
||||
let meta_description = move || {
|
||||
story
|
||||
.read(cx)
|
||||
.and_then(|story| story.map(|story| story.title))
|
||||
.unwrap_or_else(|| "Loading story...".to_string())
|
||||
};
|
||||
|
||||
view! { cx,
|
||||
<>
|
||||
|
||||
@@ -31,7 +31,7 @@ pub fn User(cx: Scope) -> impl IntoView {
|
||||
<li>
|
||||
<span class="label">"Karma: "</span> {user.karma}
|
||||
</li>
|
||||
{user.about.as_ref().map(|about| view! { cx, <li inner_html=about class="about"></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>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
extend = [{ path = "../cargo-make/common.toml" }]
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use leptos::{
|
||||
signal_prelude::*, view, Errors, For, ForProps, IntoView, RwSignal, Scope,
|
||||
View,
|
||||
};
|
||||
use leptos::{view, Errors, For, IntoView, RwSignal, Scope, View};
|
||||
|
||||
// A basic function to display errors served by the error boundaries. Feel free to do more complicated things
|
||||
// here than just displaying them
|
||||
|
||||
@@ -7,10 +7,7 @@ pub mod error_template;
|
||||
pub mod fallback;
|
||||
pub mod handlers;
|
||||
mod routes;
|
||||
use routes::nav::*;
|
||||
use routes::stories::*;
|
||||
use routes::story::*;
|
||||
use routes::users::*;
|
||||
use routes::{nav::*, stories::*, story::*, users::*};
|
||||
|
||||
#[component]
|
||||
pub fn App(cx: Scope) -> impl IntoView {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use leptos::{component, Scope, IntoView, view};
|
||||
use leptos::{component, view, IntoView, Scope};
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::api;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
use crate::api;
|
||||
|
||||
fn category(from: &str) -> &'static str {
|
||||
match from {
|
||||
"new" => "newest",
|
||||
@@ -37,8 +36,10 @@ pub fn Stories(cx: Scope) -> impl IntoView {
|
||||
);
|
||||
let (pending, set_pending) = create_signal(cx, false);
|
||||
|
||||
let hide_more_link =
|
||||
move || pending() || stories.read(cx).unwrap_or(None).unwrap_or_default().len() < 28;
|
||||
let hide_more_link = move || {
|
||||
pending()
|
||||
|| stories.read(cx).unwrap_or(None).unwrap_or_default().len() < 28
|
||||
};
|
||||
|
||||
view! {
|
||||
cx,
|
||||
|
||||
@@ -13,11 +13,20 @@ pub fn Story(cx: Scope) -> impl IntoView {
|
||||
if id.is_empty() {
|
||||
None
|
||||
} else {
|
||||
api::fetch_api::<api::Story>(cx, &api::story(&format!("item/{id}"))).await
|
||||
api::fetch_api::<api::Story>(
|
||||
cx,
|
||||
&api::story(&format!("item/{id}")),
|
||||
)
|
||||
.await
|
||||
}
|
||||
},
|
||||
);
|
||||
let meta_description = move || story.read(cx).and_then(|story| story.map(|story| story.title)).unwrap_or_else(|| "Loading story...".to_string());
|
||||
let meta_description = move || {
|
||||
story
|
||||
.read(cx)
|
||||
.and_then(|story| story.map(|story| story.title))
|
||||
.unwrap_or_else(|| "Loading story...".to_string())
|
||||
};
|
||||
|
||||
view! { cx,
|
||||
<>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
extend = { path = "../cargo-make/common.toml" }
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
extend = { path = "../../cargo-make/common.toml" }
|
||||
1
examples/login_with_token_csr_only/client/Makefile.toml
Normal file
1
examples/login_with_token_csr_only/client/Makefile.toml
Normal file
@@ -0,0 +1 @@
|
||||
extend = { path = "../../cargo-make/common.toml" }
|
||||
@@ -1,9 +1,8 @@
|
||||
use api_boundary::*;
|
||||
use gloo_net::http::{Request, Response};
|
||||
use serde::de::DeserializeOwned;
|
||||
use thiserror::Error;
|
||||
|
||||
use api_boundary::*;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct UnauthorizedApi {
|
||||
url: &'static str,
|
||||
|
||||
@@ -20,54 +20,56 @@ pub fn CredentialsForm(
|
||||
});
|
||||
|
||||
view! { cx,
|
||||
<form on:submit=|ev|ev.prevent_default()>
|
||||
<p>{ title }</p>
|
||||
{move || error.get().map(|err| view!{ cx,
|
||||
<p style ="color:red;" >{ err }</p>
|
||||
})}
|
||||
<input
|
||||
type = "email"
|
||||
required
|
||||
placeholder = "Email address"
|
||||
prop:disabled = move || disabled.get()
|
||||
on:keyup = move |ev: ev::KeyboardEvent| {
|
||||
let val = event_target_value(&ev);
|
||||
set_email.update(|v|*v = val);
|
||||
}
|
||||
// The `change` event fires when the browser fills the form automatically,
|
||||
on:change = move |ev| {
|
||||
let val = event_target_value(&ev);
|
||||
set_email.update(|v|*v = val);
|
||||
}
|
||||
/>
|
||||
<input
|
||||
type = "password"
|
||||
required
|
||||
placeholder = "Password"
|
||||
prop:disabled = move || disabled.get()
|
||||
on:keyup = move |ev: ev::KeyboardEvent| {
|
||||
match &*ev.key() {
|
||||
"Enter" => {
|
||||
dispatch_action();
|
||||
<form on:submit=|ev| ev.prevent_default()>
|
||||
<p>{title}</p>
|
||||
{move || {
|
||||
error
|
||||
.get()
|
||||
.map(|err| {
|
||||
view! { cx, <p style="color:red;">{err}</p> }
|
||||
})
|
||||
}}
|
||||
<input
|
||||
type="email"
|
||||
required
|
||||
placeholder="Email address"
|
||||
prop:disabled=move || disabled.get()
|
||||
on:keyup=move |ev: ev::KeyboardEvent| {
|
||||
let val = event_target_value(&ev);
|
||||
set_email.update(|v| *v = val);
|
||||
}
|
||||
_=> {
|
||||
let val = event_target_value(&ev);
|
||||
set_password.update(|p|*p = val);
|
||||
on:change=move |ev| {
|
||||
let val = event_target_value(&ev);
|
||||
set_email.update(|v| *v = val);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The `change` event fires when the browser fills the form automatically,
|
||||
on:change = move |ev| {
|
||||
let val = event_target_value(&ev);
|
||||
set_password.update(|p|*p = val);
|
||||
}
|
||||
/>
|
||||
<button
|
||||
prop:disabled = move || button_is_disabled.get()
|
||||
on:click = move |_| dispatch_action()
|
||||
>
|
||||
{ action_label }
|
||||
</button>
|
||||
</form>
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
required
|
||||
placeholder="Password"
|
||||
prop:disabled=move || disabled.get()
|
||||
on:keyup=move |ev: ev::KeyboardEvent| {
|
||||
match &*ev.key() {
|
||||
"Enter" => {
|
||||
dispatch_action();
|
||||
}
|
||||
_ => {
|
||||
let val = event_target_value(&ev);
|
||||
set_password.update(|p| *p = val);
|
||||
}
|
||||
}
|
||||
}
|
||||
on:change=move |ev| {
|
||||
let val = event_target_value(&ev);
|
||||
set_password.update(|p| *p = val);
|
||||
}
|
||||
/>
|
||||
<button
|
||||
prop:disabled=move || button_is_disabled.get()
|
||||
on:click=move |_| dispatch_action()
|
||||
>
|
||||
{action_label}
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::Page;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
use crate::Page;
|
||||
|
||||
#[component]
|
||||
pub fn NavBar<F>(
|
||||
cx: Scope,
|
||||
@@ -13,20 +12,27 @@ where
|
||||
F: Fn() + 'static + Clone,
|
||||
{
|
||||
view! { cx,
|
||||
<nav>
|
||||
<Show
|
||||
when = move || logged_in.get()
|
||||
fallback = |cx| view! { cx,
|
||||
<A href=Page::Login.path() >"Login"</A>
|
||||
" | "
|
||||
<A href=Page::Register.path() >"Register"</A>
|
||||
}
|
||||
>
|
||||
<a href="#" on:click={
|
||||
let on_logout = on_logout.clone();
|
||||
move |_| on_logout()
|
||||
}>"Logout"</a>
|
||||
</Show>
|
||||
</nav>
|
||||
<nav>
|
||||
<Show
|
||||
when=move || logged_in.get()
|
||||
fallback=|cx| {
|
||||
view! { cx,
|
||||
<A href=Page::Login.path()>"Login"</A>
|
||||
" | "
|
||||
<A href=Page::Register.path()>"Register"</A>
|
||||
}
|
||||
}
|
||||
>
|
||||
<a
|
||||
href="#"
|
||||
on:click={
|
||||
let on_logout = on_logout.clone();
|
||||
move |_| on_logout()
|
||||
}
|
||||
>
|
||||
"Logout"
|
||||
</a>
|
||||
</Show>
|
||||
</nav>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use api_boundary::*;
|
||||
use gloo_storage::{LocalStorage, Storage};
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
use api_boundary::*;
|
||||
|
||||
mod api;
|
||||
mod components;
|
||||
mod pages;
|
||||
@@ -86,45 +85,51 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
.expect("LocalStorage::set");
|
||||
}
|
||||
None => {
|
||||
log::debug!("API is no longer authorized: delete token from LocalStorage");
|
||||
log::debug!(
|
||||
"API is no longer authorized: delete token from \
|
||||
LocalStorage"
|
||||
);
|
||||
LocalStorage::delete(API_TOKEN_STORAGE_KEY);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
view! { cx,
|
||||
<Router>
|
||||
<NavBar logged_in on_logout />
|
||||
<main>
|
||||
<Routes>
|
||||
<Route
|
||||
path=Page::Home.path()
|
||||
view=move |cx| view! { cx,
|
||||
<Home user_info = user_info.into() />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path=Page::Login.path()
|
||||
view=move |cx| view! { cx,
|
||||
<Login
|
||||
api = unauthorized_api
|
||||
on_success = move |api| {
|
||||
log::info!("Successfully logged in");
|
||||
authorized_api.update(|v| *v = Some(api));
|
||||
let navigate = use_navigate(cx);
|
||||
navigate(Page::Home.path(), Default::default()).expect("Home route");
|
||||
fetch_user_info.dispatch(());
|
||||
} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path=Page::Register.path()
|
||||
view=move |cx| view! { cx,
|
||||
<Register api = unauthorized_api />
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
<Router>
|
||||
<NavBar logged_in on_logout/>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route
|
||||
path=Page::Home.path()
|
||||
view=move |cx| {
|
||||
view! { cx, <Home user_info=user_info.into()/> }
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path=Page::Login.path()
|
||||
view=move |cx| {
|
||||
view! { cx,
|
||||
<Login
|
||||
api=unauthorized_api
|
||||
on_success=move |api| {
|
||||
log::info!("Successfully logged in");
|
||||
authorized_api.update(|v| *v = Some(api));
|
||||
let navigate = use_navigate(cx);
|
||||
navigate(Page::Home.path(), Default::default()).expect("Home route");
|
||||
fetch_user_info.dispatch(());
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path=Page::Register.path()
|
||||
view=move |cx| {
|
||||
view! { cx, <Register api=unauthorized_api/> }
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use leptos::*;
|
||||
|
||||
use client::*;
|
||||
use leptos::*;
|
||||
|
||||
pub fn main() {
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
mount_to_body(|cx| view! { cx, <App /> })
|
||||
mount_to_body(|cx| view! { cx, <App/> })
|
||||
}
|
||||
|
||||
@@ -6,15 +6,19 @@ use leptos_router::*;
|
||||
#[component]
|
||||
pub fn Home(cx: Scope, user_info: Signal<Option<UserInfo>>) -> impl IntoView {
|
||||
view! { cx,
|
||||
<h2>"Leptos Login example"</h2>
|
||||
{move || match user_info.get() {
|
||||
Some(info) => view!{ cx,
|
||||
<p>"You are logged in with "{ info.email }"."</p>
|
||||
}.into_view(cx),
|
||||
None => view!{ cx,
|
||||
<p>"You are not logged in."</p>
|
||||
<A href=Page::Login.path() >"Login now."</A>
|
||||
}.into_view(cx)
|
||||
}}
|
||||
<h2>"Leptos Login example"</h2>
|
||||
{move || match user_info.get() {
|
||||
Some(info) => {
|
||||
view! { cx, <p>"You are logged in with " {info.email} "."</p> }
|
||||
.into_view(cx)
|
||||
}
|
||||
None => {
|
||||
view! { cx,
|
||||
<p>"You are not logged in."</p>
|
||||
<A href=Page::Login.path()>"Login now."</A>
|
||||
}
|
||||
.into_view(cx)
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
use api_boundary::*;
|
||||
|
||||
use crate::{
|
||||
api::{self, AuthorizedApi, UnauthorizedApi},
|
||||
components::credentials::*,
|
||||
Page,
|
||||
};
|
||||
use api_boundary::*;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn Login<F>(cx: Scope, api: UnauthorizedApi, on_success: F) -> impl IntoView
|
||||
@@ -53,14 +51,14 @@ where
|
||||
let disabled = Signal::derive(cx, move || wait_for_response.get());
|
||||
|
||||
view! { cx,
|
||||
<CredentialsForm
|
||||
title = "Please login to your account"
|
||||
action_label = "Login"
|
||||
action = login_action
|
||||
error = login_error.into()
|
||||
disabled
|
||||
/>
|
||||
<p>"Don't have an account?"</p>
|
||||
<A href=Page::Register.path()>"Register"</A>
|
||||
<CredentialsForm
|
||||
title="Please login to your account"
|
||||
action_label="Login"
|
||||
action=login_action
|
||||
error=login_error.into()
|
||||
disabled
|
||||
/>
|
||||
<p>"Don't have an account?"</p>
|
||||
<A href=Page::Register.path()>"Register"</A>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
use api_boundary::*;
|
||||
|
||||
use crate::{
|
||||
api::{self, UnauthorizedApi},
|
||||
components::credentials::*,
|
||||
Page,
|
||||
};
|
||||
use api_boundary::*;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn Register(cx: Scope, api: UnauthorizedApi) -> impl IntoView {
|
||||
@@ -52,26 +50,24 @@ pub fn Register(cx: Scope, api: UnauthorizedApi) -> impl IntoView {
|
||||
let disabled = Signal::derive(cx, move || wait_for_response.get());
|
||||
|
||||
view! { cx,
|
||||
<Show
|
||||
when = move || register_response.get().is_some()
|
||||
fallback = move |_| view!{ cx,
|
||||
<CredentialsForm
|
||||
title = "Please enter the desired credentials"
|
||||
action_label = "Register"
|
||||
action = register_action
|
||||
error = register_error.into()
|
||||
disabled
|
||||
/>
|
||||
<p>"Your already have an account?"</p>
|
||||
<A href=Page::Login.path()>"Login"</A>
|
||||
}
|
||||
>
|
||||
<p>"You have successfully registered."</p>
|
||||
<p>
|
||||
"You can now "
|
||||
<A href=Page::Login.path()>"login"</A>
|
||||
" with your new account."
|
||||
</p>
|
||||
</Show>
|
||||
<Show
|
||||
when=move || register_response.get().is_some()
|
||||
fallback=move |_| {
|
||||
view! { cx,
|
||||
<CredentialsForm
|
||||
title="Please enter the desired credentials"
|
||||
action_label="Register"
|
||||
action=register_action
|
||||
error=register_error.into()
|
||||
disabled
|
||||
/>
|
||||
<p>"Your already have an account?"</p>
|
||||
<A href=Page::Login.path()>"Login"</A>
|
||||
}
|
||||
}
|
||||
>
|
||||
<p>"You have successfully registered."</p>
|
||||
<p>"You can now " <A href=Page::Login.path()>"login"</A> " with your new account."</p>
|
||||
</Show>
|
||||
}
|
||||
}
|
||||
|
||||
1
examples/login_with_token_csr_only/server/Makefile.toml
Normal file
1
examples/login_with_token_csr_only/server/Makefile.toml
Normal file
@@ -0,0 +1 @@
|
||||
extend = { path = "../../cargo-make/common.toml" }
|
||||
@@ -2,8 +2,7 @@ use crate::{application::*, Error};
|
||||
use api_boundary as json;
|
||||
use axum::{
|
||||
http::StatusCode,
|
||||
response::Json,
|
||||
response::{IntoResponse, Response},
|
||||
response::{IntoResponse, Json, Response},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::{env, sync::Arc};
|
||||
|
||||
use api_boundary as json;
|
||||
use axum::{
|
||||
extract::{State, TypedHeader},
|
||||
headers::{authorization::Bearer, Authorization},
|
||||
@@ -8,10 +7,9 @@ use axum::{
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
use std::{env, sync::Arc};
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
|
||||
use api_boundary as json;
|
||||
|
||||
mod adapters;
|
||||
mod application;
|
||||
|
||||
@@ -25,7 +23,10 @@ async fn main() -> anyhow::Result<()> {
|
||||
env::set_var("RUST_LOG", "debug");
|
||||
}
|
||||
env::VarError::NotUnicode(_) => {
|
||||
return Err(anyhow::anyhow!("The value of 'RUST_LOG' does not contain valid unicode data."));
|
||||
return Err(anyhow::anyhow!(
|
||||
"The value of 'RUST_LOG' does not contain valid unicode \
|
||||
data."
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
extend = { path = "../cargo-make/common.toml" }
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -107,7 +107,7 @@ pub fn ContactList(cx: Scope) -> impl IntoView {
|
||||
<Suspense fallback=move || view! { cx, <p>"Loading contacts..."</p> }>
|
||||
{move || view! { cx, <ul>{contacts}</ul>}}
|
||||
</Suspense>
|
||||
<AnimatedOutlet
|
||||
<AnimatedOutlet
|
||||
class="outlet"
|
||||
outro="fadeOut"
|
||||
intro="fadeIn"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
extend = { path = "../cargo-make/common.toml" }
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
@@ -168,7 +166,9 @@ pub async fn login(
|
||||
.ok_or("User does not exist.")
|
||||
.map_err(|e| ServerFnError::ServerError(e.to_string()))?;
|
||||
|
||||
match verify(password, &user.password).map_err(|e| ServerFnError::ServerError(e.to_string()))? {
|
||||
match verify(password, &user.password)
|
||||
.map_err(|e| ServerFnError::ServerError(e.to_string()))?
|
||||
{
|
||||
true => {
|
||||
auth.login_user(user.id);
|
||||
auth.remember_user(remember.is_some());
|
||||
|
||||
@@ -13,7 +13,9 @@ impl TodoAppError {
|
||||
pub fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
TodoAppError::NotFound => StatusCode::NOT_FOUND,
|
||||
TodoAppError::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
TodoAppError::InternalServerError => {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::auth::*;
|
||||
use crate::error_template::{ErrorTemplate, ErrorTemplateProps};
|
||||
use crate::{auth::*, error_template::ErrorTemplate};
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
@@ -73,7 +72,8 @@ pub async fn get_todos(cx: Scope) -> Result<Vec<Todo>, ServerFnError> {
|
||||
let pool = pool(cx)?;
|
||||
|
||||
let mut todos = Vec::new();
|
||||
let mut rows = sqlx::query_as::<_, SqlTodo>("SELECT * FROM todos").fetch(&pool);
|
||||
let mut rows =
|
||||
sqlx::query_as::<_, SqlTodo>("SELECT * FROM todos").fetch(&pool);
|
||||
|
||||
while let Some(row) = rows
|
||||
.try_next()
|
||||
@@ -111,11 +111,13 @@ pub async fn add_todo(cx: Scope, title: String) -> Result<(), ServerFnError> {
|
||||
// fake API delay
|
||||
std::thread::sleep(std::time::Duration::from_millis(1250));
|
||||
|
||||
match sqlx::query("INSERT INTO todos (title, user_id, completed) VALUES (?, ?, false)")
|
||||
.bind(title)
|
||||
.bind(id)
|
||||
.execute(&pool)
|
||||
.await
|
||||
match sqlx::query(
|
||||
"INSERT INTO todos (title, user_id, completed) VALUES (?, ?, false)",
|
||||
)
|
||||
.bind(title)
|
||||
.bind(id)
|
||||
.execute(&pool)
|
||||
.await
|
||||
{
|
||||
Ok(_row) => Ok(()),
|
||||
Err(e) => Err(ServerFnError::ServerError(e.to_string())),
|
||||
@@ -304,7 +306,10 @@ pub fn Todos(cx: Scope) -> impl IntoView {
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Login(cx: Scope, action: Action<Login, Result<(), ServerFnError>>) -> impl IntoView {
|
||||
pub fn Login(
|
||||
cx: Scope,
|
||||
action: Action<Login, Result<(), ServerFnError>>,
|
||||
) -> impl IntoView {
|
||||
view! {
|
||||
cx,
|
||||
<ActionForm action=action>
|
||||
@@ -330,7 +335,10 @@ pub fn Login(cx: Scope, action: Action<Login, Result<(), ServerFnError>>) -> imp
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Signup(cx: Scope, action: Action<Signup, Result<(), ServerFnError>>) -> impl IntoView {
|
||||
pub fn Signup(
|
||||
cx: Scope,
|
||||
action: Action<Signup, Result<(), ServerFnError>>,
|
||||
) -> impl IntoView {
|
||||
view! {
|
||||
cx,
|
||||
<ActionForm action=action>
|
||||
@@ -362,7 +370,10 @@ pub fn Signup(cx: Scope, action: Action<Signup, Result<(), ServerFnError>>) -> i
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Logout(cx: Scope, action: Action<Logout, Result<(), ServerFnError>>) -> impl IntoView {
|
||||
pub fn Logout(
|
||||
cx: Scope,
|
||||
action: Action<Logout, Result<(), ServerFnError>>,
|
||||
) -> impl IntoView {
|
||||
view! {
|
||||
cx,
|
||||
<div id="loginbox">
|
||||
|
||||
@@ -8,7 +8,7 @@ crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
actix-files = { version = "0.6.2", optional = true }
|
||||
actix-web = { version = "4.2.1", features = ["macros"] }
|
||||
actix-web = { version = "4.2.1", optional = true, features = ["macros"] }
|
||||
anyhow = "1.0.68"
|
||||
broadcaster = "1.0.0"
|
||||
console_log = "1.0.0"
|
||||
@@ -19,7 +19,7 @@ cfg-if = "1.0.0"
|
||||
leptos = { path = "../../leptos", default-features = false, features = [
|
||||
"serde",
|
||||
] }
|
||||
leptos_actix = { path = "../../integrations/actix" }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_meta = { path = "../../meta", default-features = false }
|
||||
leptos_router = { path = "../../router", default-features = false }
|
||||
log = "0.4.17"
|
||||
@@ -36,8 +36,10 @@ default = ["ssr"]
|
||||
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
|
||||
ssr = [
|
||||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
"dep:sqlx",
|
||||
"leptos/ssr",
|
||||
"leptos_actix",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
extend = { path = "../cargo-make/common.toml" }
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
pub mod todo;
|
||||
|
||||
// Needs to be in lib.rs AFAIK because wasm-bindgen needs us to be compiling a lib. I may be wrong.
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "hydrate")] {
|
||||
use leptos::*;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use crate::todo::*;
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ cfg_if! {
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars.
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
|
||||
let addr = conf.leptos_options.site_addr.clone();
|
||||
let addr = conf.leptos_options.site_addr;
|
||||
|
||||
// Generate the list of routes in your Leptos App
|
||||
let routes = generate_route_list(|cx| view! { cx, <TodoApp/> });
|
||||
@@ -43,7 +43,7 @@ cfg_if! {
|
||||
.service(css)
|
||||
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||
.leptos_routes(leptos_options.to_owned(), routes.to_owned(), |cx| view! { cx, <TodoApp/> })
|
||||
.service(Files::new("/", &site_root))
|
||||
.service(Files::new("/", site_root))
|
||||
//.wrap(middleware::Compress::default())
|
||||
})
|
||||
.bind(addr)?
|
||||
|
||||
@@ -9,7 +9,7 @@ cfg_if! {
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
|
||||
pub async fn db() -> Result<SqliteConnection, ServerFnError> {
|
||||
Ok(SqliteConnection::connect("sqlite:Todos.db").await.map_err(|e| ServerFnError::ServerError(e.to_string()))?)
|
||||
SqliteConnection::connect("sqlite:Todos.db").await.map_err(|e| ServerFnError::ServerError(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn register_server_functions() {
|
||||
@@ -37,18 +37,18 @@ cfg_if! {
|
||||
#[server(GetTodos, "/api")]
|
||||
pub async fn get_todos(cx: Scope) -> Result<Vec<Todo>, ServerFnError> {
|
||||
// this is just an example of how to access server context injected in the handlers
|
||||
let req =
|
||||
use_context::<actix_web::HttpRequest>(cx);
|
||||
|
||||
if let Some(req) = req{
|
||||
println!("req.path = {:#?}", req.path());
|
||||
let req = use_context::<actix_web::HttpRequest>(cx);
|
||||
|
||||
if let Some(req) = req {
|
||||
println!("req.path = {:#?}", req.path());
|
||||
}
|
||||
use futures::TryStreamExt;
|
||||
|
||||
let mut conn = db().await?;
|
||||
|
||||
let mut todos = Vec::new();
|
||||
let mut rows = sqlx::query_as::<_, Todo>("SELECT * FROM todos").fetch(&mut conn);
|
||||
let mut rows =
|
||||
sqlx::query_as::<_, Todo>("SELECT * FROM todos").fetch(&mut conn);
|
||||
while let Some(row) = rows
|
||||
.try_next()
|
||||
.await
|
||||
@@ -73,7 +73,7 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
{
|
||||
Ok(row) => Ok(()),
|
||||
Ok(_row) => Ok(()),
|
||||
Err(e) => Err(ServerFnError::ServerError(e.to_string())),
|
||||
}
|
||||
}
|
||||
@@ -107,9 +107,6 @@ pub fn TodoApp(cx: Scope) -> impl IntoView {
|
||||
cx,
|
||||
<Todos/>
|
||||
}/>
|
||||
<Api path="bananas" route=web::get().to(|req: HttpRequest| async move {
|
||||
req.path()
|
||||
})
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
@@ -133,7 +130,7 @@ pub fn Todos(cx: Scope) -> impl IntoView {
|
||||
cx,
|
||||
<div>
|
||||
<MultiActionForm
|
||||
// we can handle client-side validation in the on:submit event
|
||||
// we can handle client-side validation in the on:submit event
|
||||
// leptos_router implements a `FromFormData` trait that lets you
|
||||
// parse deserializable types from form data and check them
|
||||
on:submit=move |ev| {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
extend = { path = "../cargo-make/common.toml" }
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
pub mod error_template;
|
||||
pub mod errors;
|
||||
pub mod fallback;
|
||||
@@ -8,6 +7,7 @@ pub mod todo;
|
||||
// Needs to be in lib.rs AFAIK because wasm-bindgen needs us to be compiling a lib. I may be wrong.
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "hydrate")] {
|
||||
use leptos::*;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use crate::todo::*;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
// boilerplate to run in different modes
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use leptos::*;
|
||||
use axum::{
|
||||
routing::{post, get},
|
||||
extract::{Extension, Path},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::error_template::{ErrorTemplate, ErrorTemplateProps};
|
||||
use crate::error_template::ErrorTemplate;
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
extend = { path = "../cargo-make/common.toml" }
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
pub mod error_template;
|
||||
pub mod errors;
|
||||
pub mod fallback;
|
||||
@@ -8,6 +7,7 @@ pub mod todo;
|
||||
// Needs to be in lib.rs AFAIK because wasm-bindgen needs us to be compiling a lib. I may be wrong.
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "hydrate")] {
|
||||
use leptos::*;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use crate::todo::*;
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
|
||||
// boilerplate to run in different modes
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use leptos::*;
|
||||
use crate::fallback::file_and_error_handler;
|
||||
use crate::todo::*;
|
||||
use leptos_viz::{generate_route_list, LeptosRoutes};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::error_template::{ErrorTemplate, ErrorTemplateProps};
|
||||
use crate::error_template::ErrorTemplate;
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
extend = { path = "../cargo-make/common.toml" }
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -136,7 +136,7 @@ pub fn TodoMVC(cx: Scope) -> impl IntoView {
|
||||
|
||||
// Handle the three filter modes: All, Active, and Completed
|
||||
let (mode, set_mode) = create_signal(cx, Mode::All);
|
||||
window_event_listener("hashchange", move |_| {
|
||||
window_event_listener_untyped("hashchange", move |_| {
|
||||
let new_mode =
|
||||
location_hash().map(|hash| route(&hash)).unwrap_or_default();
|
||||
set_mode(new_mode);
|
||||
@@ -202,15 +202,15 @@ pub fn TodoMVC(cx: Scope) -> impl IntoView {
|
||||
}
|
||||
});
|
||||
|
||||
// focus the main input on load
|
||||
// focus the main input on load
|
||||
create_effect(cx, move |_| {
|
||||
if let Some(input) = input_ref.get() {
|
||||
// We use request_animation_frame here because the NodeRef
|
||||
// is filled when the element is created, but before it's mounted
|
||||
// We use request_animation_frame here because the NodeRef
|
||||
// is filled when the element is created, but before it's mounted
|
||||
// to the DOM. Calling .focus() before it's mounted does nothing.
|
||||
// So inside, we wait a tick for the browser to mount it, then .focus()
|
||||
request_animation_frame(move || {
|
||||
input.focus();
|
||||
let _ = input.focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -348,19 +348,14 @@ pub fn Todo(cx: Scope, todo: Todo) -> impl IntoView {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Mode {
|
||||
Active,
|
||||
Completed,
|
||||
#[default]
|
||||
All,
|
||||
}
|
||||
|
||||
impl Default for Mode {
|
||||
fn default() -> Self {
|
||||
Mode::All
|
||||
}
|
||||
}
|
||||
|
||||
pub fn route(hash: &str) -> Mode {
|
||||
match hash {
|
||||
"/active" => Mode::Active,
|
||||
|
||||
@@ -1,33 +1,27 @@
|
||||
use crate::Todo;
|
||||
use leptos::{
|
||||
signal_prelude::*,
|
||||
Scope,
|
||||
};
|
||||
use serde::{
|
||||
Deserialize,
|
||||
Serialize,
|
||||
};
|
||||
use leptos::{signal_prelude::*, Scope};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct TodoSerialized {
|
||||
pub id: Uuid,
|
||||
pub title: String,
|
||||
pub completed: bool,
|
||||
pub id: Uuid,
|
||||
pub title: String,
|
||||
pub completed: bool,
|
||||
}
|
||||
|
||||
impl TodoSerialized {
|
||||
pub fn into_todo(self, cx: Scope) -> Todo {
|
||||
Todo::new_with_completed(cx, self.id, self.title, self.completed)
|
||||
}
|
||||
pub fn into_todo(self, cx: Scope) -> Todo {
|
||||
Todo::new_with_completed(cx, self.id, self.title, self.completed)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Todo> for TodoSerialized {
|
||||
fn from(todo: &Todo) -> Self {
|
||||
Self {
|
||||
id: todo.id,
|
||||
title: todo.title.get(),
|
||||
completed: todo.completed.get(),
|
||||
fn from(todo: &Todo) -> Self {
|
||||
Self {
|
||||
id: todo.id,
|
||||
title: todo.title.get(),
|
||||
completed: todo.completed.get(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use actix_web::{
|
||||
use futures::{Stream, StreamExt};
|
||||
use http::StatusCode;
|
||||
use leptos::{
|
||||
leptos_dom::{Transparent, ssr::render_to_stream_with_prefix_undisposed_with_context},
|
||||
leptos_dom::ssr::render_to_stream_with_prefix_undisposed_with_context,
|
||||
leptos_server::{server_fn_by_path, Payload},
|
||||
server_fn::Encoding,
|
||||
*,
|
||||
@@ -922,7 +922,7 @@ pub fn generate_route_list_with_exclusions<IV>(
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
let (mut routes, mut api_routes) = leptos_router::generate_route_list_inner(app_fn);
|
||||
let mut routes = leptos_router::generate_route_list_inner(app_fn);
|
||||
|
||||
// Empty strings screw with Actix pathing, they need to be "/"
|
||||
routes = routes
|
||||
@@ -1113,14 +1113,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines an API route, which mounts the given route handler at this path.
|
||||
#[component(transparent)]
|
||||
pub fn Api<P>(cx: leptos::Scope, path: P, route: actix_web::Route) -> impl IntoView
|
||||
where P: Into<String>
|
||||
{
|
||||
Transparent::new(ApiRouteListing::new(path.into(), route))
|
||||
}
|
||||
|
||||
/// A helper to make it easier to use Axum extractors in server functions. This takes
|
||||
/// a handler function as its argument. The handler follows similar rules to an Actix
|
||||
/// [Handler](actix_web::Handler): it is an async function that receives arguments that
|
||||
|
||||
@@ -1068,9 +1068,9 @@ where
|
||||
let path = listing.path();
|
||||
if path.is_empty() {
|
||||
RouteListing::new(
|
||||
"/",
|
||||
Default::default(),
|
||||
[leptos_router::Method::Get],
|
||||
"/".to_string(),
|
||||
listing.mode(),
|
||||
listing.methods(),
|
||||
)
|
||||
} else {
|
||||
listing
|
||||
|
||||
@@ -992,9 +992,9 @@ where
|
||||
let path = listing.path();
|
||||
if path.is_empty() {
|
||||
RouteListing::new(
|
||||
"/",
|
||||
Default::default(),
|
||||
[leptos_router::Method::Get],
|
||||
"/".to_string(),
|
||||
listing.mode(),
|
||||
listing.methods(),
|
||||
)
|
||||
} else {
|
||||
listing
|
||||
|
||||
@@ -23,7 +23,7 @@ server_fn = { workspace = true, default-features = false }
|
||||
leptos = { path = ".", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["csr", "serde"]
|
||||
default = ["serde", "nightly"]
|
||||
csr = [
|
||||
"leptos_dom/web",
|
||||
"leptos_macro/csr",
|
||||
@@ -44,11 +44,11 @@ ssr = [
|
||||
"leptos_reactive/ssr",
|
||||
"leptos_server/ssr",
|
||||
]
|
||||
stable = [
|
||||
"leptos_dom/stable",
|
||||
"leptos_macro/stable",
|
||||
"leptos_reactive/stable",
|
||||
"leptos_server/stable",
|
||||
nightly = [
|
||||
"leptos_dom/nightly",
|
||||
"leptos_macro/nightly",
|
||||
"leptos_reactive/nightly",
|
||||
"leptos_server/nightly",
|
||||
]
|
||||
serde = ["leptos_reactive/serde"]
|
||||
serde-lite = ["leptos_reactive/serde-lite"]
|
||||
@@ -57,8 +57,10 @@ rkyv = ["leptos_reactive/rkyv"]
|
||||
tracing = ["leptos_macro/tracing"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["stable", "tracing"]
|
||||
denylist = ["tracing"]
|
||||
skip_feature_sets = [
|
||||
[
|
||||
],
|
||||
[
|
||||
"csr",
|
||||
"ssr",
|
||||
|
||||
@@ -157,7 +157,7 @@ features = [
|
||||
default = []
|
||||
web = ["leptos_reactive/csr"]
|
||||
ssr = ["leptos_reactive/ssr"]
|
||||
stable = ["leptos_reactive/stable"]
|
||||
nightly = ["leptos_reactive/nightly"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["stable"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![deny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
#![cfg_attr(not(feature = "stable"), feature(fn_traits))]
|
||||
#![cfg_attr(not(feature = "stable"), feature(unboxed_closures))]
|
||||
#![cfg_attr(feature = "nightly", feature(fn_traits))]
|
||||
#![cfg_attr(feature = "nightly", feature(unboxed_closures))]
|
||||
|
||||
//! The DOM implementation for `leptos`.
|
||||
|
||||
@@ -1125,7 +1125,7 @@ viewable_primitive![
|
||||
];
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(not(feature = "stable"))] {
|
||||
if #[cfg(feature = "nightly")] {
|
||||
viewable_primitive! {
|
||||
std::backtrace::Backtrace
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ impl<T: ElementDescriptor> Clone for NodeRef<T> {
|
||||
impl<T: ElementDescriptor + 'static> Copy for NodeRef<T> {}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(feature = "stable"))] {
|
||||
if #[cfg(feature = "nightly")] {
|
||||
impl<T: Clone + ElementDescriptor + 'static> FnOnce<()> for NodeRef<T> {
|
||||
type Output = Option<HtmlElement<T>>;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ default = ["ssr"]
|
||||
csr = []
|
||||
hydrate = []
|
||||
ssr = []
|
||||
stable = ["server_fn_macro/stable"]
|
||||
nightly = ["server_fn_macro/nightly"]
|
||||
tracing = []
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![cfg_attr(not(feature = "stable"), feature(proc_macro_span))]
|
||||
#![cfg_attr(feature = "nightly", feature(proc_macro_span))]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
#[macro_use]
|
||||
@@ -353,7 +353,7 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
||||
|
||||
fn normalized_call_site(site: proc_macro::Span) -> Option<String> {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(all(debug_assertions, not(feature = "stable")))] {
|
||||
if #[cfg(all(debug_assertions, feature = "nightly"))] {
|
||||
Some(leptos_hot_reload::span_to_stable_id(
|
||||
site.source_file().path(),
|
||||
site.into()
|
||||
|
||||
@@ -68,15 +68,16 @@ hydrate = [
|
||||
"dep:web-sys",
|
||||
]
|
||||
ssr = ["dep:tokio"]
|
||||
stable = []
|
||||
nightly = []
|
||||
serde = []
|
||||
serde-lite = ["dep:serde-lite"]
|
||||
miniserde = ["dep:miniserde"]
|
||||
rkyv = ["dep:rkyv", "dep:bytecheck"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["stable"]
|
||||
skip_feature_sets = [
|
||||
[
|
||||
],
|
||||
[
|
||||
"csr",
|
||||
"ssr",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(not(feature = "stable"), feature(fn_traits))]
|
||||
#![cfg_attr(not(feature = "stable"), feature(unboxed_closures))]
|
||||
#![cfg_attr(not(feature = "stable"), feature(type_name_of_val))]
|
||||
#![cfg_attr(feature = "nightly", feature(fn_traits))]
|
||||
#![cfg_attr(feature = "nightly", feature(unboxed_closures))]
|
||||
#![cfg_attr(feature = "nightly", feature(type_name_of_val))]
|
||||
|
||||
//! The reactive system for the [Leptos](https://docs.rs/leptos/latest/leptos/) Web framework.
|
||||
//!
|
||||
|
||||
@@ -17,7 +17,7 @@ use thiserror::Error;
|
||||
macro_rules! impl_get_fn_traits {
|
||||
($($ty:ident $(($method_name:ident))?),*) => {
|
||||
$(
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<T: Clone> FnOnce<()> for $ty<T> {
|
||||
type Output = T;
|
||||
|
||||
@@ -27,7 +27,7 @@ macro_rules! impl_get_fn_traits {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<T: Clone> FnMut<()> for $ty<T> {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
|
||||
@@ -35,7 +35,7 @@ macro_rules! impl_get_fn_traits {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<T: Clone> Fn<()> for $ty<T> {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
|
||||
@@ -55,7 +55,7 @@ macro_rules! impl_get_fn_traits {
|
||||
macro_rules! impl_set_fn_traits {
|
||||
($($ty:ident $($method_name:ident)?),*) => {
|
||||
$(
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<T> FnOnce<(T,)> for $ty<T> {
|
||||
type Output = ();
|
||||
|
||||
@@ -65,7 +65,7 @@ macro_rules! impl_set_fn_traits {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<T> FnMut<(T,)> for $ty<T> {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call_mut(&mut self, args: (T,)) -> Self::Output {
|
||||
@@ -73,7 +73,7 @@ macro_rules! impl_set_fn_traits {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<T> Fn<(T,)> for $ty<T> {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call(&self, args: (T,)) -> Self::Output {
|
||||
@@ -315,10 +315,10 @@ pub trait SignalDispose {
|
||||
/// set_count(1);
|
||||
/// assert_eq!(count(), 1);
|
||||
///
|
||||
/// // ❌ don't try to call the getter within the setter
|
||||
/// // ❌ you could call the getter within the setter
|
||||
/// // set_count(count() + 1);
|
||||
///
|
||||
/// // ✅ instead, use .update() to mutate the value in place
|
||||
/// // ✅ however it's more efficient to use .update() and mutate the value in place
|
||||
/// set_count.update(|count: &mut i32| *count += 1);
|
||||
/// assert_eq!(count(), 2);
|
||||
///
|
||||
@@ -472,10 +472,10 @@ pub fn create_signal_from_stream<T>(
|
||||
/// set_count(1);
|
||||
/// assert_eq!(count(), 1);
|
||||
///
|
||||
/// // ❌ don't try to call the getter within the setter
|
||||
/// // ❌ you could call the getter within the setter
|
||||
/// // set_count(count() + 1);
|
||||
///
|
||||
/// // ✅ instead, use .update() to mutate the value in place
|
||||
/// // ✅ however it's more efficient to use .update() and mutate the value in place
|
||||
/// set_count.update(|count: &mut i32| *count += 1);
|
||||
/// assert_eq!(count(), 2);
|
||||
///
|
||||
@@ -845,10 +845,10 @@ impl<T> Copy for ReadSignal<T> {}
|
||||
/// set_count(1);
|
||||
/// assert_eq!(count(), 1);
|
||||
///
|
||||
/// // ❌ don't try to call the getter within the setter
|
||||
/// // ❌ you could call the getter within the setter
|
||||
/// // set_count(count() + 1);
|
||||
///
|
||||
/// // ✅ instead, use .update() to mutate the value in place
|
||||
/// // ✅ however it's more efficient to use .update() and mutate the value in place
|
||||
/// set_count.update(|count: &mut i32| *count += 1);
|
||||
/// assert_eq!(count(), 2);
|
||||
/// # }).dispose();
|
||||
@@ -1103,10 +1103,10 @@ impl<T> Copy for WriteSignal<T> {}
|
||||
/// count.set(1);
|
||||
/// assert_eq!(count(), 1);
|
||||
///
|
||||
/// // ❌ don't try to call the getter within the setter
|
||||
/// // ❌ you can call the getter within the setter
|
||||
/// // count.set(count.get() + 1);
|
||||
///
|
||||
/// // ✅ instead, use .update() to mutate the value in place
|
||||
/// // ✅ however, it's more efficient to use .update() and mutate the value in place
|
||||
/// count.update(|count: &mut i32| *count += 1);
|
||||
/// assert_eq!(count(), 2);
|
||||
/// # }).dispose();
|
||||
@@ -1163,10 +1163,10 @@ pub fn create_rw_signal<T>(cx: Scope, value: T) -> RwSignal<T> {
|
||||
/// count.set(1);
|
||||
/// assert_eq!(count(), 1);
|
||||
///
|
||||
/// // ❌ don't try to call the getter within the setter
|
||||
/// // ❌ you can call the getter within the setter
|
||||
/// // count.set(count.get() + 1);
|
||||
///
|
||||
/// // ✅ instead, use .update() to mutate the value in place
|
||||
/// // ✅ however, it's more efficient to use .update() and mutate the value in place
|
||||
/// count.update(|count: &mut i32| *count += 1);
|
||||
/// assert_eq!(count(), 2);
|
||||
/// # }).dispose();
|
||||
|
||||
@@ -225,7 +225,7 @@ impl SignalSet<()> for Trigger {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
impl FnOnce<()> for Trigger {
|
||||
type Output = ();
|
||||
|
||||
@@ -235,7 +235,7 @@ impl FnOnce<()> for Trigger {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
impl FnMut<()> for Trigger {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
|
||||
@@ -243,7 +243,7 @@ impl FnMut<()> for Trigger {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
impl Fn<()> for Trigger {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
use leptos_reactive::{
|
||||
create_isomorphic_effect, create_memo, create_runtime, create_rw_signal,
|
||||
create_scope, create_signal, SignalSet,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
#[test]
|
||||
fn effect_runs() {
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
@@ -32,7 +32,7 @@ fn effect_runs() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
#[test]
|
||||
fn effect_tracks_memo() {
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
@@ -62,7 +62,7 @@ fn effect_tracks_memo() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
#[test]
|
||||
fn untrack_mutes_effect() {
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
@@ -92,7 +92,7 @@ fn untrack_mutes_effect() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
#[test]
|
||||
fn batching_actually_batches() {
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
use leptos_reactive::{
|
||||
create_memo, create_runtime, create_scope, create_signal,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
#[test]
|
||||
fn basic_memo() {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
@@ -13,7 +13,7 @@ fn basic_memo() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
#[test]
|
||||
fn memo_with_computed_value() {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
@@ -29,7 +29,7 @@ fn memo_with_computed_value() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
#[test]
|
||||
fn nested_memos() {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
@@ -51,7 +51,7 @@ fn nested_memos() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
#[test]
|
||||
fn memo_runs_only_when_inputs_change() {
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
@@ -94,7 +94,7 @@ fn memo_runs_only_when_inputs_change() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
#[test]
|
||||
fn diamond_problem() {
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
@@ -131,7 +131,7 @@ fn diamond_problem() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
#[test]
|
||||
fn dynamic_dependencies() {
|
||||
use leptos_reactive::create_isomorphic_effect;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
use leptos_reactive::{create_runtime, create_scope, create_signal};
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
#[test]
|
||||
fn basic_signal() {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
@@ -13,7 +13,7 @@ fn basic_signal() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
#[test]
|
||||
fn derived_signals() {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
use leptos_reactive::{
|
||||
create_isomorphic_effect, create_runtime, create_scope, create_signal,
|
||||
signal_prelude::*, SignalGetUntracked, SignalSetUntracked,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
#[test]
|
||||
fn untracked_set_doesnt_trigger_effect() {
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
@@ -36,7 +36,7 @@ fn untracked_set_doesnt_trigger_effect() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[cfg(feature = "nightly")]
|
||||
#[test]
|
||||
fn untracked_get_doesnt_trigger_effect() {
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
@@ -26,7 +26,7 @@ default-tls = ["server_fn/default-tls"]
|
||||
hydrate = ["leptos_reactive/hydrate"]
|
||||
rustls = ["server_fn/rustls"]
|
||||
ssr = ["leptos_reactive/ssr", "server_fn/ssr"]
|
||||
stable = ["leptos_reactive/stable", "server_fn/stable"]
|
||||
nightly = ["leptos_reactive/nightly", "server_fn/nightly"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["stable"]
|
||||
|
||||
@@ -22,7 +22,7 @@ default = []
|
||||
csr = ["leptos/csr", "leptos/tracing"]
|
||||
hydrate = ["leptos/hydrate", "leptos/tracing"]
|
||||
ssr = ["leptos/ssr", "leptos/tracing"]
|
||||
stable = ["leptos/stable", "leptos/tracing"]
|
||||
nightly = ["leptos/nightly", "leptos/tracing"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["stable"]
|
||||
|
||||
@@ -59,7 +59,7 @@ default = []
|
||||
csr = ["leptos/csr"]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = ["leptos/ssr", "dep:cached", "dep:lru", "dep:url", "dep:regex"]
|
||||
stable = ["leptos/stable"]
|
||||
nightly = ["leptos/nightly"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
# No need to test optional dependencies as they are enabled by the ssr feature
|
||||
|
||||
@@ -206,6 +206,12 @@ where
|
||||
/// Automatically turns a server [Action](leptos_server::Action) into an HTML
|
||||
/// [`form`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)
|
||||
/// progressively enhanced to use client-side routing.
|
||||
///
|
||||
/// ## Encoding
|
||||
/// **Note:** `<ActionForm/>` only works with server functions that use the
|
||||
/// default `Url` encoding or the `GetJSON` encoding, not with `CBOR` or other
|
||||
/// encoding schemes. This is to ensure that `<ActionForm/>` works correctly
|
||||
/// both before and after WASM has loaded.
|
||||
#[cfg_attr(
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
tracing::instrument(level = "trace", skip_all,)
|
||||
@@ -264,55 +270,61 @@ where
|
||||
let resp = resp.clone().expect("couldn't get Response");
|
||||
let status = resp.status();
|
||||
spawn_local(async move {
|
||||
let body = JsFuture::from(
|
||||
resp.text().expect("couldn't get .text() from Response"),
|
||||
)
|
||||
.await;
|
||||
match body {
|
||||
Ok(json) => {
|
||||
// 500 just returns text of error, not JSON
|
||||
if status == 500 {
|
||||
let err = ServerFnError::ServerError(
|
||||
json.as_string().unwrap_or_default(),
|
||||
);
|
||||
let redirected = resp.redirected();
|
||||
|
||||
if !redirected {
|
||||
let body = JsFuture::from(
|
||||
resp.text().expect("couldn't get .text() from Response"),
|
||||
)
|
||||
.await;
|
||||
match body {
|
||||
Ok(json) => {
|
||||
// 500 just returns text of error, not JSON
|
||||
if status == 500 {
|
||||
let err = ServerFnError::ServerError(
|
||||
json.as_string().unwrap_or_default(),
|
||||
);
|
||||
if let Some(error) = error {
|
||||
error.try_set(Some(Box::new(err.clone())));
|
||||
}
|
||||
value.try_set(Some(Err(err)));
|
||||
} else {
|
||||
match O::de(
|
||||
&json.as_string().expect(
|
||||
"couldn't get String from JsString",
|
||||
),
|
||||
) {
|
||||
Ok(res) => {
|
||||
value.try_set(Some(Ok(res)));
|
||||
if let Some(error) = error {
|
||||
error.try_set(None);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
value.try_set(Some(Err(
|
||||
ServerFnError::Deserialization(
|
||||
e.to_string(),
|
||||
),
|
||||
)));
|
||||
if let Some(error) = error {
|
||||
error.try_set(Some(Box::new(e)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{e:?}");
|
||||
if let Some(error) = error {
|
||||
error.try_set(Some(Box::new(err.clone())));
|
||||
}
|
||||
value.try_set(Some(Err(err)));
|
||||
} else {
|
||||
match O::de(
|
||||
&json
|
||||
.as_string()
|
||||
.expect("couldn't get String from JsString"),
|
||||
) {
|
||||
Ok(res) => {
|
||||
value.try_set(Some(Ok(res)));
|
||||
if let Some(error) = error {
|
||||
error.try_set(None);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
value.try_set(Some(Err(
|
||||
ServerFnError::Deserialization(
|
||||
e.to_string(),
|
||||
),
|
||||
)));
|
||||
if let Some(error) = error {
|
||||
error.try_set(Some(Box::new(e)));
|
||||
}
|
||||
}
|
||||
error.try_set(Some(Box::new(
|
||||
ServerFnError::Request(
|
||||
e.as_string().unwrap_or_default(),
|
||||
),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{e:?}");
|
||||
if let Some(error) = error {
|
||||
error.try_set(Some(Box::new(ServerFnError::Request(
|
||||
e.as_string().unwrap_or_default(),
|
||||
))));
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
input.try_set(None);
|
||||
action.set_pending(false);
|
||||
});
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::{
|
||||
RouteDefinition, RouteMatch,
|
||||
},
|
||||
use_is_back_navigation, RouteContext, RouterContext,
|
||||
ApiRouteListing
|
||||
};
|
||||
use leptos::{leptos_dom::HydrationCtx, *};
|
||||
use std::{
|
||||
@@ -44,7 +43,7 @@ pub fn Routes(
|
||||
#[cfg(feature = "ssr")]
|
||||
if let Some(context) = use_context::<crate::PossibleBranchContext>(cx) {
|
||||
Branches::with(&base, |branches| {
|
||||
*context.ui.borrow_mut() = branches.to_vec();
|
||||
*context.0.borrow_mut() = branches.to_vec()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -119,7 +118,7 @@ pub fn AnimatedRoutes(
|
||||
#[cfg(feature = "ssr")]
|
||||
if let Some(context) = use_context::<crate::PossibleBranchContext>(cx) {
|
||||
Branches::with(&base, |branches| {
|
||||
*context.ui.borrow_mut() = branches.to_vec()
|
||||
*context.0.borrow_mut() = branches.to_vec()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -225,12 +224,8 @@ pub fn AnimatedRoutes(
|
||||
|
||||
pub(crate) struct Branches;
|
||||
|
||||
type AppRoutes = (Vec<Branch>, Vec<ApiRouteListing>);
|
||||
|
||||
thread_local! {
|
||||
// map is indexed by base
|
||||
// this allows multiple apps per server
|
||||
static BRANCHES: RefCell<HashMap<String, AppRoutes>> = Default::default();
|
||||
static BRANCHES: RefCell<HashMap<String, Vec<Branch>>> = RefCell::new(HashMap::new());
|
||||
}
|
||||
|
||||
impl Branches {
|
||||
@@ -238,30 +233,29 @@ impl Branches {
|
||||
BRANCHES.with(|branches| {
|
||||
let mut current = branches.borrow_mut();
|
||||
if !current.contains_key(base) {
|
||||
let mut branches = (Vec::new(), Vec::new());
|
||||
let mut route_defs = Vec::new();
|
||||
let mut api_routes = Vec::new();
|
||||
for child in children
|
||||
let mut branches = Vec::new();
|
||||
let children = children
|
||||
.as_children()
|
||||
.iter() {
|
||||
let transparent = child.as_transparent();
|
||||
if let Some(def) = transparent.and_then(|t| t.downcast_ref::<RouteDefinition>()) {
|
||||
route_defs.push(def.clone());
|
||||
} else if let Some(def) = transparent.and_then(|t| t.downcast_ref::<ApiRouteListing>()) {
|
||||
api_routes.push(def.clone());
|
||||
} else {
|
||||
.iter()
|
||||
.filter_map(|child| {
|
||||
let def = child
|
||||
.as_transparent()
|
||||
.and_then(|t| t.downcast_ref::<RouteDefinition>());
|
||||
if def.is_none() {
|
||||
warn!(
|
||||
"[NOTE] The <Routes/> component should \
|
||||
include *only* <Route/> or <ProtectedRoute/> or <ApiRoute/> \
|
||||
include *only* <Route/>or <ProtectedRoute/> \
|
||||
components, or some \
|
||||
#[component(transparent)] that returns a \
|
||||
RouteDefinition."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
def
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
create_branches(
|
||||
&route_defs,
|
||||
&children,
|
||||
base,
|
||||
&mut Vec::new(),
|
||||
&mut branches,
|
||||
@@ -278,18 +272,7 @@ impl Branches {
|
||||
"Branches::initialize() should be called before \
|
||||
Branches::with()",
|
||||
);
|
||||
cb(&branches.0)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_api_routes<T>(base: &str, cb: impl FnOnce(&[ApiRouteListing]) -> T) -> T {
|
||||
BRANCHES.with(|branches| {
|
||||
let branches = branches.borrow();
|
||||
let branches = branches.get(base).expect(
|
||||
"Branches::initialize() should be called before \
|
||||
Branches::with()",
|
||||
);
|
||||
cb(&branches.1)
|
||||
cb(branches)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -498,7 +481,7 @@ fn create_branches(
|
||||
route_defs: &[RouteDefinition],
|
||||
base: &str,
|
||||
stack: &mut Vec<RouteData>,
|
||||
branches: &mut (Vec<Branch>, Vec<ApiRouteListing>),
|
||||
branches: &mut Vec<Branch>,
|
||||
) {
|
||||
for def in route_defs {
|
||||
let routes = create_routes(def, base);
|
||||
@@ -506,8 +489,8 @@ fn create_branches(
|
||||
stack.push(route.clone());
|
||||
|
||||
if def.children.is_empty() {
|
||||
let branch = create_branch(stack, branches.0.len());
|
||||
branches.0.push(branch);
|
||||
let branch = create_branch(stack, branches.len());
|
||||
branches.push(branch);
|
||||
} else {
|
||||
create_branches(&def.children, &route.pattern, stack, branches);
|
||||
}
|
||||
@@ -517,7 +500,7 @@ fn create_branches(
|
||||
}
|
||||
|
||||
if stack.is_empty() {
|
||||
branches.0.sort_by_key(|branch| Reverse(branch.score));
|
||||
branches.sort_by_key(|branch| Reverse(branch.score));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,24 +2,14 @@ use crate::{
|
||||
Branch, Method, RouterIntegrationContext, ServerIntegration, SsrMode,
|
||||
};
|
||||
use leptos::*;
|
||||
use std::{any::Any, cell::RefCell, collections::HashSet, rc::Rc, sync::Arc};
|
||||
use std::{cell::RefCell, collections::HashSet, rc::Rc};
|
||||
|
||||
/// Context to contain all possible routes.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct PossibleBranchContext {
|
||||
pub(crate) ui: Rc<RefCell<Vec<Branch>>>,
|
||||
pub(crate) api: Rc<RefCell<Vec<ApiRouteListing>>>
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// A route that this application can serve.
|
||||
pub enum PossibleRouteListing {
|
||||
View(RouteListing),
|
||||
Api(ApiRouteListing)
|
||||
}
|
||||
pub struct PossibleBranchContext(pub(crate) Rc<RefCell<Vec<Branch>>>);
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
/// Route listing for a component-based view.
|
||||
/// A route that this application can serve.
|
||||
pub struct RouteListing {
|
||||
path: String,
|
||||
mode: SsrMode,
|
||||
@@ -56,69 +46,12 @@ impl RouteListing {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Route listing for an API route.
|
||||
pub struct ApiRouteListing {
|
||||
path: String,
|
||||
methods: Option<HashSet<Method>>,
|
||||
// this will be downcast by the implementation
|
||||
handler: Arc<dyn Any>
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ApiRouteListing {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ApiRouteListing").field("path", &self.path).field("methods", &self.methods).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiRouteListing {
|
||||
/// Create an API route listing from its parts.
|
||||
pub fn new<T: 'static>(
|
||||
path: impl ToString,
|
||||
handler: T
|
||||
) -> Self {
|
||||
Self {
|
||||
path: path.to_string(),
|
||||
methods: None,
|
||||
handler: Arc::new(handler)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an API route listing from its parts.
|
||||
pub fn new_with_methods<T: 'static>(
|
||||
path: impl ToString,
|
||||
methods: impl IntoIterator<Item = Method>,
|
||||
handler: T
|
||||
) -> Self {
|
||||
Self {
|
||||
path: path.to_string(),
|
||||
methods: Some(methods.into_iter().collect()),
|
||||
handler: Arc::new(handler)
|
||||
}
|
||||
}
|
||||
|
||||
/// The path this route handles.
|
||||
pub fn path(&self) -> &str {
|
||||
&self.path
|
||||
}
|
||||
|
||||
/// The HTTP request methods this path can handle.
|
||||
pub fn methods(&self) -> impl Iterator<Item = Method> + '_ {
|
||||
self.methods.iter().flatten().copied()
|
||||
}
|
||||
|
||||
/// The handler for a request at this route
|
||||
pub fn handler<T: 'static>(&self) -> Option<&T> {
|
||||
self.handler.downcast_ref::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a list of all routes this application could possibly serve. This returns the raw routes in the leptos_router
|
||||
/// format. Odds are you want `generate_route_list()` from either the actix, axum, or viz integrations if you want
|
||||
/// to work with their router
|
||||
pub fn generate_route_list_inner<IV>(
|
||||
app_fn: impl FnOnce(Scope) -> IV + 'static,
|
||||
) -> (Vec<RouteListing>, Vec<ApiRouteListing>)
|
||||
) -> Vec<RouteListing>
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
@@ -136,8 +69,8 @@ where
|
||||
_ = app_fn(cx).into_view(cx);
|
||||
leptos::suppress_resource_load(false);
|
||||
|
||||
let ui_branches = branches.ui.borrow();
|
||||
let ui = ui_branches
|
||||
let branches = branches.0.borrow();
|
||||
branches
|
||||
.iter()
|
||||
.flat_map(|branch| {
|
||||
let mode = branch
|
||||
@@ -160,12 +93,6 @@ where
|
||||
methods: methods.clone(),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let api_branches = branches.api.borrow();
|
||||
let api = api_branches
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
(ui, api)
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ where
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(feature = "stable"))] {
|
||||
if #[cfg(feature = "nightly")] {
|
||||
auto trait NotOption {}
|
||||
impl<T> !NotOption for Option<T> {}
|
||||
|
||||
|
||||
@@ -183,9 +183,9 @@
|
||||
//! **Important Note:** You must enable one of `csr`, `hydrate`, or `ssr` to tell Leptos
|
||||
//! which mode your app is operating in.
|
||||
|
||||
#![cfg_attr(not(feature = "stable"), feature(auto_traits))]
|
||||
#![cfg_attr(not(feature = "stable"), feature(negative_impls))]
|
||||
#![cfg_attr(not(feature = "stable"), feature(type_name_of_val))]
|
||||
#![cfg_attr(feature = "nightly", feature(auto_traits))]
|
||||
#![cfg_attr(feature = "nightly", feature(negative_impls))]
|
||||
#![cfg_attr(feature = "nightly", feature(type_name_of_val))]
|
||||
|
||||
mod animation;
|
||||
mod components;
|
||||
|
||||
@@ -34,4 +34,4 @@ default = ["default-tls"]
|
||||
default-tls = ["reqwest/default-tls"]
|
||||
rustls = ["reqwest/rustls-tls"]
|
||||
ssr = []
|
||||
stable = ["server_fn_macro_default/stable"]
|
||||
nightly = ["server_fn_macro_default/nightly"]
|
||||
|
||||
@@ -19,4 +19,4 @@ server_fn = { version = "0.2" }
|
||||
serde = "1"
|
||||
|
||||
[features]
|
||||
stable = ["server_fn_macro/stable"]
|
||||
nightly = ["server_fn_macro/nightly"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![cfg_attr(not(feature = "stable"), feature(proc_macro_span))]
|
||||
#![cfg_attr(feature = "nightly", feature(proc_macro_span))]
|
||||
//! This crate contains the default implementation of the #[macro@crate::server] macro without a context from the server. See the [server_fn_macro] crate for more information.
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
|
||||
@@ -18,4 +18,4 @@ xxhash-rust = { version = "0.8.6", features = ["const_xxh64"] }
|
||||
const_format = "0.2.30"
|
||||
|
||||
[features]
|
||||
stable = []
|
||||
nightly = []
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![cfg_attr(not(feature = "stable"), feature(proc_macro_span))]
|
||||
#![cfg_attr(feature = "nightly", feature(proc_macro_span))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
//! Implementation of the server_fn macro.
|
||||
|
||||
Reference in New Issue
Block a user