mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 10:11:56 -05:00
Compare commits
23 Commits
server-fn-
...
accidental
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8278cbbf4e | ||
|
|
3c6748b30d | ||
|
|
24945f67bf | ||
|
|
edddab1e51 | ||
|
|
acfc86d2a4 | ||
|
|
651868dec9 | ||
|
|
18bc03e660 | ||
|
|
5f0013e482 | ||
|
|
10c0a2de65 | ||
|
|
b24be2566d | ||
|
|
77439b5db5 | ||
|
|
23594a43ea | ||
|
|
601db7aa86 | ||
|
|
d15ba11104 | ||
|
|
d45d92433f | ||
|
|
97127a90c6 | ||
|
|
55bb63edea | ||
|
|
15a4e54435 | ||
|
|
3a522aef5d | ||
|
|
a98885a123 | ||
|
|
2b7923261b | ||
|
|
b043f829a6 | ||
|
|
f415f7b146 |
72
.github/workflows/check-examples.yml
vendored
72
.github/workflows/check-examples.yml
vendored
@@ -1,46 +1,46 @@
|
||||
name: Check examples
|
||||
name: Check Examples
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Check examples ${{ matrix.os }} (using rustc ${{ matrix.rust }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- nightly
|
||||
os:
|
||||
- ubuntu-latest
|
||||
|
||||
setup:
|
||||
name: Get Examples
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt
|
||||
- name: Install JQ Tool
|
||||
uses: mbround18/install-jq@v1
|
||||
|
||||
- name: Add wasm32-unknown-unknown
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- name: Set Matrix
|
||||
id: set-matrix
|
||||
run: |
|
||||
examples=$(ls examples |
|
||||
awk '{print "examples/" $0}' |
|
||||
grep -v examples/README.md |
|
||||
grep -v examples/Makefile.toml |
|
||||
grep -v examples/cargo-make |
|
||||
grep -v examples/gtk |
|
||||
jq -R -s -c 'split("\n")[:-1]')
|
||||
echo "Example Directories: $examples"
|
||||
echo "matrix={\"directory\":$examples}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Setup cargo-make
|
||||
uses: davidB/rust-cargo-make@v1
|
||||
|
||||
- name: Cargo generate-lockfile
|
||||
run: cargo generate-lockfile
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Run cargo check on all examples
|
||||
run: cargo make check-examples
|
||||
matrix-job:
|
||||
name: Check
|
||||
needs: [setup]
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
uses: ./.github/workflows/run-example-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
cargo_make_task: "check"
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
name: Verify Examples
|
||||
name: Run Example Task
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
workflow_call:
|
||||
inputs:
|
||||
directory:
|
||||
required: true
|
||||
type: string
|
||||
cargo_make_task:
|
||||
required: true
|
||||
type: string
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Verify examples ${{ matrix.os }} (using rustc ${{ matrix.rust }}
|
||||
name: Run ${{ matrix.os }} (using rustc ${{ matrix.rust }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -21,6 +25,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Setup environment
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust
|
||||
@@ -63,7 +68,6 @@ jobs:
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
@@ -75,5 +79,12 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Run verify-flow on all web examples
|
||||
run: cargo make --profile=github-actions verify-examples
|
||||
# Verify project
|
||||
- name: ${{ inputs.cargo_make_task }}
|
||||
run: |
|
||||
if [ "${{ inputs.directory }}" = "INTERNAL" ]; then
|
||||
echo No verification required
|
||||
else
|
||||
cd ${{ inputs.directory }}
|
||||
cargo make --profile=github-actions ${{ inputs.cargo_make_task }}
|
||||
fi
|
||||
47
.github/workflows/verify-all-examples.yml
vendored
Normal file
47
.github/workflows/verify-all-examples.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Verify All Examples
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
schedule:
|
||||
# Run once a day at 3:00 AM EST
|
||||
- cron: "0 8 * * *"
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
name: Get Examples
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install JQ Tool
|
||||
uses: mbround18/install-jq@v1
|
||||
|
||||
- name: Set Matrix
|
||||
id: set-matrix
|
||||
run: |
|
||||
examples=$(ls examples |
|
||||
awk '{print "examples/" $0}' |
|
||||
grep -v examples/README.md |
|
||||
grep -v examples/Makefile.toml |
|
||||
grep -v examples/cargo-make |
|
||||
grep -v examples/gtk |
|
||||
jq -R -s -c 'split("\n")[:-1]')
|
||||
echo "Example Directories: $examples"
|
||||
echo "matrix={\"directory\":$examples}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
matrix-job:
|
||||
name: Verify
|
||||
needs: [setup]
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
uses: ./.github/workflows/run-example-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
cargo_make_task: "verify-flow"
|
||||
71
.github/workflows/verify-changed-examples.yml
vendored
Normal file
71
.github/workflows/verify-changed-examples.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
name: Verify Changed Examples
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
name: Get Changes
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get all example files that changed
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v36
|
||||
with:
|
||||
files: |
|
||||
examples
|
||||
|
||||
- name: List all example files that changed
|
||||
run: echo '${{ steps.changed-files.outputs.all_changed_files }}'
|
||||
|
||||
- name: Get example project directories that changed
|
||||
id: changed-dirs
|
||||
uses: tj-actions/changed-files@v36
|
||||
with:
|
||||
dir_names: true
|
||||
dir_names_max_depth: "2"
|
||||
files: |
|
||||
examples
|
||||
!examples/cargo-make
|
||||
!examples/gtk
|
||||
!examples/Makefile.toml
|
||||
!examples/README.md
|
||||
json: true
|
||||
quotepath: false
|
||||
|
||||
- name: List example project directories that changed
|
||||
run: echo '${{ steps.changed-dirs.outputs.all_changed_files }}'
|
||||
|
||||
- name: Set Matrix
|
||||
id: set-matrix
|
||||
run: |
|
||||
if [ ${{ steps.changed-files.outputs.any_changed }} == 'true' ]; then
|
||||
# Create matrix with changed directories
|
||||
echo "matrix={\"directory\":${{ steps.changed-dirs.outputs.all_changed_files }}}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
# Create matrix with one item to prevent an empty vector error
|
||||
echo "matrix={\"directory\":[\"INTERNAL\"]}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
matrix-job:
|
||||
name: Verify
|
||||
needs: [setup]
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
uses: ./.github/workflows/run-example-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
cargo_make_task: "verify-flow"
|
||||
@@ -45,6 +45,8 @@ dependencies = [
|
||||
{ name = "check", path = "examples/fetch" },
|
||||
{ name = "check", path = "examples/hackernews" },
|
||||
{ name = "check", path = "examples/hackernews_axum" },
|
||||
{ name = "check", path = "examples/js-framework-benchmark" },
|
||||
{ name = "check", path = "examples/leptos-tailwind-axum" },
|
||||
{ name = "check", path = "examples/login_with_token_csr_only" },
|
||||
{ name = "check", path = "examples/parent_child" },
|
||||
{ name = "check", path = "examples/router" },
|
||||
@@ -54,6 +56,7 @@ dependencies = [
|
||||
{ name = "check", path = "examples/ssr_modes_axum" },
|
||||
{ name = "check", path = "examples/tailwind" },
|
||||
{ name = "check", path = "examples/tailwind_csr_trunk" },
|
||||
{ name = "check", path = "examples/timer" },
|
||||
{ name = "check", path = "examples/todo_app_sqlite" },
|
||||
{ name = "check", path = "examples/todo_app_sqlite_axum" },
|
||||
{ name = "check", path = "examples/todo_app_sqlite_viz" },
|
||||
@@ -102,6 +105,7 @@ args = ["make", "test-unit-and-web"]
|
||||
|
||||
[tasks.verify-examples]
|
||||
description = "Run all quality checks and tests for examples"
|
||||
env = { CLEAN_AFTER_VERIFY = "true" }
|
||||
cwd = "examples"
|
||||
command = "cargo"
|
||||
args = ["make", "verify-flow"]
|
||||
|
||||
@@ -47,6 +47,12 @@ Note that if you're using this with SSR too, the same Cargo profile will be appl
|
||||
target = "x86_64-unknown-linux-gnu" # or whatever
|
||||
```
|
||||
|
||||
Also note that in some cases, the cfg feature `has_std` will not be set, which may cause build errors with some dependencies which check for `has_std`. You may fix any build errors due to this by adding:
|
||||
```toml
|
||||
[build]
|
||||
rustflags = ["--cfg=has_std"]
|
||||
```
|
||||
|
||||
And you'll need to add `panic = "abort"` to `[profile.release]` in `Cargo.toml`. Note that this applies the same `build-std` and panic settings to your server binary, which may not be desirable. Some further exploration is probably needed here.
|
||||
|
||||
5. One of the sources of binary size in WASM binaries can be `serde` serialization/deserialization code. Leptos uses `serde` by default to serialize and deserialize resources created with `create_resource`. You might try experimenting with the `miniserde` and `serde-lite` features, which allow you to use those crates for serialization and deserialization instead; each only implements a subset of `serde`’s functionality, but typically optimizes for size over speed.
|
||||
|
||||
@@ -11,7 +11,7 @@ Actions and resources seem similar, but they represent fundamentally different t
|
||||
Say we have some `async` function we want to run.
|
||||
|
||||
```rust
|
||||
async fn add_todo(new_title: &str) -> Uuid {
|
||||
async fn add_todo_request(new_title: &str) -> Uuid {
|
||||
/* do some stuff on the server to add a new todo */
|
||||
}
|
||||
```
|
||||
@@ -41,16 +41,16 @@ async fn add_todo(new_title: &str) -> Uuid {
|
||||
So in this case, all we need to do to create an action is
|
||||
|
||||
```rust
|
||||
let add_todo = create_action(cx, |input: &String| {
|
||||
let add_todo_action = create_action(cx, |input: &String| {
|
||||
let input = input.to_owned();
|
||||
async move { add_todo(&input).await }
|
||||
async move { add_todo_request(&input).await }
|
||||
});
|
||||
```
|
||||
|
||||
Rather than calling `add_todo` directly, we’ll call it with `.dispatch()`, as in
|
||||
Rather than calling `add_todo_action` directly, we’ll call it with `.dispatch()`, as in
|
||||
|
||||
```rust
|
||||
add_todo.dispatch("Some value".to_string());
|
||||
add_todo_action.dispatch("Some value".to_string());
|
||||
```
|
||||
|
||||
You can do this from an event listener, a timeout, or anywhere; because `.dispatch()` isn’t an `async` function, it can be called from a synchronous context.
|
||||
@@ -58,9 +58,9 @@ You can do this from an event listener, a timeout, or anywhere; because `.dispat
|
||||
Actions provide access to a few signals that synchronize between the asynchronous action you’re calling and the synchronous reactive system:
|
||||
|
||||
```rust
|
||||
let submitted = add_todo.input(); // RwSignal<Option<String>>
|
||||
let pending = add_todo.pending(); // ReadSignal<bool>
|
||||
let todo_id = add_todo.value(); // RwSignal<Option<Uuid>>
|
||||
let submitted = add_todo_action.input(); // RwSignal<Option<String>>
|
||||
let pending = add_todo_action.pending(); // ReadSignal<bool>
|
||||
let todo_id = add_todo_action.value(); // RwSignal<Option<Uuid>>
|
||||
```
|
||||
|
||||
This makes it easy to track the current state of your request, show a loading indicator, or do “optimistic UI” based on the assumption that the submission will succeed.
|
||||
@@ -73,7 +73,7 @@ view! { cx,
|
||||
on:submit=move |ev| {
|
||||
ev.prevent_default(); // don't reload the page...
|
||||
let input = input_ref.get().expect("input to exist");
|
||||
add_todo.dispatch(input.value());
|
||||
add_todo_action.dispatch(input.value());
|
||||
}
|
||||
>
|
||||
<label>
|
||||
|
||||
@@ -15,16 +15,33 @@ CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = [
|
||||
"fetch",
|
||||
"hackernews",
|
||||
"hackernews_axum",
|
||||
"js-framework-benchmark",
|
||||
"leptos-tailwind-axum",
|
||||
"login_with_token_csr_only",
|
||||
"parent_child",
|
||||
"router",
|
||||
"session_auth_axum",
|
||||
"slots",
|
||||
"ssr_modes",
|
||||
"ssr_modes_axum",
|
||||
"tailwind",
|
||||
"tailwind_csr_trunk",
|
||||
"timer",
|
||||
"todo_app_sqlite",
|
||||
"todo_app_sqlite_axum",
|
||||
"todo_app_sqlite_viz",
|
||||
"todomvc",
|
||||
]
|
||||
|
||||
[tasks.gen-members]
|
||||
workspace = false
|
||||
description = "Generate the list of workspace members"
|
||||
script = '''
|
||||
examples=$(ls |
|
||||
grep -v README.md |
|
||||
grep -v Makefile.toml |
|
||||
grep -v cargo-make |
|
||||
grep -v gtk |
|
||||
jq -R -s -c 'split("\n")[:-1]')
|
||||
echo "CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = $examples"
|
||||
'''
|
||||
|
||||
@@ -6,7 +6,8 @@ description = "Check for style violations"
|
||||
dependencies = ["check-format-flow", "clippy-flow"]
|
||||
|
||||
[tasks.check-format]
|
||||
args = ["fmt", "--", "--check", "--config-path", "../../"]
|
||||
env = { LEPTOS_PROJECT_DIRECTORY = "../../" }
|
||||
args = ["fmt", "--", "--check", "--config-path", "${LEPTOS_PROJECT_DIRECTORY}"]
|
||||
|
||||
[tasks.clean-cargo]
|
||||
description = "Runs the cargo clean command."
|
||||
|
||||
@@ -15,7 +15,11 @@ dependencies = ["test-flow", "test-e2e-flow"]
|
||||
[tasks.pre-verify]
|
||||
|
||||
[tasks.post-verify]
|
||||
dependencies = ["clean-all"]
|
||||
dependencies = ["maybe-clean-all"]
|
||||
|
||||
[tasks.maybe-clean-all]
|
||||
description = "Used to clean up locally after call to verify-examples"
|
||||
condition = { env_true = ["CLEAN_AFTER_VERIFY"] }
|
||||
|
||||
[tasks.test-e2e-flow]
|
||||
description = "Provides pre and post hooks for test-e2e"
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
[tasks.test]
|
||||
env = { RUN_CARGO_TEST = false }
|
||||
condition = { env_true = ["RUN_CARGO_TEST"] }
|
||||
|
||||
[tasks.post-test]
|
||||
dependencies = ["test-wasm"]
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use counter_without_macros::counter;
|
||||
use leptos::*;
|
||||
|
||||
/// Show the counter
|
||||
pub fn main() {
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
@@ -5,10 +5,13 @@ extend = [
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
args = ["+nightly", "build-all-features", "--target", "wasm32-unknown-unknown"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.check]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "check-all-features"]
|
||||
args = ["+nightly", "check-all-features", "--target", "wasm32-unknown-unknown"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.pre-clippy]
|
||||
env = { CARGO_MAKE_CLIPPY_ARGS = "--all-targets --all-features --target wasm32-unknown-unknown -- -D warnings" }
|
||||
|
||||
11
examples/leptos-tailwind-axum/Makefile.toml
Normal file
11
examples/leptos-tailwind-axum/Makefile.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
extend = [{ path = "../cargo-make/main.toml" }]
|
||||
|
||||
[tasks.build]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.check]
|
||||
command = "cargo"
|
||||
args = ["+nightly", "check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
@@ -20,26 +20,25 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
|
||||
#[component]
|
||||
fn Home(cx: Scope) -> impl IntoView {
|
||||
|
||||
let (value, set_value) = create_signal(cx, 0);
|
||||
|
||||
// thanks to https://tailwindcomponents.com/component/blue-buttons-example for the showcase layout
|
||||
view! { cx,
|
||||
<Title text="Leptos + Tailwindcss"/>
|
||||
<main>
|
||||
<div class="bg-gradient-to-tl from-blue-800 to-blue-500 text-white font-mono flex flex-col min-h-screen">
|
||||
<div class="flex flex-row-reverse flex-wrap m-auto">
|
||||
<button on:click=move |_| set_value.update(|value| *value += 1) class="rounded px-3 py-2 m-1 border-b-4 border-l-2 shadow-lg bg-blue-700 border-blue-800 text-white">
|
||||
"+"
|
||||
</button>
|
||||
<button class="rounded px-3 py-2 m-1 border-b-4 border-l-2 shadow-lg bg-blue-800 border-blue-900 text-white">
|
||||
{value}
|
||||
</button>
|
||||
<button on:click=move |_| set_value.update(|value| *value -= 1) class="rounded px-3 py-2 m-1 border-b-4 border-l-2 shadow-lg bg-blue-700 border-blue-800 text-white">
|
||||
"-"
|
||||
</button>
|
||||
</div>
|
||||
<main>
|
||||
<div class="bg-gradient-to-tl from-blue-800 to-blue-500 text-white font-mono flex flex-col min-h-screen">
|
||||
<div class="flex flex-row-reverse flex-wrap m-auto">
|
||||
<button on:click=move |_| set_value.update(|value| *value += 1) class="rounded px-3 py-2 m-1 border-b-4 border-l-2 shadow-lg bg-blue-700 border-blue-800 text-white">
|
||||
"+"
|
||||
</button>
|
||||
<button class="rounded px-3 py-2 m-1 border-b-4 border-l-2 shadow-lg bg-blue-800 border-blue-900 text-white">
|
||||
{value}
|
||||
</button>
|
||||
<button on:click=move |_| set_value.update(|value| *value -= 1) class="rounded px-3 py-2 m-1 border-b-4 border-l-2 shadow-lg bg-blue-700 border-blue-800 text-white">
|
||||
"-"
|
||||
</button>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</main>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use cfg_if::cfg_if;
|
||||
use http::status::StatusCode;
|
||||
use leptos::*;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
use leptos_axum::ResponseOptions;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Clone, Debug, Error)]
|
||||
pub enum AppError {
|
||||
|
||||
@@ -4,13 +4,13 @@ async fn main() {
|
||||
use axum::{extract::Extension, routing::post, Router};
|
||||
use leptos::*;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use leptos_tailwind::{app::*, fileserv::file_and_error_handler};
|
||||
use log::info;
|
||||
use leptos_tailwind::app::*;
|
||||
use leptos_tailwind::fileserv::file_and_error_handler;
|
||||
use std::sync::Arc;
|
||||
|
||||
simple_logger::init_with_level(log::Level::Info).expect("couldn't initialize logging");
|
||||
|
||||
simple_logger::init_with_level(log::Level::Info)
|
||||
.expect("couldn't initialize logging");
|
||||
|
||||
// Setting get_configuration(None) means we'll be using cargo-leptos's env values
|
||||
// For deployment these variables are:
|
||||
// <https://github.com/leptos-rs/start-axum#executing-a-server-on-a-remote-machine-without-the-toolchain>
|
||||
@@ -24,7 +24,11 @@ async fn main() {
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
|
||||
.leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, <App/> })
|
||||
.leptos_routes(
|
||||
leptos_options.clone(),
|
||||
routes,
|
||||
|cx| view! { cx, <App/> },
|
||||
)
|
||||
.fallback(file_and_error_handler)
|
||||
.layer(Extension(Arc::new(leptos_options)));
|
||||
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
extend = { path = "../../cargo-make/main.toml" }
|
||||
|
||||
[tasks.check-format]
|
||||
env = { LEPTOS_PROJECT_DIRECTORY = "../../../" }
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
extend = { path = "../../cargo-make/main.toml" }
|
||||
|
||||
[tasks.check-format]
|
||||
env = { LEPTOS_PROJECT_DIRECTORY = "../../../" }
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
extend = { path = "../../cargo-make/main.toml" }
|
||||
|
||||
[tasks.check-format]
|
||||
env = { LEPTOS_PROJECT_DIRECTORY = "../../../" }
|
||||
|
||||
@@ -108,4 +108,4 @@ Many thanks to GreatGreg for putting together this guide. You can find the origi
|
||||
## Playwright Testing
|
||||
|
||||
- Run `cargo make setup` to install dependencies
|
||||
- Run `cargo leptos test` or `cargo leptos end-to-end` to run the tests
|
||||
- Run `cargo leptos test` or `cargo leptos end-to-end` to run the test
|
||||
|
||||
@@ -1105,19 +1105,19 @@ where
|
||||
pub trait Extractor<T> {
|
||||
type Future;
|
||||
|
||||
fn call(&self, args: T) -> Self::Future;
|
||||
fn call(self, args: T) -> Self::Future;
|
||||
}
|
||||
macro_rules! factory_tuple ({ $($param:ident)* } => {
|
||||
impl<Func, Fut, $($param,)*> Extractor<($($param,)*)> for Func
|
||||
where
|
||||
Func: Fn($($param),*) -> Fut + Clone + 'static,
|
||||
Func: FnOnce($($param),*) -> Fut + Clone + 'static,
|
||||
Fut: Future,
|
||||
{
|
||||
type Future = Fut;
|
||||
|
||||
#[inline]
|
||||
#[allow(non_snake_case)]
|
||||
fn call(&self, ($($param,)*): ($($param,)*)) -> Self::Future {
|
||||
fn call(self, ($($param,)*): ($($param,)*)) -> Self::Future {
|
||||
(self)($($param,)*)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1337,19 +1337,19 @@ pub trait Extractor<T, U>
|
||||
where
|
||||
T: FromRequestParts<()>,
|
||||
{
|
||||
fn call(&self, args: T) -> Pin<Box<dyn Future<Output = U>>>;
|
||||
fn call(self, args: T) -> Pin<Box<dyn Future<Output = U>>>;
|
||||
}
|
||||
|
||||
macro_rules! factory_tuple ({ $($param:ident)* } => {
|
||||
impl<Func, Fut, U, $($param,)*> Extractor<($($param,)*), U> for Func
|
||||
where
|
||||
$($param: FromRequestParts<()> + Send,)*
|
||||
Func: Fn($($param),*) -> Fut + 'static,
|
||||
Func: FnOnce($($param),*) -> Fut + 'static,
|
||||
Fut: Future<Output = U> + 'static,
|
||||
{
|
||||
#[inline]
|
||||
#[allow(non_snake_case)]
|
||||
fn call(&self, ($($param,)*): ($($param,)*)) -> Pin<Box<dyn Future<Output = U>>> {
|
||||
fn call(self, ($($param,)*): ($($param,)*)) -> Pin<Box<dyn Future<Output = U>>> {
|
||||
Box::pin((self)($($param,)*))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::Children;
|
||||
use leptos_dom::{Errors, IntoView};
|
||||
use leptos_dom::{Errors, HydrationCtx, IntoView};
|
||||
use leptos_macro::{component, view};
|
||||
use leptos_reactive::{
|
||||
create_rw_signal, provide_context, signal_prelude::*, RwSignal, Scope,
|
||||
@@ -28,7 +28,7 @@ use leptos_reactive::{
|
||||
/// }
|
||||
/// # });
|
||||
/// ```
|
||||
#[component(transparent)]
|
||||
#[component]
|
||||
pub fn ErrorBoundary<F, IV>(
|
||||
cx: Scope,
|
||||
/// The components inside the tag which will get rendered
|
||||
@@ -40,6 +40,7 @@ where
|
||||
F: Fn(Scope, RwSignal<Errors>) -> IV + 'static,
|
||||
IV: IntoView,
|
||||
{
|
||||
_ = HydrationCtx::next_component();
|
||||
let errors: RwSignal<Errors> = create_rw_signal(cx, Errors::default());
|
||||
|
||||
provide_context(cx, errors);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use leptos_dom::{Fragment, IntoView, View};
|
||||
use leptos_macro::component;
|
||||
use leptos_reactive::{use_context, Scope, SignalSetter, SuspenseContext};
|
||||
use leptos_reactive::{
|
||||
create_isomorphic_effect, use_context, Scope, SignalGet, SignalSetter,
|
||||
SuspenseContext,
|
||||
};
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
rc::Rc,
|
||||
@@ -97,9 +100,6 @@ where
|
||||
is_first_run(&first_run, &suspense_context);
|
||||
first_run.set(is_first_run);
|
||||
|
||||
if let Some(set_pending) = &set_pending {
|
||||
set_pending.set(true);
|
||||
}
|
||||
if let Some(prev_children) = &*prev_child.borrow() {
|
||||
if is_first_run {
|
||||
fallback().into_view(cx)
|
||||
@@ -132,9 +132,12 @@ where
|
||||
}
|
||||
child_runs.set(child_runs.get() + 1);
|
||||
|
||||
if let Some(set_pending) = &set_pending {
|
||||
set_pending.set(false);
|
||||
}
|
||||
let pending = suspense_context.pending_resources;
|
||||
create_isomorphic_effect(cx, move |_| {
|
||||
if let Some(set_pending) = set_pending {
|
||||
set_pending.set(pending.get() > 0)
|
||||
}
|
||||
});
|
||||
frag
|
||||
}))
|
||||
.build(),
|
||||
|
||||
@@ -26,117 +26,118 @@ fn main() {
|
||||
mount_to_body(view_fn);
|
||||
}
|
||||
|
||||
// fn view_fn(cx: Scope) -> impl IntoView {
|
||||
// view! { cx,
|
||||
// <h2>"Passing Tests"</h2>
|
||||
// <ul>
|
||||
// /* These work! */
|
||||
// <Test from=[1] to=[] />
|
||||
// <Test from=[1, 2] to=[] />
|
||||
// <Test from=[1, 2, 3] to=[] />
|
||||
// <hr/>
|
||||
// <Test from=[] to=[1] />
|
||||
// <Test from=[1, 2] to=[1] />
|
||||
// <Test from=[2, 1] to=[1] />
|
||||
// <hr/>
|
||||
// <Test from=[1, 2, 3] to=[1, 2] />
|
||||
// <Test from=[2] to=[1, 2] />
|
||||
// <Test from=[1] to=[1, 2] />
|
||||
// <Test from=[] to=[1, 2, 3] />
|
||||
// <Test from=[2] to=[1, 2, 3] />
|
||||
// <Test from=[1] to=[1, 2, 3] />
|
||||
// <Test from=[1, 3, 2] to=[1, 2, 3] />
|
||||
// <Test from=[2, 1, 3] to=[1, 2, 3] />
|
||||
// </ul>
|
||||
// <h2>"Broken Tests"</h2>
|
||||
// <ul>
|
||||
// <Test from=[3] to=[1, 2, 3] />
|
||||
// <Test from=[3, 1] to=[1, 2, 3] />
|
||||
// <Test from=[3, 2, 1] to=[1, 2, 3] />
|
||||
// <hr/>
|
||||
// <Test from=[1, 4, 2, 3] to=[1, 2, 3, 4] />
|
||||
// <hr/>
|
||||
// <Test from=[1, 4, 3, 2, 5] to=[1, 2, 3, 4, 5] />
|
||||
// <Test from=[4, 5, 3, 1, 2] to=[1, 2, 3, 4, 5] />
|
||||
// </ul>
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[component]
|
||||
// fn Test<From, To>(cx: Scope, from: From, to: To) -> impl IntoView
|
||||
// where
|
||||
// From: IntoIterator<Item = usize>,
|
||||
// To: IntoIterator<Item = usize>,
|
||||
// {
|
||||
// let from = from.into_iter().collect::<Vec<_>>();
|
||||
// let to = to.into_iter().collect::<Vec<_>>();
|
||||
|
||||
// let (list, set_list) = create_signal(cx, from.clone());
|
||||
// request_animation_frame({
|
||||
// let to = to.clone();
|
||||
// move || {
|
||||
// set_list(to);
|
||||
// }
|
||||
// });
|
||||
|
||||
// view! { cx,
|
||||
// <li>
|
||||
// "from: [" {move ||
|
||||
// from
|
||||
// .iter()
|
||||
// .map(ToString::to_string)
|
||||
// .intersperse(", ".to_string())
|
||||
// .collect::<String>()
|
||||
// } "]"
|
||||
// <br />
|
||||
// "to: [" {move ||
|
||||
// to
|
||||
// .iter()
|
||||
// .map(ToString::to_string)
|
||||
// .intersperse(", ".to_string())
|
||||
// .collect::<String>()
|
||||
// } "]"
|
||||
// <br />
|
||||
// "result: ["
|
||||
// <For
|
||||
// each=list
|
||||
// key=|i| *i
|
||||
// view=|cx, i| {
|
||||
// view! { cx, <span>{i} ", "</span> }
|
||||
// }
|
||||
// /> "]"
|
||||
// /* <p>
|
||||
// "Pre | "
|
||||
// <For
|
||||
// each=list
|
||||
// key=|i| *i
|
||||
// view=|cx, i| {
|
||||
// view! { cx, <span>{i}</span> }
|
||||
// }
|
||||
// />
|
||||
// " | Post"
|
||||
// </p> */
|
||||
// </li>
|
||||
// }
|
||||
// }
|
||||
|
||||
fn view_fn(cx: Scope) -> impl IntoView {
|
||||
let (should_show_a, sett_should_show_a) = create_signal(cx, true);
|
||||
|
||||
let a = vec![1, 2, 3, 4];
|
||||
let b = vec![1, 2, 3];
|
||||
|
||||
view! { cx,
|
||||
<button on:click=move |_| sett_should_show_a.update(|show| *show = !*show)>"Toggle"</button>
|
||||
|
||||
<For
|
||||
each={move || if should_show_a.get() {
|
||||
a.clone()
|
||||
} else {
|
||||
b.clone()
|
||||
}}
|
||||
key=|i| *i
|
||||
view=|cx, i| view! { cx, <h1>{i}</h1> }
|
||||
/>
|
||||
<h2>"Passing Tests"</h2>
|
||||
<ul>
|
||||
<Test from=[1] to=[]/>
|
||||
<Test from=[1, 2] to=[3, 2] then=vec![2]/>
|
||||
<Test from=[1, 2] to=[]/>
|
||||
<Test from=[1, 2, 3] to=[]/>
|
||||
<hr/>
|
||||
<Test from=[] to=[1]/>
|
||||
<Test from=[1, 2] to=[1]/>
|
||||
<Test from=[2, 1] to=[1]/>
|
||||
<hr/>
|
||||
<Test from=[1, 2, 3] to=[1, 2]/>
|
||||
<Test from=[2] to=[1, 2]/>
|
||||
<Test from=[1] to=[1, 2]/>
|
||||
<Test from=[] to=[1, 2, 3]/>
|
||||
<Test from=[2] to=[1, 2, 3]/>
|
||||
<Test from=[1] to=[1, 2, 3]/>
|
||||
<Test from=[1, 3, 2] to=[1, 2, 3]/>
|
||||
<Test from=[2, 1, 3] to=[1, 2, 3]/>
|
||||
<Test from=[3] to=[1, 2, 3]/>
|
||||
<Test from=[3, 1] to=[1, 2, 3]/>
|
||||
<Test from=[3, 2, 1] to=[1, 2, 3]/>
|
||||
<hr/>
|
||||
<Test from=[1, 4, 2, 3] to=[1, 2, 3, 4]/>
|
||||
<hr/>
|
||||
<Test from=[1, 4, 3, 2, 5] to=[1, 2, 3, 4, 5]/>
|
||||
<Test from=[4, 5, 3, 1, 2] to=[1, 2, 3, 4, 5]/>
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Test<From, To>(
|
||||
cx: Scope,
|
||||
from: From,
|
||||
to: To,
|
||||
#[prop(optional)] then: Option<Vec<usize>>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
From: IntoIterator<Item = usize>,
|
||||
To: IntoIterator<Item = usize>,
|
||||
{
|
||||
let from = from.into_iter().collect::<Vec<_>>();
|
||||
let to = to.into_iter().collect::<Vec<_>>();
|
||||
|
||||
let (list, set_list) = create_signal(cx, from.clone());
|
||||
request_animation_frame({
|
||||
let to = to.clone();
|
||||
let then = then.clone();
|
||||
move || {
|
||||
set_list(to);
|
||||
|
||||
if let Some(then) = then {
|
||||
request_animation_frame({
|
||||
move || {
|
||||
set_list(then);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
view! { cx,
|
||||
<li>
|
||||
"from: [" {move || {
|
||||
from
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.intersperse(", ".to_string())
|
||||
.collect::<String>()
|
||||
}} "]" <br/> "to: [" {
|
||||
let then = then.clone();
|
||||
move || {
|
||||
then
|
||||
.clone()
|
||||
.unwrap_or(to.iter().copied().collect())
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.intersperse(", ".to_string())
|
||||
.collect::<String>()
|
||||
}
|
||||
} "]" <br/> "result: ["
|
||||
<For
|
||||
each=list
|
||||
key=|i| *i
|
||||
view=|cx, i| {
|
||||
view! { cx, <span>{i} ", "</span> }
|
||||
}
|
||||
/> "]"
|
||||
</li>
|
||||
}
|
||||
}
|
||||
|
||||
// fn view_fn(cx: Scope) -> impl IntoView {
|
||||
// let (should_show_a, sett_should_show_a) = create_signal(cx, true);
|
||||
|
||||
// let a = vec![2];
|
||||
// let b = vec![1, 2, 3];
|
||||
|
||||
// view! { cx,
|
||||
// <button on:click=move |_| sett_should_show_a.update(|show| *show = !*show)>"Toggle"</button>
|
||||
|
||||
// <For
|
||||
// each={move || if should_show_a.get() {
|
||||
// a.clone()
|
||||
// } else {
|
||||
// b.clone()
|
||||
// }}
|
||||
// key=|i| *i
|
||||
// view=|cx, i| view! { cx, <h1>{i}</h1> }
|
||||
// />
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -273,7 +273,8 @@ where
|
||||
// I can imagine some edge case that the child changes while
|
||||
// hydration is ongoing
|
||||
if !HydrationCtx::is_hydrating() {
|
||||
if !was_child_moved && child != new_child {
|
||||
let same_child = child == new_child;
|
||||
if !was_child_moved && !same_child {
|
||||
// Remove the child
|
||||
let start = child.get_opening_node();
|
||||
let end = &closing;
|
||||
@@ -308,10 +309,13 @@ where
|
||||
}
|
||||
|
||||
// Mount the new child
|
||||
mount_child(
|
||||
MountKind::Before(&closing),
|
||||
&new_child,
|
||||
);
|
||||
// If it's the same child, don't re-mount
|
||||
if !same_child {
|
||||
mount_child(
|
||||
MountKind::Before(&closing),
|
||||
&new_child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// We want to reuse text nodes, so hold onto it if
|
||||
|
||||
@@ -800,8 +800,11 @@ fn apply_diff<T, EF, V>(
|
||||
// The order of cmds needs to be:
|
||||
// 1. Clear
|
||||
// 2. Removals
|
||||
// 3. Remove holes left from removals
|
||||
// 4. Moves + Add
|
||||
// 3. Move out
|
||||
// 4. Resize
|
||||
// 5. Move in
|
||||
// 6. Additions
|
||||
// 7. Removes holes
|
||||
if diff.clear {
|
||||
if opening.previous_sibling().is_none()
|
||||
&& closing.next_sibling().is_none()
|
||||
@@ -818,32 +821,25 @@ fn apply_diff<T, EF, V>(
|
||||
#[cfg(not(debug_assertions))]
|
||||
parent.append_with_node_1(closing).unwrap();
|
||||
} else {
|
||||
range.set_start_before(opening).unwrap();
|
||||
range.set_start_after(opening).unwrap();
|
||||
range.set_end_before(closing).unwrap();
|
||||
|
||||
range.delete_contents().unwrap();
|
||||
}
|
||||
|
||||
return;
|
||||
children.clear();
|
||||
|
||||
if diff.added.is_empty() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for DiffOpRemove { at } in &diff.removed {
|
||||
let item_to_remove = std::mem::take(&mut children[*at]).unwrap();
|
||||
let item_to_remove = children[*at].take().unwrap();
|
||||
|
||||
item_to_remove.prepare_for_move();
|
||||
}
|
||||
|
||||
// Now, remove the holes that might have been left from removing
|
||||
// items
|
||||
#[allow(unstable_name_collisions)]
|
||||
children.drain_filter(|c| c.is_none());
|
||||
|
||||
// Resize children if needed
|
||||
if let Some(added) = diff.added.len().checked_sub(diff.removed.len()) {
|
||||
let target_size = children.len() + added;
|
||||
children.resize_with(target_size, || None);
|
||||
}
|
||||
|
||||
let (move_cmds, add_cmds) = unpack_moves(&diff);
|
||||
|
||||
let mut moved_children = move_cmds
|
||||
@@ -859,6 +855,8 @@ fn apply_diff<T, EF, V>(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
children.resize_with(children.len() + diff.added.len(), || None);
|
||||
|
||||
for (i, DiffOpMove { to, .. }) in move_cmds
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -906,23 +904,36 @@ fn apply_diff<T, EF, V>(
|
||||
|
||||
children[at] = Some(each_item);
|
||||
}
|
||||
|
||||
#[allow(unstable_name_collisions)]
|
||||
children.drain_filter(|c| c.is_none());
|
||||
}
|
||||
|
||||
/// Unpacks adds and moves into a sequence of interleaved
|
||||
/// add and move commands. Move commands will always return
|
||||
/// with a `len == 1` and `is_dense = true`.
|
||||
/// with a `len == 1`.
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
fn unpack_moves(diff: &Diff) -> (Vec<DiffOpMove>, Vec<DiffOpAdd>) {
|
||||
let mut moves = Vec::with_capacity(diff.items_to_move);
|
||||
let mut adds = Vec::with_capacity(diff.added.len());
|
||||
|
||||
let mut removes_iter = diff.removed.iter();
|
||||
let mut adds_iter = diff.added.iter();
|
||||
let mut moves_iter = diff.moved.iter();
|
||||
|
||||
let mut removes_next = removes_iter.next();
|
||||
let mut adds_next = adds_iter.next();
|
||||
let mut moves_next = moves_iter.next().copied();
|
||||
|
||||
for i in 0..diff.items_to_move + diff.added.len() {
|
||||
for i in 0..diff.items_to_move + diff.added.len() + diff.removed.len() {
|
||||
if let Some(DiffOpRemove { at, .. }) = removes_next {
|
||||
if i == *at {
|
||||
removes_next = removes_iter.next();
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
match (adds_next, &mut moves_next) {
|
||||
(Some(add), Some(move_)) => {
|
||||
if add.at == i {
|
||||
|
||||
@@ -80,7 +80,7 @@ where
|
||||
E: Error + Send + Sync + 'static,
|
||||
{
|
||||
fn into_view(self, cx: leptos_reactive::Scope) -> crate::View {
|
||||
let id = ErrorKey(HydrationCtx::peek().id.to_string().into());
|
||||
let id = ErrorKey(HydrationCtx::peek().fragment.to_string().into());
|
||||
let errors = use_context::<RwSignal<Errors>>(cx);
|
||||
match self {
|
||||
Ok(stuff) => {
|
||||
|
||||
@@ -20,10 +20,9 @@ pub fn block_to_primitive_expression(block: &syn::Block) -> Option<&syn::Expr> {
|
||||
return None;
|
||||
}
|
||||
match &block.stmts[0] {
|
||||
syn::Stmt::Expr(e, None) => return Some(e),
|
||||
_ => {}
|
||||
syn::Stmt::Expr(e, None) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Converts simple literals to its string representation.
|
||||
|
||||
@@ -847,7 +847,7 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
/// 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
|
||||
/// ```rust,ignore
|
||||
/// #[server(AddTodo, "/api", "Url")]
|
||||
/// #[server(AddTodo, "/api", "GetJson")]
|
||||
/// #[server(AddTodo, "/api", "Cbor")]
|
||||
|
||||
@@ -10,7 +10,7 @@ use rstml::node::{
|
||||
KeyedAttribute, Node, NodeAttribute, NodeBlock, NodeElement, NodeName,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use syn::{spanned::Spanned, Expr, ExprLit, ExprPath, Lit};
|
||||
use syn::{spanned::Spanned, Expr, Expr::Tuple, ExprLit, ExprPath, Lit};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum TagType {
|
||||
@@ -427,14 +427,14 @@ fn element_to_tokens_ssr(
|
||||
{#component}.into_view(#cx)
|
||||
}));
|
||||
} else {
|
||||
let tag_name = node
|
||||
.name()
|
||||
.to_string()
|
||||
.replace("svg::", "")
|
||||
.replace("math::", "");
|
||||
let tag_name = node.name().to_string();
|
||||
let tag_name = tag_name
|
||||
.trim_start_matches("svg::")
|
||||
.trim_start_matches("math::")
|
||||
.trim_end_matches('_');
|
||||
let is_script_or_style = tag_name == "script" || tag_name == "style";
|
||||
template.push('<');
|
||||
template.push_str(&tag_name);
|
||||
template.push_str(tag_name);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
stmts_for_ide.save_element_completion(node);
|
||||
@@ -1964,41 +1964,39 @@ fn fancy_class_name<'a>(
|
||||
// special case for complex class names:
|
||||
// e.g., Tailwind `class=("mt-[calc(100vh_-_3rem)]", true)`
|
||||
if name == "class" {
|
||||
if let Some(expr) = node.value() {
|
||||
if let syn::Expr::Tuple(tuple) = expr {
|
||||
if tuple.elems.len() == 2 {
|
||||
let span = node.key.span();
|
||||
let class = quote_spanned! {
|
||||
span => .class
|
||||
};
|
||||
let class_name = &tuple.elems[0];
|
||||
let class_name = if let Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(s),
|
||||
..
|
||||
}) = class_name
|
||||
{
|
||||
s.value()
|
||||
} else {
|
||||
proc_macro_error::emit_error!(
|
||||
class_name.span(),
|
||||
"class name must be a string literal"
|
||||
);
|
||||
Default::default()
|
||||
};
|
||||
let value = &tuple.elems[1];
|
||||
return Some((
|
||||
quote! {
|
||||
#class(#class_name, (#cx, #value))
|
||||
},
|
||||
class_name,
|
||||
value,
|
||||
));
|
||||
if let Some(Tuple(tuple)) = node.value() {
|
||||
if tuple.elems.len() == 2 {
|
||||
let span = node.key.span();
|
||||
let class = quote_spanned! {
|
||||
span => .class
|
||||
};
|
||||
let class_name = &tuple.elems[0];
|
||||
let class_name = if let Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(s),
|
||||
..
|
||||
}) = class_name
|
||||
{
|
||||
s.value()
|
||||
} else {
|
||||
proc_macro_error::emit_error!(
|
||||
tuple.span(),
|
||||
"class tuples must have two elements."
|
||||
)
|
||||
}
|
||||
class_name.span(),
|
||||
"class name must be a string literal"
|
||||
);
|
||||
Default::default()
|
||||
};
|
||||
let value = &tuple.elems[1];
|
||||
return Some((
|
||||
quote! {
|
||||
#class(#class_name, (#cx, #value))
|
||||
},
|
||||
class_name,
|
||||
value,
|
||||
));
|
||||
} else {
|
||||
proc_macro_error::emit_error!(
|
||||
tuple.span(),
|
||||
"class tuples must have two elements."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2012,41 +2010,39 @@ fn fancy_style_name<'a>(
|
||||
) -> Option<(TokenStream, String, &'a Expr)> {
|
||||
// special case for complex dynamic style names:
|
||||
if name == "style" {
|
||||
if let Some(expr) = node.value() {
|
||||
if let syn::Expr::Tuple(tuple) = expr {
|
||||
if tuple.elems.len() == 2 {
|
||||
let span = node.key.span();
|
||||
let style = quote_spanned! {
|
||||
span => .style
|
||||
};
|
||||
let style_name = &tuple.elems[0];
|
||||
let style_name = if let Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(s),
|
||||
..
|
||||
}) = style_name
|
||||
{
|
||||
s.value()
|
||||
} else {
|
||||
proc_macro_error::emit_error!(
|
||||
style_name.span(),
|
||||
"style name must be a string literal"
|
||||
);
|
||||
Default::default()
|
||||
};
|
||||
let value = &tuple.elems[1];
|
||||
return Some((
|
||||
quote! {
|
||||
#style(#style_name, (#cx, #value))
|
||||
},
|
||||
style_name,
|
||||
value,
|
||||
));
|
||||
if let Some(Tuple(tuple)) = node.value() {
|
||||
if tuple.elems.len() == 2 {
|
||||
let span = node.key.span();
|
||||
let style = quote_spanned! {
|
||||
span => .style
|
||||
};
|
||||
let style_name = &tuple.elems[0];
|
||||
let style_name = if let Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(s),
|
||||
..
|
||||
}) = style_name
|
||||
{
|
||||
s.value()
|
||||
} else {
|
||||
proc_macro_error::emit_error!(
|
||||
tuple.span(),
|
||||
"style tuples must have two elements."
|
||||
)
|
||||
}
|
||||
style_name.span(),
|
||||
"style name must be a string literal"
|
||||
);
|
||||
Default::default()
|
||||
};
|
||||
let value = &tuple.elems[1];
|
||||
return Some((
|
||||
quote! {
|
||||
#style(#style_name, (#cx, #value))
|
||||
},
|
||||
style_name,
|
||||
value,
|
||||
));
|
||||
} else {
|
||||
proc_macro_error::emit_error!(
|
||||
tuple.span(),
|
||||
"style tuples must have two elements."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ indexmap = "1"
|
||||
self_cell = "1.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.4.0", features = ["html_reports"] }
|
||||
criterion = { version = "0.5.1", features = ["html_reports"] }
|
||||
reactive-signals = { version = "0.1.0-alpha.4", features = ["profile"] }
|
||||
l021 = { package = "leptos", version = "0.2.1" }
|
||||
sycamore = { version = "0.8", features = ["ssr"] }
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
//! 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
|
||||
//! ```rust,ignore
|
||||
//! #[server(AddTodo, "/api", "Url")]
|
||||
//! #[server(AddTodo, "/api", "GetJson")]
|
||||
//! #[server(AddTodo, "/api", "Cbor")]
|
||||
@@ -161,7 +161,7 @@ impl ServerFnTraitObj {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "ssr"))]
|
||||
#[cfg(feature = "ssr")]
|
||||
inventory::collect!(ServerFnTraitObj);
|
||||
|
||||
#[allow(unused)]
|
||||
|
||||
@@ -10,7 +10,7 @@ description = "Router for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
leptos = { workspace = true }
|
||||
cached = { version = "0.43.0", optional = true }
|
||||
cached = { version = "0.44.0", optional = true }
|
||||
cfg-if = "1"
|
||||
common_macros = "0.1"
|
||||
gloo-net = { version = "0.2", features = ["http"] }
|
||||
|
||||
@@ -692,6 +692,6 @@ where
|
||||
web_sys::UrlSearchParams::new_with_str_sequence_sequence(form_data)
|
||||
.unwrap_throw();
|
||||
let data = data.to_string().as_string().unwrap_or_default();
|
||||
serde_qs::from_str::<Self>(&data)
|
||||
serde_qs::Config::new(5, false).deserialize_str::<Self>(&data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,9 +278,12 @@ impl RouterContextInner {
|
||||
let global_suspense =
|
||||
expect_context::<GlobalSuspenseContext>(cx);
|
||||
let path_stack = self.path_stack;
|
||||
path_stack.update_value(|stack| {
|
||||
stack.push(resolved_to.clone())
|
||||
});
|
||||
let is_navigating_back = self.is_back.get_untracked();
|
||||
if !is_navigating_back {
|
||||
path_stack.update_value(|stack| {
|
||||
stack.push(resolved_to.clone())
|
||||
});
|
||||
}
|
||||
|
||||
let set_is_routing = use_context::<SetIsRouting>(cx);
|
||||
if let Some(set_is_routing) = set_is_routing {
|
||||
|
||||
@@ -64,7 +64,9 @@ impl History for BrowserIntegration {
|
||||
|
||||
let is_navigating_back = path_stack.with_value(|stack| {
|
||||
stack.len() == 1
|
||||
|| stack.get(stack.len() - 2) == Some(&change.value)
|
||||
|| (stack.len() >= 2
|
||||
&& stack.get(stack.len() - 2)
|
||||
== Some(&change.value))
|
||||
});
|
||||
if is_navigating_back {
|
||||
path_stack.update_value(|stack| {
|
||||
|
||||
@@ -18,7 +18,7 @@ lazy_static::lazy_static! {
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "ssr"))]
|
||||
#[cfg(feature = "ssr")]
|
||||
inventory::collect!(DefaultServerFnTraitObj);
|
||||
|
||||
/// Attempts to find a server function registered at the given path.
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
#[doc(hidden)]
|
||||
pub use const_format;
|
||||
// used by the macro
|
||||
#[cfg(any(feature = "ssr"))]
|
||||
#[cfg(feature = "ssr")]
|
||||
#[doc(hidden)]
|
||||
pub use inventory;
|
||||
#[cfg(any(feature = "ssr", doc))]
|
||||
@@ -375,7 +375,8 @@ where
|
||||
// decode the args
|
||||
let value = match Self::encoding() {
|
||||
Encoding::Url | Encoding::GetJSON | Encoding::GetCBOR => {
|
||||
serde_qs::from_bytes(data)
|
||||
serde_qs::Config::new(5, false)
|
||||
.deserialize_bytes(data)
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
}
|
||||
Encoding::Cbor => ciborium::de::from_reader(data)
|
||||
|
||||
Reference in New Issue
Block a user