mirror of
https://github.com/leptos-rs/leptos
synced 2024-09-20 06:21:57 +00:00
Merge pull request #196 from gbj/cleanup
Clean up issues relating to `0.1.0` merge
This commit is contained in:
commit
26e90d1959
24 changed files with 269 additions and 237 deletions
47
README.md
47
README.md
|
@ -63,9 +63,20 @@ Here are some resources for learning more about Leptos:
|
|||
- [Common Bugs](https://github.com/gbj/leptos/tree/main/docs/COMMON_BUGS.md) (and how to fix them!)
|
||||
- Leptos Guide (in progress)
|
||||
|
||||
|
||||
## `nightly` Note
|
||||
|
||||
Most of the examples assume you’re using `nightly` Rust. If you’re on stable, note the following:
|
||||
Most of the examples assume you’re using `nightly` Rust.
|
||||
|
||||
To set up your Rust toolchain using `nightly` (and add the ability to compile Rust to WebAssembly, if you haven’t already)
|
||||
|
||||
```
|
||||
rustup toolchain install nightly
|
||||
rustup default nightly
|
||||
rustup target add wasm32-unknown-unknown
|
||||
```
|
||||
|
||||
If you’re on `stable`, note the following:
|
||||
|
||||
1. You need to enable the `"stable"` flag in `Cargo.toml`: `leptos = { version = "0.1.0-alpha", features = ["stable"] }`
|
||||
2. `nightly` enables the function call syntax for accessing and setting signals. If you’re using `stable`,
|
||||
|
@ -73,6 +84,17 @@ Most of the examples assume you’re using `nightly` Rust. If you’re on stable
|
|||
[`counters-stable` example](https://github.com/gbj/leptos/blob/main/examples/counters-stable/src/main.rs)
|
||||
for examples of the correct API.
|
||||
|
||||
## `cargo-leptos`
|
||||
|
||||
[`cargo-leptos`](https://github.com/akesson/cargo-leptos) is a build tool that's designed to make it easy to build apps that run on both the client and the server, with seamless integration. The best way to get started with a real Leptos project right now is to use `cargo-leptos` and our [starter template](https://github.com/leptos-rs/start).
|
||||
|
||||
```bash
|
||||
cargo install cargo-leptos
|
||||
cargo leptos new --git https://github.com/leptos-rs/start
|
||||
cd [your project name]
|
||||
cargo leptos watch
|
||||
```
|
||||
|
||||
## FAQs
|
||||
|
||||
### Can I use this for native GUI?
|
||||
|
@ -106,17 +128,16 @@ There are some practical differences that make a significant difference:
|
|||
- **Read-write segregation:** Leptos, like Solid, encourages read-write segregation between signal getters and setters, so you end up accessing signals with tuples like `let (count, set_count) = create_signal(cx, 0);` _(If you prefer or if it's more convenient for your API, you can use `create_rw_signal` to give a unified read/write signal.)_
|
||||
- **Signals are functions:** In Leptos, you can call a signal to access it rather than calling a specific method (so, `count()` instead of `count.get()`) This creates a more consistent mental model: accessing a reactive value is always a matter of calling a function. For example:
|
||||
|
||||
```rust
|
||||
let (count, set_count) = create_signal(cx, 0); // a signal
|
||||
let double_count = move || count() * 2; // a derived signal
|
||||
let memoized_count = create_memo(cx, move |_| count() * 3); // a memo
|
||||
// all are accessed by calling them
|
||||
assert_eq!(count(), 0);
|
||||
assert_eq!(double_count(), 0);
|
||||
assert_eq!(memoized_count(), 0);
|
||||
|
||||
// this function can accept any of those signals
|
||||
fn do_work_on_signal(my_signal: impl Fn() -> i32) { ... }
|
||||
```
|
||||
```rust
|
||||
let (count, set_count) = create_signal(cx, 0); // a signal
|
||||
let double_count = move || count() * 2; // a derived signal
|
||||
let memoized_count = create_memo(cx, move |_| count() * 3); // a memo
|
||||
// all are accessed by calling them
|
||||
assert_eq!(count(), 0);
|
||||
assert_eq!(double_count(), 0);
|
||||
assert_eq!(memoized_count(), 0);
|
||||
// this function can accept any of those signals
|
||||
fn do_work_on_signal(my_signal: impl Fn() -> i32) { ... }
|
||||
```
|
||||
|
||||
- **Signals and scopes are `'static`:** Both Leptos and Sycamore ease the pain of moving signals in closures (in particular, event listeners) by making them `Copy`, to avoid the `{ let count = count.clone(); move |_| ... }` that's very familiar in Rust UI code. Sycamore does this by using bump allocation to tie the lifetimes of its signals to its scopes: since references are `Copy`, `&'a Signal<T>` can be moved into a closure. Leptos does this by using arena allocation and passing around indices: types like `ReadSignal<T>`, `WriteSignal<T>`, and `Memo<T>` are actually wrapper for indices into an arena. This means that both scopes and signals are both `Copy` and `'static` in Leptos, which means that they can be moved easily into closures without adding lifetime complexity.
|
||||
|
|
|
@ -10,7 +10,7 @@ This document is intended as a running list of common issues, with example code
|
|||
|
||||
```rust
|
||||
let (a, set_a) = create_signal(cx, 0);
|
||||
let (b, set_a) = create_signal(cx, false);
|
||||
let (b, set_b) = create_signal(cx, false);
|
||||
|
||||
create_effect(cx, move |_| {
|
||||
if a() > 5 {
|
||||
|
|
|
@ -38,6 +38,7 @@ ssr = [
|
|||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
]
|
||||
stable = ["leptos/stable", "leptos_router/stable"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["actix-files", "actix-web", "leptos_actix"]
|
||||
|
|
|
@ -22,42 +22,45 @@ pub fn Story(cx: Scope) -> impl IntoView {
|
|||
view! { cx,
|
||||
<>
|
||||
<Meta name="description" content=meta_description/>
|
||||
{move || story.read().map(|story| match story {
|
||||
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
|
||||
Some(story) => view! { cx,
|
||||
<div class="item-view">
|
||||
<div class="item-view-header">
|
||||
<a href=story.url target="_blank">
|
||||
<h1>{story.title}</h1>
|
||||
</a>
|
||||
<span class="host">
|
||||
"("{story.domain}")"
|
||||
</span>
|
||||
{story.user.map(|user| view! { cx, <p class="meta">
|
||||
{story.points}
|
||||
" points | by "
|
||||
<A href=format!("/users/{}", user)>{user.clone()}</A>
|
||||
{format!(" {}", story.time_ago)}
|
||||
</p>})}
|
||||
<Suspense fallback=|| view! { cx, "Loading..." }>
|
||||
{move || story.read().map(|story| match story {
|
||||
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
|
||||
Some(story) => view! { cx,
|
||||
<div class="item-view">
|
||||
<div class="item-view-header">
|
||||
<a href=story.url target="_blank">
|
||||
<h1>{story.title}</h1>
|
||||
</a>
|
||||
<span class="host">
|
||||
"("{story.domain}")"
|
||||
</span>
|
||||
{story.user.map(|user| view! { cx, <p class="meta">
|
||||
{story.points}
|
||||
" points | by "
|
||||
<A href=format!("/users/{}", user)>{user.clone()}</A>
|
||||
{format!(" {}", story.time_ago)}
|
||||
</p>})}
|
||||
</div>
|
||||
<div class="item-view-comments">
|
||||
<p class="item-view-comments-header">
|
||||
{if story.comments_count.unwrap_or_default() > 0 {
|
||||
format!("{} comments", story.comments_count.unwrap_or_default())
|
||||
} else {
|
||||
"No comments yet.".into()
|
||||
}}
|
||||
</p>
|
||||
<ul class="comment-children">
|
||||
<For
|
||||
each=move || story.comments.clone().unwrap_or_default()
|
||||
key=|comment| comment.id
|
||||
view=move |comment| view! { cx, <Comment comment /> }
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-view-comments">
|
||||
<p class="item-view-comments-header">
|
||||
{if story.comments_count.unwrap_or_default() > 0 {
|
||||
format!("{} comments", story.comments_count.unwrap_or_default())
|
||||
} else {
|
||||
"No comments yet.".into()
|
||||
}}
|
||||
</p>
|
||||
<ul class="comment-children">
|
||||
<For
|
||||
each=move || story.comments.clone().unwrap_or_default()
|
||||
key=|comment| comment.id
|
||||
view=move |comment| view! { cx, <Comment comment /> }
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}})}
|
||||
}})
|
||||
}
|
||||
</Suspense>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,28 +18,30 @@ pub fn User(cx: Scope) -> impl IntoView {
|
|||
);
|
||||
view! { cx,
|
||||
<div class="user-view">
|
||||
{move || user.read().map(|user| match user {
|
||||
None => view! { cx, <h1>"User not found."</h1> }.into_any(),
|
||||
Some(user) => view! { cx,
|
||||
<div>
|
||||
<h1>"User: " {&user.id}</h1>
|
||||
<ul class="meta">
|
||||
<li>
|
||||
<span class="label">"Created: "</span> {user.created}
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">"Karma: "</span> {user.karma}
|
||||
</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>
|
||||
" | "
|
||||
<a href=format!("https://news.ycombinator.com/threads?id={}", user.id)>"comments"</a>
|
||||
</p>
|
||||
</div>
|
||||
}.into_any()
|
||||
})}
|
||||
<Suspense fallback=|| view! { cx, "Loading..." }>
|
||||
{move || user.read().map(|user| match user {
|
||||
None => view! { cx, <h1>"User not found."</h1> }.into_any(),
|
||||
Some(user) => view! { cx,
|
||||
<div>
|
||||
<h1>"User: " {&user.id}</h1>
|
||||
<ul class="meta">
|
||||
<li>
|
||||
<span class="label">"Created: "</span> {user.created}
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">"Karma: "</span> {user.karma}
|
||||
</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>
|
||||
" | "
|
||||
<a href=format!("https://news.ycombinator.com/threads?id={}", user.id)>"comments"</a>
|
||||
</p>
|
||||
</div>
|
||||
}.into_any()
|
||||
})}
|
||||
</Suspense>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
|||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
output-name = "leptos_hackernews"
|
||||
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
|
||||
site-root = "target/site"
|
||||
site-root = "/pkg"
|
||||
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
|
||||
# Defaults to pkg
|
||||
site-pkg-dir = "pkg"
|
||||
|
|
|
@ -22,42 +22,45 @@ pub fn Story(cx: Scope) -> impl IntoView {
|
|||
view! { cx,
|
||||
<>
|
||||
<Meta name="description" content=meta_description/>
|
||||
{move || story.read().map(|story| match story {
|
||||
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
|
||||
Some(story) => view! { cx,
|
||||
<div class="item-view">
|
||||
<div class="item-view-header">
|
||||
<a href=story.url target="_blank">
|
||||
<h1>{story.title}</h1>
|
||||
</a>
|
||||
<span class="host">
|
||||
"("{story.domain}")"
|
||||
</span>
|
||||
{story.user.map(|user| view! { cx, <p class="meta">
|
||||
{story.points}
|
||||
" points | by "
|
||||
<A href=format!("/users/{}", user)>{user.clone()}</A>
|
||||
{format!(" {}", story.time_ago)}
|
||||
</p>})}
|
||||
<Suspense fallback=|| view! { cx, "Loading..." }>
|
||||
{move || story.read().map(|story| match story {
|
||||
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
|
||||
Some(story) => view! { cx,
|
||||
<div class="item-view">
|
||||
<div class="item-view-header">
|
||||
<a href=story.url target="_blank">
|
||||
<h1>{story.title}</h1>
|
||||
</a>
|
||||
<span class="host">
|
||||
"("{story.domain}")"
|
||||
</span>
|
||||
{story.user.map(|user| view! { cx, <p class="meta">
|
||||
{story.points}
|
||||
" points | by "
|
||||
<A href=format!("/users/{}", user)>{user.clone()}</A>
|
||||
{format!(" {}", story.time_ago)}
|
||||
</p>})}
|
||||
</div>
|
||||
<div class="item-view-comments">
|
||||
<p class="item-view-comments-header">
|
||||
{if story.comments_count.unwrap_or_default() > 0 {
|
||||
format!("{} comments", story.comments_count.unwrap_or_default())
|
||||
} else {
|
||||
"No comments yet.".into()
|
||||
}}
|
||||
</p>
|
||||
<ul class="comment-children">
|
||||
<For
|
||||
each=move || story.comments.clone().unwrap_or_default()
|
||||
key=|comment| comment.id
|
||||
view=move |comment| view! { cx, <Comment comment /> }
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-view-comments">
|
||||
<p class="item-view-comments-header">
|
||||
{if story.comments_count.unwrap_or_default() > 0 {
|
||||
format!("{} comments", story.comments_count.unwrap_or_default())
|
||||
} else {
|
||||
"No comments yet.".into()
|
||||
}}
|
||||
</p>
|
||||
<ul class="comment-children">
|
||||
<For
|
||||
each=move || story.comments.clone().unwrap_or_default()
|
||||
key=|comment| comment.id
|
||||
view=move |comment| view! { cx, <Comment comment /> }
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}})}
|
||||
}})
|
||||
}
|
||||
</Suspense>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,28 +18,30 @@ pub fn User(cx: Scope) -> impl IntoView {
|
|||
);
|
||||
view! { cx,
|
||||
<div class="user-view">
|
||||
{move || user.read().map(|user| match user {
|
||||
None => view! { cx, <h1>"User not found."</h1> }.into_any(),
|
||||
Some(user) => view! { cx,
|
||||
<div>
|
||||
<h1>"User: " {&user.id}</h1>
|
||||
<ul class="meta">
|
||||
<li>
|
||||
<span class="label">"Created: "</span> {user.created}
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">"Karma: "</span> {user.karma}
|
||||
</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>
|
||||
" | "
|
||||
<a href=format!("https://news.ycombinator.com/threads?id={}", user.id)>"comments"</a>
|
||||
</p>
|
||||
</div>
|
||||
}.into_any()
|
||||
})}
|
||||
<Suspense fallback=|| view! { cx, "Loading..." }>
|
||||
{move || user.read().map(|user| match user {
|
||||
None => view! { cx, <h1>"User not found."</h1> }.into_any(),
|
||||
Some(user) => view! { cx,
|
||||
<div>
|
||||
<h1>"User: " {&user.id}</h1>
|
||||
<ul class="meta">
|
||||
<li>
|
||||
<span class="label">"Created: "</span> {user.created}
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">"Karma: "</span> {user.karma}
|
||||
</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>
|
||||
" | "
|
||||
<a href=format!("https://news.ycombinator.com/threads?id={}", user.id)>"comments"</a>
|
||||
</p>
|
||||
</div>
|
||||
}.into_any()
|
||||
})}
|
||||
</Suspense>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,7 +144,7 @@ pub fn Settings(cx: Scope) -> impl IntoView {
|
|||
<fieldset>
|
||||
<legend>"Name"</legend>
|
||||
<input type="text" name="first_name" placeholder="First"/>
|
||||
<input type="text" name="first_name" placeholder="Last"/>
|
||||
<input type="text" name="last_name" placeholder="Last"/>
|
||||
</fieldset>
|
||||
<pre>"This page is just a placeholder."</pre>
|
||||
</form>
|
||||
|
|
|
@ -14,9 +14,9 @@ cfg_if! {
|
|||
}
|
||||
|
||||
pub fn register_server_functions() {
|
||||
GetTodos::register();
|
||||
AddTodo::register();
|
||||
DeleteTodo::register();
|
||||
_ = GetTodos::register();
|
||||
_ = AddTodo::register();
|
||||
_ = DeleteTodo::register();
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
|
||||
|
@ -151,7 +151,7 @@ pub fn Todos(cx: Scope) -> impl IntoView {
|
|||
</label>
|
||||
<input type="submit" value="Add"/>
|
||||
</MultiActionForm>
|
||||
<Suspense fallback=move || view! {cx, <p>"Loading..."</p> }>
|
||||
<Transition fallback=move || view! {cx, <p>"Loading..."</p> }>
|
||||
{
|
||||
let delete_todo = delete_todo.clone();
|
||||
move || {
|
||||
|
@ -221,7 +221,7 @@ pub fn Todos(cx: Scope) -> impl IntoView {
|
|||
}
|
||||
}
|
||||
}
|
||||
</Suspense>
|
||||
</Transition>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ cfg_if! {
|
|||
use actix_files::{Files};
|
||||
use actix_web::*;
|
||||
use crate::todo::*;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
#[get("/style.css")]
|
||||
async fn css() -> impl Responder {
|
||||
|
|
|
@ -13,9 +13,9 @@ cfg_if! {
|
|||
}
|
||||
|
||||
pub fn register_server_functions() {
|
||||
GetTodos::register();
|
||||
AddTodo::register();
|
||||
DeleteTodo::register();
|
||||
_ = GetTodos::register();
|
||||
_ = AddTodo::register();
|
||||
_ = DeleteTodo::register();
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
|
||||
|
@ -135,7 +135,7 @@ pub fn Todos(cx: Scope) -> impl IntoView {
|
|||
</label>
|
||||
<input type="submit" value="Add"/>
|
||||
</MultiActionForm>
|
||||
<Suspense fallback=move || view! {cx, <p>"Loading..."</p> }>
|
||||
<Transition fallback=move || view! {cx, <p>"Loading..."</p> }>
|
||||
{
|
||||
let delete_todo = delete_todo.clone();
|
||||
move || {
|
||||
|
@ -205,7 +205,7 @@ pub fn Todos(cx: Scope) -> impl IntoView {
|
|||
}
|
||||
}
|
||||
}
|
||||
</Suspense>
|
||||
</Transition>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -187,8 +187,10 @@ where
|
|||
// If it was, `DynChild` no longer "owns" that child, and
|
||||
// is therefore no longer sound to unmount it from the DOM
|
||||
// or to reuse it in the case of a text node
|
||||
let was_child_moved =
|
||||
child.get_closing_node().next_sibling().as_ref() != Some(&closing);
|
||||
|
||||
// FIXME this was breaking DynChild updates on text nodes, at least...
|
||||
let was_child_moved = false;
|
||||
//child.get_closing_node().next_sibling().as_ref() != Some(&closing);
|
||||
|
||||
// If the previous child was a text node, we would like to
|
||||
// make use of it again if our current child is also a text
|
||||
|
|
|
@ -49,7 +49,7 @@ use std::fmt::Debug;
|
|||
/// ```
|
||||
pub fn create_effect<T>(cx: Scope, f: impl Fn(Option<T>) -> T + 'static)
|
||||
where
|
||||
T: Debug + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
cfg_if! {
|
||||
if #[cfg(not(feature = "ssr"))] {
|
||||
|
@ -90,7 +90,7 @@ where
|
|||
/// # }).dispose();
|
||||
pub fn create_isomorphic_effect<T>(cx: Scope, f: impl Fn(Option<T>) -> T + 'static)
|
||||
where
|
||||
T: Debug + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
let e = cx.runtime.create_effect(f);
|
||||
cx.with_scope_property(|prop| prop.push(ScopeProperty::Effect(e)))
|
||||
|
@ -99,7 +99,7 @@ where
|
|||
#[doc(hidden)]
|
||||
pub fn create_render_effect<T>(cx: Scope, f: impl Fn(Option<T>) -> T + 'static)
|
||||
where
|
||||
T: Debug + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
create_effect(cx, f);
|
||||
}
|
||||
|
|
|
@ -215,7 +215,7 @@ where
|
|||
#[cfg(not(feature = "stable"))]
|
||||
impl<T> FnOnce<()> for Memo<T>
|
||||
where
|
||||
T: Debug + Clone,
|
||||
T: Clone,
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
|
@ -227,7 +227,7 @@ where
|
|||
#[cfg(not(feature = "stable"))]
|
||||
impl<T> FnMut<()> for Memo<T>
|
||||
where
|
||||
T: Debug + Clone,
|
||||
T: Clone,
|
||||
{
|
||||
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
|
@ -237,7 +237,7 @@ where
|
|||
#[cfg(not(feature = "stable"))]
|
||||
impl<T> Fn<()> for Memo<T>
|
||||
where
|
||||
T: Debug + Clone,
|
||||
T: Clone,
|
||||
{
|
||||
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
|
|
|
@ -66,7 +66,7 @@ pub fn create_resource<S, T, Fu>(
|
|||
) -> Resource<S, T>
|
||||
where
|
||||
S: PartialEq + Debug + Clone + 'static,
|
||||
T: Debug + Serializable + 'static,
|
||||
T: Serializable + 'static,
|
||||
Fu: Future<Output = T> + 'static,
|
||||
{
|
||||
// can't check this on the server without running the future
|
||||
|
@ -91,7 +91,7 @@ pub fn create_resource_with_initial_value<S, T, Fu>(
|
|||
) -> Resource<S, T>
|
||||
where
|
||||
S: PartialEq + Debug + Clone + 'static,
|
||||
T: Debug + Serializable + 'static,
|
||||
T: Serializable + 'static,
|
||||
Fu: Future<Output = T> + 'static,
|
||||
{
|
||||
let resolved = initial_value.is_some();
|
||||
|
@ -173,7 +173,7 @@ pub fn create_local_resource<S, T, Fu>(
|
|||
) -> Resource<S, T>
|
||||
where
|
||||
S: PartialEq + Debug + Clone + 'static,
|
||||
T: Debug + 'static,
|
||||
T: 'static,
|
||||
Fu: Future<Output = T> + 'static,
|
||||
{
|
||||
let initial_value = None;
|
||||
|
@ -195,7 +195,7 @@ pub fn create_local_resource_with_initial_value<S, T, Fu>(
|
|||
) -> Resource<S, T>
|
||||
where
|
||||
S: PartialEq + Debug + Clone + 'static,
|
||||
T: Debug + 'static,
|
||||
T: 'static,
|
||||
Fu: Future<Output = T> + 'static,
|
||||
{
|
||||
let resolved = initial_value.is_some();
|
||||
|
@ -244,7 +244,7 @@ where
|
|||
fn load_resource<S, T>(_cx: Scope, _id: ResourceId, r: Rc<ResourceState<S, T>>)
|
||||
where
|
||||
S: PartialEq + Debug + Clone + 'static,
|
||||
T: Debug + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
r.load(false)
|
||||
}
|
||||
|
@ -253,7 +253,7 @@ where
|
|||
fn load_resource<S, T>(cx: Scope, id: ResourceId, r: Rc<ResourceState<S, T>>)
|
||||
where
|
||||
S: PartialEq + Debug + Clone + 'static,
|
||||
T: Debug + Serializable + 'static,
|
||||
T: Serializable + 'static,
|
||||
{
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
|
||||
|
@ -267,18 +267,8 @@ where
|
|||
|
||||
let res = T::from_json(&data).expect_throw("could not deserialize Resource JSON");
|
||||
|
||||
// if we're under Suspense, the HTML has already streamed in so we can just set it
|
||||
// if not under Suspense, there will be a hydration mismatch, so let's wait a tick
|
||||
if use_context::<SuspenseContext>(cx).is_some() {
|
||||
r.set_value.update(|n| *n = Some(res));
|
||||
r.set_loading.update(|n| *n = false);
|
||||
} else {
|
||||
let r = Rc::clone(&r);
|
||||
spawn_local(async move {
|
||||
r.set_value.update(|n| *n = Some(res));
|
||||
r.set_loading.update(|n| *n = false);
|
||||
});
|
||||
}
|
||||
r.set_value.update(|n| *n = Some(res));
|
||||
r.set_loading.update(|n| *n = false);
|
||||
|
||||
// for reactivity
|
||||
r.source.subscribe();
|
||||
|
@ -326,8 +316,8 @@ where
|
|||
|
||||
impl<S, T> Resource<S, T>
|
||||
where
|
||||
S: Debug + Clone + 'static,
|
||||
T: Debug + 'static,
|
||||
S: Clone + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
/// Clones and returns the current value of the resource ([Option::None] if the
|
||||
/// resource is still pending). Also subscribes the running effect to this
|
||||
|
@ -433,8 +423,8 @@ where
|
|||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Resource<S, T>
|
||||
where
|
||||
S: Debug + 'static,
|
||||
T: Debug + 'static,
|
||||
S: 'static,
|
||||
T: 'static,
|
||||
{
|
||||
runtime: RuntimeId,
|
||||
pub(crate) id: ResourceId,
|
||||
|
@ -450,8 +440,8 @@ slotmap::new_key_type! {
|
|||
|
||||
impl<S, T> Clone for Resource<S, T>
|
||||
where
|
||||
S: Debug + Clone + 'static,
|
||||
T: Debug + Clone + 'static,
|
||||
S: Clone + 'static,
|
||||
T: Clone + 'static,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
|
@ -465,16 +455,16 @@ where
|
|||
|
||||
impl<S, T> Copy for Resource<S, T>
|
||||
where
|
||||
S: Debug + Clone + 'static,
|
||||
T: Debug + Clone + 'static,
|
||||
S: Clone + 'static,
|
||||
T: Clone + 'static,
|
||||
{
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl<S, T> FnOnce<()> for Resource<S, T>
|
||||
where
|
||||
S: Debug + Clone + 'static,
|
||||
T: Debug + Clone + 'static,
|
||||
S: Clone + 'static,
|
||||
T: Clone + 'static,
|
||||
{
|
||||
type Output = Option<T>;
|
||||
|
||||
|
@ -486,8 +476,8 @@ where
|
|||
#[cfg(not(feature = "stable"))]
|
||||
impl<S, T> FnMut<()> for Resource<S, T>
|
||||
where
|
||||
S: Debug + Clone + 'static,
|
||||
T: Debug + Clone + 'static,
|
||||
S: Clone + 'static,
|
||||
T: Clone + 'static,
|
||||
{
|
||||
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
|
||||
self.read()
|
||||
|
@ -497,8 +487,8 @@ where
|
|||
#[cfg(not(feature = "stable"))]
|
||||
impl<S, T> Fn<()> for Resource<S, T>
|
||||
where
|
||||
S: Debug + Clone + 'static,
|
||||
T: Debug + Clone + 'static,
|
||||
S: Clone + 'static,
|
||||
T: Clone + 'static,
|
||||
{
|
||||
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
|
||||
self.read()
|
||||
|
@ -509,7 +499,7 @@ where
|
|||
pub(crate) struct ResourceState<S, T>
|
||||
where
|
||||
S: 'static,
|
||||
T: Debug + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
scope: Scope,
|
||||
value: ReadSignal<Option<T>>,
|
||||
|
@ -526,8 +516,8 @@ where
|
|||
|
||||
impl<S, T> ResourceState<S, T>
|
||||
where
|
||||
S: Debug + Clone + 'static,
|
||||
T: Debug + 'static,
|
||||
S: Clone + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
pub fn read(&self) -> Option<T>
|
||||
where
|
||||
|
@ -666,8 +656,8 @@ pub(crate) trait SerializableResource {
|
|||
|
||||
impl<S, T> SerializableResource for ResourceState<S, T>
|
||||
where
|
||||
S: Debug + Clone,
|
||||
T: Debug + Serializable,
|
||||
S: Clone,
|
||||
T: Serializable,
|
||||
{
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
|
@ -686,11 +676,7 @@ pub(crate) trait UnserializableResource {
|
|||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
impl<S, T> UnserializableResource for ResourceState<S, T>
|
||||
where
|
||||
S: Debug,
|
||||
T: Debug,
|
||||
{
|
||||
impl<S, T> UnserializableResource for ResourceState<S, T> {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
|
|
@ -234,8 +234,8 @@ impl Runtime {
|
|||
state: Rc<ResourceState<S, T>>,
|
||||
) -> ResourceId
|
||||
where
|
||||
S: Debug + Clone + 'static,
|
||||
T: Debug + 'static,
|
||||
S: Clone + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
self.resources
|
||||
.borrow_mut()
|
||||
|
@ -247,8 +247,8 @@ impl Runtime {
|
|||
state: Rc<ResourceState<S, T>>,
|
||||
) -> ResourceId
|
||||
where
|
||||
S: Debug + Clone + 'static,
|
||||
T: Debug + Serializable + 'static,
|
||||
S: Clone + 'static,
|
||||
T: Serializable + 'static,
|
||||
{
|
||||
self.resources
|
||||
.borrow_mut()
|
||||
|
@ -261,8 +261,8 @@ impl Runtime {
|
|||
f: impl FnOnce(&ResourceState<S, T>) -> U,
|
||||
) -> U
|
||||
where
|
||||
S: Debug + 'static,
|
||||
T: Debug + 'static,
|
||||
S: 'static,
|
||||
T: 'static,
|
||||
{
|
||||
let resources = self.resources.borrow();
|
||||
let res = resources.get(id);
|
||||
|
|
|
@ -228,7 +228,7 @@ impl<T> Copy for ReadSignal<T> {}
|
|||
#[cfg(not(feature = "stable"))]
|
||||
impl<T> FnOnce<()> for ReadSignal<T>
|
||||
where
|
||||
T: Debug + Clone,
|
||||
T: Clone,
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
|
@ -240,7 +240,7 @@ where
|
|||
#[cfg(not(feature = "stable"))]
|
||||
impl<T> FnMut<()> for ReadSignal<T>
|
||||
where
|
||||
T: Debug + Clone,
|
||||
T: Clone,
|
||||
{
|
||||
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
|
@ -250,7 +250,7 @@ where
|
|||
#[cfg(not(feature = "stable"))]
|
||||
impl<T> Fn<()> for ReadSignal<T>
|
||||
where
|
||||
T: Debug + Clone,
|
||||
T: Clone,
|
||||
{
|
||||
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
|
@ -734,7 +734,7 @@ where
|
|||
#[cfg(not(feature = "stable"))]
|
||||
impl<T> FnOnce<()> for RwSignal<T>
|
||||
where
|
||||
T: Debug + Clone,
|
||||
T: Clone,
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
|
@ -746,7 +746,7 @@ where
|
|||
#[cfg(not(feature = "stable"))]
|
||||
impl<T> FnMut<()> for RwSignal<T>
|
||||
where
|
||||
T: Debug + Clone,
|
||||
T: Clone,
|
||||
{
|
||||
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
|
@ -756,7 +756,7 @@ where
|
|||
#[cfg(not(feature = "stable"))]
|
||||
impl<T> Fn<()> for RwSignal<T>
|
||||
where
|
||||
T: Debug + Clone,
|
||||
T: Clone,
|
||||
{
|
||||
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
|
|
|
@ -9,8 +9,8 @@ description = "Tools to set HTML metadata in the Leptos web framework."
|
|||
|
||||
[dependencies]
|
||||
cfg-if = "1"
|
||||
leptos = { path = "../leptos", version = "0.1.0-alpha", default-features = false }
|
||||
tracing = "0.1"
|
||||
leptos = { path = "../leptos", default-features = false }
|
||||
typed-builder = "0.11"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
|
@ -18,6 +18,10 @@ features = ["HtmlLinkElement", "HtmlMetaElement", "HtmlTitleElement"]
|
|||
|
||||
[features]
|
||||
default = ["csr"]
|
||||
csr = ["leptos/csr", "leptos/tracing"]
|
||||
hydrate = ["leptos/hydrate", "leptos/tracing"]
|
||||
ssr = ["leptos/ssr", "leptos/tracing"]
|
||||
csr = ["leptos/csr"]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = ["leptos/ssr"]
|
||||
stable = ["leptos/stable"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["stable"]
|
||||
|
|
|
@ -57,7 +57,8 @@ default = ["csr"]
|
|||
csr = ["leptos/csr"]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = ["leptos/ssr", "dep:url", "dep:regex"]
|
||||
stable = ["leptos/stable"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
# No need to test optional dependencies as they are enabled by the ssr feature
|
||||
denylist = ["url", "regex"]
|
||||
denylist = ["url", "regex", "stable"]
|
||||
|
|
|
@ -64,7 +64,6 @@ impl std::fmt::Debug for RouterContextInner {
|
|||
f.debug_struct("RouterContextInner")
|
||||
.field("location", &self.location)
|
||||
.field("base", &self.base)
|
||||
.field("history", &std::any::type_name_of_val(&self.history))
|
||||
.field("cx", &self.cx)
|
||||
.field("reference", &self.reference)
|
||||
.field("set_reference", &self.set_reference)
|
||||
|
@ -99,13 +98,15 @@ impl RouterContext {
|
|||
let base = base.unwrap_or_default();
|
||||
let base_path = resolve_path("", base, None);
|
||||
|
||||
if let Some(base_path) = &base_path && source.with(|s| s.value.is_empty()) {
|
||||
history.navigate(&LocationChange {
|
||||
value: base_path.to_string(),
|
||||
replace: true,
|
||||
scroll: false,
|
||||
state: State(None)
|
||||
});
|
||||
if let Some(base_path) = &base_path {
|
||||
if source.with(|s| s.value.is_empty()) {
|
||||
history.navigate(&LocationChange {
|
||||
value: base_path.to_string(),
|
||||
replace: true,
|
||||
scroll: false,
|
||||
state: State(None)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// the current URL
|
||||
|
|
|
@ -126,17 +126,21 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
auto trait NotOption {}
|
||||
impl<T> !NotOption for Option<T> {}
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(feature = "stable"))] {
|
||||
auto trait NotOption {}
|
||||
impl<T> !NotOption for Option<T> {}
|
||||
|
||||
impl<T> IntoParam for T
|
||||
where
|
||||
T: FromStr + NotOption,
|
||||
<T as FromStr>::Err: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
fn into_param(value: Option<&str>, name: &str) -> Result<Self, ParamsError> {
|
||||
let value = value.ok_or_else(|| ParamsError::MissingParam(name.to_string()))?;
|
||||
Self::from_str(value).map_err(|e| ParamsError::Params(Rc::new(e)))
|
||||
impl<T> IntoParam for T
|
||||
where
|
||||
T: FromStr + NotOption,
|
||||
<T as FromStr>::Err: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
fn into_param(value: Option<&str>, name: &str) -> Result<Self, ParamsError> {
|
||||
let value = value.ok_or_else(|| ParamsError::MissingParam(name.to_string()))?;
|
||||
Self::from_str(value).map_err(|e| ParamsError::Params(Rc::new(e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -136,10 +136,9 @@
|
|||
//!
|
||||
//! ```
|
||||
|
||||
#![feature(auto_traits)]
|
||||
#![feature(let_chains)]
|
||||
#![feature(negative_impls)]
|
||||
#![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 components;
|
||||
mod history;
|
||||
|
|
|
@ -80,13 +80,15 @@ impl Matcher {
|
|||
path.push_str(loc_segment);
|
||||
}
|
||||
|
||||
if let Some(splat) = &self.splat && !splat.is_empty() {
|
||||
let value = if len_diff > 0 {
|
||||
loc_segments[self.len..].join("/")
|
||||
} else {
|
||||
"".into()
|
||||
};
|
||||
params.insert(splat.into(), value);
|
||||
if let Some(splat) = &self.splat {
|
||||
if !splat.is_empty() {
|
||||
let value = if len_diff > 0 {
|
||||
loc_segments[self.len..].join("/")
|
||||
} else {
|
||||
"".into()
|
||||
};
|
||||
params.insert(splat.into(), value);
|
||||
}
|
||||
}
|
||||
|
||||
Some(PathMatch { path, params })
|
||||
|
|
Loading…
Reference in a new issue