mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 14:52:35 -05:00
Compare commits
2 Commits
nightly-fe
...
979
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f8b6dc440 | ||
|
|
af561abdf8 |
@@ -41,7 +41,7 @@ ssr = [
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
]
|
||||
nightly = ["leptos/nightly", "leptos_router/nightly"]
|
||||
stable = ["leptos/stable", "leptos_router/stable"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["actix-files", "actix-web", "leptos_actix", "stable"]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
extend = [{ path = "../cargo-make/common.toml" }]
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
use leptos_meta::*;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
use broadcaster::BroadcastChannel;
|
||||
static COUNT: AtomicI32 = AtomicI32::new(0);
|
||||
#[cfg(feature = "ssr")]
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref COUNT_CHANNEL: BroadcastChannel<i32> = BroadcastChannel::new();
|
||||
}
|
||||
#[cfg(feature = "ssr")]
|
||||
use broadcaster::BroadcastChannel;
|
||||
|
||||
pub fn register_server_functions() {
|
||||
_ = GetServerCount::register();
|
||||
_ = AdjustServerCount::register();
|
||||
_ = ClearServerCount::register();
|
||||
}
|
||||
|
||||
}
|
||||
#[cfg(feature = "ssr")]
|
||||
pub fn register_server_functions() {
|
||||
_ = GetServerCount::register();
|
||||
_ = AdjustServerCount::register();
|
||||
_ = ClearServerCount::register();
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
static COUNT: AtomicI32 = AtomicI32::new(0);
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref COUNT_CHANNEL: BroadcastChannel<i32> = BroadcastChannel::new();
|
||||
}
|
||||
// "/api" is an optional prefix that allows you to locate server functions wherever you'd like on the server
|
||||
#[server(GetServerCount, "/api")]
|
||||
pub async fn get_server_count() -> Result<i32, ServerFnError> {
|
||||
@@ -29,10 +29,7 @@ pub async fn get_server_count() -> Result<i32, ServerFnError> {
|
||||
}
|
||||
|
||||
#[server(AdjustServerCount, "/api")]
|
||||
pub async fn adjust_server_count(
|
||||
delta: i32,
|
||||
msg: String,
|
||||
) -> Result<i32, ServerFnError> {
|
||||
pub async fn adjust_server_count(delta: i32, msg: String) -> Result<i32, ServerFnError> {
|
||||
let new = COUNT.load(Ordering::Relaxed) + delta;
|
||||
COUNT.store(new, Ordering::Relaxed);
|
||||
_ = COUNT_CHANNEL.send(&new).await;
|
||||
@@ -49,49 +46,36 @@ pub async fn clear_server_count() -> Result<i32, ServerFnError> {
|
||||
#[component]
|
||||
pub fn Counters(cx: Scope) -> impl IntoView {
|
||||
provide_meta_context(cx);
|
||||
view! { cx,
|
||||
view! {
|
||||
cx,
|
||||
<Router>
|
||||
<header>
|
||||
<h1>"Server-Side Counters"</h1>
|
||||
<p>"Each of these counters stores its data in the same variable on the server."</p>
|
||||
<p>
|
||||
"The value is shared across connections. Try opening this is another browser tab to see what I mean."
|
||||
</p>
|
||||
<p>"The value is shared across connections. Try opening this is another browser tab to see what I mean."</p>
|
||||
</header>
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<A href="">"Simple"</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="form">"Form-Based"</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="multi">"Multi-User"</A>
|
||||
</li>
|
||||
<li><A href="">"Simple"</A></li>
|
||||
<li><A href="form">"Form-Based"</A></li>
|
||||
<li><A href="multi">"Multi-User"</A></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route
|
||||
path=""
|
||||
view=|cx| {
|
||||
view! { cx, <Counter/> }
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="form"
|
||||
view=|cx| {
|
||||
view! { cx, <FormCounter/> }
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="multi"
|
||||
view=|cx| {
|
||||
view! { cx, <MultiuserCounter/> }
|
||||
}
|
||||
/>
|
||||
<Route path="" view=|cx| view! {
|
||||
cx,
|
||||
<Counter/>
|
||||
}/>
|
||||
<Route path="form" view=|cx| view! {
|
||||
cx,
|
||||
<FormCounter/>
|
||||
}/>
|
||||
<Route path="multi" view=|cx| view! {
|
||||
cx,
|
||||
<MultiuserCounter/>
|
||||
}/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
@@ -109,47 +93,33 @@ pub fn Counter(cx: Scope) -> impl IntoView {
|
||||
let clear = create_action(cx, |_| clear_server_count());
|
||||
let counter = create_resource(
|
||||
cx,
|
||||
move || {
|
||||
(
|
||||
dec.version().get(),
|
||||
inc.version().get(),
|
||||
clear.version().get(),
|
||||
)
|
||||
},
|
||||
move || (dec.version().get(), inc.version().get(), clear.version().get()),
|
||||
|_| get_server_count(),
|
||||
);
|
||||
|
||||
let value = move || {
|
||||
let value = move || counter.read(cx).map(|count| count.unwrap_or(0)).unwrap_or(0);
|
||||
let error_msg = move || {
|
||||
counter
|
||||
.read(cx)
|
||||
.map(|count| count.unwrap_or(0))
|
||||
.unwrap_or(0)
|
||||
};
|
||||
let error_msg = move || {
|
||||
counter.read(cx).and_then(|res| match res {
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(e),
|
||||
})
|
||||
.map(|res| match res {
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(e),
|
||||
})
|
||||
.flatten()
|
||||
};
|
||||
|
||||
view! { cx,
|
||||
view! {
|
||||
cx,
|
||||
<div>
|
||||
<h2>"Simple Counter"</h2>
|
||||
<p>
|
||||
"This counter sets the value on the server and automatically reloads the new value."
|
||||
</p>
|
||||
<p>"This counter sets the value on the server and automatically reloads the new value."</p>
|
||||
<div>
|
||||
<button on:click=move |_| clear.dispatch(())>"Clear"</button>
|
||||
<button on:click=move |_| dec.dispatch(())>"-1"</button>
|
||||
<span>"Value: " {value} "!"</span>
|
||||
<button on:click=move |_| inc.dispatch(())>"+1"</button>
|
||||
</div>
|
||||
{move || {
|
||||
error_msg()
|
||||
.map(|msg| {
|
||||
view! { cx, <p>"Error: " {msg.to_string()}</p> }
|
||||
})
|
||||
}}
|
||||
{move || error_msg().map(|msg| view! { cx, <p>"Error: " {msg.to_string()}</p>})}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -172,15 +142,19 @@ pub fn FormCounter(cx: Scope) -> impl IntoView {
|
||||
);
|
||||
let value = move || {
|
||||
log::debug!("FormCounter looking for value");
|
||||
counter.read(cx).and_then(|n| n.ok()).unwrap_or(0)
|
||||
counter
|
||||
.read(cx)
|
||||
.map(|n| n.ok())
|
||||
.flatten()
|
||||
.map(|n| n)
|
||||
.unwrap_or(0)
|
||||
};
|
||||
|
||||
view! { cx,
|
||||
view! {
|
||||
cx,
|
||||
<div>
|
||||
<h2>"Form Counter"</h2>
|
||||
<p>
|
||||
"This counter uses forms to set the value on the server. When progressively enhanced, it should behave identically to the “Simple Counter.”"
|
||||
</p>
|
||||
<p>"This counter uses forms to set the value on the server. When progressively enhanced, it should behave identically to the “Simple Counter.”"</p>
|
||||
<div>
|
||||
// calling a server function is the same as POSTing to its API URL
|
||||
// so we can just do that with a form and button
|
||||
@@ -211,32 +185,26 @@ pub fn FormCounter(cx: Scope) -> impl IntoView {
|
||||
// This is the primitive pattern for live chat, collaborative editing, etc.
|
||||
#[component]
|
||||
pub fn MultiuserCounter(cx: Scope) -> impl IntoView {
|
||||
let dec =
|
||||
create_action(cx, |_| adjust_server_count(-1, "dec dec goose".into()));
|
||||
let inc =
|
||||
create_action(cx, |_| adjust_server_count(1, "inc inc moose".into()));
|
||||
let dec = create_action(cx, |_| adjust_server_count(-1, "dec dec goose".into()));
|
||||
let inc = create_action(cx, |_| adjust_server_count(1, "inc inc moose".into()));
|
||||
let clear = create_action(cx, |_| clear_server_count());
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
let multiplayer_value = {
|
||||
use futures::StreamExt;
|
||||
|
||||
let mut source =
|
||||
gloo_net::eventsource::futures::EventSource::new("/api/events")
|
||||
.expect("couldn't connect to SSE stream");
|
||||
let mut source = gloo_net::eventsource::futures::EventSource::new("/api/events")
|
||||
.expect("couldn't connect to SSE stream");
|
||||
let s = create_signal_from_stream(
|
||||
cx,
|
||||
source
|
||||
.subscribe("message")
|
||||
.unwrap()
|
||||
.map(|value| match value {
|
||||
Ok(value) => value
|
||||
.1
|
||||
.data()
|
||||
.as_string()
|
||||
.expect("expected string value"),
|
||||
source.subscribe("message").unwrap().map(|value| {
|
||||
match value {
|
||||
Ok(value) => {
|
||||
value.1.data().as_string().expect("expected string value")
|
||||
},
|
||||
Err(_) => "0".to_string(),
|
||||
}),
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
on_cleanup(cx, move || source.close());
|
||||
@@ -244,20 +212,18 @@ pub fn MultiuserCounter(cx: Scope) -> impl IntoView {
|
||||
};
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
let (multiplayer_value, _) = create_signal(cx, None::<i32>);
|
||||
let (multiplayer_value, _) =
|
||||
create_signal(cx, None::<i32>);
|
||||
|
||||
view! { cx,
|
||||
view! {
|
||||
cx,
|
||||
<div>
|
||||
<h2>"Multi-User Counter"</h2>
|
||||
<p>
|
||||
"This one uses server-sent events (SSE) to live-update when other users make changes."
|
||||
</p>
|
||||
<p>"This one uses server-sent events (SSE) to live-update when other users make changes."</p>
|
||||
<div>
|
||||
<button on:click=move |_| clear.dispatch(())>"Clear"</button>
|
||||
<button on:click=move |_| dec.dispatch(())>"-1"</button>
|
||||
<span>
|
||||
"Multiplayer Value: " {move || multiplayer_value.get().unwrap_or_default()}
|
||||
</span>
|
||||
<span>"Multiplayer Value: " {move || multiplayer_value.get().unwrap_or_default().to_string()}</span>
|
||||
<button on:click=move |_| inc.dispatch(())>"+1"</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
pub mod counters;
|
||||
|
||||
// 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::counters::*;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
mod counters;
|
||||
|
||||
// boilerplate to run in different modes
|
||||
cfg_if! {
|
||||
// server-only stuff
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use leptos::*;
|
||||
use actix_files::{Files};
|
||||
use actix_web::*;
|
||||
use crate::counters::*;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use leptos::{For, *};
|
||||
use leptos::{For, ForProps, *};
|
||||
|
||||
const MANY_COUNTERS: usize = 1000;
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
extend = [{ path = "../cargo-make/common.toml" }]
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::{error_template::ErrorTemplate, errors::AppError};
|
||||
use crate::{
|
||||
error_template::{ErrorTemplate, ErrorTemplateProps},
|
||||
errors::AppError,
|
||||
};
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
@@ -51,8 +54,7 @@ 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,4 +1,5 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
pub mod error_template;
|
||||
pub mod errors;
|
||||
pub mod fallback;
|
||||
@@ -7,7 +8,6 @@ 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,8 +37,7 @@ 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();
|
||||
|
||||
@@ -52,11 +51,7 @@ 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,5 +1,3 @@
|
||||
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,5 +1,3 @@
|
||||
extend = [{ path = "../cargo-make/common.toml" }]
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -4,7 +4,10 @@ use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
mod api;
|
||||
mod routes;
|
||||
use routes::{nav::*, stories::*, story::*, users::*};
|
||||
use routes::nav::*;
|
||||
use routes::stories::*;
|
||||
use routes::story::*;
|
||||
use routes::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};
|
||||
use hackernews::{App,AppProps};
|
||||
use leptos_actix::{LeptosRoutes, generate_route_list};
|
||||
|
||||
#[get("/style.css")]
|
||||
@@ -46,7 +46,7 @@ cfg_if! {
|
||||
}
|
||||
} else {
|
||||
fn main() {
|
||||
use hackernews::{App};
|
||||
use hackernews::{App, AppProps};
|
||||
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::api;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
use crate::api;
|
||||
|
||||
fn category(from: &str) -> &'static str {
|
||||
match from {
|
||||
"new" => "newest",
|
||||
@@ -36,10 +37,8 @@ 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,20 +13,11 @@ 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>
|
||||
<li inner_html={user.about} class="about"></li>
|
||||
{user.about.as_ref().map(|about| view! { cx, <li inner_html=about class="about"></li> })}
|
||||
</ul>
|
||||
<p class="links">
|
||||
<a href=format!("https://news.ycombinator.com/submitted?id={}", user.id)>"submissions"</a>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
extend = [{ path = "../cargo-make/common.toml" }]
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use leptos::{view, Errors, For, IntoView, RwSignal, Scope, View};
|
||||
use leptos::{
|
||||
signal_prelude::*, view, Errors, For, ForProps, 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,7 +7,10 @@ pub mod error_template;
|
||||
pub mod fallback;
|
||||
pub mod handlers;
|
||||
mod routes;
|
||||
use routes::{nav::*, stories::*, story::*, users::*};
|
||||
use routes::nav::*;
|
||||
use routes::stories::*;
|
||||
use routes::story::*;
|
||||
use routes::users::*;
|
||||
|
||||
#[component]
|
||||
pub fn App(cx: Scope) -> impl IntoView {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use leptos::{component, view, IntoView, Scope};
|
||||
use leptos::{component, Scope, IntoView, view};
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::api;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
use crate::api;
|
||||
|
||||
fn category(from: &str) -> &'static str {
|
||||
match from {
|
||||
"new" => "newest",
|
||||
@@ -36,10 +37,8 @@ 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,20 +13,11 @@ 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,5 +1,3 @@
|
||||
extend = { path = "../cargo-make/common.toml" }
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
extend = { path = "../../cargo-make/common.toml" }
|
||||
@@ -1 +0,0 @@
|
||||
extend = { path = "../../cargo-make/common.toml" }
|
||||
@@ -1,8 +1,9 @@
|
||||
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,56 +20,54 @@ 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);
|
||||
<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();
|
||||
}
|
||||
on:change=move |ev| {
|
||||
let val = event_target_value(&ev);
|
||||
set_email.update(|v| *v = val);
|
||||
_=> {
|
||||
let val = event_target_value(&ev);
|
||||
set_password.update(|p|*p = val);
|
||||
}
|
||||
/>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
// 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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::Page;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
use crate::Page;
|
||||
|
||||
#[component]
|
||||
pub fn NavBar<F>(
|
||||
cx: Scope,
|
||||
@@ -12,27 +13,20 @@ 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,8 +1,9 @@
|
||||
use api_boundary::*;
|
||||
use gloo_storage::{LocalStorage, Storage};
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
|
||||
use api_boundary::*;
|
||||
|
||||
mod api;
|
||||
mod components;
|
||||
mod pages;
|
||||
@@ -85,51 +86,45 @@ 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,8 +1,9 @@
|
||||
use client::*;
|
||||
use leptos::*;
|
||||
|
||||
use client::*;
|
||||
|
||||
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,19 +6,15 @@ 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,11 +1,13 @@
|
||||
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
|
||||
@@ -51,14 +53,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,11 +1,13 @@
|
||||
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 {
|
||||
@@ -50,24 +52,26 @@ 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 +0,0 @@
|
||||
extend = { path = "../../cargo-make/common.toml" }
|
||||
@@ -2,7 +2,8 @@ use crate::{application::*, Error};
|
||||
use api_boundary as json;
|
||||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Json, Response},
|
||||
response::Json,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use api_boundary as json;
|
||||
use std::{env, sync::Arc};
|
||||
|
||||
use axum::{
|
||||
extract::{State, TypedHeader},
|
||||
headers::{authorization::Bearer, Authorization},
|
||||
@@ -7,9 +8,10 @@ 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;
|
||||
|
||||
@@ -23,10 +25,7 @@ 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,5 +1,3 @@
|
||||
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,5 +1,3 @@
|
||||
extend = { path = "../cargo-make/common.toml" }
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
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")] {
|
||||
@@ -166,9 +168,7 @@ 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,9 +13,7 @@ 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,4 +1,5 @@
|
||||
use crate::{auth::*, error_template::ErrorTemplate};
|
||||
use crate::auth::*;
|
||||
use crate::error_template::{ErrorTemplate, ErrorTemplateProps};
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
@@ -72,8 +73,7 @@ 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,13 +111,11 @@ 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())),
|
||||
@@ -306,10 +304,7 @@ 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>
|
||||
@@ -335,10 +330,7 @@ pub fn Login(
|
||||
}
|
||||
|
||||
#[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>
|
||||
@@ -370,10 +362,7 @@ pub fn Signup(
|
||||
}
|
||||
|
||||
#[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">
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
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;
|
||||
let addr = conf.leptos_options.site_addr.clone();
|
||||
|
||||
// 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> {
|
||||
SqliteConnection::connect("sqlite:Todos.db").await.map_err(|e| ServerFnError::ServerError(e.to_string()))
|
||||
Ok(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())),
|
||||
}
|
||||
}
|
||||
@@ -130,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,5 +1,3 @@
|
||||
extend = { path = "../cargo-make/common.toml" }
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
pub mod error_template;
|
||||
pub mod errors;
|
||||
pub mod fallback;
|
||||
@@ -7,7 +8,6 @@ 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")] {
|
||||
use leptos::*;
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
routing::{post, get},
|
||||
extract::{Extension, Path},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::error_template::ErrorTemplate;
|
||||
use crate::error_template::{ErrorTemplate, ErrorTemplateProps};
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
extend = { path = "../cargo-make/common.toml" }
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
pub mod error_template;
|
||||
pub mod errors;
|
||||
pub mod fallback;
|
||||
@@ -7,7 +8,6 @@ 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,9 +1,8 @@
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
use leptos::*;
|
||||
// boilerplate to run in different modes
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use leptos::*;
|
||||
if #[cfg(feature = "ssr")] {
|
||||
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;
|
||||
use crate::error_template::{ErrorTemplate, ErrorTemplateProps};
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
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_untyped("hashchange", move |_| {
|
||||
window_event_listener("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 || {
|
||||
let _ = input.focus();
|
||||
input.focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -348,14 +348,19 @@ pub fn Todo(cx: Scope, todo: Todo) -> impl IntoView {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, 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,27 +1,33 @@
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1068,9 +1068,9 @@ where
|
||||
let path = listing.path();
|
||||
if path.is_empty() {
|
||||
RouteListing::new(
|
||||
"/".to_string(),
|
||||
listing.mode(),
|
||||
listing.methods(),
|
||||
"/",
|
||||
Default::default(),
|
||||
[leptos_router::Method::Get],
|
||||
)
|
||||
} else {
|
||||
listing
|
||||
|
||||
@@ -992,9 +992,9 @@ where
|
||||
let path = listing.path();
|
||||
if path.is_empty() {
|
||||
RouteListing::new(
|
||||
"/".to_string(),
|
||||
listing.mode(),
|
||||
listing.methods(),
|
||||
"/",
|
||||
Default::default(),
|
||||
[leptos_router::Method::Get],
|
||||
)
|
||||
} else {
|
||||
listing
|
||||
|
||||
@@ -23,7 +23,7 @@ server_fn = { workspace = true, default-features = false }
|
||||
leptos = { path = ".", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["serde", "nightly"]
|
||||
default = ["csr", "serde"]
|
||||
csr = [
|
||||
"leptos_dom/web",
|
||||
"leptos_macro/csr",
|
||||
@@ -44,11 +44,11 @@ ssr = [
|
||||
"leptos_reactive/ssr",
|
||||
"leptos_server/ssr",
|
||||
]
|
||||
nightly = [
|
||||
"leptos_dom/nightly",
|
||||
"leptos_macro/nightly",
|
||||
"leptos_reactive/nightly",
|
||||
"leptos_server/nightly",
|
||||
stable = [
|
||||
"leptos_dom/stable",
|
||||
"leptos_macro/stable",
|
||||
"leptos_reactive/stable",
|
||||
"leptos_server/stable",
|
||||
]
|
||||
serde = ["leptos_reactive/serde"]
|
||||
serde-lite = ["leptos_reactive/serde-lite"]
|
||||
@@ -57,10 +57,8 @@ rkyv = ["leptos_reactive/rkyv"]
|
||||
tracing = ["leptos_macro/tracing"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["tracing"]
|
||||
denylist = ["stable", "tracing"]
|
||||
skip_feature_sets = [
|
||||
[
|
||||
],
|
||||
[
|
||||
"csr",
|
||||
"ssr",
|
||||
|
||||
@@ -240,3 +240,24 @@ pub fn component_props_builder<P: Props>(
|
||||
) -> <P as Props>::Builder {
|
||||
<P as Props>::builder()
|
||||
}
|
||||
|
||||
#[cfg(all(not(doc), feature = "csr", feature = "ssr"))]
|
||||
compile_error!(
|
||||
"You have both `csr` and `ssr` enabled as features, which may cause \
|
||||
issues like <Suspense/>` failing to work silently. `csr` is enabled by \
|
||||
default on `leptos`, and can be disabled by adding `default-features = \
|
||||
false` to your `leptos` dependency."
|
||||
);
|
||||
|
||||
#[cfg(all(not(doc), feature = "hydrate", feature = "ssr"))]
|
||||
compile_error!(
|
||||
"You have both `hydrate` and `ssr` enabled as features, which may cause \
|
||||
issues like <Suspense/>` failing to work silently."
|
||||
);
|
||||
|
||||
#[cfg(all(not(doc), feature = "hydrate", feature = "csr"))]
|
||||
compile_error!(
|
||||
"You have both `hydrate` and `csr` enabled as features, which may cause \
|
||||
issues. `csr` is enabled by default on `leptos`, and can be disabled by \
|
||||
adding `default-features = false` to your `leptos` dependency."
|
||||
);
|
||||
|
||||
@@ -157,7 +157,7 @@ features = [
|
||||
default = []
|
||||
web = ["leptos_reactive/csr"]
|
||||
ssr = ["leptos_reactive/ssr"]
|
||||
nightly = ["leptos_reactive/nightly"]
|
||||
stable = ["leptos_reactive/stable"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["stable"]
|
||||
|
||||
@@ -904,40 +904,6 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Optionally adds an event listener to this element.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust
|
||||
/// # use leptos::*;
|
||||
/// #[component]
|
||||
/// pub fn Input(
|
||||
/// cx: Scope,
|
||||
/// #[prop(optional)] value: Option<RwSignal<String>>,
|
||||
/// ) -> impl IntoView {
|
||||
/// view! { cx, <input/> }
|
||||
/// // only add event if `value` is `Some(signal)`
|
||||
/// .optional_event(
|
||||
/// ev::input,
|
||||
/// value.map(|value| move |ev| value.set(event_target_value(&ev))),
|
||||
/// )
|
||||
/// }
|
||||
/// #
|
||||
/// ```
|
||||
#[track_caller]
|
||||
#[inline(always)]
|
||||
pub fn optional_event<E: EventDescriptor + 'static>(
|
||||
self,
|
||||
event: E,
|
||||
#[allow(unused_mut)] // used for tracing in debug
|
||||
mut event_handler: Option<impl FnMut(E::EventType) + 'static>,
|
||||
) -> Self {
|
||||
if let Some(event_handler) = event_handler {
|
||||
self.on(event, event_handler)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a child to this element.
|
||||
#[track_caller]
|
||||
pub fn child(self, child: impl IntoView) -> Self {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![deny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
#![cfg_attr(feature = "nightly", feature(fn_traits))]
|
||||
#![cfg_attr(feature = "nightly", feature(unboxed_closures))]
|
||||
#![cfg_attr(not(feature = "stable"), feature(fn_traits))]
|
||||
#![cfg_attr(not(feature = "stable"), feature(unboxed_closures))]
|
||||
|
||||
//! The DOM implementation for `leptos`.
|
||||
|
||||
@@ -834,15 +834,6 @@ where
|
||||
F: FnOnce(Scope) -> N + 'static,
|
||||
N: IntoView,
|
||||
{
|
||||
#[cfg(all(feature = "web", feature = "ssr"))]
|
||||
crate::console_warn(
|
||||
"You have both `csr` and `ssr` or `hydrate` and `ssr` enabled as \
|
||||
features, which may cause issues like <Suspense/>` failing to work \
|
||||
silently. `csr` is enabled by default on `leptos`, and can be \
|
||||
disabled by adding `default-features = false` to your `leptos` \
|
||||
dependency.",
|
||||
);
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
mount_to(crate::document().body().expect("body element to exist"), f)
|
||||
@@ -1125,7 +1116,7 @@ viewable_primitive![
|
||||
];
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "nightly")] {
|
||||
if #[cfg(not(feature = "stable"))] {
|
||||
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(feature = "nightly")] {
|
||||
if #[cfg(not(feature = "stable"))] {
|
||||
impl<T: Clone + ElementDescriptor + 'static> FnOnce<()> for NodeRef<T> {
|
||||
type Output = Option<HtmlElement<T>>;
|
||||
|
||||
|
||||
@@ -271,15 +271,6 @@ impl View {
|
||||
instrument(level = "info", skip_all,)
|
||||
)]
|
||||
pub fn render_to_string(self, _cx: Scope) -> Cow<'static, str> {
|
||||
#[cfg(all(feature = "web", feature = "ssr"))]
|
||||
crate::console_error(
|
||||
"\n[DANGER] You have both `csr` and `ssr` or `hydrate` and `ssr` \
|
||||
enabled as features, which may cause issues like <Suspense/>` \
|
||||
failing to work silently. `csr` is enabled by default on \
|
||||
`leptos`, and can be disabled by adding `default-features = \
|
||||
false` to your `leptos` dependency.\n",
|
||||
);
|
||||
|
||||
self.render_to_string_helper(false)
|
||||
}
|
||||
|
||||
|
||||
@@ -55,15 +55,6 @@ pub fn render_to_stream_in_order_with_prefix(
|
||||
view: impl FnOnce(Scope) -> View + 'static,
|
||||
prefix: impl FnOnce(Scope) -> Cow<'static, str> + 'static,
|
||||
) -> impl Stream<Item = String> {
|
||||
#[cfg(all(feature = "web", feature = "ssr"))]
|
||||
crate::console_error(
|
||||
"\n[DANGER] You have both `csr` and `ssr` or `hydrate` and `ssr` \
|
||||
enabled as features, which may cause issues like <Suspense/>` \
|
||||
failing to work silently. `csr` is enabled by default on `leptos`, \
|
||||
and can be disabled by adding `default-features = false` to your \
|
||||
`leptos` dependency.\n",
|
||||
);
|
||||
|
||||
let (stream, runtime, _) =
|
||||
render_to_stream_in_order_with_prefix_undisposed_with_context(
|
||||
view,
|
||||
|
||||
@@ -39,7 +39,7 @@ default = ["ssr"]
|
||||
csr = []
|
||||
hydrate = []
|
||||
ssr = []
|
||||
nightly = ["server_fn_macro/nightly"]
|
||||
stable = ["server_fn_macro/stable"]
|
||||
tracing = []
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
|
||||
@@ -7,10 +7,9 @@ use itertools::Itertools;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{format_ident, quote_spanned, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
parse::Parse, parse_quote, spanned::Spanned,
|
||||
AngleBracketedGenericArguments, Attribute, FnArg, GenericArgument, Item,
|
||||
ItemFn, Lit, LitStr, Meta, MetaNameValue, Pat, PatIdent, Path,
|
||||
PathArguments, ReturnType, Stmt, Type, TypePath, Visibility,
|
||||
parse::Parse, parse_quote, AngleBracketedGenericArguments, Attribute,
|
||||
FnArg, GenericArgument, ItemFn, Lit, LitStr, Meta, MetaNameValue, Pat,
|
||||
PatIdent, Path, PathArguments, ReturnType, Type, TypePath, Visibility,
|
||||
};
|
||||
|
||||
pub struct Model {
|
||||
@@ -130,25 +129,6 @@ impl ToTokens for Model {
|
||||
|
||||
let mut body = body.to_owned();
|
||||
|
||||
// check for components that end ;
|
||||
if !is_transparent {
|
||||
let ends_semi =
|
||||
body.block.stmts.iter().last().and_then(|stmt| match stmt {
|
||||
Stmt::Item(Item::Macro(mac)) => mac.semi_token.as_ref(),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(semi) = ends_semi {
|
||||
proc_macro_error::emit_error!(
|
||||
semi.span(),
|
||||
"A component that ends with a `view!` macro followed by a \
|
||||
semicolon will return (), an empty view. This is usually \
|
||||
an accident, not intentional, so we prevent it. If you’d \
|
||||
like to return (), you can do it it explicitly by \
|
||||
returning () as the last item from the component."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
body.sig.ident = format_ident!("__{}", body.sig.ident);
|
||||
#[allow(clippy::redundant_clone)] // false positive
|
||||
let body_name = body.sig.ident.clone();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![cfg_attr(feature = "nightly", feature(proc_macro_span))]
|
||||
#![cfg_attr(not(feature = "stable"), 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, feature = "nightly"))] {
|
||||
if #[cfg(all(debug_assertions, not(feature = "stable")))] {
|
||||
Some(leptos_hot_reload::span_to_stable_id(
|
||||
site.source_file().path(),
|
||||
site.into()
|
||||
@@ -816,7 +816,7 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
/// - **Arguments must be implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html)
|
||||
/// and [`DeserializeOwned`](https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html).**
|
||||
/// They are serialized as an `application/x-www-form-urlencoded`
|
||||
/// form data using [`serde_qs`](https://docs.rs/serde_qs/latest/serde_qs/) or as `application/cbor`
|
||||
/// form data using [`serde_html_form`](https://docs.rs/serde_html_form/latest/serde_html_form/) or as `application/cbor`
|
||||
/// using [`cbor`](https://docs.rs/cbor/latest/cbor/). **Note**: You should explicitly include `serde` with the
|
||||
/// `derive` feature enabled in your `Cargo.toml`. You can do this by running `cargo add serde --features=derive`.
|
||||
/// - **The `Scope` comes from the server.** Optionally, the first argument of a server function
|
||||
|
||||
@@ -68,16 +68,15 @@ hydrate = [
|
||||
"dep:web-sys",
|
||||
]
|
||||
ssr = ["dep:tokio"]
|
||||
nightly = []
|
||||
stable = []
|
||||
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(feature = "nightly", feature(fn_traits))]
|
||||
#![cfg_attr(feature = "nightly", feature(unboxed_closures))]
|
||||
#![cfg_attr(feature = "nightly", feature(type_name_of_val))]
|
||||
#![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))]
|
||||
|
||||
//! 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(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl<T: Clone> FnOnce<()> for $ty<T> {
|
||||
type Output = T;
|
||||
|
||||
@@ -27,7 +27,7 @@ macro_rules! impl_get_fn_traits {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
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(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
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(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl<T> FnOnce<(T,)> for $ty<T> {
|
||||
type Output = ();
|
||||
|
||||
@@ -65,7 +65,7 @@ macro_rules! impl_set_fn_traits {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
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(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
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);
|
||||
///
|
||||
/// // ❌ you could call the getter within the setter
|
||||
/// // ❌ don't try to call the getter within the setter
|
||||
/// // set_count(count() + 1);
|
||||
///
|
||||
/// // ✅ however it's more efficient to use .update() and mutate the value in place
|
||||
/// // ✅ instead, use .update() to 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);
|
||||
///
|
||||
/// // ❌ you could call the getter within the setter
|
||||
/// // ❌ don't try to call the getter within the setter
|
||||
/// // set_count(count() + 1);
|
||||
///
|
||||
/// // ✅ however it's more efficient to use .update() and mutate the value in place
|
||||
/// // ✅ instead, use .update() to 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);
|
||||
///
|
||||
/// // ❌ you could call the getter within the setter
|
||||
/// // ❌ don't try to call the getter within the setter
|
||||
/// // set_count(count() + 1);
|
||||
///
|
||||
/// // ✅ however it's more efficient to use .update() and mutate the value in place
|
||||
/// // ✅ instead, use .update() to 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);
|
||||
///
|
||||
/// // ❌ you can call the getter within the setter
|
||||
/// // ❌ don't try to call the getter within the setter
|
||||
/// // count.set(count.get() + 1);
|
||||
///
|
||||
/// // ✅ however, it's more efficient to use .update() and mutate the value in place
|
||||
/// // ✅ instead, use .update() to 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);
|
||||
///
|
||||
/// // ❌ you can call the getter within the setter
|
||||
/// // ❌ don't try to call the getter within the setter
|
||||
/// // count.set(count.get() + 1);
|
||||
///
|
||||
/// // ✅ however, it's more efficient to use .update() and mutate the value in place
|
||||
/// // ✅ instead, use .update() to 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(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl FnOnce<()> for Trigger {
|
||||
type Output = ();
|
||||
|
||||
@@ -235,7 +235,7 @@ impl FnOnce<()> for Trigger {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
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(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl Fn<()> for Trigger {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
use leptos_reactive::{
|
||||
create_isomorphic_effect, create_memo, create_runtime, create_rw_signal,
|
||||
create_scope, create_signal, SignalSet,
|
||||
};
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn effect_runs() {
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
@@ -32,7 +32,7 @@ fn effect_runs() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn effect_tracks_memo() {
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
@@ -62,7 +62,7 @@ fn effect_tracks_memo() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn untrack_mutes_effect() {
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
@@ -92,7 +92,7 @@ fn untrack_mutes_effect() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn batching_actually_batches() {
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
use leptos_reactive::{
|
||||
create_memo, create_runtime, create_scope, create_signal,
|
||||
};
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn basic_memo() {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
@@ -13,7 +13,7 @@ fn basic_memo() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn memo_with_computed_value() {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
@@ -29,7 +29,7 @@ fn memo_with_computed_value() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn nested_memos() {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
@@ -51,7 +51,7 @@ fn nested_memos() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[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(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn diamond_problem() {
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
@@ -131,7 +131,7 @@ fn diamond_problem() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn dynamic_dependencies() {
|
||||
use leptos_reactive::create_isomorphic_effect;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
use leptos_reactive::{create_runtime, create_scope, create_signal};
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn basic_signal() {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
@@ -13,7 +13,7 @@ fn basic_signal() {
|
||||
.dispose()
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[test]
|
||||
fn derived_signals() {
|
||||
create_scope(create_runtime(), |cx| {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
use leptos_reactive::{
|
||||
create_isomorphic_effect, create_runtime, create_scope, create_signal,
|
||||
signal_prelude::*, SignalGetUntracked, SignalSetUntracked,
|
||||
};
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[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(feature = "nightly")]
|
||||
#[cfg(not(feature = "stable"))]
|
||||
#[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"]
|
||||
nightly = ["leptos_reactive/nightly", "server_fn/nightly"]
|
||||
stable = ["leptos_reactive/stable", "server_fn/stable"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["stable"]
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
//! This should be fairly obvious: we have to serialize arguments to send them to the server, and we
|
||||
//! need to deserialize the result to return it to the client.
|
||||
//! - **Arguments must be implement [serde::Serialize].** They are serialized as an `application/x-www-form-urlencoded`
|
||||
//! form data using [`serde_qs`](https://docs.rs/serde_qs/latest/serde_qs/) or as `application/cbor`
|
||||
//! form data using [`serde_html_form`](https://docs.rs/serde_html_form/latest/serde_html_form/) or as `application/cbor`
|
||||
//! using [`cbor`](https://docs.rs/cbor/latest/cbor/). **Note**: You should explicitly include `serde` with the
|
||||
//! `derive` feature enabled in your `Cargo.toml`. You can do this by running `cargo add serde --features=derive`.
|
||||
//! - **The [Scope](leptos_reactive::Scope) comes from the server.** Optionally, the first argument of a server function
|
||||
|
||||
@@ -22,7 +22,7 @@ default = []
|
||||
csr = ["leptos/csr", "leptos/tracing"]
|
||||
hydrate = ["leptos/hydrate", "leptos/tracing"]
|
||||
ssr = ["leptos/ssr", "leptos/tracing"]
|
||||
nightly = ["leptos/nightly", "leptos/tracing"]
|
||||
stable = ["leptos/stable", "leptos/tracing"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["stable"]
|
||||
|
||||
@@ -21,7 +21,7 @@ regex = { version = "1", optional = true }
|
||||
url = { version = "2", optional = true }
|
||||
percent-encoding = "2"
|
||||
thiserror = "1"
|
||||
serde_qs = "0.12"
|
||||
serde_html_form = "0.2"
|
||||
serde = "1"
|
||||
tracing = "0.1"
|
||||
js-sys = { version = "0.3" }
|
||||
@@ -59,7 +59,7 @@ default = []
|
||||
csr = ["leptos/csr"]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = ["leptos/ssr", "dep:cached", "dep:lru", "dep:url", "dep:regex"]
|
||||
nightly = ["leptos/nightly"]
|
||||
stable = ["leptos/stable"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
# No need to test optional dependencies as they are enabled by the ssr feature
|
||||
|
||||
@@ -206,12 +206,6 @@ 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,)
|
||||
@@ -270,61 +264,55 @@ where
|
||||
let resp = resp.clone().expect("couldn't get Response");
|
||||
let status = resp.status();
|
||||
spawn_local(async move {
|
||||
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:?}");
|
||||
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(
|
||||
ServerFnError::Request(
|
||||
e.as_string().unwrap_or_default(),
|
||||
),
|
||||
)));
|
||||
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(ServerFnError::Request(
|
||||
e.as_string().unwrap_or_default(),
|
||||
))));
|
||||
}
|
||||
}
|
||||
};
|
||||
input.try_set(None);
|
||||
action.set_pending(false);
|
||||
});
|
||||
@@ -549,12 +537,14 @@ where
|
||||
Self: Sized + serde::de::DeserializeOwned,
|
||||
{
|
||||
/// Tries to deserialize the data, given only the `submit` event.
|
||||
fn from_event(ev: &web_sys::Event) -> Result<Self, serde_qs::Error>;
|
||||
fn from_event(
|
||||
ev: &web_sys::Event,
|
||||
) -> Result<Self, serde_html_form::de::Error>;
|
||||
|
||||
/// Tries to deserialize the data, given the actual form data.
|
||||
fn from_form_data(
|
||||
form_data: &web_sys::FormData,
|
||||
) -> Result<Self, serde_qs::Error>;
|
||||
) -> Result<Self, serde_html_form::de::Error>;
|
||||
}
|
||||
|
||||
impl<T> FromFormData for T
|
||||
@@ -565,7 +555,9 @@ where
|
||||
any(debug_assertions, feature = "ssr"),
|
||||
tracing::instrument(level = "trace", skip_all,)
|
||||
)]
|
||||
fn from_event(ev: &web_sys::Event) -> Result<Self, serde_qs::Error> {
|
||||
fn from_event(
|
||||
ev: &web_sys::Event,
|
||||
) -> Result<Self, serde_html_form::de::Error> {
|
||||
let (form, _, _, _) = extract_form_attributes(ev);
|
||||
|
||||
let form_data = web_sys::FormData::new_with_form(&form).unwrap_throw();
|
||||
@@ -578,11 +570,11 @@ where
|
||||
)]
|
||||
fn from_form_data(
|
||||
form_data: &web_sys::FormData,
|
||||
) -> Result<Self, serde_qs::Error> {
|
||||
) -> Result<Self, serde_html_form::de::Error> {
|
||||
let data =
|
||||
web_sys::UrlSearchParams::new_with_str_sequence_sequence(form_data)
|
||||
.unwrap_throw();
|
||||
let data = data.to_string().as_string().unwrap_or_default();
|
||||
serde_qs::from_str::<Self>(&data)
|
||||
serde_html_form::from_str::<Self>(&data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ where
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "nightly")] {
|
||||
if #[cfg(not(feature = "stable"))] {
|
||||
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(feature = "nightly", feature(auto_traits))]
|
||||
#![cfg_attr(feature = "nightly", feature(negative_impls))]
|
||||
#![cfg_attr(feature = "nightly", feature(type_name_of_val))]
|
||||
#![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))]
|
||||
|
||||
mod animation;
|
||||
mod components;
|
||||
|
||||
@@ -11,7 +11,7 @@ readme = "../README.md"
|
||||
[dependencies]
|
||||
server_fn_macro_default = { workspace = true }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_qs = "0.12"
|
||||
serde_html_form = "0.2"
|
||||
thiserror = "1"
|
||||
serde_json = "1"
|
||||
quote = "1"
|
||||
@@ -34,4 +34,4 @@ default = ["default-tls"]
|
||||
default-tls = ["reqwest/default-tls"]
|
||||
rustls = ["reqwest/rustls-tls"]
|
||||
ssr = []
|
||||
nightly = ["server_fn_macro_default/nightly"]
|
||||
stable = ["server_fn_macro_default/stable"]
|
||||
|
||||
@@ -19,4 +19,4 @@ server_fn = { version = "0.2" }
|
||||
serde = "1"
|
||||
|
||||
[features]
|
||||
nightly = ["server_fn_macro/nightly"]
|
||||
stable = ["server_fn_macro/stable"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![cfg_attr(feature = "nightly", feature(proc_macro_span))]
|
||||
#![cfg_attr(not(feature = "stable"), 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)]
|
||||
|
||||
@@ -49,7 +49,7 @@ use syn::__private::ToTokens;
|
||||
/// - **Arguments must be implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html)
|
||||
/// and [`DeserializeOwned`](https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html).**
|
||||
/// They are serialized as an `application/x-www-form-urlencoded`
|
||||
/// form data using [`serde_qs`](https://docs.rs/serde_qs/latest/serde_qs/) or as `application/cbor`
|
||||
/// form data using [`serde_html_form`](https://docs.rs/serde_html_form/latest/serde_html_form/) or as `application/cbor`
|
||||
/// using [`cbor`](https://docs.rs/cbor/latest/cbor/).
|
||||
#[proc_macro_attribute]
|
||||
pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
//! This should be fairly obvious: we have to serialize arguments to send them to the server, and we
|
||||
//! need to deserialize the result to return it to the client.
|
||||
//! - **Arguments must be implement [serde::Serialize].** They are serialized as an `application/x-www-form-urlencoded`
|
||||
//! form data using [`serde_qs`](https://docs.rs/serde_qs/latest/serde_qs/) or as `application/cbor`
|
||||
//! form data using [`serde_html_form`](https://docs.rs/serde_html_form/latest/serde_html_form/) or as `application/cbor`
|
||||
//! using [`cbor`](https://docs.rs/cbor/latest/cbor/).
|
||||
|
||||
// used by the macro
|
||||
@@ -308,7 +308,7 @@ where
|
||||
// decode the args
|
||||
let value = match Self::encoding() {
|
||||
Encoding::Url | Encoding::GetJSON | Encoding::GetCBOR => {
|
||||
serde_qs::from_bytes(data).map_err(|e| {
|
||||
serde_html_form::from_bytes(data).map_err(|e| {
|
||||
ServerFnError::Deserialization(e.to_string())
|
||||
})
|
||||
}
|
||||
@@ -408,7 +408,7 @@ where
|
||||
}
|
||||
let args_encoded = match &enc {
|
||||
Encoding::Url | Encoding::GetJSON | Encoding::GetCBOR => Payload::Url(
|
||||
serde_qs::to_string(&args)
|
||||
serde_html_form::to_string(&args)
|
||||
.map_err(|e| ServerFnError::Serialization(e.to_string()))?,
|
||||
),
|
||||
Encoding::Cbor => {
|
||||
|
||||
@@ -18,4 +18,4 @@ xxhash-rust = { version = "0.8.6", features = ["const_xxh64"] }
|
||||
const_format = "0.2.30"
|
||||
|
||||
[features]
|
||||
nightly = []
|
||||
stable = []
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![cfg_attr(feature = "nightly", feature(proc_macro_span))]
|
||||
#![cfg_attr(not(feature = "stable"), feature(proc_macro_span))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
//! Implementation of the server_fn macro.
|
||||
|
||||
Reference in New Issue
Block a user