mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 16:54:41 -05:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a76b9d71f0 | ||
|
|
e768617dbb | ||
|
|
36132a5823 | ||
|
|
6f35f8d197 | ||
|
|
3973c4f420 | ||
|
|
7f2237b91e | ||
|
|
aa6cd08387 | ||
|
|
15fc7dd7be | ||
|
|
eac979e309 | ||
|
|
f7ac7be32b | ||
|
|
2462a1dc92 | ||
|
|
2c6d790cdb | ||
|
|
1c26261fd7 | ||
|
|
c2b239dba2 | ||
|
|
d4044cd5a1 | ||
|
|
43912f4fd0 | ||
|
|
a5293f0b79 | ||
|
|
998eefb8c5 | ||
|
|
63f3508818 | ||
|
|
24308bb0eb | ||
|
|
f804a69f2f | ||
|
|
b596af45f0 | ||
|
|
5206755124 | ||
|
|
53a55a1ef2 | ||
|
|
0f74332d38 | ||
|
|
bb0dff6af5 | ||
|
|
d204ac6d5e | ||
|
|
b0ad85e624 |
49
.github/workflows/autofix.yml
vendored
Normal file
49
.github/workflows/autofix.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: autofix.ci
|
||||
on:
|
||||
pull_request:
|
||||
# Running this workflow on main branch pushes requires write permission to apply changes.
|
||||
# Leave it alone for future uses.
|
||||
# push:
|
||||
# branches: ["main"]
|
||||
permissions:
|
||||
contents: read
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUST_BACKTRACE: 1
|
||||
jobs:
|
||||
autofix:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: {toolchain: nightly, components: "rustfmt, clippy", target: "wasm32-unknown-unknown", rustflags: ""}
|
||||
- name: Install jq
|
||||
run: sudo apt-get install jq
|
||||
- run: |
|
||||
echo "Formatting the workspace"
|
||||
cargo fmt --all
|
||||
|
||||
echo "Running Clippy against each member's features (default features included)"
|
||||
for member in $(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | .name'); do
|
||||
echo "Working on member $member":
|
||||
echo -e "\tdefault-features/no-features:"
|
||||
# this will also run on members with no features or default features
|
||||
cargo clippy --allow-dirty --fix --lib --package "$member"
|
||||
|
||||
features=$(cargo metadata --no-deps --format-version 1 | jq -r ".packages[] | select(.name == \"$member\") | .features | keys[]")
|
||||
for feature in $features; do
|
||||
if [ "$feature" = "default" ]; then
|
||||
continue
|
||||
fi
|
||||
echo -e "\tfeature $feature"
|
||||
cargo clippy --allow-dirty --fix --lib --package "$member" --features "$feature"
|
||||
done
|
||||
done
|
||||
- uses: autofix-ci/action@v1.3.1
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
fail-fast: false
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,3 +14,4 @@ blob.rs
|
||||
|
||||
.vscode
|
||||
vendor
|
||||
hash.txt
|
||||
|
||||
52
Cargo.lock
generated
52
Cargo.lock
generated
@@ -278,7 +278,7 @@ dependencies = [
|
||||
"async-executor",
|
||||
"futures",
|
||||
"glib",
|
||||
"thiserror 2.0.0",
|
||||
"thiserror 2.0.3",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"wasm-bindgen-futures",
|
||||
@@ -400,9 +400,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae"
|
||||
checksum = "49c41b948da08fb481a94546cd874843adc1142278b0af4badf9b1b78599d68d"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
@@ -1171,9 +1171,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "gio-sys"
|
||||
version = "0.20.5"
|
||||
version = "0.20.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "217f464cad5946ae4369c355155e2d16b488c08920601083cb4891e352ae777b"
|
||||
checksum = "b965df6f3534c84816b5c1a7d9efcb5671ae790822de5abe8e299797039529bc"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
@@ -1184,9 +1184,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "glib"
|
||||
version = "0.20.5"
|
||||
version = "0.20.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "358431b0e0eb15b9d02db52e1f19c805b953c5c168099deb3de88beab761768c"
|
||||
checksum = "86bd3e4ee7998ab5a135d900db56930cc19ad16681adf245daff54f618b9d5e1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"futures-channel",
|
||||
@@ -1218,9 +1218,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "glib-sys"
|
||||
version = "0.20.5"
|
||||
version = "0.20.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a5911863ab7ecd4a6f8d5976f12eeba076b23669c49b066d877e742544aa389"
|
||||
checksum = "3d0b1827e8621fc42c0dfb228e5d57ff6a71f9699e666ece8113f979ad87c2de"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"system-deps",
|
||||
@@ -1792,7 +1792,7 @@ dependencies = [
|
||||
"server_fn",
|
||||
"slotmap",
|
||||
"tachys",
|
||||
"thiserror 2.0.0",
|
||||
"thiserror 2.0.3",
|
||||
"throw_error",
|
||||
"tracing",
|
||||
"typed-builder",
|
||||
@@ -1870,7 +1870,7 @@ dependencies = [
|
||||
"serde",
|
||||
"temp-env",
|
||||
"tempfile",
|
||||
"thiserror 2.0.0",
|
||||
"thiserror 2.0.3",
|
||||
"tokio",
|
||||
"typed-builder",
|
||||
]
|
||||
@@ -1981,7 +1981,7 @@ dependencies = [
|
||||
"reactive_graph",
|
||||
"send_wrapper",
|
||||
"tachys",
|
||||
"thiserror 2.0.0",
|
||||
"thiserror 2.0.3",
|
||||
"tracing",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
@@ -2291,7 +2291,7 @@ version = "0.2.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.0",
|
||||
"thiserror 2.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2739,7 +2739,7 @@ dependencies = [
|
||||
"send_wrapper",
|
||||
"serde",
|
||||
"slotmap",
|
||||
"thiserror 2.0.0",
|
||||
"thiserror 2.0.3",
|
||||
"tokio",
|
||||
"tokio-test",
|
||||
"tracing",
|
||||
@@ -3156,9 +3156,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.214"
|
||||
version = "1.0.215"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
|
||||
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -3197,9 +3197,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.214"
|
||||
version = "1.0.215"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
|
||||
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3290,7 +3290,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_qs",
|
||||
"server_fn_macro_default",
|
||||
"thiserror 2.0.0",
|
||||
"thiserror 2.0.3",
|
||||
"throw_error",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
@@ -3596,9 +3596,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.13.0"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
|
||||
checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
@@ -3627,11 +3627,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.0"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15291287e9bff1bc6f9ff3409ed9af665bec7a5fc8ac079ea96be07bca0e2668"
|
||||
checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.0",
|
||||
"thiserror-impl 2.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3647,9 +3647,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.0"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972"
|
||||
checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -159,9 +159,7 @@ Sure! Obviously the `view` macro is for generating DOM nodes but you can use the
|
||||
- Use event listeners to update signals
|
||||
- Create effects to update the UI
|
||||
|
||||
I've put together a [very simple GTK example](https://github.com/leptos-rs/leptos/blob/main/examples/gtk/src/main.rs) so you can see what I mean.
|
||||
|
||||
The new rendering approach being developed for 0.7 supports “universal rendering,” i.e., it can use any rendering library that supports a small set of 6-8 functions. (This is intended as a layer over typical retained-mode, OOP-style GUI toolkits like the DOM, GTK, etc.) That future rendering work will allow creating native UI in a way that is much more similar to the declarative approach used by the web framework.
|
||||
The 0.7 update originally set out to create a "generic rendering" approach that would allow us to reuse most of the same view logic to do all of the above. Unfortunately, this has had to be shelved for now due to difficulties encountered by the Rust compiler when building larger-scale applications with the number of generics spread throughout the codebase that this required. It's an approach I'm looking forward to exploring again in the future; feel free to reach out if you're interested in this kind of work.
|
||||
|
||||
### How is this different from Yew?
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ edition.workspace = true
|
||||
[dependencies]
|
||||
async-executor = { version = "1.13.1", optional = true }
|
||||
futures = "0.3.31"
|
||||
glib = { version = "0.20.5", optional = true }
|
||||
glib = { version = "0.20.6", optional = true }
|
||||
thiserror = "2.0"
|
||||
tokio = { version = "1.41", optional = true, default-features = false, features = [
|
||||
"rt",
|
||||
|
||||
@@ -6,7 +6,9 @@ use leptos_axum::ResponseOptions;
|
||||
// A basic function to display errors served by the error boundaries.
|
||||
// Feel free to do more complicated things here than just displaying them.
|
||||
#[component]
|
||||
pub fn ErrorTemplate(#[prop(into)] errors: Signal<Errors>) -> impl IntoView {
|
||||
pub fn ErrorTemplate(
|
||||
#[prop(into)] errors: MaybeSignal<Errors>,
|
||||
) -> impl IntoView {
|
||||
// Get Errors from Signal
|
||||
// Downcast lets us take a type that implements `std::error::Error`
|
||||
let errors = Memo::new(move |_| {
|
||||
|
||||
@@ -12,7 +12,7 @@ lto = true
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
leptos = { path = "../../leptos", features = ["experimental-islands"] }
|
||||
leptos = { path = "../../leptos", features = ["islands"] }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
|
||||
@@ -12,7 +12,7 @@ futures = "0.3.30"
|
||||
http = "1.1"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"tracing",
|
||||
"experimental-islands",
|
||||
"islands",
|
||||
] }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
|
||||
@@ -12,7 +12,7 @@ futures = "0.3.30"
|
||||
http = "1.1"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"tracing",
|
||||
"experimental-islands",
|
||||
"islands",
|
||||
] }
|
||||
leptos_router = { path = "../../router" }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
|
||||
|
||||
@@ -44,8 +44,8 @@ window.addEventListener("click", async (ev) => {
|
||||
// TODO parse from the request stream instead?
|
||||
const doc = parser.parseFromString(htmlString, 'text/html');
|
||||
|
||||
// The 'doc' variable now contains the parsed DOM
|
||||
const transition = document.startViewTransition(async () => {
|
||||
// The 'doc' variable now contains the parsed DOM
|
||||
const transition = async () => {
|
||||
const oldDocWalker = document.createTreeWalker(document);
|
||||
const newDocWalker = doc.createTreeWalker(doc);
|
||||
let oldNode = oldDocWalker.currentNode;
|
||||
@@ -128,8 +128,13 @@ window.addEventListener("click", async (ev) => {
|
||||
}
|
||||
} }
|
||||
}
|
||||
});
|
||||
await transition;
|
||||
};
|
||||
// Not all browsers support startViewTransition; see https://caniuse.com/?search=startViewTransition
|
||||
if (document.startViewTransition) {
|
||||
await document.startViewTransition(transition);
|
||||
} else {
|
||||
await transition()
|
||||
}
|
||||
window.history.pushState(undefined, null, url);
|
||||
});
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ struct Then {
|
||||
// the type with Option<...> and marking the option as #[prop(optional)].
|
||||
#[slot]
|
||||
struct ElseIf {
|
||||
cond: Signal<bool>,
|
||||
cond: MaybeSignal<bool>,
|
||||
children: ChildrenFn,
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ struct Fallback {
|
||||
// Slots are added to components like any other prop.
|
||||
#[component]
|
||||
fn SlotIf(
|
||||
cond: Signal<bool>,
|
||||
cond: MaybeSignal<bool>,
|
||||
then: Then,
|
||||
#[prop(default=vec![])] else_if: Vec<ElseIf>,
|
||||
#[prop(optional)] fallback: Option<Fallback>,
|
||||
@@ -43,9 +43,9 @@ fn SlotIf(
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
let (count, set_count) = signal(0);
|
||||
let is_even = Signal::derive(move || count.get() % 2 == 0);
|
||||
let is_div5 = Signal::derive(move || count.get() % 5 == 0);
|
||||
let is_div7 = Signal::derive(move || count.get() % 7 == 0);
|
||||
let is_even = MaybeSignal::derive(move || count.get() % 2 == 0);
|
||||
let is_div5 = MaybeSignal::derive(move || count.get() % 5 == 0);
|
||||
let is_div7 = MaybeSignal::derive(move || count.get() % 7 == 0);
|
||||
|
||||
view! {
|
||||
<button on:click=move |_| set_count.update(|value| *value += 1)>"+1"</button>
|
||||
|
||||
@@ -10,7 +10,7 @@ crate-type = ["cdylib", "rlib"]
|
||||
actix-files = { version = "0.6.6", optional = true }
|
||||
actix-web = { version = "4.8", optional = true, features = ["macros"] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
js-sys = { version = "0.3.70", optional = true }
|
||||
js-sys = { version = "0.3.72" }
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
@@ -21,7 +21,7 @@ tokio = { version = "1.39", features = ["time", "rt"], optional = true }
|
||||
|
||||
[features]
|
||||
hydrate = [
|
||||
"dep:js-sys",
|
||||
|
||||
"leptos/hydrate",
|
||||
]
|
||||
ssr = [
|
||||
|
||||
121
examples/suspense_tests/e2e/features/check_aria_current.feature
Normal file
121
examples/suspense_tests/e2e/features/check_aria_current.feature
Normal file
@@ -0,0 +1,121 @@
|
||||
@check_aria_current
|
||||
Feature: Check aria-current being applied to make links bolded
|
||||
|
||||
Background:
|
||||
|
||||
Given I see the app
|
||||
|
||||
Scenario: Should see the base case working
|
||||
Then I see the Out-of-Order link being bolded
|
||||
And I see the following links being bolded
|
||||
| Out-of-Order |
|
||||
| Nested |
|
||||
And I see the In-Order link not being bolded
|
||||
And I see the following links not being bolded
|
||||
| In-Order |
|
||||
| Single |
|
||||
|
||||
Scenario: Should see client-side render the correct bolded links
|
||||
When I select the link In-Order
|
||||
And I select the link Single
|
||||
Then I see the following links being bolded
|
||||
| In-Order |
|
||||
| Single |
|
||||
And I see the following links not being bolded
|
||||
| Out-of-Order |
|
||||
| Nested |
|
||||
|
||||
Scenario: Should see server-side render the correct bolded links
|
||||
When I select the link In-Order
|
||||
And I select the link Single
|
||||
And I reload the page
|
||||
Then I see the following links being bolded
|
||||
| In-Order |
|
||||
| Single |
|
||||
And I see the following links not being bolded
|
||||
| Out-of-Order |
|
||||
| Nested |
|
||||
|
||||
Scenario: Check that the base nested route links are working
|
||||
When I select the link Instrumented
|
||||
Then I see the Instrumented link being bolded
|
||||
And I see the Item Listing link not being bolded
|
||||
|
||||
Scenario: Should see going deep down into nested routes bold links
|
||||
When I select the link Instrumented
|
||||
And I select the link Target 421
|
||||
Then I see the following links being bolded
|
||||
| Instrumented |
|
||||
| Item Listing |
|
||||
| Target 4## |
|
||||
| Target 42# |
|
||||
| Target 421 |
|
||||
| field1 |
|
||||
|
||||
Scenario: Should see going deep down into nested routes in SSR bold links
|
||||
When I select the link Instrumented
|
||||
And I select the link Target 421
|
||||
And I reload the page
|
||||
Then I see the following links being bolded
|
||||
| Instrumented |
|
||||
| Item Listing |
|
||||
| Target 4## |
|
||||
| Target 42# |
|
||||
| Target 421 |
|
||||
| field1 |
|
||||
|
||||
Scenario: Going deep down navigate around nested links bold correctly
|
||||
When I select the link Instrumented
|
||||
And I select the link Target 421
|
||||
And I select the link Inspect path2/field3
|
||||
Then I see the following links being bolded
|
||||
| Instrumented |
|
||||
| Item Listing |
|
||||
| Target 4## |
|
||||
| Target 42# |
|
||||
| field3 |
|
||||
And I see the following links not being bolded
|
||||
| Target 421 |
|
||||
| field1 |
|
||||
|
||||
Scenario: Going deep down navigate around nested links bold correctly, SSR
|
||||
When I select the link Instrumented
|
||||
And I select the link Target 421
|
||||
And I select the link Inspect path2/field3
|
||||
And I reload the page
|
||||
Then I see the following links being bolded
|
||||
| Instrumented |
|
||||
| Item Listing |
|
||||
| Target 4## |
|
||||
| Target 42# |
|
||||
| field3 |
|
||||
And I see the following links not being bolded
|
||||
| Target 421 |
|
||||
| field1 |
|
||||
|
||||
Scenario: Going deep down back out nested routes reset bolded states
|
||||
When I select the link Instrumented
|
||||
And I select the link Target 421
|
||||
And I select the link Counters
|
||||
Then I see the following links being bolded
|
||||
| Instrumented |
|
||||
| Counters |
|
||||
And I see the following links not being bolded
|
||||
| Item Listing |
|
||||
| Target 4## |
|
||||
| Target 42# |
|
||||
| Target 421 |
|
||||
|
||||
Scenario: Going deep down back out nested routes reset bolded states, SSR
|
||||
When I select the link Instrumented
|
||||
And I select the link Target 421
|
||||
And I select the link Counters
|
||||
And I reload the page
|
||||
Then I see the following links being bolded
|
||||
| Instrumented |
|
||||
| Counters |
|
||||
And I see the following links not being bolded
|
||||
| Item Listing |
|
||||
| Target 4## |
|
||||
| Target 42# |
|
||||
| Target 421 |
|
||||
@@ -81,3 +81,20 @@ pub async fn instrumented_counts(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn link_text_is_aria_current(client: &Client, text: &str) -> Result<()> {
|
||||
let link = find::link_with_text(client, text).await?;
|
||||
|
||||
link.attr("aria-current").await?
|
||||
.expect(format!("aria-current missing for {text}").as_str());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn link_text_is_not_aria_current(client: &Client, text: &str) -> Result<()> {
|
||||
let link = find::link_with_text(client, text).await?;
|
||||
|
||||
link.attr("aria-current").await?
|
||||
.map(|_| anyhow::bail!("aria-current mistakenly set for {text}"))
|
||||
.unwrap_or(Ok(()))
|
||||
}
|
||||
|
||||
@@ -124,3 +124,12 @@ async fn component_message(client: &Client, id: &str) -> Result<String> {
|
||||
|
||||
Ok(text)
|
||||
}
|
||||
|
||||
pub async fn link_with_text(client: &Client, text: &str) -> Result<Element> {
|
||||
let link = client
|
||||
.wait()
|
||||
.for_element(Locator::LinkText(text))
|
||||
.await
|
||||
.expect(format!("Link not found by `{}`", text).as_str());
|
||||
Ok(link)
|
||||
}
|
||||
|
||||
@@ -80,6 +80,58 @@ async fn i_see_the_second_count_is(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[then(regex = r"^I see the (.*) link being bolded$")]
|
||||
async fn i_see_the_link_being_bolded(
|
||||
world: &mut AppWorld,
|
||||
text: String,
|
||||
) -> Result<()> {
|
||||
let client = &world.client;
|
||||
check::link_text_is_aria_current(client, &text).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[then(expr = "I see the following links being bolded")]
|
||||
async fn i_see_the_following_links_being_bolded(
|
||||
world: &mut AppWorld,
|
||||
step: &Step,
|
||||
) -> Result<()> {
|
||||
let client = &world.client;
|
||||
if let Some(table) = step.table.as_ref() {
|
||||
for row in table.rows.iter() {
|
||||
check::link_text_is_aria_current(client, &row[0]).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[then(regex = r"^I see the (.*) link not being bolded$")]
|
||||
async fn i_see_the_link_being_not_bolded(
|
||||
world: &mut AppWorld,
|
||||
text: String,
|
||||
) -> Result<()> {
|
||||
let client = &world.client;
|
||||
check::link_text_is_not_aria_current(client, &text).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[then(expr = "I see the following links not being bolded")]
|
||||
async fn i_see_the_following_links_not_being_bolded(
|
||||
world: &mut AppWorld,
|
||||
step: &Step,
|
||||
) -> Result<()> {
|
||||
let client = &world.client;
|
||||
if let Some(table) = step.table.as_ref() {
|
||||
for row in table.rows.iter() {
|
||||
check::link_text_is_not_aria_current(client, &row[0]).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[then(expr = "I see the following counters under section")]
|
||||
#[then(expr = "the following counters under section")]
|
||||
async fn i_see_the_following_counters_under_section(
|
||||
|
||||
@@ -38,7 +38,7 @@ pub fn TimerDemo() -> impl IntoView {
|
||||
pub fn use_interval<T, F>(interval_millis: T, f: F)
|
||||
where
|
||||
F: Fn() + Clone + 'static,
|
||||
T: Into<Signal<u64>> + 'static,
|
||||
T: Into<MaybeSignal<u64>> + 'static,
|
||||
{
|
||||
let interval_millis = interval_millis.into();
|
||||
Effect::new(move |prev_handle: Option<IntervalHandle>| {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! Provides functions to easily integrate Leptos with Actix.
|
||||
//!
|
||||
@@ -9,7 +10,6 @@
|
||||
use actix_files::NamedFile;
|
||||
use actix_http::header::{HeaderName, HeaderValue, ACCEPT, LOCATION, REFERER};
|
||||
use actix_web::{
|
||||
body::BoxBody,
|
||||
dev::{ServiceFactory, ServiceRequest},
|
||||
http::header,
|
||||
test,
|
||||
@@ -56,8 +56,10 @@ use std::{
|
||||
/// Typically contained inside of a ResponseOptions. Setting this is useful for cookies and custom responses.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ResponseParts {
|
||||
pub headers: header::HeaderMap,
|
||||
/// If provided, this will overwrite any other status code for this response.
|
||||
pub status: Option<StatusCode>,
|
||||
/// The map of headers that should be added to the response.
|
||||
pub headers: header::HeaderMap,
|
||||
}
|
||||
|
||||
impl ResponseParts {
|
||||
@@ -86,10 +88,12 @@ impl ResponseParts {
|
||||
pub struct Request(SendWrapper<HttpRequest>);
|
||||
|
||||
impl Request {
|
||||
/// Wraps an existing Actix request.
|
||||
pub fn new(req: &HttpRequest) -> Self {
|
||||
Self(SendWrapper::new(req.clone()))
|
||||
}
|
||||
|
||||
/// Consumes the wrapper and returns the inner Actix request.
|
||||
pub fn into_inner(self) -> HttpRequest {
|
||||
self.0.take()
|
||||
}
|
||||
@@ -299,7 +303,7 @@ pub fn redirect(path: &str) {
|
||||
/// ## Provided Context Types
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [ResponseOptions]
|
||||
/// - [HttpRequest](actix_web::HttpRequest)
|
||||
/// - [Request]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -326,7 +330,7 @@ pub fn handle_server_fns() -> Route {
|
||||
/// ## Provided Context Types
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [ResponseOptions]
|
||||
/// - [HttpRequest](actix_web::HttpRequest)
|
||||
/// - [Request]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -459,7 +463,7 @@ pub fn handle_server_fns_with_context(
|
||||
/// ## Provided Context Types
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [ResponseOptions]
|
||||
/// - [HttpRequest](actix_web::HttpRequest)
|
||||
/// - [Request]
|
||||
/// - [MetaContext](leptos_meta::MetaContext)
|
||||
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
|
||||
#[cfg_attr(
|
||||
@@ -529,8 +533,7 @@ where
|
||||
/// ## Provided Context Types
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [ResponseOptions]
|
||||
/// - [HttpRequest](actix_web::HttpRequest)
|
||||
/// - [MetaContext](leptos_meta::MetaContext)
|
||||
/// - [Request]
|
||||
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
@@ -594,9 +597,7 @@ where
|
||||
/// ## Provided Context Types
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [ResponseOptions]
|
||||
/// - [HttpRequest](actix_web::HttpRequest)
|
||||
/// - [MetaContext](leptos_meta::MetaContext)
|
||||
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
|
||||
/// - [Request]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -620,9 +621,7 @@ where
|
||||
/// ## Provided Context Types
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [ResponseOptions]
|
||||
/// - [HttpRequest](actix_web::HttpRequest)
|
||||
/// - [MetaContext](leptos_meta::MetaContext)
|
||||
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
|
||||
/// - [Request]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -657,9 +656,7 @@ where
|
||||
/// ## Provided Context Types
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [ResponseOptions]
|
||||
/// - [HttpRequest](actix_web::HttpRequest)
|
||||
/// - [MetaContext](leptos_meta::MetaContext)
|
||||
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
|
||||
/// - [Request]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -691,7 +688,7 @@ where
|
||||
/// ## Provided Context Types
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [ResponseOptions]
|
||||
/// - [HttpRequest](actix_web::HttpRequest)
|
||||
/// - [Request]
|
||||
/// - [MetaContext](leptos_meta::MetaContext)
|
||||
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
|
||||
#[cfg_attr(
|
||||
@@ -724,9 +721,7 @@ where
|
||||
/// ## Provided Context Types
|
||||
/// This function always provides context values including the following types:
|
||||
/// - [ResponseOptions]
|
||||
/// - [HttpRequest](actix_web::HttpRequest)
|
||||
/// - [MetaContext](leptos_meta::MetaContext)
|
||||
/// - [RouterIntegrationContext](leptos_router::RouterIntegrationContext)
|
||||
/// - [Request]
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", fields(error), skip_all)
|
||||
@@ -1319,14 +1314,12 @@ where
|
||||
web::get().to(handler)
|
||||
}
|
||||
|
||||
pub enum DataResponse<T> {
|
||||
Data(T),
|
||||
Response(actix_web::dev::Response<BoxBody>),
|
||||
}
|
||||
|
||||
/// This trait allows one to pass a list of routes and a render function to Actix's router, letting us avoid
|
||||
/// having to use wildcards or manually define all routes in multiple places.
|
||||
pub trait LeptosRoutes {
|
||||
/// Adds routes to the Axum router that have either
|
||||
/// 1) been generated by `leptos_router`, or
|
||||
/// 2) handle a server function.
|
||||
fn leptos_routes<IV>(
|
||||
self,
|
||||
paths: Vec<ActixRouteListing>,
|
||||
@@ -1335,6 +1328,12 @@ pub trait LeptosRoutes {
|
||||
where
|
||||
IV: IntoView + 'static;
|
||||
|
||||
/// Adds routes to the Axum router that have either
|
||||
/// 1) been generated by `leptos_router`, or
|
||||
/// 2) handle a server function.
|
||||
///
|
||||
/// Runs `additional_context` to provide additional data to the reactive system via context,
|
||||
/// when handling a route.
|
||||
fn leptos_routes_with_context<IV>(
|
||||
self,
|
||||
paths: Vec<ActixRouteListing>,
|
||||
|
||||
@@ -11,7 +11,7 @@ edition.workspace = true
|
||||
[dependencies]
|
||||
any_spawner = { workspace = true, features = ["tokio"] }
|
||||
hydration_context = { workspace = true }
|
||||
axum = { version = "0.7.7", default-features = false, features = [
|
||||
axum = { version = "0.7.8", default-features = false, features = [
|
||||
"matched-path",
|
||||
] }
|
||||
dashmap = "6"
|
||||
@@ -30,7 +30,7 @@ tower-http = "0.6.1"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
axum = "0.7.7"
|
||||
axum = "0.7.8"
|
||||
tokio = { version = "1.41", features = ["net", "rt-multi-thread"] }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! Provides functions to easily integrate Leptos with Axum.
|
||||
//!
|
||||
//! ## JS Fetch Integration
|
||||
@@ -14,7 +16,7 @@
|
||||
//! - `default`: supports running in a typical native Tokio/Axum environment
|
||||
//! - `wasm`: with `default-features = false`, supports running in a JS Fetch-based
|
||||
//! environment
|
||||
//! - `experimental-islands`: activates Leptos [islands mode](https://leptos-rs.github.io/leptos/islands.html)
|
||||
//! - `islands`: activates Leptos [islands mode](https://leptos-rs.github.io/leptos/islands.html)
|
||||
//!
|
||||
//! ### Important Note
|
||||
//! Prior to 0.5, using `default-features = false` on `leptos_axum` simply did nothing. Now, it actively
|
||||
@@ -63,10 +65,9 @@ use leptos_meta::ServerMetaContext;
|
||||
#[cfg(feature = "default")]
|
||||
use leptos_router::static_routes::ResolvedStaticPath;
|
||||
use leptos_router::{
|
||||
components::provide_server_redirect,
|
||||
location::RequestUrl,
|
||||
static_routes::{RegenerationFn, StaticParamsMap},
|
||||
ExpandOptionals, PathSegment, RouteList, RouteListing, SsrMode,
|
||||
components::provide_server_redirect, location::RequestUrl,
|
||||
static_routes::RegenerationFn, ExpandOptionals, PathSegment, RouteList,
|
||||
RouteListing, SsrMode,
|
||||
};
|
||||
#[cfg(feature = "default")]
|
||||
use once_cell::sync::Lazy;
|
||||
@@ -85,7 +86,9 @@ use tower_http::services::ServeDir;
|
||||
/// Typically contained inside of a ResponseOptions. Setting this is useful for cookies and custom responses.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ResponseParts {
|
||||
/// If provided, this will overwrite any other status code for this response.
|
||||
pub status: Option<StatusCode>,
|
||||
/// The map of headers that should be added to the response.
|
||||
pub headers: HeaderMap,
|
||||
}
|
||||
|
||||
@@ -432,6 +435,7 @@ async fn handle_server_fns_inner(
|
||||
.expect("could not build Response")
|
||||
}
|
||||
|
||||
/// A stream of bytes of HTML.
|
||||
pub type PinnedHtmlStream =
|
||||
Pin<Box<dyn Stream<Item = io::Result<Bytes>> + Send>>;
|
||||
|
||||
@@ -1207,32 +1211,6 @@ where
|
||||
generate_route_list_with_exclusions_and_ssg(app_fn, excluded_routes).0
|
||||
}
|
||||
|
||||
/// Builds all routes that have been defined using [`StaticRoute`].
|
||||
#[allow(unused)]
|
||||
pub async fn build_static_routes<IV>(
|
||||
options: &LeptosOptions,
|
||||
app_fn: impl Fn() -> IV + 'static + Send + Clone,
|
||||
routes: &[RouteListing],
|
||||
static_data_map: StaticParamsMap,
|
||||
) where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
todo!()
|
||||
/*
|
||||
let options = options.clone();
|
||||
let routes = routes.to_owned();
|
||||
spawn_task!(async move {
|
||||
leptos_router::build_static_routes(
|
||||
&options,
|
||||
app_fn,
|
||||
&routes,
|
||||
&static_data_map,
|
||||
)
|
||||
.await
|
||||
.expect("could not build static routes")
|
||||
});*/
|
||||
}
|
||||
|
||||
/// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically
|
||||
/// create routes in Axum's Router without having to use wildcard matching or fallbacks. Takes in your root app Element
|
||||
/// as an argument so it can walk you app tree. This version is tailored to generate Axum compatible paths. Adding excluded_routes
|
||||
@@ -1683,6 +1661,9 @@ where
|
||||
S: Clone + Send + Sync + 'static,
|
||||
LeptosOptions: FromRef<S>,
|
||||
{
|
||||
/// Adds routes to the Axum router that have either
|
||||
/// 1) been generated by `leptos_router`, or
|
||||
/// 2) handle a server function.
|
||||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: &S,
|
||||
@@ -1692,6 +1673,12 @@ where
|
||||
where
|
||||
IV: IntoView + 'static;
|
||||
|
||||
/// Adds routes to the Axum router that have either
|
||||
/// 1) been generated by `leptos_router`, or
|
||||
/// 2) handle a server function.
|
||||
///
|
||||
/// Runs `additional_context` to provide additional data to the reactive system via context,
|
||||
/// when handling a route.
|
||||
fn leptos_routes_with_context<IV>(
|
||||
self,
|
||||
options: &S,
|
||||
@@ -1702,6 +1689,8 @@ where
|
||||
where
|
||||
IV: IntoView + 'static;
|
||||
|
||||
/// Extends the Axum router with the given paths, and handles the requests with the given
|
||||
/// handler.
|
||||
fn leptos_routes_with_handler<H, T>(
|
||||
self,
|
||||
paths: Vec<AxumRouteListing>,
|
||||
@@ -2014,6 +2003,10 @@ where
|
||||
.map_err(|e| ServerFnError::ServerError(format!("{e:?}")))
|
||||
}
|
||||
|
||||
/// A reasonable handler for serving static files (like JS/WASM/CSS) and 404 errors.
|
||||
///
|
||||
/// This is provided as a convenience, but is a fairly simple function. If you need to adapt it,
|
||||
/// simply reuse the source code of this function in your own application.
|
||||
#[cfg(feature = "default")]
|
||||
pub fn file_and_error_handler<S, IV>(
|
||||
shell: fn(LeptosOptions) -> IV,
|
||||
|
||||
@@ -86,7 +86,7 @@ tracing = [
|
||||
]
|
||||
nonce = ["base64", "rand"]
|
||||
spin = ["leptos-spin-macro"]
|
||||
experimental-islands = ["leptos_macro/experimental-islands", "dep:serde_json"]
|
||||
islands = ["leptos_macro/islands", "dep:serde_json"]
|
||||
trace-component-props = [
|
||||
"leptos_macro/trace-component-props",
|
||||
"leptos_dom/trace-component-props"
|
||||
@@ -104,7 +104,7 @@ denylist = [
|
||||
"rkyv", # was causing clippy issues on nightly
|
||||
"trace-component-props",
|
||||
"spin",
|
||||
"experimental-islands",
|
||||
"islands",
|
||||
]
|
||||
skip_feature_sets = [
|
||||
["csr", "ssr"],
|
||||
|
||||
@@ -223,14 +223,14 @@ mod tests {
|
||||
#[test]
|
||||
fn clone_callback() {
|
||||
let callback = Callback::new(move |_no_clone: NoClone| NoClone {});
|
||||
let _cloned = callback.clone();
|
||||
let _cloned = callback;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone_unsync_callback() {
|
||||
let callback =
|
||||
UnsyncCallback::new(move |_no_clone: NoClone| NoClone {});
|
||||
let _cloned = callback.clone();
|
||||
let _cloned = callback;
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -228,6 +228,7 @@ impl ViewFnOnce {
|
||||
pub struct TypedChildren<T>(Box<dyn FnOnce() -> View<T> + Send>);
|
||||
|
||||
impl<T> TypedChildren<T> {
|
||||
/// Extracts the inner `children` function.
|
||||
pub fn into_inner(self) -> impl FnOnce() -> View<T> + Send {
|
||||
self.0
|
||||
}
|
||||
@@ -256,6 +257,7 @@ impl<T> Debug for TypedChildrenMut<T> {
|
||||
}
|
||||
|
||||
impl<T> TypedChildrenMut<T> {
|
||||
/// Extracts the inner `children` function.
|
||||
pub fn into_inner(self) -> impl FnMut() -> View<T> + Send {
|
||||
self.0
|
||||
}
|
||||
@@ -284,6 +286,7 @@ impl<T> Debug for TypedChildrenFn<T> {
|
||||
}
|
||||
|
||||
impl<T> TypedChildrenFn<T> {
|
||||
/// Extracts the inner `children` function.
|
||||
pub fn into_inner(self) -> Arc<dyn Fn() -> View<T> + Send + Sync> {
|
||||
self.0
|
||||
}
|
||||
|
||||
@@ -251,12 +251,16 @@ where
|
||||
) -> Result<Self, serde_qs::Error>;
|
||||
}
|
||||
|
||||
/// Errors that can arise when coverting from an HTML event or form into a Rust data type.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum FromFormDataError {
|
||||
/// Could not find a `<form>` connected to the event.
|
||||
#[error("Could not find <form> connected to event.")]
|
||||
MissingForm(Event),
|
||||
/// Could not create `FormData` from the form.
|
||||
#[error("Could not create FormData from <form>: {0:?}")]
|
||||
FormData(JsValue),
|
||||
/// Failed to deserialize this Rust type from the form data.
|
||||
#[error("Deserialization error: {0:?}")]
|
||||
Deserialization(serde_qs::Error),
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(function (root, pkg_path, output_name, wasm_output_name) {
|
||||
import(`${root}/${pkg_path}/${output_name}.js`)
|
||||
.then(mod => {
|
||||
mod.default(`${root}/${pkg_path}/${wasm_output_name}.wasm`).then(() => {
|
||||
mod.default({module_or_path: `${root}/${pkg_path}/${wasm_output_name}.wasm`}).then(() => {
|
||||
mod.hydrate();
|
||||
});
|
||||
})
|
||||
|
||||
@@ -4,9 +4,15 @@ use crate::prelude::*;
|
||||
use leptos_config::LeptosOptions;
|
||||
use leptos_macro::{component, view};
|
||||
|
||||
/// Inserts auto-reloading code used in `cargo-leptos`.
|
||||
///
|
||||
/// This should be included in the `<head>` of your application shell during development.
|
||||
#[component]
|
||||
pub fn AutoReload(
|
||||
#[prop(optional)] disable_watch: bool,
|
||||
/// Whether the file-watching feature should be disabled.
|
||||
#[prop(optional)]
|
||||
disable_watch: bool,
|
||||
/// Configuration options for this project.
|
||||
options: LeptosOptions,
|
||||
) -> impl IntoView {
|
||||
(!disable_watch && std::env::var("LEPTOS_WATCH").is_ok()).then(|| {
|
||||
@@ -34,10 +40,16 @@ pub fn AutoReload(
|
||||
})
|
||||
}
|
||||
|
||||
/// Inserts hydration scripts that add interactivity to your server-rendered HTML.
|
||||
///
|
||||
/// This should be included in the `<head>` of your application shell.
|
||||
#[component]
|
||||
pub fn HydrationScripts(
|
||||
/// Configuration options for this project.
|
||||
options: LeptosOptions,
|
||||
#[prop(optional)] islands: bool,
|
||||
/// Should be `true` to hydrate in `islands` mode.
|
||||
#[prop(optional)]
|
||||
islands: bool,
|
||||
/// A base url, not including a trailing slash
|
||||
#[prop(optional, into)]
|
||||
root: Option<String>,
|
||||
|
||||
@@ -9,6 +9,7 @@ use tachys::{
|
||||
},
|
||||
};
|
||||
|
||||
/// A wrapper for any kind of view.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct View<T>
|
||||
where
|
||||
@@ -20,6 +21,7 @@ where
|
||||
}
|
||||
|
||||
impl<T> View<T> {
|
||||
/// Wraps the view.
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
@@ -28,10 +30,12 @@ impl<T> View<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwraps the view, returning the inner type.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
|
||||
/// Adds a view marker, which is used for hot-reloading and debug purposes.
|
||||
#[inline(always)]
|
||||
pub fn with_view_marker(
|
||||
#[allow(unused_mut)] // used in debug
|
||||
@@ -47,10 +51,12 @@ impl<T> View<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait that is implemented for types that can be rendered.
|
||||
pub trait IntoView
|
||||
where
|
||||
Self: Sized + Render + RenderHtml + Send,
|
||||
{
|
||||
/// Wraps the inner type.
|
||||
fn into_view(self) -> View<Self>;
|
||||
}
|
||||
|
||||
@@ -188,9 +194,15 @@ impl<T: AddAnyAttr> AddAnyAttr for View<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Collects some iterator of views into a list, so they can be rendered.
|
||||
///
|
||||
/// This is a shorthand for `.collect::<Vec<_>>()`, and allows any iterator of renderable
|
||||
/// items to be collected into a renderable collection.
|
||||
pub trait CollectView {
|
||||
/// The inner view type.
|
||||
type View: IntoView;
|
||||
|
||||
/// Collects the iterator into a list of views.
|
||||
fn collect_view(self) -> Vec<Self::View>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!rdeny(missing_docs)]
|
||||
#![deny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! # About Leptos
|
||||
//!
|
||||
//! Leptos is a full-stack framework for building web applications in Rust. You can use it to build
|
||||
@@ -287,6 +288,7 @@ pub mod logging {
|
||||
pub use leptos_dom::{debug_warn, error, log, warn};
|
||||
}
|
||||
|
||||
/// Utilities for working with asynchronous tasks.
|
||||
pub mod task {
|
||||
pub use any_spawner::Executor;
|
||||
use std::future::Future;
|
||||
@@ -316,10 +318,10 @@ pub mod task {
|
||||
}
|
||||
|
||||
// these reexports are used in islands
|
||||
#[cfg(feature = "experimental-islands")]
|
||||
#[cfg(feature = "islands")]
|
||||
#[doc(hidden)]
|
||||
pub use serde;
|
||||
#[cfg(feature = "experimental-islands")]
|
||||
#[cfg(feature = "islands")]
|
||||
#[doc(hidden)]
|
||||
pub use serde_json;
|
||||
#[cfg(feature = "tracing")]
|
||||
|
||||
@@ -87,7 +87,12 @@ use throw_error::ErrorHookFuture;
|
||||
/// ```
|
||||
#[component]
|
||||
pub fn Suspense<Chil>(
|
||||
#[prop(optional, into)] fallback: ViewFnOnce,
|
||||
/// A function that returns a fallback that will be shown while resources are still loading.
|
||||
/// By default this is an empty view.
|
||||
#[prop(optional, into)]
|
||||
fallback: ViewFnOnce,
|
||||
/// Children will be rendered once initially to catch any resource reads, then hidden until all
|
||||
/// data have loaded.
|
||||
children: TypedChildren<Chil>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
|
||||
22
leptos/tests/generic_component.rs
Normal file
22
leptos/tests/generic_component.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
#[test]
|
||||
fn generic_component_signal_inference() {
|
||||
use leptos::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn SimpleCounter(#[prop(into)] step: Signal<i32>) -> impl IntoView {
|
||||
_ = step;
|
||||
view! {
|
||||
<div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
let a = RwSignal::new(1);
|
||||
let (b, _) = signal(1);
|
||||
|
||||
view! {
|
||||
<SimpleCounter step=a/>
|
||||
<SimpleCounter step=b/>
|
||||
<SimpleCounter step=Signal::stored(1)/>
|
||||
};
|
||||
}
|
||||
2851
leptos/tests/test_examples/suspense-tests/Cargo.lock
generated
Normal file
2851
leptos/tests/test_examples/suspense-tests/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@ typed-builder = "0.20.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.41", features = ["rt", "macros"] }
|
||||
tempfile = "3.13"
|
||||
tempfile = "3.14"
|
||||
temp-env = { version = "0.3.6", features = ["async_closure"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
@@ -30,14 +30,14 @@ fn ws_from_str_test() {
|
||||
|
||||
#[test]
|
||||
fn env_w_default_test() {
|
||||
_ = temp_env::with_var("LEPTOS_CONFIG_ENV_TEST", Some("custom"), || {
|
||||
temp_env::with_var("LEPTOS_CONFIG_ENV_TEST", Some("custom"), || {
|
||||
assert_eq!(
|
||||
env_w_default("LEPTOS_CONFIG_ENV_TEST", "default").unwrap(),
|
||||
String::from("custom")
|
||||
);
|
||||
});
|
||||
|
||||
_ = temp_env::with_var_unset("LEPTOS_CONFIG_ENV_TEST", || {
|
||||
temp_env::with_var_unset("LEPTOS_CONFIG_ENV_TEST", || {
|
||||
assert_eq!(
|
||||
env_w_default("LEPTOS_CONFIG_ENV_TEST", "default").unwrap(),
|
||||
String::from("default")
|
||||
@@ -47,14 +47,14 @@ fn env_w_default_test() {
|
||||
|
||||
#[test]
|
||||
fn env_wo_default_test() {
|
||||
_ = temp_env::with_var("LEPTOS_CONFIG_ENV_TEST", Some("custom"), || {
|
||||
temp_env::with_var("LEPTOS_CONFIG_ENV_TEST", Some("custom"), || {
|
||||
assert_eq!(
|
||||
env_wo_default("LEPTOS_CONFIG_ENV_TEST").unwrap(),
|
||||
Some(String::from("custom"))
|
||||
);
|
||||
});
|
||||
|
||||
_ = temp_env::with_var_unset("LEPTOS_CONFIG_ENV_TEST", || {
|
||||
temp_env::with_var_unset("LEPTOS_CONFIG_ENV_TEST", || {
|
||||
assert_eq!(env_wo_default("LEPTOS_CONFIG_ENV_TEST").unwrap(), None);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ hydrate = []
|
||||
ssr = ["server_fn_macro/ssr", "leptos/ssr"]
|
||||
nightly = ["server_fn_macro/nightly"]
|
||||
tracing = ["dep:tracing"]
|
||||
experimental-islands = []
|
||||
islands = []
|
||||
trace-component-props = []
|
||||
actix = ["server_fn_macro/actix"]
|
||||
axum = ["server_fn_macro/axum"]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Macros for use with the [`leptos`] framework.
|
||||
|
||||
#![cfg_attr(feature = "nightly", feature(proc_macro_span))]
|
||||
#![forbid(unsafe_code)]
|
||||
// to prevent warnings from popping up when a nightly feature is stabilized
|
||||
@@ -5,6 +7,7 @@
|
||||
// FIXME? every use of quote! {} is warning here -- false positive?
|
||||
#![allow(unknown_lints)]
|
||||
#![allow(private_macro_use)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate proc_macro_error2;
|
||||
@@ -556,10 +559,10 @@ pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
}
|
||||
|
||||
/// Defines a component as an interactive island when you are using the
|
||||
/// `experimental-islands` feature of Leptos. Apart from the macro name,
|
||||
/// `islands` feature of Leptos. Apart from the macro name,
|
||||
/// the API is the same as the [`component`](macro@component) macro.
|
||||
///
|
||||
/// When you activate the `experimental-islands` feature, every `#[component]`
|
||||
/// When you activate the `islands` feature, every `#[component]`
|
||||
/// is server-only by default. This "default to server" behavior is important:
|
||||
/// you opt into shipping code to the client, rather than opting out. You can
|
||||
/// opt into client-side interactivity for any given component by changing from
|
||||
|
||||
@@ -23,8 +23,11 @@ use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
};
|
||||
use syn::{
|
||||
spanned::Spanned, Expr, Expr::Tuple, ExprArray, ExprLit, ExprRange, Lit,
|
||||
LitStr, RangeLimits, Stmt,
|
||||
punctuated::Pair::{End, Punctuated},
|
||||
spanned::Spanned,
|
||||
Expr,
|
||||
Expr::Tuple,
|
||||
ExprArray, ExprLit, ExprRange, Lit, LitStr, RangeLimits, Stmt,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
@@ -194,7 +197,7 @@ enum InertElementBuilder<'a> {
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> ToTokens for InertElementBuilder<'a> {
|
||||
impl ToTokens for InertElementBuilder<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
InertElementBuilder::GlobalClass { strs, .. } => {
|
||||
@@ -216,7 +219,7 @@ enum GlobalClassItem<'a> {
|
||||
String(String),
|
||||
}
|
||||
|
||||
impl<'a> ToTokens for GlobalClassItem<'a> {
|
||||
impl ToTokens for GlobalClassItem<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let addl_tokens = match self {
|
||||
GlobalClassItem::Global(v) => v.to_token_stream(),
|
||||
@@ -998,10 +1001,14 @@ pub(crate) fn attribute_absolute(
|
||||
) -> Option<TokenStream> {
|
||||
let key = node.key.to_string();
|
||||
let contains_dash = key.contains('-');
|
||||
let attr_aira = key.starts_with("attr:aria-");
|
||||
let attr_colon = key.starts_with("attr:")
|
||||
|| key.starts_with("style:")
|
||||
|| key.starts_with("class:")
|
||||
|| key.starts_with("prop:")
|
||||
|| key.starts_with("use:");
|
||||
// anything that follows the x:y pattern
|
||||
match &node.key {
|
||||
NodeName::Punctuated(parts) if !contains_dash || attr_aira => {
|
||||
NodeName::Punctuated(parts) if !contains_dash || attr_colon => {
|
||||
if parts.len() >= 2 {
|
||||
let id = &parts[0];
|
||||
match id {
|
||||
@@ -1010,7 +1017,8 @@ pub(crate) fn attribute_absolute(
|
||||
if id == "let" || id == "clone" {
|
||||
None
|
||||
} else if id == "attr" {
|
||||
let value = attribute_value(node, true);
|
||||
let value = attribute_value(node, true);
|
||||
let multipart = parts.len() > 2;
|
||||
let key = &parts[1];
|
||||
let key_name = key.to_string();
|
||||
if key_name == "class" || key_name == "style" {
|
||||
@@ -1026,6 +1034,15 @@ pub(crate) fn attribute_absolute(
|
||||
Some(
|
||||
quote! { ::leptos::tachys::html::attribute::#key(#value) },
|
||||
)
|
||||
} else if multipart {
|
||||
// e.g., attr:data-foo="bar"
|
||||
let key_name = parts.pairs().skip(1).map(|p| match p {
|
||||
Punctuated(n, p) => format!("{n}{p}"),
|
||||
End(n) => n.to_string(),
|
||||
}).collect::<String>();
|
||||
Some(
|
||||
quote! { ::leptos::tachys::html::attribute::custom::custom_attribute(#key_name, #value) },
|
||||
)
|
||||
} else {
|
||||
Some(
|
||||
quote! { ::leptos::tachys::html::attribute::#key(#value) },
|
||||
|
||||
@@ -6,6 +6,10 @@ use reactive_graph::{
|
||||
use server_fn::{error::ServerFnErrorSerde, ServerFn, ServerFnError};
|
||||
use std::{ops::Deref, panic::Location, sync::Arc};
|
||||
|
||||
/// An error that can be caused by a server action.
|
||||
///
|
||||
/// This is used for propagating errors from the server to the client when JS/WASM are not
|
||||
/// supported.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ServerActionError {
|
||||
path: Arc<str>,
|
||||
@@ -13,6 +17,7 @@ pub struct ServerActionError {
|
||||
}
|
||||
|
||||
impl ServerActionError {
|
||||
/// Creates a new error associated with the given path.
|
||||
pub fn new(path: &str, err: &str) -> Self {
|
||||
Self {
|
||||
path: path.into(),
|
||||
@@ -20,15 +25,18 @@ impl ServerActionError {
|
||||
}
|
||||
}
|
||||
|
||||
/// The path with which this error is associated.
|
||||
pub fn path(&self) -> &str {
|
||||
&self.path
|
||||
}
|
||||
|
||||
/// The error message.
|
||||
pub fn err(&self) -> &str {
|
||||
&self.err
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`ArcAction`] that can be used to call a server function.
|
||||
pub struct ArcServerAction<S>
|
||||
where
|
||||
S: ServerFn + 'static,
|
||||
@@ -45,6 +53,7 @@ where
|
||||
S::Output: Send + Sync + 'static,
|
||||
S::Error: Send + Sync + 'static,
|
||||
{
|
||||
/// Creates a new [`ArcAction`] that will call the server function `S` when dispatched.
|
||||
#[track_caller]
|
||||
pub fn new() -> Self {
|
||||
let err = use_context::<ServerActionError>().and_then(|error| {
|
||||
@@ -116,6 +125,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`Action`] that can be used to call a server function.
|
||||
pub struct ServerAction<S>
|
||||
where
|
||||
S: ServerFn + 'static,
|
||||
@@ -132,6 +142,7 @@ where
|
||||
S::Output: Send + Sync + 'static,
|
||||
S::Error: Send + Sync + 'static,
|
||||
{
|
||||
/// Creates a new [`Action`] that will call the server function `S` when dispatched.
|
||||
pub fn new() -> Self {
|
||||
let err = use_context::<ServerActionError>().and_then(|error| {
|
||||
(error.path() == S::PATH)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
//#![deny(missing_docs)]
|
||||
//! Utilities for communicating between the server and the client with [`leptos`].
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod action;
|
||||
@@ -13,135 +15,25 @@ pub use once_resource::*;
|
||||
mod resource;
|
||||
pub use resource::*;
|
||||
mod shared;
|
||||
////! # Leptos Server Functions
|
||||
////!
|
||||
////! This package is based on a simple idea: sometimes it’s useful to write functions
|
||||
////! that will only run on the server, and call them from the client.
|
||||
////!
|
||||
////! If you’re creating anything beyond a toy app, you’ll need to do this all the time:
|
||||
////! reading from or writing to a database that only runs on the server, running expensive
|
||||
////! computations using libraries you don’t want to ship down to the client, accessing
|
||||
////! APIs that need to be called from the server rather than the client for CORS reasons
|
||||
////! or because you need a secret API key that’s stored on the server and definitely
|
||||
////! shouldn’t be shipped down to a user’s browser.
|
||||
////!
|
||||
////! Traditionally, this is done by separating your server and client code, and by setting
|
||||
////! up something like a REST API or GraphQL API to allow your client to fetch and mutate
|
||||
////! data on the server. This is fine, but it requires you to write and maintain your code
|
||||
////! in multiple separate places (client-side code for fetching, server-side functions to run),
|
||||
////! as well as creating a third thing to manage, which is the API contract between the two.
|
||||
////!
|
||||
////! This package provides two simple primitives that allow you instead to write co-located,
|
||||
////! isomorphic server functions. (*Co-located* means you can write them in your app code so
|
||||
////! that they are “located alongside” the client code that calls them, rather than separating
|
||||
////! the client and server sides. *Isomorphic* means you can call them from the client as if
|
||||
////! you were simply calling a function; the function call has the “same shape” on the client
|
||||
////! as it does on the server.)
|
||||
////!
|
||||
////! ### `#[server]`
|
||||
////!
|
||||
////! The [`#[server]`](https://docs.rs/leptos/latest/leptos/attr.server.html) macro allows you to annotate a function to
|
||||
////! indicate that it should only run on the server (i.e., when you have an `ssr` feature in your
|
||||
////! crate that is enabled).
|
||||
////!
|
||||
////! ```rust,ignore
|
||||
////! use leptos::prelude::*;
|
||||
////! #[server(ReadFromDB)]
|
||||
////! async fn read_posts(how_many: usize, query: String) -> Result<Vec<Posts>, ServerFnError> {
|
||||
////! // do some server-only work here to access the database
|
||||
////! let posts = todo!();;
|
||||
////! Ok(posts)
|
||||
////! }
|
||||
////!
|
||||
////! // call the function
|
||||
////! spawn_local(async {
|
||||
////! let posts = read_posts(3, "my search".to_string()).await;
|
||||
////! log::debug!("posts = {posts:#?}");
|
||||
////! });
|
||||
////! ```
|
||||
////!
|
||||
////! If you call this function from the client, it will serialize the function arguments and `POST`
|
||||
////! them to the server as if they were the inputs in `<form method="POST">`.
|
||||
////!
|
||||
////! Here’s what you need to remember:
|
||||
////! - **Server functions must be `async`.** Even if the work being done inside the function body
|
||||
////! can run synchronously on the server, from the client’s perspective it involves an asynchronous
|
||||
////! function call.
|
||||
////! - **Server functions must return `Result<T, ServerFnError>`.** Even if the work being done
|
||||
////! inside the function body can’t fail, the processes of serialization/deserialization and the
|
||||
////! network call are fallible.
|
||||
////! - **Return types must be [Serializable](leptos_reactive::Serializable).**
|
||||
////! 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`
|
||||
////! 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`.
|
||||
////! - Context comes from the server. [`use_context`](leptos_reactive::use_context) can be used to access specific
|
||||
////! server-related data, as documented in the server integrations. This allows accessing things like HTTP request
|
||||
////! headers as needed. However, server functions *not* have access to reactive state that exists in the client.
|
||||
////!
|
||||
////! ## Server Function Encodings
|
||||
////!
|
||||
////! By default, the server function call is a `POST` request that serializes the arguments as URL-encoded form data in the body
|
||||
////! of the request. But there are a few other methods supported. Optionally, we can provide another argument to the `#[server]`
|
||||
////! macro to specify an alternate encoding:
|
||||
////!
|
||||
////! ```rust,ignore
|
||||
////! #[server(AddTodo, "/api", "Url")]
|
||||
////! #[server(AddTodo, "/api", "GetJson")]
|
||||
////! #[server(AddTodo, "/api", "Cbor")]
|
||||
////! #[server(AddTodo, "/api", "GetCbor")]
|
||||
////! ```
|
||||
////!
|
||||
////! The four options use different combinations of HTTP verbs and encoding methods:
|
||||
////!
|
||||
////! | Name | Method | Request | Response |
|
||||
////! | ----------------- | ------ | ----------- | -------- |
|
||||
////! | **Url** (default) | POST | URL encoded | JSON |
|
||||
////! | **GetJson** | GET | URL encoded | JSON |
|
||||
////! | **Cbor** | POST | CBOR | CBOR |
|
||||
////! | **GetCbor** | GET | URL encoded | CBOR |
|
||||
////!
|
||||
////! In other words, you have two choices:
|
||||
////!
|
||||
////! - `GET` or `POST`? This has implications for things like browser or CDN caching; while `POST` requests should not be cached,
|
||||
////! `GET` requests can be.
|
||||
////! - Plain text (arguments sent with URL/form encoding, results sent as JSON) or a binary format (CBOR, encoded as a base64
|
||||
////! string)?
|
||||
////!
|
||||
////! ## Why not `PUT` or `DELETE`? Why URL/form encoding, and not JSON?**
|
||||
////!
|
||||
////! These are reasonable questions. Much of the web is built on REST API patterns that encourage the use of semantic HTTP
|
||||
////! methods like `DELETE` to delete an item from a database, and many devs are accustomed to sending data to APIs in the
|
||||
////! JSON format.
|
||||
////!
|
||||
////! The reason we use `POST` or `GET` with URL-encoded data by default is the `<form>` support. For better or for worse,
|
||||
////! HTML forms don’t support `PUT` or `DELETE`, and they don’t support sending JSON. This means that if you use anything
|
||||
////! but a `GET` or `POST` request with URL-encoded data, it can only work once WASM has loaded.
|
||||
////!
|
||||
////! The CBOR encoding is supported for historical reasons; an earlier version of server functions used a URL encoding that
|
||||
////! didn’t support nested objects like structs or vectors as server function arguments, which CBOR did. But note that the
|
||||
////! CBOR forms encounter the same issue as `PUT`, `DELETE`, or JSON: they do not degrade gracefully if the WASM version of
|
||||
////! your app is not available.
|
||||
|
||||
//pub use server_fn::{error::ServerFnErrorErr, ServerFnError};
|
||||
|
||||
//mod action;
|
||||
//mod multi_action;
|
||||
//pub use action::*;
|
||||
//pub use multi_action::*;
|
||||
//extern crate tracing;
|
||||
use base64::{engine::general_purpose::STANDARD_NO_PAD, DecodeError, Engine};
|
||||
pub use shared::*;
|
||||
|
||||
/// Encodes data into a string.
|
||||
pub trait IntoEncodedString {
|
||||
/// Encodes the data.
|
||||
fn into_encoded_string(self) -> String;
|
||||
}
|
||||
|
||||
/// Decodes data from a string.
|
||||
pub trait FromEncodedStr {
|
||||
/// The decoded data.
|
||||
type DecodedType<'a>: Borrow<Self>;
|
||||
|
||||
/// The type of an error encountered during decoding.
|
||||
type DecodingError;
|
||||
|
||||
/// Decodes the string.
|
||||
fn from_encoded_str(
|
||||
data: &str,
|
||||
) -> Result<Self::DecodedType<'_>, Self::DecodingError>;
|
||||
|
||||
@@ -17,6 +17,7 @@ use std::{
|
||||
panic::Location,
|
||||
};
|
||||
|
||||
/// A reference-counted resource that only loads its data locally on the client.
|
||||
pub struct ArcLocalResource<T> {
|
||||
data: ArcAsyncDerived<SendWrapper<T>>,
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -34,6 +35,10 @@ impl<T> Clone for ArcLocalResource<T> {
|
||||
}
|
||||
|
||||
impl<T> ArcLocalResource<T> {
|
||||
/// Creates the resource.
|
||||
///
|
||||
/// This will only begin loading data if you are on the client (i.e., if you do not have the
|
||||
/// `ssr` feature activated).
|
||||
#[track_caller]
|
||||
pub fn new<Fut>(fetcher: impl Fn() -> Fut + 'static) -> Self
|
||||
where
|
||||
@@ -192,6 +197,7 @@ impl<T> Subscriber for ArcLocalResource<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A resource that only loads its data locally on the client.
|
||||
pub struct LocalResource<T> {
|
||||
data: AsyncDerived<SendWrapper<T>>,
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -207,6 +213,10 @@ impl<T> Clone for LocalResource<T> {
|
||||
impl<T> Copy for LocalResource<T> {}
|
||||
|
||||
impl<T> LocalResource<T> {
|
||||
/// Creates the resource.
|
||||
///
|
||||
/// This will only begin loading data if you are on the client (i.e., if you do not have the
|
||||
/// `ssr` feature activated).
|
||||
#[track_caller]
|
||||
pub fn new<Fut>(fetcher: impl Fn() -> Fut + 'static) -> Self
|
||||
where
|
||||
|
||||
@@ -5,6 +5,7 @@ use reactive_graph::{
|
||||
use server_fn::{ServerFn, ServerFnError};
|
||||
use std::{ops::Deref, panic::Location};
|
||||
|
||||
/// An [`ArcMultiAction`] that can be used to call a server function.
|
||||
pub struct ArcServerMultiAction<S>
|
||||
where
|
||||
S: ServerFn + 'static,
|
||||
@@ -21,6 +22,7 @@ where
|
||||
S::Output: Send + Sync + 'static,
|
||||
S::Error: Send + Sync + 'static,
|
||||
{
|
||||
/// Creates a new [`ArcMultiAction`] which, when dispatched, will call the server function `S`.
|
||||
#[track_caller]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -87,6 +89,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`MultiAction`] that can be used to call a server function.
|
||||
pub struct ServerMultiAction<S>
|
||||
where
|
||||
S: ServerFn + 'static,
|
||||
@@ -114,6 +117,7 @@ where
|
||||
S::Output: Send + Sync + 'static,
|
||||
S::Error: Send + Sync + 'static,
|
||||
{
|
||||
/// Creates a new [`MultiAction`] which, when dispatched, will call the server function `S`.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: MultiAction::new(|input: &S| {
|
||||
|
||||
@@ -1,344 +0,0 @@
|
||||
use leptos_reactive::{
|
||||
is_suppressing_resource_load, signal_prelude::*, spawn_local, store_value,
|
||||
untrack, StoredValue,
|
||||
};
|
||||
use server_fn::{ServerFn, ServerFnError};
|
||||
use std::{future::Future, pin::Pin, rc::Rc};
|
||||
|
||||
/// An action that synchronizes multiple imperative `async` calls to the reactive system,
|
||||
/// tracking the progress of each one.
|
||||
///
|
||||
/// Where an [Action](crate::Action) fires a single call, a `MultiAction` allows you to
|
||||
/// keep track of multiple in-flight actions.
|
||||
///
|
||||
/// If you’re trying to load data by running an `async` function reactively, you probably
|
||||
/// want to use a [Resource](leptos_reactive::Resource) instead. If you’re trying to occasionally
|
||||
/// run an `async` function in response to something like a user adding a task to a todo list,
|
||||
/// you’re in the right place.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos::*;
|
||||
/// # let runtime = create_runtime();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
/// // return a task id
|
||||
/// 42
|
||||
/// }
|
||||
/// let add_todo = create_multi_action(|task: &String| {
|
||||
/// // `task` is given as `&String` because its value is available in `input`
|
||||
/// send_new_todo_to_api(task.clone())
|
||||
/// });
|
||||
///
|
||||
/// # if false {
|
||||
/// add_todo.dispatch("Buy milk".to_string());
|
||||
/// add_todo.dispatch("???".to_string());
|
||||
/// add_todo.dispatch("Profit!!!".to_string());
|
||||
/// # }
|
||||
///
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
///
|
||||
/// The input to the `async` function should always be a single value,
|
||||
/// but it can be of any type. The argument is always passed by reference to the
|
||||
/// function, because it is stored in [Submission::input] as well.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos::*;
|
||||
/// # let runtime = create_runtime();
|
||||
/// // if there's a single argument, just use that
|
||||
/// let action1 = create_multi_action(|input: &String| {
|
||||
/// let input = input.clone();
|
||||
/// async move { todo!() }
|
||||
/// });
|
||||
///
|
||||
/// // if there are no arguments, use the unit type `()`
|
||||
/// let action2 = create_multi_action(|input: &()| async { todo!() });
|
||||
///
|
||||
/// // if there are multiple arguments, use a tuple
|
||||
/// let action3 =
|
||||
/// create_multi_action(|input: &(usize, String)| async { todo!() });
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
pub struct MultiAction<I, O>(StoredValue<MultiActionState<I, O>>)
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static;
|
||||
|
||||
impl<I, O> MultiAction<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
{
|
||||
}
|
||||
|
||||
impl<I, O> Clone for MultiAction<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O> Copy for MultiAction<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
{
|
||||
}
|
||||
|
||||
impl<I, O> MultiAction<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
{
|
||||
/// Calls the `async` function with a reference to the input type as its argument.
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip_all)
|
||||
)]
|
||||
pub fn dispatch(&self, input: I) {
|
||||
self.0.with_value(|a| a.dispatch(input))
|
||||
}
|
||||
|
||||
/// The set of all submissions to this multi-action.
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip_all)
|
||||
)]
|
||||
pub fn submissions(&self) -> ReadSignal<Vec<Submission<I, O>>> {
|
||||
self.0.with_value(|a| a.submissions())
|
||||
}
|
||||
|
||||
/// The URL associated with the action (typically as part of a server function.)
|
||||
/// This enables integration with the `MultiActionForm` component in `leptos_router`.
|
||||
pub fn url(&self) -> Option<String> {
|
||||
self.0.with_value(|a| a.url.as_ref().cloned())
|
||||
}
|
||||
|
||||
/// How many times an action has successfully resolved.
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip_all)
|
||||
)]
|
||||
pub fn version(&self) -> RwSignal<usize> {
|
||||
self.0.with_value(|a| a.version)
|
||||
}
|
||||
|
||||
/// Associates the URL of the given server function with this action.
|
||||
/// This enables integration with the `MultiActionForm` component in `leptos_router`.
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip_all)
|
||||
)]
|
||||
pub fn using_server_fn<T: ServerFn>(self) -> Self {
|
||||
self.0.update_value(|a| {
|
||||
a.url = Some(T::url().to_string());
|
||||
});
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
struct MultiActionState<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
{
|
||||
/// How many times an action has successfully resolved.
|
||||
pub version: RwSignal<usize>,
|
||||
submissions: RwSignal<Vec<Submission<I, O>>>,
|
||||
url: Option<String>,
|
||||
#[allow(clippy::complexity)]
|
||||
action_fn: Rc<dyn Fn(&I) -> Pin<Box<dyn Future<Output = O>>>>,
|
||||
}
|
||||
|
||||
/// An action that has been submitted by dispatching it to a [MultiAction](crate::MultiAction).
|
||||
pub struct Submission<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
{
|
||||
/// The current argument that was dispatched to the `async` function.
|
||||
/// `Some` while we are waiting for it to resolve, `None` if it has resolved.
|
||||
pub input: RwSignal<Option<I>>,
|
||||
/// The most recent return value of the `async` function.
|
||||
pub value: RwSignal<Option<O>>,
|
||||
pub(crate) pending: RwSignal<bool>,
|
||||
/// Controls this submission has been canceled.
|
||||
pub canceled: RwSignal<bool>,
|
||||
}
|
||||
|
||||
impl<I, O> Clone for Submission<I, O> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O> Copy for Submission<I, O> {}
|
||||
|
||||
impl<I, O> Submission<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
{
|
||||
/// Whether this submission is currently waiting to resolve.
|
||||
pub fn pending(&self) -> ReadSignal<bool> {
|
||||
self.pending.read_only()
|
||||
}
|
||||
|
||||
/// Cancels the submission, preventing it from resolving.
|
||||
pub fn cancel(&self) {
|
||||
self.canceled.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O> MultiActionState<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
{
|
||||
/// Calls the `async` function with a reference to the input type as its argument.
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(level = "trace", skip_all)
|
||||
)]
|
||||
pub fn dispatch(&self, input: I) {
|
||||
if !is_suppressing_resource_load() {
|
||||
let fut = (self.action_fn)(&input);
|
||||
|
||||
let submission = Submission {
|
||||
input: create_rw_signal(Some(input)),
|
||||
value: create_rw_signal(None),
|
||||
pending: create_rw_signal(true),
|
||||
canceled: create_rw_signal(false),
|
||||
};
|
||||
|
||||
self.submissions.update(|subs| subs.push(submission));
|
||||
|
||||
let canceled = submission.canceled;
|
||||
let input = submission.input;
|
||||
let pending = submission.pending;
|
||||
let value = submission.value;
|
||||
let version = self.version;
|
||||
|
||||
spawn_local(async move {
|
||||
let new_value = fut.await;
|
||||
let canceled = untrack(move || canceled.get());
|
||||
if !canceled {
|
||||
value.set(Some(new_value));
|
||||
}
|
||||
input.set(None);
|
||||
pending.set(false);
|
||||
version.update(|n| *n += 1);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The set of all submissions to this multi-action.
|
||||
pub fn submissions(&self) -> ReadSignal<Vec<Submission<I, O>>> {
|
||||
self.submissions.read_only()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an [MultiAction] to synchronize an imperative `async` call to the synchronous reactive system.
|
||||
///
|
||||
/// If you’re trying to load data by running an `async` function reactively, you probably
|
||||
/// want to use a [create_resource](leptos_reactive::create_resource) instead. If you’re trying
|
||||
/// to occasionally run an `async` function in response to something like a user clicking a button,
|
||||
/// you're in the right place.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos::*;
|
||||
/// # let runtime = create_runtime();
|
||||
/// async fn send_new_todo_to_api(task: String) -> usize {
|
||||
/// // do something...
|
||||
/// // return a task id
|
||||
/// 42
|
||||
/// }
|
||||
/// let add_todo = create_multi_action(|task: &String| {
|
||||
/// // `task` is given as `&String` because its value is available in `input`
|
||||
/// send_new_todo_to_api(task.clone())
|
||||
/// });
|
||||
/// # if false {
|
||||
///
|
||||
/// add_todo.dispatch("Buy milk".to_string());
|
||||
/// add_todo.dispatch("???".to_string());
|
||||
/// add_todo.dispatch("Profit!!!".to_string());
|
||||
///
|
||||
/// assert_eq!(add_todo.submissions().get().len(), 3);
|
||||
/// # }
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
///
|
||||
/// The input to the `async` function should always be a single value,
|
||||
/// but it can be of any type. The argument is always passed by reference to the
|
||||
/// function, because it is stored in [Submission::input] as well.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use leptos::*;
|
||||
/// # let runtime = create_runtime();
|
||||
/// // if there's a single argument, just use that
|
||||
/// let action1 = create_multi_action(|input: &String| {
|
||||
/// let input = input.clone();
|
||||
/// async move { todo!() }
|
||||
/// });
|
||||
///
|
||||
/// // if there are no arguments, use the unit type `()`
|
||||
/// let action2 = create_multi_action(|input: &()| async { todo!() });
|
||||
///
|
||||
/// // if there are multiple arguments, use a tuple
|
||||
/// let action3 =
|
||||
/// create_multi_action(|input: &(usize, String)| async { todo!() });
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||
pub fn create_multi_action<I, O, F, Fu>(action_fn: F) -> MultiAction<I, O>
|
||||
where
|
||||
I: 'static,
|
||||
O: 'static,
|
||||
F: Fn(&I) -> Fu + 'static,
|
||||
Fu: Future<Output = O> + 'static,
|
||||
{
|
||||
let version = create_rw_signal(0);
|
||||
let submissions = create_rw_signal(Vec::new());
|
||||
let action_fn = Rc::new(move |input: &I| {
|
||||
let fut = action_fn(input);
|
||||
Box::pin(fut) as Pin<Box<dyn Future<Output = O>>>
|
||||
});
|
||||
|
||||
MultiAction(store_value(MultiActionState {
|
||||
version,
|
||||
submissions,
|
||||
url: None,
|
||||
action_fn,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Creates an [MultiAction] that can be used to call a server function.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # use leptos::*;
|
||||
///
|
||||
/// #[server(MyServerFn)]
|
||||
/// async fn my_server_fn() -> Result<(), ServerFnError> {
|
||||
/// todo!()
|
||||
/// }
|
||||
///
|
||||
/// # let runtime = create_runtime();
|
||||
/// let my_server_multi_action = create_server_multi_action::<MyServerFn>();
|
||||
/// # runtime.dispose();
|
||||
/// ```
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "trace", skip_all))]
|
||||
pub fn create_server_multi_action<S>(
|
||||
) -> MultiAction<S, Result<S::Output, ServerFnError<S::Error>>>
|
||||
where
|
||||
S: Clone + ServerFn,
|
||||
{
|
||||
#[cfg(feature = "ssr")]
|
||||
let c = move |args: &S| S::run_body(args.clone());
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
let c = move |args: &S| S::run_on_client(args.clone());
|
||||
create_multi_action(c).using_server_fn::<S>()
|
||||
}
|
||||
@@ -43,6 +43,15 @@ use std::{
|
||||
task::{Context, Poll, Waker},
|
||||
};
|
||||
|
||||
/// A reference-counted resource that only loads once.
|
||||
///
|
||||
/// Resources allow asynchronously loading data and serializing it from the server to the client,
|
||||
/// so that it loads on the server, and is then deserialized on the client. This improves
|
||||
/// performance by beginning data loading on the server when the request is made, rather than
|
||||
/// beginning it on the client after WASM has been loaded.
|
||||
///
|
||||
/// You can access the value of the resource either synchronously using `.get()` or asynchronously
|
||||
/// using `.await`.
|
||||
#[derive(Debug)]
|
||||
pub struct ArcOnceResource<T, Ser = JsonSerdeCodec> {
|
||||
trigger: ArcTrigger,
|
||||
@@ -80,6 +89,12 @@ where
|
||||
<Ser as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<Ser as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a new resource with the encoding `Ser`. If `blocking` is `true`, this is a blocking
|
||||
/// resource.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_with_options(
|
||||
fut: impl Future<Output = T> + Send + 'static,
|
||||
@@ -287,11 +302,17 @@ where
|
||||
<JsonSerdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<JsonSerdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a resource using [`JsonSerdeCodec`] for encoding/decoding the value.
|
||||
#[track_caller]
|
||||
pub fn new(fut: impl Future<Output = T> + Send + 'static) -> Self {
|
||||
ArcOnceResource::new_with_options(fut, false)
|
||||
}
|
||||
|
||||
/// Creates a blocking resource using [`JsonSerdeCodec`] for encoding/decoding the value.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_blocking(fut: impl Future<Output = T> + Send + 'static) -> Self {
|
||||
ArcOnceResource::new_with_options(fut, true)
|
||||
@@ -307,6 +328,7 @@ T: Send + Sync + 'static,
|
||||
<FromToStringCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<FromToStringCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a resource using [`FromToStringCodec`] for encoding/decoding the value.
|
||||
pub fn new_str(
|
||||
fut: impl Future<Output = T> + Send + 'static
|
||||
) -> Self
|
||||
@@ -314,6 +336,11 @@ T: Send + Sync + 'static,
|
||||
ArcOnceResource::new_with_options(fut, false)
|
||||
}
|
||||
|
||||
/// Creates a blocking resource using [`FromToStringCodec`] for encoding/decoding the value.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
pub fn new_str_blocking(
|
||||
fut: impl Future<Output = T> + Send + 'static
|
||||
) -> Self
|
||||
@@ -332,6 +359,7 @@ T: Send + Sync + 'static,
|
||||
<JsonSerdeWasmCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<JsonSerdeWasmCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a resource using [`JsonSerdeWasmCodec`] for encoding/decoding the value.
|
||||
#[track_caller]
|
||||
pub fn new_serde_wb(
|
||||
fut: impl Future<Output = T> + Send + 'static
|
||||
@@ -340,6 +368,11 @@ fut: impl Future<Output = T> + Send + 'static
|
||||
ArcOnceResource::new_with_options(fut, false)
|
||||
}
|
||||
|
||||
/// Creates a blocking resource using [`JsonSerdeWasmCodec`] for encoding/decoding the value.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_serde_wb_blocking(
|
||||
fut: impl Future<Output = T> + Send + 'static
|
||||
@@ -360,6 +393,7 @@ where
|
||||
<MiniserdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<MiniserdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a resource using [`MiniserdeCodec`] for encoding/decoding the value.
|
||||
#[track_caller]
|
||||
pub fn new_miniserde(
|
||||
fut: impl Future<Output = T> + Send + 'static,
|
||||
@@ -367,6 +401,11 @@ where
|
||||
ArcOnceResource::new_with_options(fut, false)
|
||||
}
|
||||
|
||||
/// Creates a blocking resource using [`MiniserdeCodec`] for encoding/decoding the value.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_miniserde_blocking(
|
||||
fut: impl Future<Output = T> + Send + 'static,
|
||||
@@ -385,6 +424,7 @@ T: Send + Sync + 'static,
|
||||
<SerdeLite<JsonSerdeCodec> as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a resource using [`SerdeLite`] for encoding/decoding the value.
|
||||
#[track_caller]
|
||||
pub fn new_serde_lite(
|
||||
fut: impl Future<Output = T> + Send + 'static
|
||||
@@ -393,6 +433,11 @@ fut: impl Future<Output = T> + Send + 'static
|
||||
ArcOnceResource::new_with_options(fut, false)
|
||||
}
|
||||
|
||||
/// Creates a blocking resource using [`SerdeLite`] for encoding/decoding the value.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_serde_lite_blocking(
|
||||
fut: impl Future<Output = T> + Send + 'static
|
||||
@@ -414,11 +459,17 @@ where
|
||||
<RkyvCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<RkyvCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a resource using [`RkyvCodec`] for encoding/decoding the value.
|
||||
#[track_caller]
|
||||
pub fn new_rkyv(fut: impl Future<Output = T> + Send + 'static) -> Self {
|
||||
ArcOnceResource::new_with_options(fut, false)
|
||||
}
|
||||
|
||||
/// Creates a blocking resource using [`RkyvCodec`] for encoding/decoding the value.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_rkyv_blocking(
|
||||
fut: impl Future<Output = T> + Send + 'static,
|
||||
@@ -427,6 +478,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A resource that only loads once.
|
||||
///
|
||||
/// Resources allow asynchronously loading data and serializing it from the server to the client,
|
||||
/// so that it loads on the server, and is then deserialized on the client. This improves
|
||||
/// performance by beginning data loading on the server when the request is made, rather than
|
||||
/// beginning it on the client after WASM has been loaded.
|
||||
///
|
||||
/// You can access the value of the resource either synchronously using `.get()` or asynchronously
|
||||
/// using `.await`.
|
||||
#[derive(Debug)]
|
||||
pub struct OnceResource<T, Ser = JsonSerdeCodec> {
|
||||
inner: ArenaItem<ArcOnceResource<T, Ser>>,
|
||||
@@ -452,6 +512,12 @@ where
|
||||
<Ser as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<Ser as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a new resource with the encoding `Ser`. If `blocking` is `true`, this is a blocking
|
||||
/// resource.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_with_options(
|
||||
fut: impl Future<Output = T> + Send + 'static,
|
||||
@@ -567,11 +633,17 @@ where
|
||||
<JsonSerdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<JsonSerdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a resource using [`JsonSerdeCodec`] for encoding/decoding the value.
|
||||
#[track_caller]
|
||||
pub fn new(fut: impl Future<Output = T> + Send + 'static) -> Self {
|
||||
OnceResource::new_with_options(fut, false)
|
||||
}
|
||||
|
||||
/// Creates a blocking resource using [`JsonSerdeCodec`] for encoding/decoding the value.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_blocking(fut: impl Future<Output = T> + Send + 'static) -> Self {
|
||||
OnceResource::new_with_options(fut, true)
|
||||
@@ -587,6 +659,7 @@ T: Send + Sync + 'static,
|
||||
<FromToStringCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<FromToStringCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a resource using [`FromToStringCodec`] for encoding/decoding the value.
|
||||
pub fn new_str(
|
||||
fut: impl Future<Output = T> + Send + 'static
|
||||
) -> Self
|
||||
@@ -594,6 +667,11 @@ T: Send + Sync + 'static,
|
||||
OnceResource::new_with_options(fut, false)
|
||||
}
|
||||
|
||||
/// Creates a blocking resource using [`FromToStringCodec`] for encoding/decoding the value.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
pub fn new_str_blocking(
|
||||
fut: impl Future<Output = T> + Send + 'static
|
||||
) -> Self
|
||||
@@ -612,6 +690,7 @@ T: Send + Sync + 'static,
|
||||
<JsonSerdeWasmCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<JsonSerdeWasmCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a resource using [`JsonSerdeWasmCodec`] for encoding/decoding the value.
|
||||
#[track_caller]
|
||||
pub fn new_serde_wb(
|
||||
fut: impl Future<Output = T> + Send + 'static
|
||||
@@ -620,6 +699,11 @@ fut: impl Future<Output = T> + Send + 'static
|
||||
OnceResource::new_with_options(fut, false)
|
||||
}
|
||||
|
||||
/// Creates a blocking resource using [`JsonSerdeWasmCodec`] for encoding/decoding the value.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_serde_wb_blocking(
|
||||
fut: impl Future<Output = T> + Send + 'static
|
||||
@@ -640,6 +724,7 @@ where
|
||||
<MiniserdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<MiniserdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a resource using [`MiniserdeCodec`] for encoding/decoding the value.
|
||||
#[track_caller]
|
||||
pub fn new_miniserde(
|
||||
fut: impl Future<Output = T> + Send + 'static,
|
||||
@@ -647,6 +732,11 @@ where
|
||||
OnceResource::new_with_options(fut, false)
|
||||
}
|
||||
|
||||
/// Creates a blocking resource using [`MiniserdeCodec`] for encoding/decoding the value.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_miniserde_blocking(
|
||||
fut: impl Future<Output = T> + Send + 'static,
|
||||
@@ -665,6 +755,7 @@ T: Send + Sync + 'static,
|
||||
<SerdeLite<JsonSerdeCodec> as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a resource using [`SerdeLite`] for encoding/decoding the value.
|
||||
#[track_caller]
|
||||
pub fn new_serde_lite(
|
||||
fut: impl Future<Output = T> + Send + 'static
|
||||
@@ -673,6 +764,11 @@ fut: impl Future<Output = T> + Send + 'static
|
||||
OnceResource::new_with_options(fut, false)
|
||||
}
|
||||
|
||||
/// Creates a blocking resource using [`SerdeLite`] for encoding/decoding the value.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_serde_lite_blocking(
|
||||
fut: impl Future<Output = T> + Send + 'static
|
||||
@@ -694,11 +790,17 @@ where
|
||||
<RkyvCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<RkyvCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a resource using [`RkyvCodec`] for encoding/decoding the value.
|
||||
#[track_caller]
|
||||
pub fn new_rkyv(fut: impl Future<Output = T> + Send + 'static) -> Self {
|
||||
OnceResource::new_with_options(fut, false)
|
||||
}
|
||||
|
||||
/// Creates a blocking resource using [`RkyvCodec`] for encoding/decoding the value.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_rkyv_blocking(
|
||||
fut: impl Future<Output = T> + Send + 'static,
|
||||
|
||||
@@ -37,9 +37,12 @@ use std::{
|
||||
pub(crate) static IS_SUPPRESSING_RESOURCE_LOAD: AtomicBool =
|
||||
AtomicBool::new(false);
|
||||
|
||||
/// Used to prevent resources from actually loading, in environments (like server route generation)
|
||||
/// where they are not needed.
|
||||
pub struct SuppressResourceLoad;
|
||||
|
||||
impl SuppressResourceLoad {
|
||||
/// Prevents resources from loading until this is dropped.
|
||||
pub fn new() -> Self {
|
||||
IS_SUPPRESSING_RESOURCE_LOAD.store(true, Ordering::Relaxed);
|
||||
Self
|
||||
@@ -58,6 +61,15 @@ impl Drop for SuppressResourceLoad {
|
||||
}
|
||||
}
|
||||
|
||||
/// A reference-counted asynchronous resource.
|
||||
///
|
||||
/// Resources allow asynchronously loading data and serializing it from the server to the client,
|
||||
/// so that it loads on the server, and is then deserialized on the client. This improves
|
||||
/// performance by beginning data loading on the server when the request is made, rather than
|
||||
/// beginning it on the client after WASM has been loaded.
|
||||
///
|
||||
/// You can access the value of the resource either synchronously using `.get()` or asynchronously
|
||||
/// using `.await`.
|
||||
pub struct ArcResource<T, Ser = JsonSerdeCodec> {
|
||||
ser: PhantomData<Ser>,
|
||||
refetch: ArcRwSignal<usize>,
|
||||
@@ -194,6 +206,21 @@ where
|
||||
<Ser as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<Ser as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a new resource with the encoding `Ser`.
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
///
|
||||
/// If `blocking` is `true`, this is a blocking resource.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_with_options<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
@@ -278,6 +305,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Synchronously, reactively reads the current value of the resource and applies the function
|
||||
/// `f` to its value if it is `Some(_)`.
|
||||
#[track_caller]
|
||||
pub fn map<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U>
|
||||
where
|
||||
@@ -351,6 +380,13 @@ where
|
||||
T: Send + Sync + 'static,
|
||||
E: Send + Sync + Clone + 'static,
|
||||
{
|
||||
/// Applies the given function when a resource that returns `Result<T, E>`
|
||||
/// has resolved and loaded an `Ok(_)`, rather than requiring nested `.map()`
|
||||
/// calls over the `Option<Result<_, _>>` returned by the resource.
|
||||
///
|
||||
/// This is useful when used with features like server functions, in conjunction
|
||||
/// with `<ErrorBoundary/>` and `<Suspense/>`, when these other components are
|
||||
/// left to handle the `None` and `Err(_)` states.
|
||||
#[track_caller]
|
||||
pub fn and_then<U>(&self, f: impl FnOnce(&T) -> U) -> Option<Result<U, E>> {
|
||||
self.map(|data| data.as_ref().map(f).map_err(|e| e.clone()))
|
||||
@@ -367,6 +403,15 @@ where
|
||||
<JsonSerdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<JsonSerdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a new resource with the encoding [`JsonSerdeCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
#[track_caller]
|
||||
pub fn new<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
@@ -380,6 +425,19 @@ where
|
||||
ArcResource::new_with_options(source, fetcher, false)
|
||||
}
|
||||
|
||||
/// Creates a new blocking resource with the encoding [`JsonSerdeCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_blocking<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
@@ -402,6 +460,15 @@ where
|
||||
<FromToStringCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<FromToStringCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a new resource with the encoding [`FromToStringCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
pub fn new_str<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
@@ -414,6 +481,19 @@ where
|
||||
ArcResource::new_with_options(source, fetcher, false)
|
||||
}
|
||||
|
||||
/// Creates a new blocking resource with the encoding [`FromToStringCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
pub fn new_str_blocking<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
@@ -436,6 +516,15 @@ where
|
||||
<JsonSerdeWasmCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<JsonSerdeWasmCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a new resource with the encoding [`JsonSerdeWasmCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
#[track_caller]
|
||||
pub fn new_serde_wb<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
@@ -449,6 +538,19 @@ where
|
||||
ArcResource::new_with_options(source, fetcher, false)
|
||||
}
|
||||
|
||||
/// Creates a new blocking resource with the encoding [`JsonSerdeWasmCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_serde_wb_blocking<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
@@ -473,6 +575,15 @@ where
|
||||
<MiniserdeCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<MiniserdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a new resource with the encoding [`MiniserdeCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
#[track_caller]
|
||||
pub fn new_miniserde<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
@@ -486,6 +597,19 @@ where
|
||||
ArcResource::new_with_options(source, fetcher, false)
|
||||
}
|
||||
|
||||
/// Creates a new blocking resource with the encoding [`MiniserdeCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_miniserde_blocking<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
@@ -509,6 +633,15 @@ where
|
||||
<SerdeLite<JsonSerdeCodec> as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a new resource with the encoding [`SerdeLite`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
#[track_caller]
|
||||
pub fn new_serde_lite<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
@@ -522,6 +655,19 @@ where
|
||||
ArcResource::new_with_options(source, fetcher, false)
|
||||
}
|
||||
|
||||
/// Creates a new blocking resource with the encoding [`SerdeLite`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_serde_lite_blocking<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
@@ -547,6 +693,15 @@ where
|
||||
<RkyvCodec as Encoder<T>>::Encoded: IntoEncodedString,
|
||||
<RkyvCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
{
|
||||
/// Creates a new resource with the encoding [`RkyvCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
#[track_caller]
|
||||
pub fn new_rkyv<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
@@ -560,6 +715,19 @@ where
|
||||
ArcResource::new_with_options(source, fetcher, false)
|
||||
}
|
||||
|
||||
/// Creates a new blocking resource with the encoding [`RkyvCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_rkyv_blocking<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
@@ -590,11 +758,22 @@ impl<T, Ser> ArcResource<T, Ser>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
/// Returns a new [`Future`] that is ready when the resource has loaded, and accesses its inner
|
||||
/// value by reference.
|
||||
pub fn by_ref(&self) -> AsyncDerivedRefFuture<T> {
|
||||
self.data.by_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// An asynchronous resource.
|
||||
///
|
||||
/// Resources allow asynchronously loading data and serializing it from the server to the client,
|
||||
/// so that it loads on the server, and is then deserialized on the client. This improves
|
||||
/// performance by beginning data loading on the server when the request is made, rather than
|
||||
/// beginning it on the client after WASM has been loaded.
|
||||
///
|
||||
/// You can access the value of the resource either synchronously using `.get()` or asynchronously
|
||||
/// using `.await`.
|
||||
pub struct Resource<T, Ser = JsonSerdeCodec>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
@@ -707,6 +886,15 @@ where
|
||||
<FromToStringCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
T: Send + Sync,
|
||||
{
|
||||
/// Creates a new resource with the encoding [`FromToStringCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
#[track_caller]
|
||||
pub fn new_str<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
@@ -720,6 +908,19 @@ where
|
||||
Resource::new_with_options(source, fetcher, false)
|
||||
}
|
||||
|
||||
/// Creates a new blocking resource with the encoding [`FromToStringCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_str_blocking<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
@@ -745,6 +946,15 @@ where
|
||||
<JsonSerdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
T: Send + Sync,
|
||||
{
|
||||
/// Creates a new resource with the encoding [`JsonSerdeCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
#[track_caller]
|
||||
pub fn new<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
@@ -758,6 +968,19 @@ where
|
||||
Resource::new_with_options(source, fetcher, false)
|
||||
}
|
||||
|
||||
/// Creates a new blocking resource with the encoding [`JsonSerdeCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_blocking<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
@@ -782,6 +1005,15 @@ where
|
||||
<JsonSerdeWasmCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
T: Send + Sync,
|
||||
{
|
||||
/// Creates a new resource with the encoding [`JsonSerdeWasmCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
pub fn new_serde_wb<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
@@ -794,6 +1026,19 @@ where
|
||||
Resource::new_with_options(source, fetcher, false)
|
||||
}
|
||||
|
||||
/// Creates a new blocking resource with the encoding [`JsonSerdeWasmCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
pub fn new_serde_wb_blocking<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
@@ -819,6 +1064,15 @@ where
|
||||
<MiniserdeCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
T: Send + Sync,
|
||||
{
|
||||
/// Creates a new resource with the encoding [`MiniserdeCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
pub fn new_miniserde<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
@@ -830,6 +1084,31 @@ where
|
||||
{
|
||||
Resource::new_with_options(source, fetcher, false)
|
||||
}
|
||||
|
||||
/// Creates a new blocking resource with the encoding [`MiniserdeCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
pub fn new_miniserde_blocking<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
) -> Self
|
||||
where
|
||||
S: PartialEq + Clone + Send + Sync + 'static,
|
||||
T: Send + Sync + 'static,
|
||||
Fut: Future<Output = T> + Send + 'static,
|
||||
{
|
||||
Resource::new_with_options(source, fetcher, true)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde-lite")]
|
||||
@@ -843,6 +1122,15 @@ where
|
||||
<SerdeLite<JsonSerdeCodec> as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
T: Send + Sync,
|
||||
{
|
||||
/// Creates a new resource with the encoding [`SerdeLite`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
pub fn new_serde_lite<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
@@ -855,6 +1143,19 @@ where
|
||||
Resource::new_with_options(source, fetcher, false)
|
||||
}
|
||||
|
||||
/// Creates a new blocking resource with the encoding [`SerdeLite`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
pub fn new_serde_lite_blocking<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
@@ -880,6 +1181,15 @@ where
|
||||
<RkyvCodec as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
T: Send + Sync,
|
||||
{
|
||||
/// Creates a new resource with the encoding [`RkyvCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
pub fn new_rkyv<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
@@ -892,6 +1202,19 @@ where
|
||||
Resource::new_with_options(source, fetcher, false)
|
||||
}
|
||||
|
||||
/// Creates a new blocking resource with the encoding [`RkyvCodec`].
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
pub fn new_rkyv_blocking<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
fetcher: impl Fn(S) -> Fut + Send + Sync + 'static,
|
||||
@@ -915,6 +1238,21 @@ where
|
||||
<Ser as Decoder<T>>::Encoded: FromEncodedStr,
|
||||
T: Send + Sync,
|
||||
{
|
||||
/// Creates a new resource with the encoding `Ser`.
|
||||
///
|
||||
/// This takes a `source` function and a `fetcher`. The resource memoizes and reactively tracks
|
||||
/// the value returned by `source`. Whenever that value changes, it will run the `fetcher` to
|
||||
/// generate a new [`Future`] to load data.
|
||||
///
|
||||
/// On creation, if you are on the server, this will run the `fetcher` once to generate
|
||||
/// a `Future` whose value will be serialized from the server to the client. If you are on
|
||||
/// the client, the initial value will be deserialized without re-running that async task.
|
||||
///
|
||||
/// If `blocking` is `true`, this is a blocking resource.
|
||||
///
|
||||
/// Blocking resources prevent any of the HTTP response from being sent until they have loaded.
|
||||
/// This is useful if you need their data to set HTML document metadata or information that
|
||||
/// needs to appear in HTTP headers.
|
||||
#[track_caller]
|
||||
pub fn new_with_options<S, Fut>(
|
||||
source: impl Fn() -> S + Send + Sync + 'static,
|
||||
@@ -937,6 +1275,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Synchronously, reactively reads the current value of the resource and applies the function
|
||||
/// `f` to its value if it is `Some(_)`.
|
||||
pub fn map<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
self.data
|
||||
.try_with(|n| n.as_ref().map(|n| Some(f(n))))?
|
||||
@@ -961,6 +1301,13 @@ where
|
||||
T: Send + Sync,
|
||||
E: Send + Sync + Clone,
|
||||
{
|
||||
/// Applies the given function when a resource that returns `Result<T, E>`
|
||||
/// has resolved and loaded an `Ok(_)`, rather than requiring nested `.map()`
|
||||
/// calls over the `Option<Result<_, _>>` returned by the resource.
|
||||
///
|
||||
/// This is useful when used with features like server functions, in conjunction
|
||||
/// with `<ErrorBoundary/>` and `<Suspense/>`, when these other components are
|
||||
/// left to handle the `None` and `Err(_)` states.
|
||||
#[track_caller]
|
||||
pub fn and_then<U>(&self, f: impl FnOnce(&T) -> U) -> Option<Result<U, E>> {
|
||||
self.map(|data| data.as_ref().map(f).map_err(|e| e.clone()))
|
||||
@@ -984,6 +1331,8 @@ impl<T, Ser> Resource<T, Ser>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
/// Returns a new [`Future`] that is ready when the resource has loaded, and accesses its inner
|
||||
/// value by reference.
|
||||
pub fn by_ref(&self) -> AsyncDerivedRefFuture<T> {
|
||||
self.data.by_ref()
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ pub struct SharedValue<T, Ser = JsonSerdeCodec> {
|
||||
}
|
||||
|
||||
impl<T, Ser> SharedValue<T, Ser> {
|
||||
/// Returns the inner value.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.value
|
||||
}
|
||||
@@ -46,6 +47,12 @@ where
|
||||
<<JsonSerdeCodec as codee::Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
|
||||
Debug,
|
||||
{
|
||||
/// Wraps the initial value.
|
||||
///
|
||||
/// If this is on the server, the function will be invoked and the value serialized. When it runs
|
||||
/// on the client, it will be deserialized without running the function again.
|
||||
///
|
||||
/// This uses the [`JsonSerdeCodec`] encoding.
|
||||
pub fn new(initial: impl FnOnce() -> T) -> Self {
|
||||
SharedValue::new_with_encoding(initial)
|
||||
}
|
||||
@@ -61,6 +68,12 @@ where
|
||||
<<FromToStringCodec as codee::Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
|
||||
Debug,
|
||||
{
|
||||
/// Wraps the initial value.
|
||||
///
|
||||
/// If this is on the server, the function will be invoked and the value serialized. When it runs
|
||||
/// on the client, it will be deserialized without running the function again.
|
||||
///
|
||||
/// This uses the [`FromToStringCodec`] encoding.
|
||||
pub fn new_str(initial: impl FnOnce() -> T) -> Self {
|
||||
SharedValue::new_with_encoding(initial)
|
||||
}
|
||||
@@ -77,7 +90,13 @@ where
|
||||
<<SerdeLite<JsonSerdeCodec> as codee::Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
|
||||
Debug,
|
||||
{
|
||||
pub fn new(initial: impl FnOnce() -> T) -> Self {
|
||||
/// Wraps the initial value.
|
||||
///
|
||||
/// If this is on the server, the function will be invoked and the value serialized. When it runs
|
||||
/// on the client, it will be deserialized without running the function again.
|
||||
///
|
||||
/// This uses the [`SerdeLite`] encoding.
|
||||
pub fn new_serde_lite(initial: impl FnOnce() -> T) -> Self {
|
||||
SharedValue::new_with_encoding(initial)
|
||||
}
|
||||
}
|
||||
@@ -93,7 +112,13 @@ where
|
||||
<<JsonSerdeWasmCodec as codee::Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
|
||||
Debug,
|
||||
{
|
||||
pub fn new(initial: impl FnOnce() -> T) -> Self {
|
||||
/// Wraps the initial value.
|
||||
///
|
||||
/// If this is on the server, the function will be invoked and the value serialized. When it runs
|
||||
/// on the client, it will be deserialized without running the function again.
|
||||
///
|
||||
/// This uses the [`JsonSerdeWasmCodec`] encoding.
|
||||
pub fn new_serde_wb(initial: impl FnOnce() -> T) -> Self {
|
||||
SharedValue::new_with_encoding(initial)
|
||||
}
|
||||
}
|
||||
@@ -109,7 +134,13 @@ where
|
||||
<<MiniserdeCodec as codee::Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
|
||||
Debug,
|
||||
{
|
||||
pub fn new(initial: impl FnOnce() -> T) -> Self {
|
||||
/// Wraps the initial value.
|
||||
///
|
||||
/// If this is on the server, the function will be invoked and the value serialized. When it runs
|
||||
/// on the client, it will be deserialized without running the function again.
|
||||
///
|
||||
/// This uses the [`MiniserdeCodec`] encoding.
|
||||
pub fn new_miniserde(initial: impl FnOnce() -> T) -> Self {
|
||||
SharedValue::new_with_encoding(initial)
|
||||
}
|
||||
}
|
||||
@@ -125,7 +156,13 @@ where
|
||||
<<RkyvCodec as codee::Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
|
||||
Debug,
|
||||
{
|
||||
pub fn new(initial: impl FnOnce() -> T) -> Self {
|
||||
/// Wraps the initial value.
|
||||
///
|
||||
/// If this is on the server, the function will be invoked and the value serialized. When it runs
|
||||
/// on the client, it will be deserialized without running the function again.
|
||||
///
|
||||
/// This uses the [`RkyvCodec`] encoding.
|
||||
pub fn new_rkyv(initial: impl FnOnce() -> T) -> Self {
|
||||
SharedValue::new_with_encoding(initial)
|
||||
}
|
||||
}
|
||||
@@ -140,6 +177,12 @@ where
|
||||
<<Ser as codee::Decoder<T>>::Encoded as FromEncodedStr>::DecodingError:
|
||||
Debug,
|
||||
{
|
||||
/// Wraps the initial value.
|
||||
///
|
||||
/// If this is on the server, the function will be invoked and the value serialized. When it runs
|
||||
/// on the client, it will be deserialized without running the function again.
|
||||
///
|
||||
/// This uses `Ser` as an encoding.
|
||||
pub fn new_with_encoding(initial: impl FnOnce() -> T) -> Self {
|
||||
let value: T;
|
||||
#[cfg(feature = "hydration")]
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
//! Defines a trait that allows you to extend a tuple, by returning
|
||||
//! a new tuple with an element of an arbitrary type added.
|
||||
|
||||
#![no_std]
|
||||
#![allow(non_snake_case)]
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
/// Allows extending a tuple, or creating a new tuple, by adding the next value.
|
||||
pub trait NextTuple {
|
||||
|
||||
@@ -70,7 +70,7 @@ pub enum Oco<'a, T: ?Sized + ToOwned + 'a> {
|
||||
Owned(<T as ToOwned>::Owned),
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized + ToOwned> Oco<'a, T> {
|
||||
impl<T: ?Sized + ToOwned> Oco<'_, T> {
|
||||
/// Converts the value into an owned value.
|
||||
pub fn into_owned(self) -> <T as ToOwned>::Owned {
|
||||
match self {
|
||||
@@ -339,7 +339,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, A: ?Sized, B: ?Sized> PartialEq<Oco<'b, B>> for Oco<'a, A>
|
||||
impl<'b, A: ?Sized, B: ?Sized> PartialEq<Oco<'b, B>> for Oco<'_, A>
|
||||
where
|
||||
A: PartialEq<B>,
|
||||
A: ToOwned,
|
||||
@@ -352,7 +352,7 @@ where
|
||||
|
||||
impl<T: ?Sized + ToOwned + Eq> Eq for Oco<'_, T> {}
|
||||
|
||||
impl<'a, 'b, A: ?Sized, B: ?Sized> PartialOrd<Oco<'b, B>> for Oco<'a, A>
|
||||
impl<'b, A: ?Sized, B: ?Sized> PartialOrd<Oco<'b, B>> for Oco<'_, A>
|
||||
where
|
||||
A: PartialOrd<B>,
|
||||
A: ToOwned,
|
||||
@@ -551,7 +551,7 @@ impl_slice_eq!(['a, 'b, T: PartialEq] (where [T]: ToOwned), Oco<'a, [T]>, &'b [T
|
||||
impl_slice_eq!([T: PartialEq] (where [T]: ToOwned), Oco<'_, [T]>, Vec<T>);
|
||||
impl_slice_eq!(['a, 'b, T: PartialEq] (where [T]: ToOwned), Oco<'a, [T]>, Cow<'b, [T]>);
|
||||
|
||||
impl<'a, 'b> Add<&'b str> for Oco<'a, str> {
|
||||
impl<'b> Add<&'b str> for Oco<'_, str> {
|
||||
type Output = Oco<'static, str>;
|
||||
|
||||
fn add(self, rhs: &'b str) -> Self::Output {
|
||||
@@ -559,7 +559,7 @@ impl<'a, 'b> Add<&'b str> for Oco<'a, str> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Add<Cow<'b, str>> for Oco<'a, str> {
|
||||
impl<'b> Add<Cow<'b, str>> for Oco<'_, str> {
|
||||
type Output = Oco<'static, str>;
|
||||
|
||||
fn add(self, rhs: Cow<'b, str>) -> Self::Output {
|
||||
@@ -567,7 +567,7 @@ impl<'a, 'b> Add<Cow<'b, str>> for Oco<'a, str> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Add<Oco<'b, str>> for Oco<'a, str> {
|
||||
impl<'b> Add<Oco<'b, str>> for Oco<'_, str> {
|
||||
type Output = Oco<'static, str>;
|
||||
|
||||
fn add(self, rhs: Oco<'b, str>) -> Self::Output {
|
||||
|
||||
@@ -21,6 +21,6 @@ CREATE TABLE IF NOT EXISTS google_tokens (
|
||||
access_secret TEXT NOT NULL,
|
||||
refresh_secret TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) CONFLICT REPLACE
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ pub async fn refresh_token(email: String) -> Result<u64, ServerFnError> {
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
sqlx::query(
|
||||
"INSERT INTO google_tokens (user_id,access_secret,refresh_secret) \
|
||||
"INSERT OR REPLACE INTO google_tokens (user_id,access_secret,refresh_secret) \
|
||||
VALUES (?,?,?)",
|
||||
)
|
||||
.bind(user.id)
|
||||
|
||||
@@ -96,7 +96,7 @@ async fn main() {
|
||||
let client = oauth2::basic::BasicClient::new(
|
||||
oauth2::ClientId::new(
|
||||
std::env::var("G_AUTH_CLIENT_ID")
|
||||
.expect("G_AUTH_CLIENT Env var to be set."),
|
||||
.expect("G_AUTH_CLIENT_ID Env var to be set."),
|
||||
),
|
||||
Some(oauth2::ClientSecret::new(
|
||||
std::env::var("G_AUTH_SECRET")
|
||||
|
||||
@@ -84,7 +84,6 @@ pub mod owner;
|
||||
#[cfg(feature = "serde")]
|
||||
mod serde;
|
||||
pub mod signal;
|
||||
mod trait_options;
|
||||
pub mod traits;
|
||||
pub mod transition;
|
||||
pub mod wrappers;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#[allow(deprecated)]
|
||||
use crate::wrappers::read::{MaybeProp, MaybeSignal};
|
||||
use crate::{
|
||||
computed::{ArcMemo, Memo},
|
||||
owner::Storage,
|
||||
@@ -9,7 +7,7 @@ use crate::{
|
||||
},
|
||||
traits::{Get, Set},
|
||||
wrappers::{
|
||||
read::{ArcSignal, Signal, SignalTypes},
|
||||
read::{ArcSignal, MaybeProp, MaybeSignal, Signal},
|
||||
write::SignalSetter,
|
||||
},
|
||||
};
|
||||
@@ -114,8 +112,7 @@ macro_rules! impl_get_fn_traits_get_arena {
|
||||
($($ty:ident),*) => {
|
||||
$(
|
||||
#[cfg(feature = "nightly")]
|
||||
#[allow(deprecated)]
|
||||
impl<T, S> FnOnce<()> for $ty<T, S> where $ty<T, S>: Get, S: Storage<T> + Storage<Option<T>> + Storage<SignalTypes<Option<T>, S>> {
|
||||
impl<T, S> FnOnce<()> for $ty<T, S> where $ty<T, S>: Get, S: Storage<T> + Storage<Option<T>> {
|
||||
type Output = <Self as Get>::Value;
|
||||
|
||||
#[inline(always)]
|
||||
@@ -125,8 +122,7 @@ macro_rules! impl_get_fn_traits_get_arena {
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[allow(deprecated)]
|
||||
impl<T, S> FnMut<()> for $ty<T, S> where $ty<T, S>: Get, S: Storage<T> + Storage<Option<T>> + Storage<SignalTypes<Option<T>, S>> {
|
||||
impl<T, S> FnMut<()> for $ty<T, S> where $ty<T, S>: Get, S: Storage<T> + Storage<Option<T>> {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
@@ -134,8 +130,7 @@ macro_rules! impl_get_fn_traits_get_arena {
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
#[allow(deprecated)]
|
||||
impl<T, S> Fn<()> for $ty<T, S> where $ty<T, S>: Get, S: Storage<T> + Storage<Option<T>> + Storage<SignalTypes<Option<T>, S>> {
|
||||
impl<T, S> Fn<()> for $ty<T, S> where $ty<T, S>: Get, S: Storage<T> + Storage<Option<T>> {
|
||||
#[inline(always)]
|
||||
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
|
||||
self.get()
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
#[allow(deprecated)]
|
||||
use crate::wrappers::read::{MaybeProp, MaybeSignal};
|
||||
use crate::{
|
||||
computed::{ArcMemo, Memo},
|
||||
owner::Storage,
|
||||
signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal},
|
||||
traits::With,
|
||||
wrappers::read::{Signal, SignalTypes},
|
||||
wrappers::read::{MaybeProp, MaybeSignal, Signal, SignalTypes},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -75,7 +73,6 @@ impl<T: Serialize + 'static, St: Storage<T>> Serialize for ArcMemo<T, St> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T, St> Serialize for MaybeSignal<T, St>
|
||||
where
|
||||
T: Clone + Send + Sync + Serialize,
|
||||
@@ -99,8 +96,15 @@ where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match &self.0 {
|
||||
None => None::<T>.serialize(serializer),
|
||||
Some(signal) => signal.with(|value| value.serialize(serializer)),
|
||||
None | Some(MaybeSignal::Static(None)) => {
|
||||
None::<T>.serialize(serializer)
|
||||
}
|
||||
Some(MaybeSignal::Static(Some(value))) => {
|
||||
value.serialize(serializer)
|
||||
}
|
||||
Some(MaybeSignal::Dynamic(signal)) => {
|
||||
signal.with(|value| value.serialize(serializer))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,7 +146,6 @@ impl<'de, T: Deserialize<'de>> Deserialize<'de> for ArcRwSignal<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<'de, T: Deserialize<'de>, St> Deserialize<'de> for MaybeSignal<T, St>
|
||||
where
|
||||
St: Storage<T>,
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
use crate::{
|
||||
traits::{
|
||||
DefinedAt, Get, GetUntracked, Read, ReadUntracked, Track, With,
|
||||
WithUntracked,
|
||||
},
|
||||
unwrap_signal,
|
||||
};
|
||||
use std::panic::Location;
|
||||
|
||||
impl<T> DefinedAt for Option<T>
|
||||
where
|
||||
T: DefinedAt,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
self.as_ref().map(DefinedAt::defined_at).unwrap_or(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Track for Option<T>
|
||||
where
|
||||
T: Track,
|
||||
{
|
||||
fn track(&self) {
|
||||
if let Some(signal) = self {
|
||||
signal.track();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An alternative [`ReadUntracked`](crate) trait that works with `Option<Readable>` types.
|
||||
pub trait ReadUntrackedOptional: Sized + DefinedAt {
|
||||
/// The guard type that will be returned, which can be dereferenced to the value.
|
||||
type Value;
|
||||
|
||||
/// Returns the guard, or `None` if the signal has already been disposed.
|
||||
#[track_caller]
|
||||
fn try_read_untracked(&self) -> Option<Self::Value>;
|
||||
|
||||
/// Returns the guard.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that has been disposed.
|
||||
#[track_caller]
|
||||
fn read_untracked(&self) -> Self::Value {
|
||||
self.try_read_untracked()
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ReadUntrackedOptional for Option<T>
|
||||
where
|
||||
Self: DefinedAt,
|
||||
T: ReadUntracked,
|
||||
{
|
||||
type Value = Option<<T as ReadUntracked>::Value>;
|
||||
|
||||
fn try_read_untracked(&self) -> Option<Self::Value> {
|
||||
Some(if let Some(signal) = self {
|
||||
Some(signal.try_read_untracked()?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// An alternative [`Read`](crate) trait that works with `Option<Readable>` types.
|
||||
pub trait ReadOptional: DefinedAt {
|
||||
/// The guard type that will be returned, which can be dereferenced to the value.
|
||||
type Value;
|
||||
|
||||
/// Subscribes to the signal, and returns the guard, or `None` if the signal has already been disposed.
|
||||
#[track_caller]
|
||||
fn try_read(&self) -> Option<Self::Value>;
|
||||
|
||||
/// Subscribes to the signal, and returns the guard.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that has been disposed.
|
||||
#[track_caller]
|
||||
fn read(&self) -> Self::Value {
|
||||
self.try_read().unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ReadOptional for Option<T>
|
||||
where
|
||||
Self: DefinedAt,
|
||||
T: Read,
|
||||
{
|
||||
type Value = Option<<T as Read>::Value>;
|
||||
|
||||
fn try_read(&self) -> Option<Self::Value> {
|
||||
Some(if let Some(readable) = self {
|
||||
Some(readable.try_read()?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// An alternative [`WithUntracked`](crate) trait that works with `Option<Withable>` types.
|
||||
pub trait WithUntrackedOptional: DefinedAt {
|
||||
/// The type of the value contained in the signal.
|
||||
type Value: ?Sized;
|
||||
|
||||
/// Applies the closure to the value, and returns the result,
|
||||
/// or `None` if the signal has already been disposed.
|
||||
#[track_caller]
|
||||
fn try_with_untracked<U>(
|
||||
&self,
|
||||
fun: impl FnOnce(Option<&Self::Value>) -> U,
|
||||
) -> Option<U>;
|
||||
|
||||
/// Applies the closure to the value, and returns the result.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that has been disposed.
|
||||
#[track_caller]
|
||||
fn with_untracked<U>(
|
||||
&self,
|
||||
fun: impl FnOnce(Option<&Self::Value>) -> U,
|
||||
) -> U {
|
||||
self.try_with_untracked(fun)
|
||||
.unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> WithUntrackedOptional for Option<T>
|
||||
where
|
||||
Self: DefinedAt,
|
||||
T: WithUntracked,
|
||||
<T as WithUntracked>::Value: Sized,
|
||||
{
|
||||
type Value = <T as WithUntracked>::Value;
|
||||
|
||||
fn try_with_untracked<U>(
|
||||
&self,
|
||||
fun: impl FnOnce(Option<&Self::Value>) -> U,
|
||||
) -> Option<U> {
|
||||
if let Some(signal) = self {
|
||||
Some(signal.try_with_untracked(|val| fun(Some(val)))?)
|
||||
} else {
|
||||
Some(fun(None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An alternative [`With`](crate) trait that works with `Option<Withable>` types.
|
||||
pub trait WithOptional: DefinedAt {
|
||||
/// The type of the value contained in the signal.
|
||||
type Value: ?Sized;
|
||||
|
||||
/// Subscribes to the signal, applies the closure to the value, and returns the result,
|
||||
/// or `None` if the signal has already been disposed.
|
||||
#[track_caller]
|
||||
fn try_with<U>(
|
||||
&self,
|
||||
fun: impl FnOnce(Option<&Self::Value>) -> U,
|
||||
) -> Option<U>;
|
||||
|
||||
/// Subscribes to the signal, applies the closure to the value, and returns the result.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if you try to access a signal that has been disposed.
|
||||
#[track_caller]
|
||||
fn with<U>(&self, fun: impl FnOnce(Option<&Self::Value>) -> U) -> U {
|
||||
self.try_with(fun).unwrap_or_else(unwrap_signal!(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> WithOptional for Option<T>
|
||||
where
|
||||
Self: DefinedAt,
|
||||
T: With,
|
||||
<T as With>::Value: Sized,
|
||||
{
|
||||
type Value = <T as With>::Value;
|
||||
|
||||
fn try_with<U>(
|
||||
&self,
|
||||
fun: impl FnOnce(Option<&Self::Value>) -> U,
|
||||
) -> Option<U> {
|
||||
if let Some(signal) = self {
|
||||
Some(signal.try_with(|val| fun(Some(val)))?)
|
||||
} else {
|
||||
Some(fun(None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> GetUntracked for Option<T>
|
||||
where
|
||||
Self: DefinedAt,
|
||||
T: GetUntracked,
|
||||
{
|
||||
type Value = Option<<T as GetUntracked>::Value>;
|
||||
|
||||
fn try_get_untracked(&self) -> Option<Self::Value> {
|
||||
Some(if let Some(signal) = self {
|
||||
Some(signal.try_get_untracked()?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Get for Option<T>
|
||||
where
|
||||
Self: DefinedAt,
|
||||
T: Get,
|
||||
{
|
||||
type Value = Option<<T as Get>::Value>;
|
||||
|
||||
fn try_get(&self) -> Option<Self::Value> {
|
||||
Some(if let Some(signal) = self {
|
||||
Some(signal.try_get()?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait to implement flatten() on Option<&Option<T>>.
|
||||
pub trait FlattenOptionRefOption {
|
||||
/// The type of the value contained in the double option.
|
||||
type Value;
|
||||
|
||||
/// Converts from `Option<&Option<T>>` to `Option<&T>`.
|
||||
fn flatten(&self) -> Option<&Self::Value>;
|
||||
}
|
||||
|
||||
impl<'a, T> FlattenOptionRefOption for Option<&'a Option<T>> {
|
||||
type Value = T;
|
||||
|
||||
fn flatten(&self) -> Option<&'a T> {
|
||||
self.map(Option::as_ref).flatten()
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,6 @@
|
||||
//! there isn't an `RwLock` so you can't wrap in a [`ReadGuard`](crate::signal::guards::ReadGuard),
|
||||
//! but you can still implement [`WithUntracked`] and [`Track`], the same traits will still be implemented.
|
||||
|
||||
pub use crate::trait_options::*;
|
||||
use crate::{
|
||||
effect::Effect,
|
||||
graph::{Observer, Source, Subscriber, ToAnySource},
|
||||
|
||||
@@ -606,33 +606,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + Sync + 'static> From<T> for ArcSignal<T, SyncStorage> {
|
||||
#[track_caller]
|
||||
fn from(value: T) -> Self {
|
||||
ArcSignal::stored(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Signal<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: T) -> Self {
|
||||
Self::stored(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Signal<T, LocalStorage>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: T) -> Self {
|
||||
Self::stored_local(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<ArcSignal<T, SyncStorage>> for Signal<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
@@ -708,34 +681,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<ArcReadSignal<T>> for Signal<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: ArcReadSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new(SignalTypes::ReadSignal(value)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<ArcReadSignal<T>> for Signal<T, LocalStorage>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: ArcReadSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new_local(SignalTypes::ReadSignal(value)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<RwSignal<T>> for Signal<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
@@ -768,38 +713,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<ArcRwSignal<T>> for Signal<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: ArcRwSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new(SignalTypes::ReadSignal(
|
||||
value.read_only(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<ArcRwSignal<T>> for Signal<T, LocalStorage>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: ArcRwSignal<T>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new_local(SignalTypes::ReadSignal(
|
||||
value.read_only(),
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Memo<T>> for Signal<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
@@ -828,74 +741,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<ArcMemo<T>> for Signal<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: ArcMemo<T>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new(SignalTypes::Memo(value)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<ArcMemo<T, LocalStorage>> for Signal<T, LocalStorage>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: ArcMemo<T, LocalStorage>) -> Self {
|
||||
Self {
|
||||
inner: ArenaItem::new_local(SignalTypes::Memo(value)),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Signal<Option<T>>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: T) -> Self {
|
||||
Signal::stored(Some(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Signal<Option<T>, LocalStorage>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: T) -> Self {
|
||||
Signal::stored_local(Some(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Signal<T>> for Signal<Option<T>>
|
||||
where
|
||||
T: Clone + Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: Signal<T>) -> Self {
|
||||
Signal::derive(move || Some(value.get()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Signal<T, LocalStorage>> for Signal<Option<T>, LocalStorage>
|
||||
where
|
||||
T: Clone + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: Signal<T, LocalStorage>) -> Self {
|
||||
Signal::derive_local(move || Some(value.get()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Signal<String> {
|
||||
#[track_caller]
|
||||
fn from(value: &str) -> Self {
|
||||
@@ -968,7 +813,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<MaybeSignal<T>> for Signal<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
@@ -982,7 +826,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<MaybeSignal<T, LocalStorage>> for Signal<T, LocalStorage>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
@@ -996,7 +839,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<MaybeSignal<T>> for Signal<Option<T>>
|
||||
where
|
||||
T: Clone + Send + Sync + 'static,
|
||||
@@ -1012,7 +854,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<MaybeSignal<T, LocalStorage>> for Signal<Option<T>, LocalStorage>
|
||||
where
|
||||
T: Clone + Send + Sync + 'static,
|
||||
@@ -1028,27 +869,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<MaybeProp<T>> for Option<Signal<Option<T>>>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: MaybeProp<T>) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<MaybeProp<T, LocalStorage>>
|
||||
for Option<Signal<Option<T>, LocalStorage>>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
#[track_caller]
|
||||
fn from(value: MaybeProp<T, LocalStorage>) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper for a value that is *either* `T` or [`Signal<T>`].
|
||||
///
|
||||
/// This allows you to create APIs that take either a reactive or a non-reactive value
|
||||
@@ -1077,12 +897,6 @@ pub mod read {
|
||||
/// assert_eq!(above_3(&memoized_double_count.into()), true);
|
||||
/// ```
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[deprecated(
|
||||
since = "0.7.0-rc1",
|
||||
note = "`MaybeSignal<T>` is deprecated in favour of `Signal<T>` which \
|
||||
is `Copy`, now has a more efficient From<T> implementation \
|
||||
and other benefits in 0.7."
|
||||
)]
|
||||
pub enum MaybeSignal<T, S = SyncStorage>
|
||||
where
|
||||
T: 'static,
|
||||
@@ -1094,7 +908,6 @@ pub mod read {
|
||||
Dynamic(Signal<T, S>),
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T: Clone, S> Clone for MaybeSignal<T, S>
|
||||
where
|
||||
S: Storage<T>,
|
||||
@@ -1107,10 +920,8 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T: Copy, S> Copy for MaybeSignal<T, S> where S: Storage<T> {}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T: Default, S> Default for MaybeSignal<T, S>
|
||||
where
|
||||
S: Storage<T>,
|
||||
@@ -1120,7 +931,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T, S> DefinedAt for MaybeSignal<T, S>
|
||||
where
|
||||
S: Storage<T>,
|
||||
@@ -1132,7 +942,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T, S> Track for MaybeSignal<T, S>
|
||||
where
|
||||
S: Storage<T> + Storage<SignalTypes<T, S>>,
|
||||
@@ -1145,7 +954,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T, S> ReadUntracked for MaybeSignal<T, S>
|
||||
where
|
||||
T: Clone,
|
||||
@@ -1170,7 +978,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> MaybeSignal<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
@@ -1184,7 +991,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> MaybeSignal<T, LocalStorage> {
|
||||
/// Wraps a derived signal, i.e., any computation that accesses one or more
|
||||
/// reactive signals.
|
||||
@@ -1193,7 +999,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<T> for MaybeSignal<T, SyncStorage>
|
||||
where
|
||||
SyncStorage: Storage<T>,
|
||||
@@ -1203,7 +1008,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> FromLocal<T> for MaybeSignal<T, LocalStorage>
|
||||
where
|
||||
LocalStorage: Storage<T>,
|
||||
@@ -1213,7 +1017,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<ReadSignal<T>> for MaybeSignal<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
@@ -1223,14 +1026,12 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<ReadSignal<T, LocalStorage>> for MaybeSignal<T, LocalStorage> {
|
||||
fn from(value: ReadSignal<T, LocalStorage>) -> Self {
|
||||
Self::Dynamic(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<RwSignal<T>> for MaybeSignal<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
@@ -1240,14 +1041,12 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<RwSignal<T, LocalStorage>> for MaybeSignal<T, LocalStorage> {
|
||||
fn from(value: RwSignal<T, LocalStorage>) -> Self {
|
||||
Self::Dynamic(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<Memo<T>> for MaybeSignal<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
@@ -1257,14 +1056,12 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<Memo<T, LocalStorage>> for MaybeSignal<T, LocalStorage> {
|
||||
fn from(value: Memo<T, LocalStorage>) -> Self {
|
||||
Self::Dynamic(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<ArcReadSignal<T>> for MaybeSignal<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
@@ -1274,14 +1071,12 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> FromLocal<ArcReadSignal<T>> for MaybeSignal<T, LocalStorage> {
|
||||
fn from_local(value: ArcReadSignal<T>) -> Self {
|
||||
ReadSignal::from_local(value).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<ArcRwSignal<T>> for MaybeSignal<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
@@ -1291,7 +1086,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> FromLocal<ArcRwSignal<T>> for MaybeSignal<T, LocalStorage>
|
||||
where
|
||||
T: 'static,
|
||||
@@ -1301,7 +1095,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<ArcMemo<T>> for MaybeSignal<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
@@ -1311,14 +1104,12 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> FromLocal<ArcMemo<T, LocalStorage>> for MaybeSignal<T, LocalStorage> {
|
||||
fn from_local(value: ArcMemo<T, LocalStorage>) -> Self {
|
||||
Memo::from_local(value).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T, S> From<Signal<T, S>> for MaybeSignal<T, S>
|
||||
where
|
||||
S: Storage<T>,
|
||||
@@ -1328,7 +1119,6 @@ pub mod read {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<S> From<&str> for MaybeSignal<String, S>
|
||||
where
|
||||
S: Storage<String> + Storage<Arc<RwLock<String>>>,
|
||||
@@ -1372,28 +1162,25 @@ pub mod read {
|
||||
/// ```
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct MaybeProp<T: 'static, S = SyncStorage>(
|
||||
pub(crate) Option<Signal<Option<T>, S>>,
|
||||
pub(crate) Option<MaybeSignal<Option<T>, S>>,
|
||||
)
|
||||
where
|
||||
S: Storage<Option<T>> + Storage<SignalTypes<Option<T>, S>>;
|
||||
S: Storage<Option<T>>;
|
||||
|
||||
impl<T, S> Clone for MaybeProp<T, S>
|
||||
impl<T: Clone, S> Clone for MaybeProp<T, S>
|
||||
where
|
||||
S: Storage<Option<T>> + Storage<SignalTypes<Option<T>, S>>,
|
||||
S: Storage<Option<T>>,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> Copy for MaybeProp<T, S> where
|
||||
S: Storage<Option<T>> + Storage<SignalTypes<Option<T>, S>>
|
||||
{
|
||||
}
|
||||
impl<T: Copy, S> Copy for MaybeProp<T, S> where S: Storage<Option<T>> {}
|
||||
|
||||
impl<T, S> Default for MaybeProp<T, S>
|
||||
where
|
||||
S: Storage<Option<T>> + Storage<SignalTypes<Option<T>, S>>,
|
||||
S: Storage<Option<T>>,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self(None)
|
||||
@@ -1402,7 +1189,7 @@ pub mod read {
|
||||
|
||||
impl<T, S> DefinedAt for MaybeProp<T, S>
|
||||
where
|
||||
S: Storage<Option<T>> + Storage<SignalTypes<Option<T>, S>>,
|
||||
S: Storage<Option<T>>,
|
||||
{
|
||||
fn defined_at(&self) -> Option<&'static Location<'static>> {
|
||||
// TODO this can be improved by adding a defined_at field
|
||||
@@ -1425,7 +1212,7 @@ pub mod read {
|
||||
impl<T, S> ReadUntracked for MaybeProp<T, S>
|
||||
where
|
||||
T: Clone,
|
||||
S: Storage<Option<T>> + Storage<SignalTypes<Option<T>, S>>,
|
||||
S: Storage<SignalTypes<Option<T>, S>> + Storage<Option<T>>,
|
||||
{
|
||||
type Value = ReadGuard<Option<T>, SignalReadGuard<Option<T>, S>>;
|
||||
|
||||
@@ -1453,49 +1240,43 @@ pub mod read {
|
||||
pub fn derive(
|
||||
derived_signal: impl Fn() -> Option<T> + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
Self(Some(Signal::derive(derived_signal)))
|
||||
Self(Some(MaybeSignal::derive(derived_signal)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for MaybeProp<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
SyncStorage: Storage<Option<T>>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self(Some(Signal::stored(Some(value))))
|
||||
Self(Some(MaybeSignal::from(Some(value))))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Option<T>> for MaybeProp<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
SyncStorage: Storage<Option<T>>,
|
||||
{
|
||||
fn from(value: Option<T>) -> Self {
|
||||
Self(Some(Signal::stored(value)))
|
||||
Self(Some(MaybeSignal::from(value)))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<MaybeSignal<Option<T>>> for MaybeProp<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
SyncStorage: Storage<Option<T>>,
|
||||
{
|
||||
fn from(value: MaybeSignal<Option<T>>) -> Self {
|
||||
Self(Some(value.into()))
|
||||
Self(Some(value))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<Option<MaybeSignal<Option<T>>>> for MaybeProp<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
SyncStorage: Storage<Option<T>>,
|
||||
{
|
||||
fn from(value: Option<MaybeSignal<Option<T>>>) -> Self {
|
||||
Self(value.map(Into::into))
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1528,11 +1309,10 @@ pub mod read {
|
||||
|
||||
impl<T> From<Signal<Option<T>>> for MaybeProp<T>
|
||||
where
|
||||
T: Send + Sync,
|
||||
SyncStorage: Storage<Option<T>>,
|
||||
{
|
||||
fn from(value: Signal<Option<T>>) -> Self {
|
||||
Self(Some(value))
|
||||
Self(Some(value.into()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1541,7 +1321,7 @@ pub mod read {
|
||||
T: Send + Sync + Clone,
|
||||
{
|
||||
fn from(value: ReadSignal<T>) -> Self {
|
||||
Self(Some(Signal::derive(move || Some(value.get()))))
|
||||
Self(Some(MaybeSignal::derive(move || Some(value.get()))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1550,7 +1330,7 @@ pub mod read {
|
||||
T: Send + Sync + Clone,
|
||||
{
|
||||
fn from(value: RwSignal<T>) -> Self {
|
||||
Self(Some(Signal::derive(move || Some(value.get()))))
|
||||
Self(Some(MaybeSignal::derive(move || Some(value.get()))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1559,7 +1339,7 @@ pub mod read {
|
||||
T: Send + Sync + Clone,
|
||||
{
|
||||
fn from(value: Memo<T>) -> Self {
|
||||
Self(Some(Signal::derive(move || Some(value.get()))))
|
||||
Self(Some(MaybeSignal::derive(move || Some(value.get()))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1568,13 +1348,13 @@ pub mod read {
|
||||
T: Send + Sync + Clone,
|
||||
{
|
||||
fn from(value: Signal<T>) -> Self {
|
||||
Self(Some(Signal::derive(move || Some(value.get()))))
|
||||
Self(Some(MaybeSignal::derive(move || Some(value.get()))))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for MaybeProp<String> {
|
||||
fn from(value: &str) -> Self {
|
||||
Self(Some(Signal::from(Some(value.to_string()))))
|
||||
Self(Some(MaybeSignal::from(Some(value.to_string()))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1584,41 +1364,35 @@ pub mod read {
|
||||
pub fn derive_local(
|
||||
derived_signal: impl Fn() -> Option<T> + 'static,
|
||||
) -> Self {
|
||||
Self(Some(Signal::derive_local(derived_signal)))
|
||||
Self(Some(MaybeSignal::derive_local(derived_signal)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromLocal<T> for MaybeProp<T, LocalStorage> {
|
||||
fn from_local(value: T) -> Self {
|
||||
Self(Some(Signal::stored_local(Some(value))))
|
||||
Self(Some(MaybeSignal::from_local(Some(value))))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromLocal<Option<T>> for MaybeProp<T, LocalStorage> {
|
||||
fn from_local(value: Option<T>) -> Self {
|
||||
Self(Some(Signal::stored_local(value)))
|
||||
Self(Some(MaybeSignal::from_local(value)))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<MaybeSignal<Option<T>, LocalStorage>>
|
||||
for MaybeProp<T, LocalStorage>
|
||||
where
|
||||
T: Send + Sync,
|
||||
{
|
||||
fn from(value: MaybeSignal<Option<T>, LocalStorage>) -> Self {
|
||||
Self(Some(value.into()))
|
||||
Self(Some(value))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T> From<Option<MaybeSignal<Option<T>, LocalStorage>>>
|
||||
for MaybeProp<T, LocalStorage>
|
||||
where
|
||||
T: Send + Sync,
|
||||
{
|
||||
fn from(value: Option<MaybeSignal<Option<T>, LocalStorage>>) -> Self {
|
||||
Self(value.map(Into::into))
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1651,7 +1425,7 @@ pub mod read {
|
||||
|
||||
impl<T> From<Signal<Option<T>, LocalStorage>> for MaybeProp<T, LocalStorage> {
|
||||
fn from(value: Signal<Option<T>, LocalStorage>) -> Self {
|
||||
Self(Some(value))
|
||||
Self(Some(value.into()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1660,7 +1434,7 @@ pub mod read {
|
||||
T: Send + Sync + Clone,
|
||||
{
|
||||
fn from(value: ReadSignal<T, LocalStorage>) -> Self {
|
||||
Self(Some(Signal::derive_local(move || Some(value.get()))))
|
||||
Self(Some(MaybeSignal::derive_local(move || Some(value.get()))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1669,7 +1443,7 @@ pub mod read {
|
||||
T: Send + Sync + Clone,
|
||||
{
|
||||
fn from(value: RwSignal<T, LocalStorage>) -> Self {
|
||||
Self(Some(Signal::derive_local(move || Some(value.get()))))
|
||||
Self(Some(MaybeSignal::derive_local(move || Some(value.get()))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1678,7 +1452,7 @@ pub mod read {
|
||||
T: Send + Sync + Clone,
|
||||
{
|
||||
fn from(value: Memo<T, LocalStorage>) -> Self {
|
||||
Self(Some(Signal::derive_local(move || Some(value.get()))))
|
||||
Self(Some(MaybeSignal::derive_local(move || Some(value.get()))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1687,13 +1461,13 @@ pub mod read {
|
||||
T: Send + Sync + Clone,
|
||||
{
|
||||
fn from(value: Signal<T, LocalStorage>) -> Self {
|
||||
Self(Some(Signal::derive_local(move || Some(value.get()))))
|
||||
Self(Some(MaybeSignal::derive_local(move || Some(value.get()))))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for MaybeProp<String, LocalStorage> {
|
||||
fn from(value: &str) -> Self {
|
||||
Self(Some(Signal::stored_local(Some(value.to_string()))))
|
||||
Self(Some(MaybeSignal::from_local(Some(value.to_string()))))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// Reference-counted access to a single field of type `T`.
|
||||
///
|
||||
/// This can be used to erase the chain of field-accessors, to make it easier to pass this into
|
||||
/// another component or function without needing to specify the full type signature.
|
||||
pub struct ArcField<T>
|
||||
where
|
||||
T: 'static,
|
||||
|
||||
@@ -11,6 +11,10 @@ use reactive_graph::{
|
||||
};
|
||||
use std::{fmt::Debug, hash::Hash, ops::IndexMut, panic::Location};
|
||||
|
||||
/// Wraps access to a single field of type `T`.
|
||||
///
|
||||
/// This can be used to erase the chain of field-accessors, to make it easier to pass this into
|
||||
/// another component or function without needing to specify the full type signature.
|
||||
pub struct Field<T, S = SyncStorage>
|
||||
where
|
||||
T: 'static,
|
||||
|
||||
@@ -20,6 +20,7 @@ use std::{
|
||||
panic::Location,
|
||||
};
|
||||
|
||||
/// Provides access to the data at some index in another collection.
|
||||
#[derive(Debug)]
|
||||
pub struct AtIndex<Inner, Prev> {
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -47,6 +48,7 @@ where
|
||||
impl<Inner, Prev> Copy for AtIndex<Inner, Prev> where Inner: Copy {}
|
||||
|
||||
impl<Inner, Prev> AtIndex<Inner, Prev> {
|
||||
/// Creates a new accessor for the inner collection at the given index.
|
||||
#[track_caller]
|
||||
pub fn new(inner: Inner, index: usize) -> Self {
|
||||
Self {
|
||||
@@ -191,12 +193,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides unkeyed reactive access to the fields of some collection.
|
||||
pub trait StoreFieldIterator<Prev>
|
||||
where
|
||||
Self: StoreField<Value = Prev>,
|
||||
{
|
||||
/// Reactive access to the value at some index.
|
||||
fn at_unkeyed(self, index: usize) -> AtIndex<Self, Prev>;
|
||||
|
||||
/// An iterator over the values in the collection.
|
||||
fn iter_unkeyed(self) -> StoreFieldIter<Self, Prev>;
|
||||
}
|
||||
|
||||
@@ -231,6 +236,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the values in a collection, as reactive fields.
|
||||
pub struct StoreFieldIter<Inner, Prev> {
|
||||
inner: Inner,
|
||||
idx: usize,
|
||||
|
||||
@@ -22,6 +22,7 @@ use std::{
|
||||
panic::Location,
|
||||
};
|
||||
|
||||
/// Provides access to a subfield that contains some kind of keyed collection.
|
||||
#[derive(Debug)]
|
||||
pub struct KeyedSubfield<Inner, Prev, K, T>
|
||||
where
|
||||
@@ -65,6 +66,7 @@ impl<Inner, Prev, K, T> KeyedSubfield<Inner, Prev, K, T>
|
||||
where
|
||||
for<'a> &'a T: IntoIterator,
|
||||
{
|
||||
/// Creates a keyed subfield of the inner data type with the given key function.
|
||||
#[track_caller]
|
||||
pub fn new(
|
||||
inner: Inner,
|
||||
@@ -154,6 +156,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Gives keyed write access to a value in some collection.
|
||||
pub struct KeyedSubfieldWriteGuard<Inner, Prev, K, T, Guard>
|
||||
where
|
||||
KeyedSubfield<Inner, Prev, K, T>: Clone,
|
||||
@@ -347,6 +350,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Gives access to the value in a collection based on some key.
|
||||
#[derive(Debug)]
|
||||
pub struct AtKeyed<Inner, Prev, K, T>
|
||||
where
|
||||
@@ -386,6 +390,7 @@ impl<Inner, Prev, K, T> AtKeyed<Inner, Prev, K, T>
|
||||
where
|
||||
for<'a> &'a T: IntoIterator,
|
||||
{
|
||||
/// Provides access to the item in the inner collection at this key.
|
||||
#[track_caller]
|
||||
pub fn new(inner: KeyedSubfield<Inner, Prev, K, T>, key: K) -> Self {
|
||||
Self {
|
||||
@@ -610,6 +615,7 @@ where
|
||||
Prev: 'static,
|
||||
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
|
||||
{
|
||||
/// Generates a new set of keys and registers those keys with the parent store.
|
||||
pub fn update_keys(&self) {
|
||||
let inner_path = self.path().into_iter().collect();
|
||||
let keys = self
|
||||
@@ -660,6 +666,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over a [`KeyedSubfield`].
|
||||
pub struct StoreFieldKeyedIter<Inner, Prev, K, T>
|
||||
where
|
||||
for<'a> &'a T: IntoIterator,
|
||||
|
||||
@@ -1,3 +1,111 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! Stores are a primitive for creating deeply-nested reactive state, based on [`reactive_graph`].
|
||||
//!
|
||||
//! Reactive signals allow you to define atomic units of reactive state. However, signals are
|
||||
//! imperfect as a mechanism for tracking reactive change in structs or collections, because
|
||||
//! they do not allow you to track access to individual struct fields or individual items in a
|
||||
//! collection, rather than the struct as a whole or the collection as a whole. Reactivity for
|
||||
//! individual fields can be achieved by creating a struct of signals, but this has issues; it
|
||||
//! means that a struct is no longer a plain data structure, but requires wrappers on each field.
|
||||
//!
|
||||
//! Stores attempt to solve this problem by allowing arbitrarily-deep access to the fields of some
|
||||
//! data structure, while still maintaining fine-grained reactivity.
|
||||
//!
|
||||
//! The [`Store`](macro@Store) macro adds getters and setters for the fields of a struct. Call those getters or
|
||||
//! setters on a reactive [`Store`](struct@Store) or [`ArcStore`], or to a subfield, gives you
|
||||
//! access to a reactive subfield. This value of this field can be accessed via the ordinary signal
|
||||
//! traits (`Get`, `Set`, and so on).
|
||||
//!
|
||||
//! The [`Patch`](macro@Patch) macro allows you to annotate a struct such that stores and fields have a
|
||||
//! [`.patch()`](Patch::patch) method, which allows you to provide an entirely new value, but only
|
||||
//! notify fields that have changed.
|
||||
//!
|
||||
//! Updating a field will notify its parents and children, but not its siblings.
|
||||
//!
|
||||
//! Stores can therefore
|
||||
//! 1) work with plain Rust data types, and
|
||||
//! 2) provide reactive access to individual fields
|
||||
//!
|
||||
//! ### Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use reactive_graph::{
|
||||
//! effect::Effect,
|
||||
//! traits::{Read, Write},
|
||||
//! };
|
||||
//! use reactive_stores::{Patch, Store};
|
||||
//!
|
||||
//! #[derive(Debug, Store, Patch, Default)]
|
||||
//! struct Todos {
|
||||
//! user: String,
|
||||
//! todos: Vec<Todo>,
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Debug, Store, Patch, Default)]
|
||||
//! struct Todo {
|
||||
//! label: String,
|
||||
//! completed: bool,
|
||||
//! }
|
||||
//!
|
||||
//! let store = Store::new(Todos {
|
||||
//! user: "Alice".to_string(),
|
||||
//! todos: Vec::new(),
|
||||
//! });
|
||||
//!
|
||||
//! # if false { // don't run effect in doctests
|
||||
//! Effect::new(move |_| {
|
||||
//! // you can access individual store withs field a getter
|
||||
//! println!("todos: {:?}", &*store.todos().read());
|
||||
//! });
|
||||
//! # }
|
||||
//!
|
||||
//! // won't notify the effect that listen to `todos`
|
||||
//! store.todos().write().push(Todo {
|
||||
//! label: "Test".to_string(),
|
||||
//! completed: false,
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! ### Implementation Notes
|
||||
//!
|
||||
//! Every struct field can be understood as an index. For example, given the following definition
|
||||
//! ```rust
|
||||
//! # use reactive_stores::{Store, Patch};
|
||||
//! #[derive(Debug, Store, Patch, Default)]
|
||||
//! struct Name {
|
||||
//! first: String,
|
||||
//! last: String,
|
||||
//! }
|
||||
//! ```
|
||||
//! We can think of `first` as `0` and `last` as `1`. This means that any deeply-nested field of a
|
||||
//! struct can be described as a path of indices. So, for example:
|
||||
//! ```rust
|
||||
//! # use reactive_stores::{Store, Patch};
|
||||
//! #[derive(Debug, Store, Patch, Default)]
|
||||
//! struct User {
|
||||
//! user: Name,
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Debug, Store, Patch, Default)]
|
||||
//! struct Name {
|
||||
//! first: String,
|
||||
//! last: String,
|
||||
//! }
|
||||
//! ```
|
||||
//! Here, given a `User`, `first` can be understood as [`0`, `0`] and `last` is [`0`, `1`].
|
||||
//!
|
||||
//! This means we can implement a store as the combination of two things:
|
||||
//! 1) An `Arc<RwLock<T>>` that holds the actual value
|
||||
//! 2) A map from field paths to reactive "triggers," which are signals that have no value but
|
||||
//! track reactivity
|
||||
//!
|
||||
//! Accessing a field via its getters returns an iterator-like data structure that describes how to
|
||||
//! get to that subfield. Calling `.read()` returns a guard that dereferences to the value of that
|
||||
//! field in the signal inner `Arc<RwLock<_>>`, and tracks the trigger that corresponds with its
|
||||
//! path; calling `.write()` returns a writeable guard, and notifies that same trigger.
|
||||
|
||||
use or_poisoned::OrPoisoned;
|
||||
use reactive_graph::{
|
||||
owner::{ArenaItem, LocalStorage, Storage, SyncStorage},
|
||||
@@ -10,7 +118,7 @@ use reactive_graph::{
|
||||
Write,
|
||||
},
|
||||
};
|
||||
pub use reactive_stores_macro::*;
|
||||
pub use reactive_stores_macro::{Patch, Store};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{
|
||||
any::Any,
|
||||
@@ -45,13 +153,15 @@ pub use subfield::Subfield;
|
||||
#[derive(Debug, Default)]
|
||||
struct TriggerMap(FxHashMap<StorePath, StoreFieldTrigger>);
|
||||
|
||||
/// The reactive trigger that can be used to track updates to a store field.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct StoreFieldTrigger {
|
||||
pub this: ArcTrigger,
|
||||
pub children: ArcTrigger,
|
||||
pub(crate) this: ArcTrigger,
|
||||
pub(crate) children: ArcTrigger,
|
||||
}
|
||||
|
||||
impl StoreFieldTrigger {
|
||||
/// Creates a new trigger.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
@@ -74,7 +184,8 @@ impl TriggerMap {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FieldKeys<K> {
|
||||
/// Manages the keys for a keyed field, including the ability to remove and reuse keys.
|
||||
pub(crate) struct FieldKeys<K> {
|
||||
spare_keys: Vec<StorePathSegment>,
|
||||
current_key: usize,
|
||||
keys: FxHashMap<K, (StorePathSegment, usize)>,
|
||||
@@ -84,6 +195,7 @@ impl<K> FieldKeys<K>
|
||||
where
|
||||
K: Debug + Hash + PartialEq + Eq,
|
||||
{
|
||||
/// Creates a new set of keys.
|
||||
pub fn new(from_keys: Vec<K>) -> Self {
|
||||
let mut keys = FxHashMap::with_capacity_and_hasher(
|
||||
from_keys.len(),
|
||||
@@ -106,7 +218,7 @@ impl<K> FieldKeys<K>
|
||||
where
|
||||
K: Hash + PartialEq + Eq,
|
||||
{
|
||||
pub fn get(&self, key: &K) -> Option<(StorePathSegment, usize)> {
|
||||
fn get(&self, key: &K) -> Option<(StorePathSegment, usize)> {
|
||||
self.keys.get(key).copied()
|
||||
}
|
||||
|
||||
@@ -117,7 +229,7 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update(&mut self, iter: impl IntoIterator<Item = K>) {
|
||||
fn update(&mut self, iter: impl IntoIterator<Item = K>) {
|
||||
let new_keys = iter
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
@@ -159,11 +271,12 @@ impl<K> Default for FieldKeys<K> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A map of the keys for a keyed subfield.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct KeyMap(Arc<RwLock<HashMap<StorePath, Box<dyn Any + Send + Sync>>>>);
|
||||
|
||||
impl KeyMap {
|
||||
pub fn with_field_keys<K, T>(
|
||||
fn with_field_keys<K, T>(
|
||||
&self,
|
||||
path: StorePath,
|
||||
fun: impl FnOnce(&mut FieldKeys<K>) -> T,
|
||||
@@ -195,6 +308,12 @@ impl KeyMap {
|
||||
}
|
||||
}
|
||||
|
||||
/// A reference-counted container for a reactive store.
|
||||
///
|
||||
/// The type `T` should be a struct that has been annotated with `#[derive(Store)]`.
|
||||
///
|
||||
/// This adds a getter method for each field to `Store<T>`, which allow accessing reactive versions
|
||||
/// of each individual field of the struct.
|
||||
pub struct ArcStore<T> {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
@@ -204,6 +323,7 @@ pub struct ArcStore<T> {
|
||||
}
|
||||
|
||||
impl<T> ArcStore<T> {
|
||||
/// Creates a new store from the initial value.
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -303,6 +423,15 @@ impl<T: 'static> Notify for ArcStore<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// An arena-allocated container for a reactive store.
|
||||
///
|
||||
/// The type `T` should be a struct that has been annotated with `#[derive(Store)]`.
|
||||
///
|
||||
/// This adds a getter method for each field to `Store<T>`, which allow accessing reactive versions
|
||||
/// of each individual field of the struct.
|
||||
///
|
||||
/// This follows the same ownership rules as arena-allocated types like
|
||||
/// [`RwSignal`](reactive_graph::signal::RwSignal).
|
||||
pub struct Store<T, S = SyncStorage> {
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: &'static Location<'static>,
|
||||
@@ -313,6 +442,7 @@ impl<T> Store<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
/// Creates a new store with the initial value.
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -326,6 +456,9 @@ impl<T> Store<T, LocalStorage>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
/// Creates a new store for a type that is `!Send`.
|
||||
///
|
||||
/// This pins the value to the current thread. Accessing it from any other thread will panic.
|
||||
pub fn new_local(value: T) -> Self {
|
||||
Self {
|
||||
#[cfg(debug_assertions)]
|
||||
|
||||
@@ -2,14 +2,23 @@ use crate::{StoreField, Subfield};
|
||||
use reactive_graph::traits::Read;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Extends optional store fields, with the ability to unwrap or map over them.
|
||||
pub trait OptionStoreExt
|
||||
where
|
||||
Self: StoreField<Value = Option<Self::Output>>,
|
||||
{
|
||||
/// The inner type of the `Option<_>` this field holds.
|
||||
type Output;
|
||||
|
||||
/// Provides access to the inner value, as a subfield, unwrapping the outer value.
|
||||
fn unwrap(self) -> Subfield<Self, Option<Self::Output>, Self::Output>;
|
||||
|
||||
/// Reactively maps over the field.
|
||||
///
|
||||
/// This returns `None` if the subfield is currently `None`,
|
||||
/// and a new store subfield with the inner value if it is `Some`. This can be used in some
|
||||
/// other reactive context, which will cause it to re-run if the field toggles betwen `None`
|
||||
/// and `Some(_)`.
|
||||
fn map<U>(
|
||||
self,
|
||||
map_fn: impl FnOnce(Subfield<Self, Option<Self::Output>, Self::Output>) -> U,
|
||||
|
||||
@@ -13,9 +13,12 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// Allows updating a store or field in place with a new value.
|
||||
pub trait Patch {
|
||||
/// The type of the new value.
|
||||
type Value;
|
||||
|
||||
/// Patches a store or field with a new value, only notifying fields that have changed.
|
||||
fn patch(&self, new: Self::Value);
|
||||
}
|
||||
|
||||
@@ -42,6 +45,7 @@ where
|
||||
|
||||
/// Allows patching a store field with some new value.
|
||||
pub trait PatchField {
|
||||
/// Patches the field with some new value, only notifying if the value has changed.
|
||||
fn patch_field(
|
||||
&mut self,
|
||||
new: Self,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/// The path of a field within some store.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||
pub struct StorePath(Vec<StorePathSegment>);
|
||||
|
||||
@@ -17,14 +18,17 @@ impl From<Vec<StorePathSegment>> for StorePath {
|
||||
}
|
||||
|
||||
impl StorePath {
|
||||
/// Adds a new segment to the path.
|
||||
pub fn push(&mut self, segment: impl Into<StorePathSegment>) {
|
||||
self.0.push(segment.into());
|
||||
}
|
||||
|
||||
/// Removes a segment from the path and returns it.
|
||||
pub fn pop(&mut self) -> Option<StorePathSegment> {
|
||||
self.0.pop()
|
||||
}
|
||||
|
||||
/// Updates the last segment in the place in place.
|
||||
pub fn replace_last(&mut self, segment: impl Into<StorePathSegment>) {
|
||||
if let Some(last) = self.0.last_mut() {
|
||||
*last = segment.into();
|
||||
@@ -32,6 +36,7 @@ impl StorePath {
|
||||
}
|
||||
}
|
||||
|
||||
/// One segment of a [`StorePath`].
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct StorePathSegment(pub(crate) usize);
|
||||
|
||||
|
||||
@@ -14,15 +14,22 @@ use reactive_graph::{
|
||||
};
|
||||
use std::{iter, ops::Deref, sync::Arc};
|
||||
|
||||
/// Describes a type that can be accessed as a reactive store field.
|
||||
pub trait StoreField: Sized {
|
||||
/// The value this field contains.
|
||||
type Value;
|
||||
/// A read guard to access this field.
|
||||
type Reader: Deref<Target = Self::Value>;
|
||||
/// A write guard to update this field.
|
||||
type Writer: UntrackableGuard<Target = Self::Value>;
|
||||
|
||||
/// Returns the trigger that tracks access and updates for this field.
|
||||
fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger;
|
||||
|
||||
/// The path of this field (see [`StorePath`]).
|
||||
fn path(&self) -> impl IntoIterator<Item = StorePathSegment>;
|
||||
|
||||
/// Reactively tracks this field.
|
||||
fn track_field(&self) {
|
||||
let path = self.path().into_iter().collect();
|
||||
let trigger = self.get_trigger(path);
|
||||
@@ -30,10 +37,13 @@ pub trait StoreField: Sized {
|
||||
trigger.children.track();
|
||||
}
|
||||
|
||||
/// Returns a read guard to access this field.
|
||||
fn reader(&self) -> Option<Self::Reader>;
|
||||
|
||||
/// Returns a write guard to update this field.
|
||||
fn writer(&self) -> Option<Self::Writer>;
|
||||
|
||||
/// The keys for this field, if it is a keyed field.
|
||||
fn keys(&self) -> Option<KeyMap>;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ use reactive_graph::{
|
||||
};
|
||||
use std::{iter, marker::PhantomData, ops::DerefMut, panic::Location};
|
||||
|
||||
/// Accesses a single field of a reactive structure.
|
||||
#[derive(Debug)]
|
||||
pub struct Subfield<Inner, Prev, T> {
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -46,6 +47,7 @@ where
|
||||
impl<Inner, Prev, T> Copy for Subfield<Inner, Prev, T> where Inner: Copy {}
|
||||
|
||||
impl<Inner, Prev, T> Subfield<Inner, Prev, T> {
|
||||
/// Creates an accessor for a single field of the inner structure.
|
||||
#[track_caller]
|
||||
pub fn new(
|
||||
inner: Inner,
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
navigate::NavigateOptions,
|
||||
nested_router::NestedRoutesView,
|
||||
resolve_path::resolve_path,
|
||||
ChooseView, MatchNestedRoutes, NestedRoute, Routes, SsrMode,
|
||||
ChooseView, MatchNestedRoutes, NestedRoute, RouteDefs, SsrMode,
|
||||
};
|
||||
use either_of::Either;
|
||||
use leptos::prelude::*;
|
||||
@@ -30,10 +30,13 @@ use std::{
|
||||
};
|
||||
use tachys::view::any_view::AnyView;
|
||||
|
||||
/// A wrapper that allows passing route definitions as children to a component like [`Routes`],
|
||||
/// [`FlatRoutes`], [`ParentRoute`], or [`ProtectedParentRoute`].
|
||||
#[derive(Debug)]
|
||||
pub struct RouteChildren<Children>(Children);
|
||||
|
||||
impl<Children> RouteChildren<Children> {
|
||||
/// Extracts the inner route definition.
|
||||
pub fn into_inner(self) -> Children {
|
||||
self.0
|
||||
}
|
||||
@@ -211,30 +214,15 @@ impl Debug for RouterContext {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#[component]
|
||||
pub fn FlatRouter<Children, FallbackFn, Fallback>(
|
||||
#[prop(optional, into)] base: Option<Cow<'static, str>>,
|
||||
fallback: FallbackFn,
|
||||
children: RouteChildren<Children>,
|
||||
) -> FlatRouter<Dom, BrowserUrl, Children, FallbackFn>
|
||||
where
|
||||
FallbackFn: Fn() -> Fallback,
|
||||
{
|
||||
let children = Routes::new(children.into_inner());
|
||||
if let Some(base) = base {
|
||||
FlatRouter::new_with_base(base, children, fallback)
|
||||
} else {
|
||||
FlatRouter::new(children, fallback)
|
||||
}
|
||||
}*/
|
||||
|
||||
#[component(transparent)]
|
||||
pub fn Routes<Defs, FallbackFn, Fallback>(
|
||||
/// A function that returns the view that should be shown if no route is matched.
|
||||
fallback: FallbackFn,
|
||||
/// Whether to use the View Transition API during navigation.
|
||||
#[prop(optional)]
|
||||
transition: bool,
|
||||
/// The route definitions. This should consist of one or more [`ParentRoute`] or [`Route`]
|
||||
/// components.
|
||||
children: RouteChildren<Defs>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
@@ -255,7 +243,7 @@ where
|
||||
base.upgrade_inplace();
|
||||
base
|
||||
});
|
||||
let routes = Routes::new_with_base(
|
||||
let routes = RouteDefs::new_with_base(
|
||||
children.into_inner(),
|
||||
base.clone().unwrap_or_default(),
|
||||
);
|
||||
@@ -281,10 +269,13 @@ where
|
||||
|
||||
#[component(transparent)]
|
||||
pub fn FlatRoutes<Defs, FallbackFn, Fallback>(
|
||||
/// A function that returns the view that should be shown if no route is matched.
|
||||
fallback: FallbackFn,
|
||||
/// Whether to use the View Transition API during navigation.
|
||||
#[prop(optional)]
|
||||
transition: bool,
|
||||
/// The route definitions. This should consist of one or more [`ParentRoute`] or [`Route`]
|
||||
/// components.
|
||||
children: RouteChildren<Defs>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
@@ -308,7 +299,7 @@ where
|
||||
base.upgrade_inplace();
|
||||
base
|
||||
});
|
||||
let routes = Routes::new_with_base(
|
||||
let routes = RouteDefs::new_with_base(
|
||||
children.into_inner(),
|
||||
base.clone().unwrap_or_default(),
|
||||
);
|
||||
@@ -333,11 +324,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes a portion of the nested layout of the app, specifying the route it should match
|
||||
/// and the element it should display.
|
||||
#[component(transparent)]
|
||||
pub fn Route<Segments, View>(
|
||||
/// The path fragment that this route should match. This can be created using the [`path`]
|
||||
/// macro, or path segments ([`StaticSegment`], [`ParamSegment`], [`WildcardSegment`], and
|
||||
/// [`OptionalParamSegment`]).
|
||||
path: Segments,
|
||||
/// The view for this route.
|
||||
view: View,
|
||||
#[prop(optional)] ssr: SsrMode,
|
||||
/// The mode that this route prefers during server-side rendering.
|
||||
/// Defaults to out-of-order streaming.
|
||||
#[prop(optional)]
|
||||
ssr: SsrMode,
|
||||
) -> NestedRoute<Segments, (), (), View>
|
||||
where
|
||||
View: ChooseView,
|
||||
@@ -345,12 +345,22 @@ where
|
||||
NestedRoute::new(path, view).ssr_mode(ssr)
|
||||
}
|
||||
|
||||
/// Describes a portion of the nested layout of the app, specifying the route it should match
|
||||
/// and the element it should display.
|
||||
#[component(transparent)]
|
||||
pub fn ParentRoute<Segments, View, Children>(
|
||||
/// The path fragment that this route should match. This can be created using the [`path`]
|
||||
/// macro, or path segments ([`StaticSegment`], [`ParamSegment`], [`WildcardSegment`], and
|
||||
/// [`OptionalParamSegment`]).
|
||||
path: Segments,
|
||||
/// The view for this route.
|
||||
view: View,
|
||||
/// Nested child routes.
|
||||
children: RouteChildren<Children>,
|
||||
#[prop(optional)] ssr: SsrMode,
|
||||
/// The mode that this route prefers during server-side rendering.
|
||||
/// Defaults to out-of-order streaming.
|
||||
#[prop(optional)]
|
||||
ssr: SsrMode,
|
||||
) -> NestedRoute<Segments, Children, (), View>
|
||||
where
|
||||
View: ChooseView,
|
||||
@@ -359,13 +369,27 @@ where
|
||||
NestedRoute::new(path, view).ssr_mode(ssr).child(children)
|
||||
}
|
||||
|
||||
/// Describes a route that is guarded by a certain condition. This works the same way as
|
||||
/// [`<Route/>`], except that if the `condition` function evaluates to `Some(false)`, it
|
||||
/// redirects to `redirect_path` instead of displaying its `view`.
|
||||
#[component(transparent)]
|
||||
pub fn ProtectedRoute<Segments, ViewFn, View, C, PathFn, P>(
|
||||
/// The path fragment that this route should match. This can be created using the [`path`]
|
||||
/// macro, or path segments ([`StaticSegment`], [`ParamSegment`], [`WildcardSegment`], and
|
||||
/// [`OptionalParamSegment`]).
|
||||
path: Segments,
|
||||
/// The view for this route.
|
||||
view: ViewFn,
|
||||
/// A function that returns `Option<bool>`, where `Some(true)` means that the user can access
|
||||
/// the page, `Some(false)` means the user cannot access the page, and `None` means this
|
||||
/// information is still loading.
|
||||
condition: C,
|
||||
/// The path that will be redirected to if the condition is `Some(false)`.
|
||||
redirect_path: PathFn,
|
||||
#[prop(optional)] ssr: SsrMode,
|
||||
/// The mode that this route prefers during server-side rendering.
|
||||
/// Defaults to out-of-order streaming.
|
||||
#[prop(optional)]
|
||||
ssr: SsrMode,
|
||||
) -> NestedRoute<Segments, (), (), impl Fn() -> AnyView + Send + Clone>
|
||||
where
|
||||
ViewFn: Fn() -> View + Send + Clone + 'static,
|
||||
@@ -403,12 +427,24 @@ where
|
||||
|
||||
#[component(transparent)]
|
||||
pub fn ProtectedParentRoute<Segments, ViewFn, View, C, PathFn, P, Children>(
|
||||
/// The path fragment that this route should match. This can be created using the [`path`]
|
||||
/// macro, or path segments ([`StaticSegment`], [`ParamSegment`], [`WildcardSegment`], and
|
||||
/// [`OptionalParamSegment`]).
|
||||
path: Segments,
|
||||
/// The view for this route.
|
||||
view: ViewFn,
|
||||
/// A function that returns `Option<bool>`, where `Some(true)` means that the user can access
|
||||
/// the page, `Some(false)` means the user cannot access the page, and `None` means this
|
||||
/// information is still loading.
|
||||
condition: C,
|
||||
/// The path that will be redirected to if the condition is `Some(false)`.
|
||||
redirect_path: PathFn,
|
||||
/// Nested child routes.
|
||||
children: RouteChildren<Children>,
|
||||
#[prop(optional)] ssr: SsrMode,
|
||||
/// The mode that this route prefers during server-side rendering.
|
||||
/// Defaults to out-of-order streaming.
|
||||
#[prop(optional)]
|
||||
ssr: SsrMode,
|
||||
) -> NestedRoute<Segments, Children, (), impl Fn() -> AnyView + Send + Clone>
|
||||
where
|
||||
ViewFn: Fn() -> View + Send + Clone + 'static,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
hooks::Matched,
|
||||
location::{LocationProvider, Url},
|
||||
matching::{MatchParams, Routes},
|
||||
matching::{MatchParams, RouteDefs},
|
||||
params::ParamsMap,
|
||||
view_transition::start_view_transition,
|
||||
ChooseView, MatchInterface, MatchNestedRoutes, PathSegment, RouteList,
|
||||
@@ -33,14 +33,15 @@ use tachys::{
|
||||
pub(crate) struct FlatRoutesView<Loc, Defs, FalFn> {
|
||||
pub current_url: ArcRwSignal<Url>,
|
||||
pub location: Option<Loc>,
|
||||
pub routes: Routes<Defs>,
|
||||
pub routes: RouteDefs<Defs>,
|
||||
pub fallback: FalFn,
|
||||
pub outer_owner: Owner,
|
||||
pub set_is_routing: Option<SignalSetter<bool>>,
|
||||
pub transition: bool,
|
||||
}
|
||||
|
||||
pub struct FlatRoutesViewState {
|
||||
/// Retained view state for the flat router.
|
||||
pub(crate) struct FlatRoutesViewState {
|
||||
#[allow(clippy::type_complexity)]
|
||||
view: AnyViewState,
|
||||
id: Option<RouteMatchId>,
|
||||
|
||||
@@ -75,11 +75,13 @@ impl RouteListing {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the set of static paths for this route listing, depending on prerendered params.
|
||||
pub async fn into_static_paths(self) -> Option<Vec<ResolvedStaticPath>> {
|
||||
let params = self.static_route()?.to_prerendered_params().await;
|
||||
Some(StaticPath::new(self.path).into_paths(params))
|
||||
}
|
||||
|
||||
/// Generates static files for this route listing.
|
||||
pub async fn generate_static_files<Fut, WriterFut>(
|
||||
mut self,
|
||||
render_fn: impl Fn(&ResolvedStaticPath) -> Fut + Send + Clone + 'static,
|
||||
@@ -148,6 +150,7 @@ impl RouteListing {
|
||||
*/
|
||||
}
|
||||
|
||||
/// A set of routes generated from the route definitions.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct RouteList(Vec<RouteListing>);
|
||||
|
||||
@@ -158,24 +161,29 @@ impl From<Vec<RouteListing>> for RouteList {
|
||||
}
|
||||
|
||||
impl RouteList {
|
||||
/// Adds a route listing.
|
||||
pub fn push(&mut self, data: RouteListing) {
|
||||
self.0.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
impl RouteList {
|
||||
/// Creates an empty list of routes.
|
||||
pub fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
/// Returns the list of routes.
|
||||
pub fn into_inner(self) -> Vec<RouteListing> {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns and iterator over the list of routes.
|
||||
pub fn iter(&self) -> impl Iterator<Item = &RouteListing> {
|
||||
self.0.iter()
|
||||
}
|
||||
|
||||
/// Generates a list of resolved static paths based on the inner list of route listings.
|
||||
pub async fn into_static_paths(self) -> Vec<ResolvedStaticPath> {
|
||||
futures::future::join_all(
|
||||
self.into_inner()
|
||||
@@ -189,6 +197,7 @@ impl RouteList {
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Generates static files for the inner list of route listings.
|
||||
pub async fn generate_static_files<Fut, WriterFut>(
|
||||
self,
|
||||
render_fn: impl Fn(&ResolvedStaticPath) -> Fut + Send + Clone + 'static,
|
||||
@@ -220,6 +229,7 @@ impl RouteList {
|
||||
static GENERATED: RefCell<Option<RouteList>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
/// Creates a list of routes, based on route definitions in the given app.
|
||||
pub fn generate<T>(app: impl FnOnce() -> T) -> Option<Self>
|
||||
where
|
||||
T: RenderHtml,
|
||||
@@ -233,10 +243,12 @@ impl RouteList {
|
||||
Self::GENERATED.take()
|
||||
}
|
||||
|
||||
/// Returns `true` if we are currently in a [`RouteList::generate`] call.
|
||||
pub fn is_generating() -> bool {
|
||||
Self::IS_GENERATING.get()
|
||||
}
|
||||
|
||||
/// Sets the given routes as the list of generated routes.
|
||||
pub fn register(routes: RouteList) {
|
||||
Self::GENERATED.with(|inner| {
|
||||
*inner.borrow_mut() = Some(routes);
|
||||
|
||||
@@ -17,6 +17,7 @@ use std::{
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
/// See [`query_signal`].
|
||||
#[track_caller]
|
||||
#[deprecated = "This has been renamed to `query_signal` to match Rust naming \
|
||||
conventions."]
|
||||
@@ -29,6 +30,7 @@ where
|
||||
query_signal(key)
|
||||
}
|
||||
|
||||
/// See [`query_signal_with_options`].
|
||||
#[track_caller]
|
||||
#[deprecated = "This has been renamed to `query_signal_with_options` to mtch \
|
||||
Rust naming conventions."]
|
||||
@@ -88,6 +90,9 @@ where
|
||||
query_signal_with_options::<T>(key, NavigateOptions::default())
|
||||
}
|
||||
|
||||
/// Constructs a signal synchronized with a specific URL query parameter.
|
||||
///
|
||||
/// This is the same as [`query_signal`], but allows you to specify additional navigation options.
|
||||
#[track_caller]
|
||||
pub fn query_signal_with_options<T>(
|
||||
key: impl Into<Oco<'static, str>>,
|
||||
@@ -205,6 +210,7 @@ fn use_url_raw() -> ArcRwSignal<Url> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Gives reactive access to the current URL.
|
||||
#[track_caller]
|
||||
pub fn use_url() -> ReadSignal<Url> {
|
||||
use_url_raw().read_only().into()
|
||||
|
||||
@@ -1,20 +1,148 @@
|
||||
//! # Leptos Router
|
||||
//!
|
||||
//! Leptos Router is a router and state management tool for web applications
|
||||
//! written in Rust using the Leptos web framework.
|
||||
//! It is ”isomorphic”, i.e., it can be used for client-side applications/single-page
|
||||
//! apps (SPAs), server-side rendering/multi-page apps (MPAs), or to synchronize
|
||||
//! state between the two.
|
||||
//!
|
||||
//! ## Philosophy
|
||||
//!
|
||||
//! Leptos Router is built on a few simple principles:
|
||||
//! 1. **URL drives state.** For web applications, the URL should be the ultimate
|
||||
//! source of truth for most of your app’s state. (It’s called a **Universal
|
||||
//! Resource Locator** for a reason!)
|
||||
//!
|
||||
//! 2. **Nested routing.** A URL can match multiple routes that exist in a nested tree
|
||||
//! and are rendered by different components. This means you can navigate between siblings
|
||||
//! in this tree without re-rendering or triggering any change in the parent routes.
|
||||
//!
|
||||
//! 3. **Progressive enhancement.** The [`A`] and [`Form`] components resolve any relative
|
||||
//! nested routes, render actual `<a>` and `<form>` elements, and (when possible)
|
||||
//! upgrading them to handle those navigations with client-side routing. If you’re using
|
||||
//! them with server-side rendering (with or without hydration), they just work,
|
||||
//! whether JS/WASM have loaded or not.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! use leptos::prelude::*;
|
||||
//! use leptos_router::components::*;
|
||||
//! use leptos_router::path;
|
||||
//! use leptos_router::hooks::use_params_map;
|
||||
//!
|
||||
//! #[component]
|
||||
//! pub fn RouterExample() -> impl IntoView {
|
||||
//! view! {
|
||||
//!
|
||||
//! <div id="root">
|
||||
//! // we wrap the whole app in a <Router/> to allow client-side navigation
|
||||
//! // from our nav links below
|
||||
//! <Router>
|
||||
//! <main>
|
||||
//! // <Routes/> both defines our routes and shows them on the page
|
||||
//! <Routes fallback=|| "Not found.">
|
||||
//! // our root route: the contact list is always shown
|
||||
//! <ParentRoute
|
||||
//! path=path!("")
|
||||
//! view=ContactList
|
||||
//! >
|
||||
//! // users like /gbj or /bob
|
||||
//! <Route
|
||||
//! path=path!(":id")
|
||||
//! view=Contact
|
||||
//! />
|
||||
//! // a fallback if the /:id segment is missing from the URL
|
||||
//! <Route
|
||||
//! path=path!("")
|
||||
//! view=move || view! { <p class="contact">"Select a contact."</p> }
|
||||
//! />
|
||||
//! </ParentRoute>
|
||||
//! </Routes>
|
||||
//! </main>
|
||||
//! </Router>
|
||||
//! </div>
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! type ContactSummary = (); // TODO!
|
||||
//! type Contact = (); // TODO!()
|
||||
//!
|
||||
//! // contact_data reruns whenever the :id param changes
|
||||
//! async fn contact_data(id: String) -> Contact {
|
||||
//! todo!()
|
||||
//! }
|
||||
//!
|
||||
//! // contact_list_data *doesn't* rerun when the :id changes,
|
||||
//! // because that param is nested lower than the <ContactList/> route
|
||||
//! async fn contact_list_data() -> Vec<ContactSummary> {
|
||||
//! todo!()
|
||||
//! }
|
||||
//!
|
||||
//! #[component]
|
||||
//! fn ContactList() -> impl IntoView {
|
||||
//! // loads the contact list data once; doesn't reload when nested routes change
|
||||
//! let contacts = Resource::new(|| (), |_| contact_list_data());
|
||||
//! view! {
|
||||
//!
|
||||
//! <div>
|
||||
//! // show the contacts
|
||||
//! <ul>
|
||||
//! {move || contacts.get().map(|contacts| view! { <li>"todo contact info"</li> } )}
|
||||
//! </ul>
|
||||
//!
|
||||
//! // insert the nested child route here
|
||||
//! <Outlet/>
|
||||
//! </div>
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! #[component]
|
||||
//! fn Contact() -> impl IntoView {
|
||||
//! let params = use_params_map();
|
||||
//! let data = Resource::new(
|
||||
//! move || params.read().get("id").unwrap_or_default(),
|
||||
//! move |id| contact_data(id)
|
||||
//! );
|
||||
//! todo!()
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! You can find examples of additional APIs in the [`router`] example.
|
||||
//!
|
||||
//! # Feature Flags
|
||||
//! - `ssr` Server-side rendering: Generate an HTML string (typically on the server)
|
||||
//! - `nightly`: On `nightly` Rust, enables the function-call syntax for signal getters and setters.
|
||||
//! - `tracing`: Enables support for the `tracing` crate.
|
||||
//!
|
||||
//! [`Leptos`]: <https://github.com/leptos-rs/leptos>
|
||||
//! [`router`]: <https://github.com/leptos-rs/leptos/blob/main/examples/router/src/lib.rs>
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(feature = "nightly", feature(auto_traits))]
|
||||
#![cfg_attr(feature = "nightly", feature(negative_impls))]
|
||||
|
||||
/// Components for route definition and for enhanced links and forms.
|
||||
pub mod components;
|
||||
/// An optimized "flat" router without nested routes.
|
||||
pub mod flat_router;
|
||||
mod form;
|
||||
mod generate_route_list;
|
||||
/// Hooks that can be used to access router state inside your components.
|
||||
pub mod hooks;
|
||||
mod link;
|
||||
/// Utilities for accessing the current location.
|
||||
pub mod location;
|
||||
mod matching;
|
||||
mod method;
|
||||
mod navigate;
|
||||
/// A nested router that supports multiple levels of route definitions.
|
||||
pub mod nested_router;
|
||||
/// Support for maps of parameters in the path or in the query.
|
||||
pub mod params;
|
||||
mod ssr_mode;
|
||||
/// Support for static routing.
|
||||
pub mod static_routes;
|
||||
|
||||
pub use generate_route_list::*;
|
||||
|
||||
@@ -107,9 +107,9 @@ where
|
||||
) -> impl IntoView {
|
||||
let RouterContext { current_url, .. } =
|
||||
use_context().expect("tried to use <A/> outside a <Router/>.");
|
||||
let is_active = ArcMemo::new({
|
||||
let is_active = {
|
||||
let href = href.clone();
|
||||
move |_| {
|
||||
move || {
|
||||
href.read().as_deref().is_some_and(|to| {
|
||||
let path = to.split(['?', '#']).next().unwrap_or_default();
|
||||
current_url.with(|loc| {
|
||||
@@ -122,16 +122,13 @@ where
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
view! {
|
||||
<a
|
||||
href=move || href.get().unwrap_or_default()
|
||||
target=target
|
||||
aria-current={
|
||||
let is_active = is_active.clone();
|
||||
move || if is_active.get() { Some("page") } else { None }
|
||||
}
|
||||
aria-current=move || if is_active() { Some("page") } else { None }
|
||||
>
|
||||
|
||||
{children()}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use any_spawner::Executor;
|
||||
use core::fmt::Debug;
|
||||
use js_sys::Reflect;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
mod choose_view;
|
||||
mod path_segment;
|
||||
pub(crate) mod resolve_path;
|
||||
@@ -13,12 +15,12 @@ use std::{borrow::Cow, collections::HashSet};
|
||||
pub use vertical::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Routes<Children> {
|
||||
pub struct RouteDefs<Children> {
|
||||
base: Option<Cow<'static, str>>,
|
||||
children: Children,
|
||||
}
|
||||
|
||||
impl<Children> Clone for Routes<Children>
|
||||
impl<Children> Clone for RouteDefs<Children>
|
||||
where
|
||||
Children: Clone,
|
||||
{
|
||||
@@ -30,7 +32,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Children> Routes<Children> {
|
||||
impl<Children> RouteDefs<Children> {
|
||||
pub fn new(children: Children) -> Self {
|
||||
Self {
|
||||
base: None,
|
||||
@@ -49,7 +51,7 @@ impl<Children> Routes<Children> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Children> Routes<Children>
|
||||
impl<Children> RouteDefs<Children>
|
||||
where
|
||||
Children: MatchNestedRoutes,
|
||||
{
|
||||
@@ -62,10 +64,7 @@ where
|
||||
} else {
|
||||
(base.as_ref(), path)
|
||||
};
|
||||
match path.strip_prefix(base) {
|
||||
Some(path) => path,
|
||||
None => return None,
|
||||
}
|
||||
path.strip_prefix(base)?
|
||||
}
|
||||
};
|
||||
|
||||
@@ -130,7 +129,7 @@ pub struct GeneratedRouteData {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{NestedRoute, ParamSegment, Routes};
|
||||
use super::{NestedRoute, ParamSegment, RouteDefs};
|
||||
use crate::{
|
||||
matching::MatchParams, MatchInterface, PathSegment, StaticSegment,
|
||||
WildcardSegment,
|
||||
@@ -140,7 +139,7 @@ mod tests {
|
||||
#[test]
|
||||
pub fn matches_single_root_route() {
|
||||
let routes =
|
||||
Routes::<_>::new(NestedRoute::new(StaticSegment("/"), || ()));
|
||||
RouteDefs::<_>::new(NestedRoute::new(StaticSegment("/"), || ()));
|
||||
let matched = routes.match_route("/");
|
||||
assert!(matched.is_some());
|
||||
// this case seems like it should match, but implementing it interferes with
|
||||
@@ -156,13 +155,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
pub fn matches_nested_route() {
|
||||
let routes: Routes<_> =
|
||||
Routes::new(NestedRoute::new(StaticSegment(""), || "Home").child(
|
||||
let routes: RouteDefs<_> = RouteDefs::new(
|
||||
NestedRoute::new(StaticSegment(""), || "Home").child(
|
||||
NestedRoute::new(
|
||||
(StaticSegment("author"), StaticSegment("contact")),
|
||||
|| "Contact Me",
|
||||
),
|
||||
));
|
||||
),
|
||||
);
|
||||
|
||||
// route generation
|
||||
let (base, paths) = routes.generate_routes();
|
||||
@@ -188,7 +188,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
pub fn does_not_match_route_unless_full_param_matches() {
|
||||
let routes = Routes::<_>::new((
|
||||
let routes = RouteDefs::<_>::new((
|
||||
NestedRoute::new(StaticSegment("/property-api"), || ()),
|
||||
NestedRoute::new(StaticSegment("/property"), || ()),
|
||||
));
|
||||
@@ -198,20 +198,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
pub fn does_not_match_incomplete_route() {
|
||||
let routes: Routes<_> =
|
||||
Routes::new(NestedRoute::new(StaticSegment(""), || "Home").child(
|
||||
let routes: RouteDefs<_> = RouteDefs::new(
|
||||
NestedRoute::new(StaticSegment(""), || "Home").child(
|
||||
NestedRoute::new(
|
||||
(StaticSegment("author"), StaticSegment("contact")),
|
||||
|| "Contact Me",
|
||||
),
|
||||
));
|
||||
),
|
||||
);
|
||||
let matched = routes.match_route("/");
|
||||
assert!(matched.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn chooses_between_nested_routes() {
|
||||
let routes: Routes<_> = Routes::new((
|
||||
let routes: RouteDefs<_> = RouteDefs::new((
|
||||
NestedRoute::new(StaticSegment("/"), || ()).child((
|
||||
NestedRoute::new(StaticSegment(""), || ()),
|
||||
NestedRoute::new(StaticSegment("about"), || ()),
|
||||
@@ -265,7 +266,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
pub fn arbitrary_nested_routes() {
|
||||
let routes: Routes<_> = Routes::new_with_base(
|
||||
let routes: RouteDefs<_> = RouteDefs::new_with_base(
|
||||
(
|
||||
NestedRoute::new(StaticSegment("/"), || ()).child((
|
||||
NestedRoute::new(StaticSegment("/"), || ()),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
hooks::Matched,
|
||||
location::{LocationProvider, Url},
|
||||
matching::Routes,
|
||||
matching::RouteDefs,
|
||||
params::ParamsMap,
|
||||
view_transition::start_view_transition,
|
||||
ChooseView, MatchInterface, MatchNestedRoutes, MatchParams, PathSegment,
|
||||
@@ -44,7 +44,7 @@ use tachys::{
|
||||
|
||||
pub(crate) struct NestedRoutesView<Loc, Defs, FalFn> {
|
||||
pub location: Option<Loc>,
|
||||
pub routes: Routes<Defs>,
|
||||
pub routes: RouteDefs<Defs>,
|
||||
pub outer_owner: Owner,
|
||||
pub current_url: ArcRwSignal<Url>,
|
||||
pub base: Option<Oco<'static, str>>,
|
||||
@@ -53,7 +53,8 @@ pub(crate) struct NestedRoutesView<Loc, Defs, FalFn> {
|
||||
pub transition: bool,
|
||||
}
|
||||
|
||||
pub struct NestedRouteViewState<Fal>
|
||||
/// Retained view state for the nested router.
|
||||
pub(crate) struct NestedRouteViewState<Fal>
|
||||
where
|
||||
Fal: Render,
|
||||
{
|
||||
@@ -873,6 +874,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays the child route nested in a parent route, allowing you to control exactly where
|
||||
/// that child route is displayed. Renders nothing if there is no nested child.
|
||||
#[component]
|
||||
pub fn Outlet() -> impl RenderHtml
|
||||
where
|
||||
|
||||
@@ -4,6 +4,7 @@ use thiserror::Error;
|
||||
|
||||
type ParamsMapInner = Vec<(Cow<'static, str>, Vec<String>)>;
|
||||
|
||||
/// A key-value map of the current named route params and their values.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ParamsMap(ParamsMapInner);
|
||||
|
||||
@@ -169,10 +170,12 @@ impl Params for () {
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts some parameter value from the URL into a typed parameter with the given name.
|
||||
pub trait IntoParam
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
/// Converts the param.
|
||||
fn into_param(value: Option<&str>, name: &str)
|
||||
-> Result<Self, ParamsError>;
|
||||
}
|
||||
|
||||
@@ -20,17 +20,34 @@ use crate::static_routes::StaticRoute;
|
||||
/// 5. **`Async`**: Load all resources on the server. Wait until all data are loaded, and render HTML in one sweep.
|
||||
/// - *Pros*: Better handling for meta tags (because you know async data even before you render the `<head>`). Faster complete load than **synchronous** because async resources begin loading on server.
|
||||
/// - *Cons*: Slower load time/TTFB: you need to wait for all async resources to load before displaying anything on the client.
|
||||
/// 6. **`Static`**:
|
||||
/// 6. **`Static`**: Renders the page when the server starts up, or incrementally, using the
|
||||
/// configuration provided by a [`StaticRoute`].
|
||||
///
|
||||
/// The mode defaults to out-of-order streaming. For a path that includes multiple nested routes, the most
|
||||
/// restrictive mode will be used: i.e., if even a single nested route asks for `Async` rendering, the whole initial
|
||||
/// request will be rendered `Async`. (`Async` is the most restricted requirement, followed by `InOrder`, `PartiallyBlocked`, and `OutOfOrder`.)
|
||||
#[derive(Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum SsrMode {
|
||||
/// **Out-of-order streaming** (`OutOfOrder`, the default): Serve an HTML shell that includes `fallback` for any `Suspense`. Load data on the **server**, streaming it down to the client as it resolves, and streaming down HTML for `Suspense` nodes.
|
||||
/// - *Pros*: Combines the best of **synchronous** and `Async`, with a very fast shell and resources that begin loading on the server.
|
||||
/// - *Cons*: Requires JS for suspended fragments to appear in correct order. Weaker meta tag support when it depends on data that's under suspense (has already streamed down `<head>`)
|
||||
#[default]
|
||||
OutOfOrder,
|
||||
/// **In-order streaming** (`InOrder`): Walk through the tree, returning HTML synchronously as in synchronous rendering and out-of-order streaming until you hit a `Suspense`. At that point, wait for all its data to load, then render it, then the rest of the tree.
|
||||
/// - *Pros*: Does not require JS for HTML to appear in correct order.
|
||||
/// - *Cons*: Loads the shell more slowly than out-of-order streaming or synchronous rendering because it needs to pause at every `Suspense`. Cannot begin hydration until the entire page has loaded, so earlier pieces
|
||||
/// of the page will not be interactive until the suspended chunks have loaded.
|
||||
PartiallyBlocked,
|
||||
/// **In-order streaming** (`InOrder`): Walk through the tree, returning HTML synchronously as in synchronous rendering and out-of-order streaming until you hit a `Suspense`. At that point, wait for all its data to load, then render it, then the rest of the tree.
|
||||
/// - *Pros*: Does not require JS for HTML to appear in correct order.
|
||||
/// - *Cons*: Loads the shell more slowly than out-of-order streaming or synchronous rendering because it needs to pause at every `Suspense`. Cannot begin hydration until the entire page has loaded, so earlier pieces
|
||||
/// of the page will not be interactive until the suspended chunks have loaded.
|
||||
InOrder,
|
||||
/// **`Async`**: Load all resources on the server. Wait until all data are loaded, and render HTML in one sweep.
|
||||
/// - *Pros*: Better handling for meta tags (because you know async data even before you render the `<head>`). Faster complete load than **synchronous** because async resources begin loading on server.
|
||||
/// - *Cons*: Slower load time/TTFB: you need to wait for all async resources to load before displaying anything on the client.
|
||||
Async,
|
||||
/// **`Static`**: Renders the page when the server starts up, or incrementally, using the
|
||||
/// configuration provided by a [`StaticRoute`].
|
||||
Static(StaticRoute),
|
||||
}
|
||||
|
||||
@@ -13,10 +13,14 @@ use std::{
|
||||
type PinnedFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
|
||||
type PinnedStream<T> = Pin<Box<dyn Stream<Item = T> + Send>>;
|
||||
|
||||
/// A reference-counted pointer to a function that can generate a set of params for static site
|
||||
/// generation.
|
||||
pub type StaticParams = Arc<StaticParamsFn>;
|
||||
/// A function that generates a set of params for generating a static route.
|
||||
pub type StaticParamsFn =
|
||||
dyn Fn() -> PinnedFuture<StaticParamsMap> + Send + Sync + 'static;
|
||||
|
||||
/// A function that defines when a statically-generated page should be regenerated.
|
||||
#[derive(Clone)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub struct RegenerationFn(
|
||||
@@ -43,6 +47,7 @@ impl PartialEq for RegenerationFn {
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines how a static route should be generated.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct StaticRoute {
|
||||
pub(crate) prerender_params: Option<StaticParams>,
|
||||
@@ -50,10 +55,13 @@ pub struct StaticRoute {
|
||||
}
|
||||
|
||||
impl StaticRoute {
|
||||
/// Creates a new static route listing.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Defines a set of params that should be prerendered on server start-up, depending on some
|
||||
/// asynchronous function that returns their values.
|
||||
pub fn prerender_params<Fut>(
|
||||
mut self,
|
||||
params: impl Fn() -> Fut + Send + Sync + 'static,
|
||||
@@ -65,6 +73,7 @@ impl StaticRoute {
|
||||
self
|
||||
}
|
||||
|
||||
/// Defines when the route should be regenerated.
|
||||
pub fn regenerate<St>(
|
||||
mut self,
|
||||
invalidate: impl Fn(&ParamsMap) -> St + Send + Sync + 'static,
|
||||
@@ -78,6 +87,7 @@ impl StaticRoute {
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns a set of params that should be prerendered.
|
||||
pub async fn to_prerendered_params(&self) -> Option<StaticParamsMap> {
|
||||
match &self.prerender_params {
|
||||
None => None,
|
||||
@@ -118,6 +128,7 @@ impl PartialEq for StaticRoute {
|
||||
|
||||
impl Eq for StaticRoute {}
|
||||
|
||||
/// A map of params for static routes.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct StaticParamsMap(pub Vec<(String, Vec<String>)>);
|
||||
|
||||
@@ -159,6 +170,7 @@ impl IntoIterator for StaticParamsMap {
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over a set of (key, value) pairs for statically-routed params.
|
||||
#[derive(Debug)]
|
||||
pub struct StaticParamsIter(
|
||||
<Vec<(String, Vec<String>)> as IntoIterator>::IntoIter,
|
||||
@@ -208,7 +220,7 @@ impl StaticPath {
|
||||
paths = paths
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
if s.starts_with("/") {
|
||||
if s.starts_with("/") || s.is_empty() {
|
||||
ResolvedStaticPath {
|
||||
path: format!("{}{s}", p.path),
|
||||
}
|
||||
@@ -254,12 +266,14 @@ impl StaticPath {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// A path to be used in static route generation.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ResolvedStaticPath {
|
||||
pub(crate) path: String,
|
||||
}
|
||||
|
||||
impl ResolvedStaticPath {
|
||||
/// Defines a path to be used for static route generation.
|
||||
pub fn new(path: impl Into<String>) -> Self {
|
||||
Self { path: path.into() }
|
||||
}
|
||||
@@ -278,6 +292,7 @@ impl Display for ResolvedStaticPath {
|
||||
}
|
||||
|
||||
impl ResolvedStaticPath {
|
||||
/// Builds the page that corresponds to this path.
|
||||
pub async fn build<Fut, WriterFut>(
|
||||
self,
|
||||
render_fn: impl Fn(&ResolvedStaticPath) -> Fut + Send + Clone + 'static,
|
||||
@@ -362,3 +377,67 @@ impl ResolvedStaticPath {
|
||||
rx.await.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn static_path_segments_into_path_ignore_empty_segments() {
|
||||
let segments = StaticPath::new(vec![
|
||||
PathSegment::Static("".into()),
|
||||
PathSegment::Static("post".into()),
|
||||
]);
|
||||
assert_eq!(
|
||||
segments.into_paths(None),
|
||||
vec![ResolvedStaticPath::new("/post")]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_path_segments_into_path_flatten_param() {
|
||||
let mut params = StaticParamsMap::new();
|
||||
params
|
||||
.0
|
||||
.push(("slug".into(), vec!["first".into(), "second".into()]));
|
||||
let segments = StaticPath::new(vec![
|
||||
PathSegment::Static("/post".into()),
|
||||
PathSegment::Param("slug".into()),
|
||||
]);
|
||||
assert_eq!(
|
||||
segments.into_paths(Some(params)),
|
||||
vec![
|
||||
ResolvedStaticPath::new("/post/first"),
|
||||
ResolvedStaticPath::new("/post/second")
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_path_segments_into_path_no_double_slash() {
|
||||
let segments = StaticPath::new(vec![
|
||||
PathSegment::Static("/post".into()),
|
||||
PathSegment::Static("/leptos".into()),
|
||||
]);
|
||||
assert_eq!(
|
||||
segments.into_paths(None),
|
||||
vec![ResolvedStaticPath::new("/post/leptos")]
|
||||
);
|
||||
|
||||
let mut params = StaticParamsMap::new();
|
||||
params
|
||||
.0
|
||||
.push(("slug".into(), vec!["/first".into(), "/second".into()]));
|
||||
let segments = StaticPath::new(vec![
|
||||
PathSegment::Static("/post".into()),
|
||||
PathSegment::Param("slug".into()),
|
||||
]);
|
||||
assert_eq!(
|
||||
segments.into_paths(Some(params)),
|
||||
vec![
|
||||
ResolvedStaticPath::new("/post/first"),
|
||||
ResolvedStaticPath::new("/post/second")
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
//! A macro to make path definitions easier with [`leptos_router`].
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use proc_macro::{TokenStream, TokenTree};
|
||||
use proc_macro2::Span;
|
||||
use proc_macro_error2::abort;
|
||||
|
||||
2555
server_fn/Cargo.lock
generated
Normal file
2555
server_fn/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,7 @@ once_cell = "1.20"
|
||||
actix-web = { version = "4.9", optional = true }
|
||||
|
||||
# axum
|
||||
axum = { version = "0.7.7", optional = true, default-features = false, features = [
|
||||
axum = { version = "0.7.8", optional = true, default-features = false, features = [
|
||||
"multipart",
|
||||
] }
|
||||
tower = { version = "0.5.1", optional = true }
|
||||
|
||||
@@ -267,7 +267,7 @@ impl<T: IntoClass> IntoClass for Option<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoClass for &'a str {
|
||||
impl IntoClass for &str {
|
||||
type AsyncOutput = Self;
|
||||
type State = (crate::renderer::types::Element, Self);
|
||||
type Cloneable = Self;
|
||||
|
||||
@@ -308,7 +308,7 @@ impl InnerHtmlValue for Arc<str> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> InnerHtmlValue for &'a str {
|
||||
impl InnerHtmlValue for &str {
|
||||
type AsyncOutput = Self;
|
||||
type State = (crate::renderer::types::Element, Self);
|
||||
type Cloneable = Self;
|
||||
|
||||
@@ -606,7 +606,7 @@ impl<'a> IntoStyle for (&'a str, String) {
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
impl<'a, const V: &'static str> IntoStyle for (&'a str, Static<V>) {
|
||||
impl<const V: &'static str> IntoStyle for (&str, Static<V>) {
|
||||
type AsyncOutput = Self;
|
||||
type State = ();
|
||||
type Cloneable = (Arc<str>, Static<V>);
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
//! This view tree is generic over rendering backends, and agnostic about reactivity/change
|
||||
//! detection.
|
||||
|
||||
#![allow(incomplete_features)] // yolo
|
||||
// this is specifically used for `unsized_const_params` below
|
||||
// this allows us to use const generic &'static str for static text nodes and attributes
|
||||
#![allow(incomplete_features)]
|
||||
#![cfg_attr(feature = "nightly", feature(unsized_const_params))]
|
||||
//#![deny(missing_docs)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
/// Commonly-used traits.
|
||||
pub mod prelude {
|
||||
|
||||
@@ -519,7 +519,6 @@ mod stable {
|
||||
|
||||
macro_rules! class_signal_arena {
|
||||
($sig:ident) => {
|
||||
#[allow(deprecated)]
|
||||
impl<C, S> IntoClass for $sig<C, S>
|
||||
where
|
||||
$sig<C, S>: Get<Value = C>,
|
||||
@@ -589,7 +588,6 @@ mod stable {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<S> IntoClass for (&'static str, $sig<bool, S>)
|
||||
where
|
||||
$sig<bool, S>: Get<Value = bool>,
|
||||
@@ -827,14 +825,12 @@ mod stable {
|
||||
|
||||
use super::RenderEffect;
|
||||
use crate::html::class::IntoClass;
|
||||
#[allow(deprecated)]
|
||||
use reactive_graph::wrappers::read::MaybeSignal;
|
||||
use reactive_graph::{
|
||||
computed::{ArcMemo, Memo},
|
||||
owner::Storage,
|
||||
signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal},
|
||||
traits::Get,
|
||||
wrappers::read::{ArcSignal, Signal},
|
||||
wrappers::read::{ArcSignal, MaybeSignal, Signal},
|
||||
};
|
||||
|
||||
class_signal_arena!(RwSignal);
|
||||
|
||||
@@ -89,15 +89,13 @@ where
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
mod stable {
|
||||
use crate::html::element::InnerHtmlValue;
|
||||
#[allow(deprecated)]
|
||||
use reactive_graph::wrappers::read::MaybeSignal;
|
||||
use reactive_graph::{
|
||||
computed::{ArcMemo, Memo},
|
||||
effect::RenderEffect,
|
||||
owner::Storage,
|
||||
signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal},
|
||||
traits::Get,
|
||||
wrappers::read::{ArcSignal, Signal},
|
||||
wrappers::read::{ArcSignal, MaybeSignal, Signal},
|
||||
};
|
||||
|
||||
macro_rules! inner_html_signal {
|
||||
@@ -161,7 +159,6 @@ mod stable {
|
||||
|
||||
macro_rules! inner_html_signal_arena {
|
||||
($sig:ident) => {
|
||||
#[allow(deprecated)]
|
||||
impl<V, S> InnerHtmlValue for $sig<V, S>
|
||||
where
|
||||
$sig<V, S>: Get<Value = V>,
|
||||
|
||||
@@ -507,15 +507,13 @@ mod stable {
|
||||
RenderHtml,
|
||||
},
|
||||
};
|
||||
#[allow(deprecated)]
|
||||
use reactive_graph::wrappers::read::MaybeSignal;
|
||||
use reactive_graph::{
|
||||
computed::{ArcMemo, Memo},
|
||||
effect::RenderEffect,
|
||||
owner::Storage,
|
||||
signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal},
|
||||
traits::Get,
|
||||
wrappers::read::{ArcSignal, Signal},
|
||||
wrappers::read::{ArcSignal, MaybeSignal, Signal},
|
||||
};
|
||||
|
||||
macro_rules! signal_impl {
|
||||
@@ -685,7 +683,6 @@ mod stable {
|
||||
|
||||
macro_rules! signal_impl_arena {
|
||||
($sig:ident $dry_resolve:literal) => {
|
||||
#[allow(deprecated)]
|
||||
impl<V, S> Render for $sig<V, S>
|
||||
where
|
||||
$sig<V, S>: Get<Value = V>,
|
||||
@@ -710,7 +707,6 @@ mod stable {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<V, S> AddAnyAttr for $sig<V, S>
|
||||
where
|
||||
$sig<V, S>: Get<Value = V>,
|
||||
@@ -732,7 +728,6 @@ mod stable {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<V, S> RenderHtml for $sig<V, S>
|
||||
where
|
||||
$sig<V, S>: Get<Value = V>,
|
||||
@@ -798,7 +793,6 @@ mod stable {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<V, S> AttributeValue for $sig<V, S>
|
||||
where
|
||||
$sig<V, S>: Get<Value = V>,
|
||||
|
||||
@@ -82,15 +82,13 @@ where
|
||||
#[cfg(not(feature = "nightly"))]
|
||||
mod stable {
|
||||
use crate::html::property::IntoProperty;
|
||||
#[allow(deprecated)]
|
||||
use reactive_graph::wrappers::read::MaybeSignal;
|
||||
use reactive_graph::{
|
||||
computed::{ArcMemo, Memo},
|
||||
effect::RenderEffect,
|
||||
owner::Storage,
|
||||
signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal},
|
||||
traits::Get,
|
||||
wrappers::read::{ArcSignal, Signal},
|
||||
wrappers::read::{ArcSignal, MaybeSignal, Signal},
|
||||
};
|
||||
|
||||
macro_rules! property_signal {
|
||||
@@ -138,7 +136,6 @@ mod stable {
|
||||
|
||||
macro_rules! property_signal_arena {
|
||||
($sig:ident) => {
|
||||
#[allow(deprecated)]
|
||||
impl<V, S> IntoProperty for $sig<V, S>
|
||||
where
|
||||
$sig<V, S>: Get<Value = V>,
|
||||
|
||||
@@ -393,7 +393,6 @@ mod stable {
|
||||
|
||||
macro_rules! style_signal_arena {
|
||||
($sig:ident) => {
|
||||
#[allow(deprecated)]
|
||||
impl<C, S> IntoStyle for $sig<C, S>
|
||||
where
|
||||
$sig<C, S>: Get<Value = C>,
|
||||
@@ -459,7 +458,6 @@ mod stable {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<S, St> IntoStyle for (&'static str, $sig<S, St>)
|
||||
where
|
||||
$sig<S, St>: Get<Value = S>,
|
||||
@@ -536,14 +534,12 @@ mod stable {
|
||||
|
||||
use super::RenderEffect;
|
||||
use crate::html::style::IntoStyle;
|
||||
#[allow(deprecated)]
|
||||
use reactive_graph::wrappers::read::MaybeSignal;
|
||||
use reactive_graph::{
|
||||
computed::{ArcMemo, Memo},
|
||||
owner::Storage,
|
||||
signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal},
|
||||
traits::Get,
|
||||
wrappers::read::{ArcSignal, Signal},
|
||||
wrappers::read::{ArcSignal, MaybeSignal, Signal},
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
//! See [`Renderer`](super::Renderer) and [`Rndr`](super::Rndr) for additional information.
|
||||
|
||||
use super::{CastFrom, RemoveEventHandler};
|
||||
use crate::{
|
||||
dom::{document, window},
|
||||
|
||||
@@ -5,7 +5,22 @@ use wasm_bindgen::JsValue;
|
||||
/// A DOM renderer.
|
||||
pub mod dom;
|
||||
|
||||
/// The renderer being used for the application.
|
||||
///
|
||||
/// ### Note
|
||||
/// This was designed to be included as a generic on view types, to support different rendering
|
||||
/// backends using the same view tree structure. However, adding the number of generics that was
|
||||
/// required to make this work caused catastrophic compile times and linker errors on larger
|
||||
/// applications, so this "generic rendering" approach was removed before 0.7.0 release.
|
||||
///
|
||||
/// It is possible that we will try a different approach to achieve the same functionality in the
|
||||
/// future, so to the extent possible the rest of the crate tries to stick to using [`Renderer`]
|
||||
/// methods rather than directly manipulating the DOM inline.
|
||||
pub type Rndr = dom::Dom;
|
||||
|
||||
/// Types used by the renderer.
|
||||
///
|
||||
/// See [`Rndr`] for additional information on this rendering approach.
|
||||
pub mod types {
|
||||
pub use super::dom::{
|
||||
ClassList, CssStyleDeclaration, Element, Event, Node, Placeholder,
|
||||
@@ -194,7 +209,6 @@ pub trait DomRenderer: Renderer {
|
||||
/// This works in a similar way to `TryFrom`. We implement it as a separate trait
|
||||
/// simply so we don't have to create wrappers for the `web_sys` types; it can't be
|
||||
/// implemented on them directly because of the orphan rules.
|
||||
|
||||
pub trait CastFrom<T>
|
||||
where
|
||||
Self: Sized,
|
||||
|
||||
@@ -197,7 +197,6 @@ where
|
||||
/// Renders a view to an out-of-order stream of HTML with branch markers. This can be used to support libraries that diff
|
||||
/// HTML pages against one another, by marking sections of the view that branch to different
|
||||
/// types with marker comments.
|
||||
|
||||
fn to_html_stream_out_of_order_branching(self) -> StreamBuilder
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -372,7 +371,7 @@ pub trait ToTemplate {
|
||||
/// The `style` attribute content known at compile time.
|
||||
const STYLE: &'static str = "";
|
||||
/// The length of the template.
|
||||
const LEN: usize = Self::TEMPLATE.as_bytes().len();
|
||||
const LEN: usize = Self::TEMPLATE.len();
|
||||
|
||||
/// Renders a view type to a template. This does not take actual view data,
|
||||
/// but can be used for constructing part of an HTML `<template>` that corresponds
|
||||
|
||||
@@ -36,7 +36,7 @@ impl<'a> Render for &'a str {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RenderHtml for &'a str {
|
||||
impl RenderHtml for &str {
|
||||
type AsyncOutput = Self;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
@@ -102,7 +102,7 @@ impl<'a> RenderHtml for &'a str {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToTemplate for &'a str {
|
||||
impl ToTemplate for &str {
|
||||
const TEMPLATE: &'static str = " <!>";
|
||||
|
||||
fn to_template(
|
||||
@@ -120,7 +120,7 @@ impl<'a> ToTemplate for &'a str {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mountable for StrState<'a> {
|
||||
impl Mountable for StrState<'_> {
|
||||
fn unmount(&mut self) {
|
||||
self.node.unmount()
|
||||
}
|
||||
@@ -451,7 +451,7 @@ impl<'a> Render for Cow<'a, str> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RenderHtml for Cow<'a, str> {
|
||||
impl RenderHtml for Cow<'_, str> {
|
||||
type AsyncOutput = Self;
|
||||
|
||||
const MIN_LENGTH: usize = 0;
|
||||
@@ -494,7 +494,7 @@ impl<'a> RenderHtml for Cow<'a, str> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToTemplate for Cow<'a, str> {
|
||||
impl ToTemplate for Cow<'_, str> {
|
||||
const TEMPLATE: &'static str = <&str as ToTemplate>::TEMPLATE;
|
||||
|
||||
fn to_template(
|
||||
@@ -510,7 +510,7 @@ impl<'a> ToTemplate for Cow<'a, str> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mountable for CowStrState<'a> {
|
||||
impl Mountable for CowStrState<'_> {
|
||||
fn unmount(&mut self) {
|
||||
self.node.unmount()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user