mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 09:02:37 -05:00
Compare commits
1 Commits
timings
...
actionform
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a787f21a49 |
13
.github/dependabot.yml
vendored
13
.github/dependabot.yml
vendored
@@ -1,13 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "cargo"
|
||||
directories:
|
||||
- "/"
|
||||
- "/examples/*"
|
||||
- "/benchmarks"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
6
.github/workflows/ci-changed-examples.yml
vendored
6
.github/workflows/ci-changed-examples.yml
vendored
@@ -1,21 +1,23 @@
|
||||
name: CI Changed Examples
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
|
||||
jobs:
|
||||
get-example-changed:
|
||||
uses: ./.github/workflows/get-example-changed.yml
|
||||
|
||||
get-matrix:
|
||||
needs: [get-example-changed]
|
||||
uses: ./.github/workflows/get-changed-examples-matrix.yml
|
||||
with:
|
||||
example_changed: ${{ fromJSON(needs.get-example-changed.outputs.example_changed) }}
|
||||
|
||||
test:
|
||||
name: CI
|
||||
needs: [get-example-changed, get-matrix]
|
||||
|
||||
4
.github/workflows/ci-examples.yml
vendored
4
.github/workflows/ci-examples.yml
vendored
@@ -1,13 +1,13 @@
|
||||
name: CI Examples
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
|
||||
jobs:
|
||||
get-leptos-changed:
|
||||
uses: ./.github/workflows/get-leptos-changed.yml
|
||||
|
||||
13
.github/workflows/ci-semver.yml
vendored
13
.github/workflows/ci-semver.yml
vendored
@@ -1,25 +1,28 @@
|
||||
name: CI semver
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
|
||||
jobs:
|
||||
get-leptos-changed:
|
||||
uses: ./.github/workflows/get-leptos-changed.yml
|
||||
|
||||
test:
|
||||
needs: [get-leptos-changed]
|
||||
if: github.event.pull_request.labels[0].name == 'semver' # needs.get-leptos-changed.outputs.leptos_changed == 'true' && github.event.pull_request.labels[0].name != 'breaking'
|
||||
name: Run semver check (nightly-2024-08-01)
|
||||
if: needs.get-leptos-changed.outputs.leptos_changed == 'true'
|
||||
name: Run semver check (nightly-2024-04-14)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Semver Checks
|
||||
uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
with:
|
||||
rust-toolchain: nightly-2024-08-01
|
||||
rust-toolchain: nightly-2024-04-14
|
||||
|
||||
32
.github/workflows/ci.yml
vendored
32
.github/workflows/ci.yml
vendored
@@ -1,27 +1,43 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
|
||||
jobs:
|
||||
get-leptos-changed:
|
||||
uses: ./.github/workflows/get-leptos-changed.yml
|
||||
get-leptos-matrix:
|
||||
uses: ./.github/workflows/get-leptos-matrix.yml
|
||||
|
||||
test:
|
||||
name: CI
|
||||
needs: [get-leptos-changed, get-leptos-matrix]
|
||||
needs: [get-leptos-changed]
|
||||
if: needs.get-leptos-changed.outputs.leptos_changed == 'true'
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.get-leptos-matrix.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
matrix:
|
||||
directory:
|
||||
[
|
||||
integrations/actix,
|
||||
integrations/axum,
|
||||
integrations/utils,
|
||||
leptos,
|
||||
leptos_config,
|
||||
leptos_dom,
|
||||
leptos_hot_reload,
|
||||
leptos_macro,
|
||||
leptos_reactive,
|
||||
leptos_server,
|
||||
meta,
|
||||
router,
|
||||
server_fn,
|
||||
server_fn/server_fn_macro_default,
|
||||
server_fn_macro,
|
||||
]
|
||||
uses: ./.github/workflows/run-cargo-make-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
cargo_make_task: "ci"
|
||||
toolchain: nightly-2024-08-01
|
||||
toolchain: nightly-2024-04-14
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
- name: Get example project directories that changed
|
||||
id: changed-dirs
|
||||
uses: tj-actions/changed-files@v45
|
||||
uses: tj-actions/changed-files@v41
|
||||
with:
|
||||
dir_names: true
|
||||
dir_names_max_depth: "2"
|
||||
|
||||
8
.github/workflows/get-example-changed.yml
vendored
8
.github/workflows/get-example-changed.yml
vendored
@@ -1,10 +1,12 @@
|
||||
name: Examples Changed Call
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
outputs:
|
||||
example_changed:
|
||||
description: "Example Changed"
|
||||
value: ${{ jobs.get-example-changed.outputs.example_changed }}
|
||||
|
||||
jobs:
|
||||
get-example-changed:
|
||||
name: Get Example Changed
|
||||
@@ -16,17 +18,21 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get example files that changed
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v45
|
||||
uses: tj-actions/changed-files@v43
|
||||
with:
|
||||
files: |
|
||||
examples/**
|
||||
!examples/cargo-make/**
|
||||
!examples/gtk/**
|
||||
!examples/Makefile.toml
|
||||
!examples/*.md
|
||||
|
||||
- name: List example files that changed
|
||||
run: echo '${{ steps.changed-files.outputs.all_changed_files }}'
|
||||
|
||||
- name: Set example_changed
|
||||
id: set-example-changed
|
||||
run: |
|
||||
|
||||
18
.github/workflows/get-examples-matrix.yml
vendored
18
.github/workflows/get-examples-matrix.yml
vendored
@@ -1,34 +1,38 @@
|
||||
name: Get Examples Matrix Call
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
outputs:
|
||||
matrix:
|
||||
description: "Matrix"
|
||||
value: ${{ jobs.create.outputs.matrix }}
|
||||
|
||||
jobs:
|
||||
create:
|
||||
name: Create Examples Matrix
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
env:
|
||||
# separate examples using "|" (vertical bar) char like "a|b|c".
|
||||
# cargo-make should be excluded by default.
|
||||
EXCLUDED_EXAMPLES: cargo-make
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install jq
|
||||
run: sudo apt-get install jq
|
||||
|
||||
- name: Set Matrix
|
||||
id: set-matrix
|
||||
run: |
|
||||
examples=$(ls -1d examples/*/ |
|
||||
grep -vE "($EXCLUDED_EXAMPLES)" |
|
||||
sed 's/\/$//' |
|
||||
examples=$(ls examples |
|
||||
awk '{print "examples/" $0}' |
|
||||
grep -v .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: Print Location Info
|
||||
run: |
|
||||
echo "Workspace: ${{ github.workspace }}"
|
||||
|
||||
27
.github/workflows/get-leptos-changed.yml
vendored
27
.github/workflows/get-leptos-changed.yml
vendored
@@ -1,10 +1,12 @@
|
||||
name: Get Leptos Changed Call
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
outputs:
|
||||
leptos_changed:
|
||||
description: "Leptos Changed"
|
||||
value: ${{ jobs.create.outputs.leptos_changed }}
|
||||
|
||||
jobs:
|
||||
create:
|
||||
name: Detect Source Change
|
||||
@@ -16,19 +18,28 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get source files that changed
|
||||
id: changed-source
|
||||
uses: tj-actions/changed-files@v45
|
||||
uses: tj-actions/changed-files@v43
|
||||
with:
|
||||
files_ignore: |
|
||||
.*/**/*
|
||||
cargo-make/**/*
|
||||
examples/**/*
|
||||
projects/**/*
|
||||
benchmarks/**/*
|
||||
docs/**/*
|
||||
files: |
|
||||
integrations/**
|
||||
leptos/**
|
||||
leptos_config/**
|
||||
leptos_dom/**
|
||||
leptos_hot_reload/**
|
||||
leptos_macro/**
|
||||
leptos_reactive/**
|
||||
leptos_server/**
|
||||
meta/**
|
||||
router/**
|
||||
server_fn/**
|
||||
server_fn_macro/**
|
||||
|
||||
- name: List source files that changed
|
||||
run: echo '${{ steps.changed-source.outputs.all_changed_files }}'
|
||||
|
||||
- name: Set leptos_changed
|
||||
id: set-source-changed
|
||||
run: |
|
||||
|
||||
32
.github/workflows/get-leptos-matrix.yml
vendored
32
.github/workflows/get-leptos-matrix.yml
vendored
@@ -1,32 +0,0 @@
|
||||
name: Get Leptos Matrix Call
|
||||
on:
|
||||
workflow_call:
|
||||
outputs:
|
||||
matrix:
|
||||
description: "Matrix"
|
||||
value: ${{ jobs.create.outputs.matrix }}
|
||||
jobs:
|
||||
create:
|
||||
name: Create Leptos Matrix
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install jq
|
||||
run: sudo apt-get install jq
|
||||
- name: Set Matrix
|
||||
id: set-matrix
|
||||
run: |
|
||||
crates=$(cargo metadata --no-deps --quiet --format-version 1 |
|
||||
jq -r '.packages[] | select(.name != "workspace") | .manifest_path| rtrimstr("/Cargo.toml")' |
|
||||
sed "s|$(pwd)/||" |
|
||||
jq -R -s -c 'split("\n")[:-1]')
|
||||
echo "Leptos Directories: $crates"
|
||||
echo "matrix={\"directory\":$crates}" >> "$GITHUB_OUTPUT"
|
||||
- name: Print Location Info
|
||||
run: |
|
||||
echo "Workspace: ${{ github.workspace }}"
|
||||
pwd
|
||||
ls | sort -u
|
||||
37
.github/workflows/run-cargo-make-task.yml
vendored
37
.github/workflows/run-cargo-make-task.yml
vendored
@@ -1,4 +1,5 @@
|
||||
name: Run Task
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
@@ -11,53 +12,73 @@ on:
|
||||
toolchain:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run ${{ inputs.cargo_make_task }} (${{ inputs.toolchain }})
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Setup environment
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ inputs.toolchain }}
|
||||
|
||||
- name: Add wasm32-unknown-unknown
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
|
||||
- name: Setup cargo-make
|
||||
uses: davidB/rust-cargo-make@v1
|
||||
|
||||
- name: Cargo generate-lockfile
|
||||
run: cargo generate-lockfile
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install binstall
|
||||
uses: cargo-bins/cargo-binstall@main
|
||||
|
||||
- name: Install wasm-bindgen
|
||||
run: cargo binstall wasm-bindgen-cli --no-confirm
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: cargo binstall wasm-pack --no-confirm
|
||||
|
||||
- name: Install cargo-leptos
|
||||
run: cargo binstall cargo-leptos --no-confirm
|
||||
|
||||
- name: Install Trunk
|
||||
uses: jetli/trunk-action@v0.5.0
|
||||
uses: jetli/trunk-action@v0.4.0
|
||||
with:
|
||||
version: "latest"
|
||||
|
||||
- name: Print Trunk Version
|
||||
run: trunk --version
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: pnpm/action-setup@v3
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
@@ -65,6 +86,7 @@ jobs:
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Maybe install chromedriver
|
||||
run: |
|
||||
project_makefile=${{inputs.directory}}/Makefile.toml
|
||||
@@ -80,6 +102,7 @@ jobs:
|
||||
else
|
||||
echo chromedriver is not required
|
||||
fi
|
||||
|
||||
- name: Maybe install playwright browser dependencies
|
||||
run: |
|
||||
for pw_path in $(find ${{inputs.directory}} -name playwright.config.ts)
|
||||
@@ -93,16 +116,12 @@ jobs:
|
||||
echo Playwright is not required
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Install Deno
|
||||
uses: denoland/setup-deno@v2
|
||||
uses: denoland/setup-deno@v1
|
||||
with:
|
||||
deno-version: v1.x
|
||||
- name: Maybe install gtk-rs dependencies
|
||||
run: |
|
||||
if [ ! -z $(echo ${{inputs.directory}} | grep gtk) ]; then
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libglib2.0-dev libgio2.0-cil-dev libgraphene-1.0-dev libcairo2-dev libpango1.0-dev libgtk-4-dev
|
||||
fi
|
||||
|
||||
# Run Cargo Make Task
|
||||
- name: ${{ inputs.cargo_make_task }}
|
||||
run: |
|
||||
|
||||
62
Cargo.toml
62
Cargo.toml
@@ -1,30 +1,17 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
# utilities
|
||||
"oco",
|
||||
"any_spawner",
|
||||
"const_str_slice_concat",
|
||||
"either_of",
|
||||
"next_tuple",
|
||||
"oco",
|
||||
"or_poisoned",
|
||||
|
||||
# core
|
||||
"hydration_context",
|
||||
"leptos",
|
||||
"leptos_dom",
|
||||
"leptos_config",
|
||||
"leptos_hot_reload",
|
||||
"leptos_macro",
|
||||
"leptos_reactive",
|
||||
"leptos_server",
|
||||
"reactive_graph",
|
||||
"reactive_stores",
|
||||
"reactive_stores_macro",
|
||||
"server_fn",
|
||||
"server_fn_macro",
|
||||
"server_fn/server_fn_macro_default",
|
||||
"tachys",
|
||||
|
||||
# integrations
|
||||
"integrations/actix",
|
||||
@@ -34,42 +21,27 @@ members = [
|
||||
# libraries
|
||||
"meta",
|
||||
"router",
|
||||
"router_macro",
|
||||
"any_error",
|
||||
]
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
exclude = ["benchmarks", "examples"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.7.0-gamma3"
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
version = "0.6.11"
|
||||
rust-version = "1.75"
|
||||
|
||||
[workspace.dependencies]
|
||||
throw_error = { path = "./any_error/", version = "0.2.0-gamma3" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.1.0" }
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1.0" }
|
||||
either_of = { path = "./either_of/", version = "0.1.0" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0-gamma3" }
|
||||
leptos = { path = "./leptos", version = "0.7.0-gamma3" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.0-gamma3" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.0-gamma3" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-gamma3" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-gamma3" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.0-gamma3" }
|
||||
leptos_router = { path = "./router", version = "0.7.0-gamma3" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.0-gamma3" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.0-gamma3" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.0-gamma3" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0-gamma3" }
|
||||
oco_ref = { path = "./oco", version = "0.2.0" }
|
||||
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.0-gamma3" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0-gamma3" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0-gamma3" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.0-gamma3" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-gamma3" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-gamma3" }
|
||||
tachys = { path = "./tachys", version = "0.1.0-gamma3" }
|
||||
leptos = { path = "./leptos", version = "0.6.11" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.6.11" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.11" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.6.11" }
|
||||
leptos_reactive = { path = "./leptos_reactive", version = "0.6.11" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.6.11" }
|
||||
server_fn = { path = "./server_fn", version = "0.6.11" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.6.11" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.6" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.6.11" }
|
||||
leptos_router = { path = "./router", version = "0.6.11" }
|
||||
leptos_meta = { path = "./meta", version = "0.6.11" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.6.11" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
|
||||
You can find a list of useful libraries and example projects at [`awesome-leptos`](https://github.com/leptos-rs/awesome-leptos).
|
||||
|
||||
# The `main` branch is currently undergoing major changes in preparation for the [0.7](https://github.com/leptos-rs/leptos/milestone/4) release. For a stable version, please use the [v0.6.13 tag](https://github.com/leptos-rs/leptos/tree/v0.6.13)
|
||||
|
||||
# Leptos
|
||||
|
||||
```rust
|
||||
|
||||
40
TODO.md
40
TODO.md
@@ -1,40 +0,0 @@
|
||||
- core examples
|
||||
- [x] counter
|
||||
- [x] counters
|
||||
- [x] fetch
|
||||
- [x] todomvc
|
||||
- [x] error_boundary
|
||||
- [x] parent\_child
|
||||
- [x] on: on components
|
||||
- [ ] router
|
||||
- [ ] slots
|
||||
- [ ] hackernews
|
||||
- [ ] counter\_isomorphic
|
||||
- [ ] todo\_app\_sqlite
|
||||
- other ssr examples
|
||||
- [ ] error boundary SSR
|
||||
- reactivity
|
||||
- Signal wrappers
|
||||
- SignalDispose implementations on all Copy types
|
||||
- untracked access warnings
|
||||
- ErrorBoundary
|
||||
- [ ] RenderHtml implementation
|
||||
- [ ] Separate component?
|
||||
- Suspense/Transition components?
|
||||
- callbacks
|
||||
- unsync StoredValue
|
||||
- SSR
|
||||
- escaping HTML correctly (attributes + text nodes)
|
||||
- router
|
||||
- nested routes
|
||||
- trailing slashes
|
||||
- \_meta package (and use in hackernews)
|
||||
- integrations
|
||||
- update tests
|
||||
- hackernews example
|
||||
- TODOs
|
||||
- Suspense/Transition/Await components
|
||||
- nicer routing components
|
||||
- async routing (waiting for data to load before navigation)
|
||||
- `<A>` component
|
||||
- figure out rebuilding issues: list (needs new signal IDs) vs. regular rebuild
|
||||
@@ -1,13 +0,0 @@
|
||||
[package]
|
||||
name = "throw_error"
|
||||
version = "0.2.0-gamma3"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Utilities for wrapping, throwing, and catching errors."
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pin-project-lite = "0.2.14"
|
||||
@@ -1,2 +0,0 @@
|
||||
A utility library for wrapping arbitrary errors, and for “throwing” errors in a way
|
||||
that can be caught by user-defined error hooks.
|
||||
@@ -1,165 +0,0 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! A utility library for wrapping arbitrary errors, and for “throwing” errors in a way
|
||||
//! that can be caught by user-defined error hooks.
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
error,
|
||||
fmt::{self, Display},
|
||||
future::Future,
|
||||
mem, ops,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
/* Wrapper Types */
|
||||
|
||||
/// This is a result type into which any error can be converted.
|
||||
///
|
||||
/// Results are stored as [`Error`].
|
||||
pub type Result<T, E = Error> = core::result::Result<T, E>;
|
||||
|
||||
/// A generic wrapper for any error.
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct Error(Arc<dyn error::Error + Send + Sync>);
|
||||
|
||||
impl Error {
|
||||
/// Converts the wrapper into the inner reference-counted error.
|
||||
pub fn into_inner(self) -> Arc<dyn error::Error + Send + Sync> {
|
||||
Arc::clone(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for Error {
|
||||
type Target = Arc<dyn error::Error + Send + Sync>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Error
|
||||
where
|
||||
T: error::Error + Send + Sync + 'static,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Error(Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements behavior that allows for global or scoped error handling.
|
||||
///
|
||||
/// This allows for both "throwing" errors to register them, and "clearing" errors when they are no
|
||||
/// longer valid. This is useful for something like a user interface, in which an error can be
|
||||
/// "thrown" on some invalid user input, and later "cleared" if the user corrects the input.
|
||||
/// Keeping a unique identifier for each error allows the UI to be updated accordingly.
|
||||
pub trait ErrorHook: Send + Sync {
|
||||
/// Handles the given error, returning a unique identifier.
|
||||
fn throw(&self, error: Error) -> ErrorId;
|
||||
|
||||
/// Clears the error associated with the given identifier.
|
||||
fn clear(&self, id: &ErrorId);
|
||||
}
|
||||
|
||||
/// A unique identifier for an error. This is returned when you call [`throw`], which calls a
|
||||
/// global error handler.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Default)]
|
||||
pub struct ErrorId(usize);
|
||||
|
||||
impl Display for ErrorId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for ErrorId {
|
||||
fn from(value: usize) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static ERROR_HOOK: RefCell<Option<Arc<dyn ErrorHook>>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
/// Resets the error hook to its previous state when dropped.
|
||||
pub struct ResetErrorHookOnDrop(Option<Arc<dyn ErrorHook>>);
|
||||
|
||||
impl Drop for ResetErrorHookOnDrop {
|
||||
fn drop(&mut self) {
|
||||
ERROR_HOOK.with_borrow_mut(|this| *this = self.0.take())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current error hook.
|
||||
pub fn get_error_hook() -> Option<Arc<dyn ErrorHook>> {
|
||||
ERROR_HOOK.with_borrow(Clone::clone)
|
||||
}
|
||||
|
||||
/// Sets the current thread-local error hook, which will be invoked when [`throw`] is called.
|
||||
pub fn set_error_hook(hook: Arc<dyn ErrorHook>) -> ResetErrorHookOnDrop {
|
||||
ResetErrorHookOnDrop(
|
||||
ERROR_HOOK.with_borrow_mut(|this| mem::replace(this, Some(hook))),
|
||||
)
|
||||
}
|
||||
|
||||
/// Invokes the error hook set by [`set_error_hook`] with the given error.
|
||||
pub fn throw(error: impl Into<Error>) -> ErrorId {
|
||||
ERROR_HOOK
|
||||
.with_borrow(|hook| hook.as_ref().map(|hook| hook.throw(error.into())))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Clears the given error from the current error hook.
|
||||
pub fn clear(id: &ErrorId) {
|
||||
ERROR_HOOK
|
||||
.with_borrow(|hook| hook.as_ref().map(|hook| hook.clear(id)))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pin_project_lite::pin_project! {
|
||||
/// A [`Future`] that reads the error hook that is set when it is created, and sets this as the
|
||||
/// current error hook whenever it is polled.
|
||||
pub struct ErrorHookFuture<Fut> {
|
||||
hook: Option<Arc<dyn ErrorHook>>,
|
||||
#[pin]
|
||||
inner: Fut
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fut> ErrorHookFuture<Fut> {
|
||||
/// Reads the current hook and wraps the given [`Future`], returning a new `Future` that will
|
||||
/// set the error hook whenever it is polled.
|
||||
pub fn new(inner: Fut) -> Self {
|
||||
Self {
|
||||
hook: ERROR_HOOK.with_borrow(Clone::clone),
|
||||
inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fut> Future for ErrorHookFuture<Fut>
|
||||
where
|
||||
Fut: Future,
|
||||
{
|
||||
type Output = Fut::Output;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
let _hook = this
|
||||
.hook
|
||||
.as_ref()
|
||||
.map(|hook| set_error_hook(Arc::clone(hook)));
|
||||
this.inner.poll(cx)
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
[package]
|
||||
name = "any_spawner"
|
||||
version = "0.1.1"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Spawn asynchronous tasks in an executor-independent way."
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-executor = { version = "1.13.1", optional = true }
|
||||
futures = "0.3.30"
|
||||
glib = { version = "0.20.0", optional = true }
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.39", optional = true, default-features = false, features = [
|
||||
"rt",
|
||||
] }
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4.42", optional = true }
|
||||
|
||||
[features]
|
||||
async-executor = ["dep:async-executor"]
|
||||
tracing = ["dep:tracing"]
|
||||
tokio = ["dep:tokio"]
|
||||
glib = ["dep:glib"]
|
||||
wasm-bindgen = ["dep:wasm-bindgen-futures"]
|
||||
futures-executor = ["futures/thread-pool", "futures/executor"]
|
||||
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["tracing"]
|
||||
@@ -1,26 +0,0 @@
|
||||
This crate makes it easier to write asynchronous code that is executor-agnostic, by providing a
|
||||
utility that can be used to spawn tasks in a variety of executors.
|
||||
|
||||
It only supports single executor per program, but that executor can be set at runtime, anywhere
|
||||
in your crate (or an application that depends on it).
|
||||
|
||||
This can be extended to support any executor or runtime that supports spawning [`Future`]s.
|
||||
|
||||
This is a least common denominator implementation in many ways. Limitations include:
|
||||
|
||||
- setting an executor is a one-time, global action
|
||||
- no "join handle" or other result is returned from the spawn
|
||||
- the `Future` must output `()`
|
||||
|
||||
```rust
|
||||
use any_spawner::Executor;
|
||||
|
||||
Executor::init_futures_executor()
|
||||
.expect("executor should only be initialized once");
|
||||
|
||||
// spawn a thread-safe Future
|
||||
Executor::spawn(async { /* ... */ });
|
||||
|
||||
// spawn a Future that is !Send
|
||||
Executor::spawn_local(async { /* ... */ });
|
||||
```
|
||||
@@ -1,327 +0,0 @@
|
||||
//! This crate makes it easier to write asynchronous code that is executor-agnostic, by providing a
|
||||
//! utility that can be used to spawn tasks in a variety of executors.
|
||||
//!
|
||||
//! It only supports single executor per program, but that executor can be set at runtime, anywhere
|
||||
//! in your crate (or an application that depends on it).
|
||||
//!
|
||||
//! This can be extended to support any executor or runtime that supports spawning [`Future`]s.
|
||||
//!
|
||||
//! This is a least common denominator implementation in many ways. Limitations include:
|
||||
//! - setting an executor is a one-time, global action
|
||||
//! - no "join handle" or other result is returned from the spawn
|
||||
//! - the `Future` must output `()`
|
||||
//!
|
||||
//! ```rust
|
||||
//! use any_spawner::Executor;
|
||||
//!
|
||||
//! // make sure an Executor has been initialized with one of the init_ functions
|
||||
//!
|
||||
//! # if false {
|
||||
//! // spawn a thread-safe Future
|
||||
//! Executor::spawn(async { /* ... */ });
|
||||
//!
|
||||
//! // spawn a Future that is !Send
|
||||
//! Executor::spawn_local(async { /* ... */ });
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
use std::{future::Future, pin::Pin, sync::OnceLock};
|
||||
use thiserror::Error;
|
||||
|
||||
/// A future that has been pinned.
|
||||
pub type PinnedFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
|
||||
/// A future that has been pinned.
|
||||
pub type PinnedLocalFuture<T> = Pin<Box<dyn Future<Output = T>>>;
|
||||
|
||||
static SPAWN: OnceLock<fn(PinnedFuture<()>)> = OnceLock::new();
|
||||
static SPAWN_LOCAL: OnceLock<fn(PinnedLocalFuture<()>)> = OnceLock::new();
|
||||
static POLL_LOCAL: OnceLock<fn()> = OnceLock::new();
|
||||
|
||||
/// Errors that can occur when using the executor.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ExecutorError {
|
||||
/// The executor has already been set.
|
||||
#[error("Executor has already been set.")]
|
||||
AlreadySet,
|
||||
}
|
||||
|
||||
/// A global async executor that can spawn tasks.
|
||||
pub struct Executor;
|
||||
|
||||
impl Executor {
|
||||
/// Spawns a thread-safe [`Future`].
|
||||
/// ```rust
|
||||
/// use any_spawner::Executor;
|
||||
/// # if false {
|
||||
/// // spawn a thread-safe Future
|
||||
/// Executor::spawn(async { /* ... */ });
|
||||
/// # }
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn spawn(fut: impl Future<Output = ()> + Send + 'static) {
|
||||
if let Some(spawner) = SPAWN.get() {
|
||||
spawner(Box::pin(fut))
|
||||
} else {
|
||||
#[cfg(all(debug_assertions, feature = "tracing"))]
|
||||
tracing::error!(
|
||||
"At {}, tried to spawn a Future with Executor::spawn() before \
|
||||
the Executor had been set.",
|
||||
std::panic::Location::caller()
|
||||
);
|
||||
#[cfg(all(debug_assertions, not(feature = "tracing")))]
|
||||
panic!(
|
||||
"At {}, tried to spawn a Future with Executor::spawn() before \
|
||||
the Executor had been set.",
|
||||
std::panic::Location::caller()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns a [`Future`] that cannot be sent across threads.
|
||||
/// ```rust
|
||||
/// use any_spawner::Executor;
|
||||
///
|
||||
/// # if false {
|
||||
/// // spawn a thread-safe Future
|
||||
/// Executor::spawn_local(async { /* ... */ });
|
||||
/// # }
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn spawn_local(fut: impl Future<Output = ()> + 'static) {
|
||||
if let Some(spawner) = SPAWN_LOCAL.get() {
|
||||
spawner(Box::pin(fut))
|
||||
} else {
|
||||
#[cfg(all(debug_assertions, feature = "tracing"))]
|
||||
tracing::error!(
|
||||
"At {}, tried to spawn a Future with Executor::spawn_local() \
|
||||
before the Executor had been set.",
|
||||
std::panic::Location::caller()
|
||||
);
|
||||
#[cfg(all(debug_assertions, not(feature = "tracing")))]
|
||||
panic!(
|
||||
"At {}, tried to spawn a Future with Executor::spawn_local() \
|
||||
before the Executor had been set.",
|
||||
std::panic::Location::caller()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits until the next "tick" of the current async executor.
|
||||
pub async fn tick() {
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
Executor::spawn(async move {
|
||||
_ = tx.send(());
|
||||
});
|
||||
_ = rx.await;
|
||||
}
|
||||
|
||||
/// Polls the current async executor.
|
||||
/// Not all async executors support polling, so this function may not do anything.
|
||||
pub fn poll_local() {
|
||||
if let Some(poller) = POLL_LOCAL.get() {
|
||||
poller()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Globally sets the [`tokio`] runtime as the executor used to spawn tasks.
|
||||
///
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
///
|
||||
/// Requires the `tokio` feature to be activated on this crate.
|
||||
#[cfg(feature = "tokio")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
|
||||
pub fn init_tokio() -> Result<(), ExecutorError> {
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
tokio::task::spawn_local(fut);
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Globally sets the [`wasm-bindgen-futures`] runtime as the executor used to spawn tasks.
|
||||
///
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
///
|
||||
/// Requires the `wasm-bindgen` feature to be activated on this crate.
|
||||
#[cfg(feature = "wasm-bindgen")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "wasm-bindgen")))]
|
||||
pub fn init_wasm_bindgen() -> Result<(), ExecutorError> {
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
wasm_bindgen_futures::spawn_local(fut);
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
wasm_bindgen_futures::spawn_local(fut);
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Globally sets the [`glib`] runtime as the executor used to spawn tasks.
|
||||
///
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
///
|
||||
/// Requires the `glib` feature to be activated on this crate.
|
||||
#[cfg(feature = "glib")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "glib")))]
|
||||
pub fn init_glib() -> Result<(), ExecutorError> {
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
let main_context = glib::MainContext::default();
|
||||
main_context.spawn(fut);
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
let main_context = glib::MainContext::default();
|
||||
main_context.spawn_local(fut);
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Globally sets the [`futures`] executor as the executor used to spawn tasks,
|
||||
/// lazily creating a thread pool to spawn tasks into.
|
||||
///
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
///
|
||||
/// Requires the `futures-executor` feature to be activated on this crate.
|
||||
#[cfg(feature = "futures-executor")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "futures-executor")))]
|
||||
pub fn init_futures_executor() -> Result<(), ExecutorError> {
|
||||
use futures::{
|
||||
executor::{LocalPool, LocalSpawner, ThreadPool},
|
||||
task::{LocalSpawnExt, SpawnExt},
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
|
||||
static THREAD_POOL: OnceLock<ThreadPool> = OnceLock::new();
|
||||
thread_local! {
|
||||
static LOCAL_POOL: RefCell<LocalPool> = RefCell::new(LocalPool::new());
|
||||
static SPAWNER: LocalSpawner = LOCAL_POOL.with(|pool| pool.borrow().spawner());
|
||||
}
|
||||
|
||||
fn get_thread_pool() -> &'static ThreadPool {
|
||||
THREAD_POOL.get_or_init(|| {
|
||||
ThreadPool::new()
|
||||
.expect("could not create futures executor ThreadPool")
|
||||
})
|
||||
}
|
||||
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
get_thread_pool()
|
||||
.spawn(fut)
|
||||
.expect("failed to spawn future");
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
SPAWNER.with(|spawner| {
|
||||
spawner.spawn_local(fut).expect("failed to spawn future");
|
||||
});
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
POLL_LOCAL
|
||||
.set(|| {
|
||||
LOCAL_POOL.with(|pool| {
|
||||
if let Ok(mut pool) = pool.try_borrow_mut() {
|
||||
pool.run_until_stalled();
|
||||
}
|
||||
// If we couldn't borrow_mut, we're in a nested call to poll, so we don't need to do anything.
|
||||
});
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Globally sets the [`async_executor`] executor as the executor used to spawn tasks,
|
||||
/// lazily creating a thread pool to spawn tasks into.
|
||||
///
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
///
|
||||
/// Requires the `async-executor` feature to be activated on this crate.
|
||||
#[cfg(feature = "async-executor")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async-executor")))]
|
||||
pub fn init_async_executor() -> Result<(), ExecutorError> {
|
||||
use async_executor::{Executor, LocalExecutor};
|
||||
|
||||
static THREAD_POOL: OnceLock<Executor> = OnceLock::new();
|
||||
thread_local! {
|
||||
static LOCAL_POOL: LocalExecutor<'static> = const { LocalExecutor::new() };
|
||||
}
|
||||
|
||||
fn get_thread_pool() -> &'static Executor<'static> {
|
||||
THREAD_POOL.get_or_init(Executor::new)
|
||||
}
|
||||
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
get_thread_pool().spawn(fut).detach();
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
LOCAL_POOL.with(|pool| pool.spawn(fut).detach());
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
POLL_LOCAL
|
||||
.set(|| {
|
||||
LOCAL_POOL.with(|pool| pool.try_tick());
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Globally sets a custom executor as the executor used to spawn tasks.
|
||||
///
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
pub fn init_custom_executor(
|
||||
custom_executor: impl CustomExecutor + 'static,
|
||||
) -> Result<(), ExecutorError> {
|
||||
static EXECUTOR: OnceLock<Box<dyn CustomExecutor>> = OnceLock::new();
|
||||
EXECUTOR
|
||||
.set(Box::new(custom_executor))
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
EXECUTOR.get().unwrap().spawn(fut);
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| EXECUTOR.get().unwrap().spawn_local(fut))
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
POLL_LOCAL
|
||||
.set(|| EXECUTOR.get().unwrap().poll_local())
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for custom executors.
|
||||
/// Custom executors can be used to integrate with any executor that supports spawning futures.
|
||||
///
|
||||
/// All methods can be called recursively.
|
||||
pub trait CustomExecutor: Send + Sync {
|
||||
/// Spawns a future, usually on a thread pool.
|
||||
fn spawn(&self, fut: PinnedFuture<()>);
|
||||
/// Spawns a local future. May require calling `poll_local` to make progress.
|
||||
fn spawn_local(&self, fut: PinnedLocalFuture<()>);
|
||||
/// Polls the executor, if it supports polling.
|
||||
fn poll_local(&self);
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
#[cfg(feature = "futures-executor")]
|
||||
use any_spawner::{CustomExecutor, Executor, PinnedFuture, PinnedLocalFuture};
|
||||
#[cfg(feature = "futures-executor")]
|
||||
#[test]
|
||||
fn can_create_custom_executor() {
|
||||
use futures::{
|
||||
executor::{LocalPool, LocalSpawner},
|
||||
task::LocalSpawnExt,
|
||||
};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
thread_local! {
|
||||
static LOCAL_POOL: RefCell<LocalPool> = RefCell::new(LocalPool::new());
|
||||
static SPAWNER: LocalSpawner = LOCAL_POOL.with(|pool| pool.borrow().spawner());
|
||||
}
|
||||
|
||||
struct CustomFutureExecutor;
|
||||
impl CustomExecutor for CustomFutureExecutor {
|
||||
fn spawn(&self, _fut: PinnedFuture<()>) {
|
||||
panic!("not supported in this test");
|
||||
}
|
||||
|
||||
fn spawn_local(&self, fut: PinnedLocalFuture<()>) {
|
||||
SPAWNER.with(|spawner| {
|
||||
spawner.spawn_local(fut).expect("failed to spawn future");
|
||||
});
|
||||
}
|
||||
|
||||
fn poll_local(&self) {
|
||||
LOCAL_POOL.with(|pool| {
|
||||
if let Ok(mut pool) = pool.try_borrow_mut() {
|
||||
pool.run_until_stalled();
|
||||
}
|
||||
// If we couldn't borrow_mut, we're in a nested call to poll, so we don't need to do anything.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Executor::init_custom_executor(CustomFutureExecutor)
|
||||
.expect("couldn't set executor");
|
||||
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
let counter_clone = Arc::clone(&counter);
|
||||
Executor::spawn_local(async move {
|
||||
counter_clone.store(1, Ordering::Release);
|
||||
});
|
||||
Executor::poll_local();
|
||||
assert_eq!(counter.load(Ordering::Acquire), 1);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
#[cfg(feature = "futures-executor")]
|
||||
use any_spawner::Executor;
|
||||
// All tests in this file use the same executor.
|
||||
|
||||
#[cfg(feature = "futures-executor")]
|
||||
#[test]
|
||||
fn can_spawn_local_future() {
|
||||
use std::rc::Rc;
|
||||
|
||||
let _ = Executor::init_futures_executor();
|
||||
let rc = Rc::new(());
|
||||
Executor::spawn_local(async {
|
||||
_ = rc;
|
||||
});
|
||||
Executor::spawn(async {});
|
||||
}
|
||||
|
||||
#[cfg(feature = "futures-executor")]
|
||||
#[test]
|
||||
fn can_make_local_progress() {
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
};
|
||||
let _ = Executor::init_futures_executor();
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
Executor::spawn_local({
|
||||
let counter = Arc::clone(&counter);
|
||||
async move {
|
||||
assert_eq!(counter.fetch_add(1, Ordering::AcqRel), 0);
|
||||
Executor::spawn_local(async {
|
||||
// Should not crash
|
||||
});
|
||||
}
|
||||
});
|
||||
Executor::poll_local();
|
||||
assert_eq!(counter.load(Ordering::Acquire), 1);
|
||||
}
|
||||
@@ -2,36 +2,35 @@
|
||||
name = "benchmarks"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
# std::sync::LazyLock is stabilized in Rust version 1.80.0
|
||||
rust-version = "1.80.0"
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
l0410 = { package = "leptos", version = "0.4.10", features = [
|
||||
"nightly",
|
||||
"ssr",
|
||||
"nightly",
|
||||
"ssr",
|
||||
] }
|
||||
leptos = { path = "../leptos", features = ["ssr", "nightly"] }
|
||||
leptos_reactive = { path = "../leptos_reactive", features = ["ssr", "nightly"] }
|
||||
tachydom = { git = "https://github.com/gbj/tachys", features = [
|
||||
"nightly",
|
||||
"leptos",
|
||||
"nightly",
|
||||
"leptos",
|
||||
] }
|
||||
tachy_maccy = { git = "https://github.com/gbj/tachys", features = ["nightly"] }
|
||||
sycamore = { version = "0.8.0", features = ["ssr"] }
|
||||
yew = { version = "0.20.0", features = ["ssr"] }
|
||||
tokio-test = "0.4.0"
|
||||
miniserde = "0.1.0"
|
||||
gloo = "0.8.0"
|
||||
uuid = { version = "1.0", features = ["serde", "v4", "wasm-bindgen"] }
|
||||
wasm-bindgen = "0.2.0"
|
||||
lazy_static = "1.0"
|
||||
log = "0.4.0"
|
||||
strum = "0.24.0"
|
||||
strum_macros = "0.24.0"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = "1.0"
|
||||
tera = "1.0"
|
||||
sycamore = { version = "0.8", features = ["ssr"] }
|
||||
yew = { version = "0.20", features = ["ssr"] }
|
||||
tokio-test = "0.4"
|
||||
miniserde = "0.1"
|
||||
gloo = "0.8"
|
||||
uuid = { version = "1", features = ["serde", "v4", "wasm-bindgen"] }
|
||||
wasm-bindgen = "0.2"
|
||||
lazy_static = "1"
|
||||
log = "0.4"
|
||||
strum = "0.24"
|
||||
strum_macros = "0.24"
|
||||
serde = { version = "1", features = ["derive", "rc"] }
|
||||
serde_json = "1"
|
||||
tera = "1"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.0"
|
||||
version = "0.3"
|
||||
features = ["Window", "Document", "HtmlElement", "HtmlInputElement"]
|
||||
|
||||
@@ -18,7 +18,7 @@ fn leptos_ssr_bench(b: &mut Bencher) {
|
||||
}
|
||||
}
|
||||
|
||||
let rendered = view! {
|
||||
let rendered = view! {
|
||||
<main>
|
||||
<h1>"Welcome to our benchmark page."</h1>
|
||||
<p>"Here's some introductory text."</p>
|
||||
@@ -58,7 +58,7 @@ fn tachys_ssr_bench(b: &mut Bencher) {
|
||||
}
|
||||
}
|
||||
|
||||
let rendered = view! {
|
||||
let rendered = view! {
|
||||
<main>
|
||||
<h1>"Welcome to our benchmark page."</h1>
|
||||
<p>"Here's some introductory text."</p>
|
||||
@@ -92,13 +92,13 @@ fn tera_ssr_bench(b: &mut Bencher) {
|
||||
{% endfor %}
|
||||
</main>"#;
|
||||
|
||||
|
||||
static LazyCell<TERA>: Tera = LazyLock::new(|| {
|
||||
let mut tera = Tera::default();
|
||||
tera.add_raw_templates(vec![("template.html", TEMPLATE)]).unwrap();
|
||||
tera
|
||||
});
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref TERA: Tera = {
|
||||
let mut tera = Tera::default();
|
||||
tera.add_raw_templates(vec![("template.html", TEMPLATE)]).unwrap();
|
||||
tera
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Counter {
|
||||
|
||||
@@ -55,7 +55,7 @@ static TEMPLATE: &str = r#"<main>
|
||||
{% else %}
|
||||
<li><a href="/">All</a></li>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if mode_active %}
|
||||
<li><a href="/active" class="selected">Active</a></li>
|
||||
{% else %}
|
||||
@@ -91,13 +91,13 @@ fn tera_todomvc_ssr(b: &mut Bencher) {
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tera::*;
|
||||
|
||||
|
||||
static LazyLock<TERA>: Tera = LazyLock( || {
|
||||
lazy_static::lazy_static! {
|
||||
static ref TERA: Tera = {
|
||||
let mut tera = Tera::default();
|
||||
tera.add_raw_templates(vec![("template.html", TEMPLATE)]).unwrap();
|
||||
tera
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Todo {
|
||||
@@ -131,13 +131,13 @@ fn tera_todomvc_ssr_1000(b: &mut Bencher) {
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tera::*;
|
||||
|
||||
|
||||
static TERA: LazyLock<Tera> = LazyLock::new(|| {
|
||||
let mut tera = Tera::default();
|
||||
tera.add_raw_templates(vec![("template.html", TEMPLATE)]).unwrap();
|
||||
tera
|
||||
});
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref TERA: Tera = {
|
||||
let mut tera = Tera::default();
|
||||
tera.add_raw_templates(vec![("template.html", TEMPLATE)]).unwrap();
|
||||
tera
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Todo {
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
[package]
|
||||
name = "const_str_slice_concat"
|
||||
version = "0.1.0"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Utilities for const concatenation of string slices."
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
@@ -1 +0,0 @@
|
||||
extend = { path = "../cargo-make/main.toml" }
|
||||
@@ -1,139 +0,0 @@
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
//! Utilities for const concatenation of string slices.
|
||||
|
||||
pub(crate) const MAX_TEMPLATE_SIZE: usize = 4096;
|
||||
|
||||
/// Converts a zero-terminated buffer of bytes into a UTF-8 string.
|
||||
pub const fn str_from_buffer(buf: &[u8; MAX_TEMPLATE_SIZE]) -> &str {
|
||||
match core::ffi::CStr::from_bytes_until_nul(buf) {
|
||||
Ok(cstr) => match cstr.to_str() {
|
||||
Ok(str) => str,
|
||||
Err(_) => panic!("TEMPLATE FAILURE"),
|
||||
},
|
||||
Err(_) => panic!("TEMPLATE FAILURE"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Concatenates any number of static strings into a single array.
|
||||
// credit to Rainer Stropek, "Constant fun," Rust Linz, June 2022
|
||||
pub const fn const_concat(
|
||||
strs: &'static [&'static str],
|
||||
) -> [u8; MAX_TEMPLATE_SIZE] {
|
||||
let mut buffer = [0; MAX_TEMPLATE_SIZE];
|
||||
let mut position = 0;
|
||||
let mut remaining = strs;
|
||||
|
||||
while let [current, tail @ ..] = remaining {
|
||||
let x = current.as_bytes();
|
||||
let mut i = 0;
|
||||
|
||||
// have it iterate over bytes manually, because, again,
|
||||
// no mutable refernces in const fns
|
||||
while i < x.len() {
|
||||
buffer[position] = x[i];
|
||||
position += 1;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
remaining = tail;
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
|
||||
/// Converts a zero-terminated buffer of bytes into a UTF-8 string with the given prefix.
|
||||
pub const fn const_concat_with_prefix(
|
||||
strs: &'static [&'static str],
|
||||
prefix: &'static str,
|
||||
suffix: &'static str,
|
||||
) -> [u8; MAX_TEMPLATE_SIZE] {
|
||||
let mut buffer = [0; MAX_TEMPLATE_SIZE];
|
||||
let mut position = 0;
|
||||
let mut remaining = strs;
|
||||
|
||||
while let [current, tail @ ..] = remaining {
|
||||
let x = current.as_bytes();
|
||||
let mut i = 0;
|
||||
|
||||
// have it iterate over bytes manually, because, again,
|
||||
// no mutable refernces in const fns
|
||||
while i < x.len() {
|
||||
buffer[position] = x[i];
|
||||
position += 1;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
remaining = tail;
|
||||
}
|
||||
|
||||
if buffer[0] == 0 {
|
||||
buffer
|
||||
} else {
|
||||
let mut new_buf = [0; MAX_TEMPLATE_SIZE];
|
||||
let prefix = prefix.as_bytes();
|
||||
let suffix = suffix.as_bytes();
|
||||
let mut position = 0;
|
||||
let mut i = 0;
|
||||
while i < prefix.len() {
|
||||
new_buf[position] = prefix[i];
|
||||
position += 1;
|
||||
i += 1;
|
||||
}
|
||||
i = 0;
|
||||
while i < buffer.len() {
|
||||
if buffer[i] == 0 {
|
||||
break;
|
||||
}
|
||||
new_buf[position] = buffer[i];
|
||||
position += 1;
|
||||
i += 1;
|
||||
}
|
||||
i = 0;
|
||||
while i < suffix.len() {
|
||||
new_buf[position] = suffix[i];
|
||||
position += 1;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
new_buf
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts any number of strings into a UTF-8 string, separated by the given string.
|
||||
pub const fn const_concat_with_separator(
|
||||
strs: &[&str],
|
||||
separator: &'static str,
|
||||
) -> [u8; MAX_TEMPLATE_SIZE] {
|
||||
let mut buffer = [0; MAX_TEMPLATE_SIZE];
|
||||
let mut position = 0;
|
||||
let mut remaining = strs;
|
||||
|
||||
while let [current, tail @ ..] = remaining {
|
||||
let x = current.as_bytes();
|
||||
let mut i = 0;
|
||||
|
||||
// have it iterate over bytes manually, because, again,
|
||||
// no mutable refernces in const fns
|
||||
while i < x.len() {
|
||||
buffer[position] = x[i];
|
||||
position += 1;
|
||||
i += 1;
|
||||
}
|
||||
if !x.is_empty() {
|
||||
let mut position = 0;
|
||||
let separator = separator.as_bytes();
|
||||
while i < separator.len() {
|
||||
buffer[position] = separator[i];
|
||||
position += 1;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
remaining = tail;
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
1
docs/book_ru/.gitignore
vendored
1
docs/book_ru/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
book
|
||||
@@ -1,2 +0,0 @@
|
||||
Перевод в процессе, книга скоро будет доступна
|
||||
> Translation underway, book will be available soon
|
||||
@@ -1,10 +0,0 @@
|
||||
[output.html]
|
||||
additional-css = ["./mdbook-admonish.css"]
|
||||
[output.html.playground]
|
||||
runnable = false
|
||||
|
||||
[preprocessor]
|
||||
|
||||
[preprocessor.admonish]
|
||||
command = "mdbook-admonish"
|
||||
assets_version = "3.0.1" # не редактировать: управляется `mdbook-admonish install`
|
||||
@@ -1,345 +0,0 @@
|
||||
@charset "UTF-8";
|
||||
:root {
|
||||
--md-admonition-icon--admonish-note: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z'/></svg>");
|
||||
--md-admonition-icon--admonish-abstract: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'/></svg>");
|
||||
--md-admonition-icon--admonish-info: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z'/></svg>");
|
||||
--md-admonition-icon--admonish-tip: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M17.66 11.2c-.23-.3-.51-.56-.77-.82-.67-.6-1.43-1.03-2.07-1.66C13.33 7.26 13 4.85 13.95 3c-.95.23-1.78.75-2.49 1.32-2.59 2.08-3.61 5.75-2.39 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.23.1-.47.04-.66-.12a.58.58 0 0 1-.14-.17c-1.13-1.43-1.31-3.48-.55-5.12C5.78 10 4.87 12.3 5 14.47c.06.5.12 1 .29 1.5.14.6.41 1.2.71 1.73 1.08 1.73 2.95 2.97 4.96 3.22 2.14.27 4.43-.12 6.07-1.6 1.83-1.66 2.47-4.32 1.53-6.6l-.13-.26c-.21-.46-.77-1.26-.77-1.26m-3.16 6.3c-.28.24-.74.5-1.1.6-1.12.4-2.24-.16-2.9-.82 1.19-.28 1.9-1.16 2.11-2.05.17-.8-.15-1.46-.28-2.23-.12-.74-.1-1.37.17-2.06.19.38.39.76.63 1.06.77 1 1.98 1.44 2.24 2.8.04.14.06.28.06.43.03.82-.33 1.72-.93 2.27z'/></svg>");
|
||||
--md-admonition-icon--admonish-success: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m9 20.42-6.21-6.21 2.83-2.83L9 14.77l9.88-9.89 2.83 2.83L9 20.42z'/></svg>");
|
||||
--md-admonition-icon--admonish-question: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10z'/></svg>");
|
||||
--md-admonition-icon--admonish-warning: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M13 14h-2V9h2m0 9h-2v-2h2M1 21h22L12 2 1 21z'/></svg>");
|
||||
--md-admonition-icon--admonish-failure: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6.91 17.09 4 12 9.09 6.91 4 4 6.91 9.09 12 4 17.09 6.91 20 12 14.91 17.09 20 20 17.09 14.91 12 20 6.91z'/></svg>");
|
||||
--md-admonition-icon--admonish-danger: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M11 15H6l7-14v8h5l-7 14v-8z'/></svg>");
|
||||
--md-admonition-icon--admonish-bug: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 0 0-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z'/></svg>");
|
||||
--md-admonition-icon--admonish-example: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 0 1 .75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z'/></svg>");
|
||||
--md-admonition-icon--admonish-quote: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z'/></svg>");
|
||||
--md-details-icon: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.42Z'/></svg>");
|
||||
}
|
||||
|
||||
:is(.admonition) {
|
||||
display: flow-root;
|
||||
margin: 1.5625em 0;
|
||||
padding: 0 1.2rem;
|
||||
color: var(--fg);
|
||||
page-break-inside: avoid;
|
||||
background-color: var(--bg);
|
||||
border: 0 solid black;
|
||||
border-inline-start-width: 0.4rem;
|
||||
border-radius: 0.2rem;
|
||||
box-shadow: 0 0.2rem 1rem rgba(0, 0, 0, 0.05), 0 0 0.1rem rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
@media print {
|
||||
:is(.admonition) {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
:is(.admonition) > * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:is(.admonition) :is(.admonition) {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
:is(.admonition) > .tabbed-set:only-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
html :is(.admonition) > :last-child {
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
a.admonition-anchor-link {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: -1.2rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
a.admonition-anchor-link:link, a.admonition-anchor-link:visited {
|
||||
color: var(--fg);
|
||||
}
|
||||
a.admonition-anchor-link:link:hover, a.admonition-anchor-link:visited:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
a.admonition-anchor-link::before {
|
||||
content: "§";
|
||||
}
|
||||
|
||||
:is(.admonition-title, summary.admonition-title) {
|
||||
position: relative;
|
||||
min-height: 4rem;
|
||||
margin-block: 0;
|
||||
margin-inline: -1.6rem -1.2rem;
|
||||
padding-block: 0.8rem;
|
||||
padding-inline: 4.4rem 1.2rem;
|
||||
font-weight: 700;
|
||||
background-color: rgba(68, 138, 255, 0.1);
|
||||
print-color-adjust: exact;
|
||||
-webkit-print-color-adjust: exact;
|
||||
display: flex;
|
||||
}
|
||||
:is(.admonition-title, summary.admonition-title) p {
|
||||
margin: 0;
|
||||
}
|
||||
html :is(.admonition-title, summary.admonition-title):last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
:is(.admonition-title, summary.admonition-title)::before {
|
||||
position: absolute;
|
||||
top: 0.625em;
|
||||
inset-inline-start: 1.6rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background-color: #448aff;
|
||||
print-color-adjust: exact;
|
||||
-webkit-print-color-adjust: exact;
|
||||
mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>');
|
||||
-webkit-mask-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"></svg>');
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-size: contain;
|
||||
content: "";
|
||||
}
|
||||
:is(.admonition-title, summary.admonition-title):hover a.admonition-anchor-link {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
details.admonition > summary.admonition-title::after {
|
||||
position: absolute;
|
||||
top: 0.625em;
|
||||
inset-inline-end: 1.6rem;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
background-color: currentcolor;
|
||||
mask-image: var(--md-details-icon);
|
||||
-webkit-mask-image: var(--md-details-icon);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-size: contain;
|
||||
content: "";
|
||||
transform: rotate(0deg);
|
||||
transition: transform 0.25s;
|
||||
}
|
||||
details[open].admonition > summary.admonition-title::after {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
:is(.admonition):is(.admonish-note) {
|
||||
border-color: #448aff;
|
||||
}
|
||||
|
||||
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(68, 138, 255, 0.1);
|
||||
}
|
||||
:is(.admonish-note) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #448aff;
|
||||
mask-image: var(--md-admonition-icon--admonish-note);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-note);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.admonish-abstract, .admonish-summary, .admonish-tldr) {
|
||||
border-color: #00b0ff;
|
||||
}
|
||||
|
||||
:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(0, 176, 255, 0.1);
|
||||
}
|
||||
:is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #00b0ff;
|
||||
mask-image: var(--md-admonition-icon--admonish-abstract);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-abstract);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.admonish-info, .admonish-todo) {
|
||||
border-color: #00b8d4;
|
||||
}
|
||||
|
||||
:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(0, 184, 212, 0.1);
|
||||
}
|
||||
:is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #00b8d4;
|
||||
mask-image: var(--md-admonition-icon--admonish-info);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-info);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.admonish-tip, .admonish-hint, .admonish-important) {
|
||||
border-color: #00bfa5;
|
||||
}
|
||||
|
||||
:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(0, 191, 165, 0.1);
|
||||
}
|
||||
:is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #00bfa5;
|
||||
mask-image: var(--md-admonition-icon--admonish-tip);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-tip);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.admonish-success, .admonish-check, .admonish-done) {
|
||||
border-color: #00c853;
|
||||
}
|
||||
|
||||
:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(0, 200, 83, 0.1);
|
||||
}
|
||||
:is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #00c853;
|
||||
mask-image: var(--md-admonition-icon--admonish-success);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-success);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.admonish-question, .admonish-help, .admonish-faq) {
|
||||
border-color: #64dd17;
|
||||
}
|
||||
|
||||
:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(100, 221, 23, 0.1);
|
||||
}
|
||||
:is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #64dd17;
|
||||
mask-image: var(--md-admonition-icon--admonish-question);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-question);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.admonish-warning, .admonish-caution, .admonish-attention) {
|
||||
border-color: #ff9100;
|
||||
}
|
||||
|
||||
:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(255, 145, 0, 0.1);
|
||||
}
|
||||
:is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #ff9100;
|
||||
mask-image: var(--md-admonition-icon--admonish-warning);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-warning);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.admonish-failure, .admonish-fail, .admonish-missing) {
|
||||
border-color: #ff5252;
|
||||
}
|
||||
|
||||
:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(255, 82, 82, 0.1);
|
||||
}
|
||||
:is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #ff5252;
|
||||
mask-image: var(--md-admonition-icon--admonish-failure);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-failure);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.admonish-danger, .admonish-error) {
|
||||
border-color: #ff1744;
|
||||
}
|
||||
|
||||
:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(255, 23, 68, 0.1);
|
||||
}
|
||||
:is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #ff1744;
|
||||
mask-image: var(--md-admonition-icon--admonish-danger);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-danger);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.admonish-bug) {
|
||||
border-color: #f50057;
|
||||
}
|
||||
|
||||
:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(245, 0, 87, 0.1);
|
||||
}
|
||||
:is(.admonish-bug) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #f50057;
|
||||
mask-image: var(--md-admonition-icon--admonish-bug);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-bug);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.admonish-example) {
|
||||
border-color: #7c4dff;
|
||||
}
|
||||
|
||||
:is(.admonish-example) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(124, 77, 255, 0.1);
|
||||
}
|
||||
:is(.admonish-example) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #7c4dff;
|
||||
mask-image: var(--md-admonition-icon--admonish-example);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-example);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
:is(.admonition):is(.admonish-quote, .admonish-cite) {
|
||||
border-color: #9e9e9e;
|
||||
}
|
||||
|
||||
:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title) {
|
||||
background-color: rgba(158, 158, 158, 0.1);
|
||||
}
|
||||
:is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title)::before {
|
||||
background-color: #9e9e9e;
|
||||
mask-image: var(--md-admonition-icon--admonish-quote);
|
||||
-webkit-mask-image: var(--md-admonition-icon--admonish-quote);
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.navy :is(.admonition) {
|
||||
background-color: var(--sidebar-bg);
|
||||
}
|
||||
|
||||
.ayu :is(.admonition),
|
||||
.coal :is(.admonition) {
|
||||
background-color: var(--theme-hover);
|
||||
}
|
||||
|
||||
.rust :is(.admonition) {
|
||||
background-color: var(--sidebar-bg);
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
.rust .admonition-anchor-link:link, .rust .admonition-anchor-link:visited {
|
||||
color: var(--sidebar-fg);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/">
|
||||
<link rel="canonical" href="https://book.leptos.dev/">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/15_global_state.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/15_global_state.html">
|
||||
@@ -1,55 +0,0 @@
|
||||
# Оглавление
|
||||
|
||||
- [Вступление](./01_introduction.md)
|
||||
- [Начало работы](./getting_started/README.md)
|
||||
- [Leptos DX](./getting_started/leptos_dx.md)
|
||||
- [Сообщество Leptos и leptos-* Крейты](./getting_started/community_crates.md)
|
||||
- [Часть 1: Построение UI](./view/README.md)
|
||||
- [Простой компонент](./view/01_basic_component.md)
|
||||
- [Динамические атрибуты](./view/02_dynamic_attributes.md)
|
||||
- [Компоненты и свойства](./view/03_components.md)
|
||||
- [Итерирование](./view/04_iteration.md)
|
||||
- [Итерирование более сложных структур через `<For>`](./view/04b_iteration.md)
|
||||
- [Формы и поля ввода](./view/05_forms.md)
|
||||
- [Порядок выполнения](./view/06_control_flow.md)
|
||||
- [Обработка ошибок](./view/07_errors.md)
|
||||
- [Общение Родитель-Ребёнок в дереве компонентов](./view/08_parent_child.md)
|
||||
- [Передача Детей другим компонентам](./view/09_component_children.md)
|
||||
- [Без макросов: синтаксис билдера View](./view/builder.md)
|
||||
- [Реактивность](./reactivity/README.md)
|
||||
- [Работа с сигналами](./reactivity/working_with_signals.md)
|
||||
- [Реагирование на изменения с помощью `create_effect`](./reactivity/14_create_effect.md)
|
||||
- [Примечание: Реактивность и функции](./reactivity/interlude_functions.md)
|
||||
- [Тестирование](./testing.md)
|
||||
- [Асинхронность](./async/README.md)
|
||||
- [Подгрузка данных с помощью ресурсов (Resource)](./async/10_resources.md)
|
||||
- [Ожидания (Suspense)](./async/11_suspense.md)
|
||||
- [Переходы (Transition)](./async/12_transition.md)
|
||||
- [Действия (Action)](./async/13_actions.md)
|
||||
- [Примечание: Пробрасывание дочерних элементов](./interlude_projecting_children.md)
|
||||
- [Управление глобальным состоянием](./15_global_state.md)
|
||||
- [Маршрутизатор URL](./router/README.md)
|
||||
- [Определение `<Routes/>`](./router/16_routes.md)
|
||||
- [Вложенная маршрутизация](./router/17_nested_routing.md)
|
||||
- [Параметры в пути и в строке запроса](./router/18_params_and_queries.md)
|
||||
- [`<A/>`](./router/19_a.md)
|
||||
- [`<Form/>`](./router/20_form.md)
|
||||
- [Примечание: Стили](./interlude_styling.md)
|
||||
- [Метаданные](./metadata.md)
|
||||
- [Рендеринг на стороне клиента (CSR): Заключение](./csr_wrapping_up.md)
|
||||
- [Часть 2: Рендеринг на стороне сервера (SSR)](./ssr/README.md)
|
||||
- [`cargo-leptos`](./ssr/21_cargo_leptos.md)
|
||||
- [Жизненный цикл загрузки страницы](./ssr/22_life_cycle.md)
|
||||
- [Асинхронный рендеринг и режимы SSR](./ssr/23_ssr_modes.md)
|
||||
- [Баги возникающие при гидратации](./ssr/24_hydration_bugs.md)
|
||||
- [Работа с сервером](./server/README.md)
|
||||
- [Серверные функции](./server/25_server_functions.md)
|
||||
- [Экстракторы](./server/26_extractors.md)
|
||||
- [Ответы и перенаправления](./server/27_response.md)
|
||||
- [Постепенное улучшение и Изящная деградация](./progressive_enhancement/README.md)
|
||||
- [`<ActionForm/>`](./progressive_enhancement/action_form.md)
|
||||
- [Развёртывание](./deployment/README.md)
|
||||
- [Оптимизация размера бинарника WASM](./deployment/binary_size.md)
|
||||
- [Руководство: Острова](./islands.md)
|
||||
|
||||
- [Приложение: Как работает реактивная система?](./appendix_reactive_graph.md)
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/appendix_reactive_graph.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/appendix_reactive_graph.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/async/10_resources.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/async/10_resources.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/async/11_suspense.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/async/11_suspense.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/async/12_transition.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/async/12_transition.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/async/13_action.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/async/13_action.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/async/index.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/async/index.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/csr_wrapping_up.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/csr_wrapping_up.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/deployment/index.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/deployment/index.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/deployment/binary_size.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/deployment/binary_size.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/getting_started/index.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/getting_started/index.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/getting_started/community_crates.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/getting_started/community_crates.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/getting_started/leptos_dx.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/getting_started/leptos_dx.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/interlude_projecting_children.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/interlude_projecting_children.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/interlude_styling.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/interlude_styling.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/islands.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/islands.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/metadata.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/metadata.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/progressive_enhancement/index.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/progressive_enhancement/index.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/progressive_enhancement/action_form.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/progressive_enhancement/action_form.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/reactivity/14_create_effect.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/reactivity/14_create_effect.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/reactivity/index.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/reactivity/index.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/reactivity/interlude_functions.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/reactivity/interlude_functions.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/reactivity/working_with_signals.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/reactivity/working_with_signals.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/router/16_routes.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/router/16_routes.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/router/17_nested_routing.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/router/17_nested_routing.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/router/18_params_and_queries.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/router/18_params_and_queries.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/router/19_a.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/router/19_a.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/router/20_form.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/router/20_form.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/router/index.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/router/index.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/server/25_server_functions.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/server/25_server_functions.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/server/26_extractors.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/server/26_extractors.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/server/27_response.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/server/27_response.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/server/index.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/server/index.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/ssr/21_cargo_leptos.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/ssr/21_cargo_leptos.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/ssr/22_life_cycle.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/ssr/22_life_cycle.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/ssr/23_ssr_modes.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/ssr/23_ssr_modes.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/ssr/24_hydration_bugs.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/ssr/24_hydration_bugs.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/ssr/index.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/ssr/index.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/testing.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/testing.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/01_basic_component.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/view/01_basic_component.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/02_dynamic_attributes.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/view/02_dynamic_attributes.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/03_components.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/view/03_components.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/04_iteration.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/view/04_iteration.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/04b_iteration.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/view/04b_iteration.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/05_forms.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/view/05_forms.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/06_control_flow.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/view/06_control_flow.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/07_errors.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/view/07_errors.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/08_parent_child.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/view/08_parent_child.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/09_component_children.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/view/09_component_children.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/index.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/view/index.html">
|
||||
@@ -1,2 +0,0 @@
|
||||
<meta http-equiv="refresh" content="0; URL=https://book.leptos.dev/view/builder.html">
|
||||
<link rel="canonical" href="https://book.leptos.dev/view/builder.html">
|
||||
@@ -1,13 +0,0 @@
|
||||
[package]
|
||||
name = "either_of"
|
||||
version = "0.1.0"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Utilities for working with enumerated types that contain one of 2..n other types."
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pin-project-lite = "0.2.14"
|
||||
@@ -1 +0,0 @@
|
||||
extend = { path = "../cargo-make/main.toml" }
|
||||
@@ -1 +0,0 @@
|
||||
Utilities for working with enumerated types that contain one of `2..n` other types.
|
||||
@@ -1,236 +0,0 @@
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! Utilities for working with enumerated types that contain one of `2..n` other types.
|
||||
|
||||
use core::{
|
||||
fmt::Display,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Either<A, B> {
|
||||
Left(A),
|
||||
Right(B),
|
||||
}
|
||||
|
||||
impl<Item, A, B> Iterator for Either<A, B>
|
||||
where
|
||||
A: Iterator<Item = Item>,
|
||||
B: Iterator<Item = Item>,
|
||||
{
|
||||
type Item = Item;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Either::Left(i) => i.next(),
|
||||
Either::Right(i) => i.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = EitherFutureProj]
|
||||
pub enum EitherFuture<A, B> {
|
||||
Left { #[pin] inner: A },
|
||||
Right { #[pin] inner: B },
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> Future for EitherFuture<A, B>
|
||||
where
|
||||
A: Future,
|
||||
B: Future,
|
||||
{
|
||||
type Output = Either<A::Output, B::Output>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
match this {
|
||||
EitherFutureProj::Left { inner } => match inner.poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(inner) => Poll::Ready(Either::Left(inner)),
|
||||
},
|
||||
EitherFutureProj::Right { inner } => match inner.poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(inner) => Poll::Ready(Either::Right(inner)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! tuples {
|
||||
($name:ident + $fut_name:ident + $fut_proj:ident => $($ty:ident),*) => {
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
pub enum $name<$($ty,)*> {
|
||||
$($ty ($ty),)*
|
||||
}
|
||||
|
||||
impl<$($ty,)*> Display for $name<$($ty,)*>
|
||||
where
|
||||
$($ty: Display,)*
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
$($name::$ty(this) => this.fmt(f),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Item, $($ty,)*> Iterator for $name<$($ty,)*>
|
||||
where
|
||||
$($ty: Iterator<Item = Item>,)*
|
||||
{
|
||||
type Item = Item;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
$($name::$ty(i) => i.next(),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = $fut_proj]
|
||||
pub enum $fut_name<$($ty,)*> {
|
||||
$($ty { #[pin] inner: $ty },)*
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($ty,)*> Future for $fut_name<$($ty,)*>
|
||||
where
|
||||
$($ty: Future,)*
|
||||
{
|
||||
type Output = $name<$($ty::Output,)*>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
match this {
|
||||
$($fut_proj::$ty { inner } => match inner.poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(inner) => Poll::Ready($name::$ty(inner)),
|
||||
},)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tuples!(EitherOf3 + EitherOf3Future + EitherOf3FutureProj => A, B, C);
|
||||
tuples!(EitherOf4 + EitherOf4Future + EitherOf4FutureProj => A, B, C, D);
|
||||
tuples!(EitherOf5 + EitherOf5Future + EitherOf5FutureProj => A, B, C, D, E);
|
||||
tuples!(EitherOf6 + EitherOf6Future + EitherOf6FutureProj => A, B, C, D, E, F);
|
||||
tuples!(EitherOf7 + EitherOf7Future + EitherOf7FutureProj => A, B, C, D, E, F, G);
|
||||
tuples!(EitherOf8 + EitherOf8Future + EitherOf8FutureProj => A, B, C, D, E, F, G, H);
|
||||
tuples!(EitherOf9 + EitherOf9Future + EitherOf9FutureProj => A, B, C, D, E, F, G, H, I);
|
||||
tuples!(EitherOf10 + EitherOf10Future + EitherOf10FutureProj => A, B, C, D, E, F, G, H, I, J);
|
||||
tuples!(EitherOf11 + EitherOf11Future + EitherOf11FutureProj => A, B, C, D, E, F, G, H, I, J, K);
|
||||
tuples!(EitherOf12 + EitherOf12Future + EitherOf12FutureProj => A, B, C, D, E, F, G, H, I, J, K, L);
|
||||
tuples!(EitherOf13 + EitherOf13Future + EitherOf13FutureProj => A, B, C, D, E, F, G, H, I, J, K, L, M);
|
||||
tuples!(EitherOf14 + EitherOf14Future + EitherOf14FutureProj => A, B, C, D, E, F, G, H, I, J, K, L, M, N);
|
||||
tuples!(EitherOf15 + EitherOf15Future + EitherOf15FutureProj => A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
|
||||
tuples!(EitherOf16 + EitherOf16Future + EitherOf16FutureProj => A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
|
||||
|
||||
/// Matches over the first expression and returns an either ([`Either`], [`EitherOf3`], ... [`EitherOf6`])
|
||||
/// composed of the values returned by the match arms.
|
||||
///
|
||||
/// The pattern syntax is exactly the same as found in a match arm.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use either_of::*;
|
||||
/// let either2 = either!(Some("hello"),
|
||||
/// Some(s) => s.len(),
|
||||
/// None => 0.0,
|
||||
/// );
|
||||
/// assert!(matches!(either2, Either::<usize, f64>::Left(5)));
|
||||
///
|
||||
/// let either3 = either!(Some("admin"),
|
||||
/// Some("admin") => "hello admin",
|
||||
/// Some(_) => 'x',
|
||||
/// _ => 0,
|
||||
/// );
|
||||
/// assert!(matches!(either3, EitherOf3::<&str, char, i32>::A("hello admin")));
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! either {
|
||||
($match:expr, $left_pattern:pat => $left_expression:expr, $right_pattern:pat => $right_expression:expr,) => {
|
||||
match $match {
|
||||
$left_pattern => $crate::Either::Left($left_expression),
|
||||
$right_pattern => $crate::Either::Right($right_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr,) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf3::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf3::B($b_expression),
|
||||
$c_pattern => $crate::EitherOf3::C($c_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr,) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf4::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf4::B($b_expression),
|
||||
$c_pattern => $crate::EitherOf4::C($c_expression),
|
||||
$d_pattern => $crate::EitherOf4::D($d_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr,) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf5::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf5::B($b_expression),
|
||||
$c_pattern => $crate::EitherOf5::C($c_expression),
|
||||
$d_pattern => $crate::EitherOf5::D($d_expression),
|
||||
$e_pattern => $crate::EitherOf5::E($e_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr,) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf6::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf6::B($b_expression),
|
||||
$c_pattern => $crate::EitherOf6::C($c_expression),
|
||||
$d_pattern => $crate::EitherOf6::D($d_expression),
|
||||
$e_pattern => $crate::EitherOf6::E($e_expression),
|
||||
$f_pattern => $crate::EitherOf6::F($f_expression),
|
||||
}
|
||||
}; // if you need more eithers feel free to open a PR ;-)
|
||||
}
|
||||
|
||||
// compile time test
|
||||
#[test]
|
||||
fn either_macro() {
|
||||
let _: Either<&str, f64> = either!(12,
|
||||
12 => "12",
|
||||
_ => 0.0,
|
||||
);
|
||||
let _: EitherOf3<&str, f64, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf4<&str, f64, char, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf5<&str, f64, char, f32, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
15 => 0.0f32,
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf6<&str, f64, char, f32, u8, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
15 => 0.0f32,
|
||||
16 => 24u8,
|
||||
_ => 12,
|
||||
);
|
||||
}
|
||||
@@ -47,11 +47,11 @@ CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = [
|
||||
workspace = false
|
||||
description = "Generate the list of workspace members"
|
||||
script = '''
|
||||
examples=$(ls |
|
||||
grep -v .md |
|
||||
grep -v Makefile.toml |
|
||||
grep -v cargo-make |
|
||||
grep -v gtk |
|
||||
examples=$(ls |
|
||||
grep -v .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"
|
||||
'''
|
||||
|
||||
@@ -7,19 +7,21 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
actix-files = { version = "0.6.6", optional = true }
|
||||
actix-web = { version = "4.8", optional = true, features = ["macros"] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
cfg-if = "1.0"
|
||||
actix-files = { version = "0.6", optional = true }
|
||||
actix-web = { version = "4", optional = true, features = ["macros"] }
|
||||
console_error_panic_hook = "0.1"
|
||||
cfg-if = "1"
|
||||
http = { version = "0.2", optional = true }
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
wasm-bindgen = "0.2.93"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
wasm-bindgen = "0.2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
|
||||
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
|
||||
ssr = [
|
||||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
|
||||
@@ -1,9 +1,68 @@
|
||||
# Action Form Error Handling Example
|
||||
<picture>
|
||||
<source srcset="https://raw.githubusercontent.com/leptos-rs/leptos/main/docs/logos/Leptos_logo_Solid_White.svg" media="(prefers-color-scheme: dark)">
|
||||
<img src="https://raw.githubusercontent.com/leptos-rs/leptos/main/docs/logos/Leptos_logo_RGB.svg" alt="Leptos Logo">
|
||||
</picture>
|
||||
|
||||
## Getting Started
|
||||
# Leptos Starter Template
|
||||
|
||||
See the [Examples README](../README.md) for setup and run instructions.
|
||||
This is a template for use with the [Leptos](https://github.com/leptos-rs/leptos) web framework and the [cargo-leptos](https://github.com/akesson/cargo-leptos) tool.
|
||||
|
||||
## Quick Start
|
||||
## Creating your template repo
|
||||
|
||||
Execute `cargo leptos watch` to run this example.
|
||||
If you don't have `cargo-leptos` installed you can install it with
|
||||
|
||||
`cargo install cargo-leptos`
|
||||
|
||||
Then run
|
||||
|
||||
`cargo leptos new --git leptos-rs/start`
|
||||
|
||||
to generate a new project template (you will be prompted to enter a project name).
|
||||
|
||||
`cd {projectname}`
|
||||
|
||||
to go to your newly created project.
|
||||
|
||||
Of course, you should explore around the project structure, but the best place to start with your application code is in `src/app.rs`.
|
||||
|
||||
## Running your project
|
||||
|
||||
`cargo leptos watch`
|
||||
By default, you can access your local project at `http://localhost:3000`
|
||||
|
||||
## Installing Additional Tools
|
||||
|
||||
By default, `cargo-leptos` uses `nightly` Rust, `cargo-generate`, and `sass`. If you run into any trouble, you may need to install one or more of these tools.
|
||||
|
||||
1. `rustup toolchain install nightly --allow-downgrade` - make sure you have Rust nightly
|
||||
2. `rustup target add wasm32-unknown-unknown` - add the ability to compile Rust to WebAssembly
|
||||
3. `cargo install cargo-generate` - install `cargo-generate` binary (should be installed automatically in future)
|
||||
4. `npm install -g sass` - install `dart-sass` (should be optional in future)
|
||||
|
||||
## Executing a Server on a Remote Machine Without the Toolchain
|
||||
After running a `cargo leptos build --release` the minimum files needed are:
|
||||
|
||||
1. The server binary located in `target/server/release`
|
||||
2. The `site` directory and all files within located in `target/site`
|
||||
|
||||
Copy these files to your remote server. The directory structure should be:
|
||||
```text
|
||||
leptos_start
|
||||
site/
|
||||
```
|
||||
Set the following environment variables (updating for your project as needed):
|
||||
```sh
|
||||
export LEPTOS_OUTPUT_NAME="leptos_start"
|
||||
export LEPTOS_SITE_ROOT="site"
|
||||
export LEPTOS_SITE_PKG_DIR="pkg"
|
||||
export LEPTOS_SITE_ADDR="127.0.0.1:3000"
|
||||
export LEPTOS_RELOAD_PORT="3001"
|
||||
```
|
||||
Finally, run the server binary.
|
||||
|
||||
## Notes about CSR and Trunk:
|
||||
Although it is not recommended, you can also run your project without server integration using the feature `csr` and `trunk serve`:
|
||||
|
||||
`trunk serve --open --features csr`
|
||||
|
||||
This may be useful for integrating external tools which require a static site, e.g. `tauri`.
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
use leptos::{logging, prelude::*};
|
||||
use leptos_router::{
|
||||
components::{FlatRoutes, Route, Router},
|
||||
StaticSegment,
|
||||
};
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
// Provides context that manages stylesheets, titles, meta tags, etc.
|
||||
provide_meta_context();
|
||||
|
||||
view! {
|
||||
// injects a stylesheet into the document <head>
|
||||
// id=leptos means cargo-leptos will hot-reload this stylesheet
|
||||
<Stylesheet id="leptos" href="/pkg/leptos_start.css"/>
|
||||
|
||||
// sets the document title
|
||||
<Title text="Welcome to Leptos"/>
|
||||
|
||||
// content for this welcome page
|
||||
<Router>
|
||||
<main id="app">
|
||||
<FlatRoutes fallback=NotFound>
|
||||
<Route path=StaticSegment("") view=HomePage/>
|
||||
</FlatRoutes>
|
||||
<Routes>
|
||||
<Route path="" view=HomePage/>
|
||||
<Route path="/*any" view=NotFound/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
}
|
||||
@@ -34,7 +43,7 @@ async fn do_something(
|
||||
/// Renders the home page of your application.
|
||||
#[component]
|
||||
fn HomePage() -> impl IntoView {
|
||||
let do_something_action = ServerAction::<DoSomething>::new();
|
||||
let do_something_action = Action::<DoSomething, _>::server();
|
||||
let value = Signal::derive(move || {
|
||||
do_something_action
|
||||
.value()
|
||||
@@ -48,12 +57,17 @@ fn HomePage() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<h1>"Test the action form!"</h1>
|
||||
<ErrorBoundary fallback=move |error| {
|
||||
move || format!("{:#?}", error.get())
|
||||
}>
|
||||
<pre>{value}</pre>
|
||||
<ActionForm action=do_something_action attr:class="form">
|
||||
<label>"Should error: "<input type="checkbox" name="should_error"/></label>
|
||||
<ErrorBoundary fallback=move |error| format!("{:#?}", error
|
||||
.get()
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.1.into_inner()
|
||||
.to_string())
|
||||
>
|
||||
{value}
|
||||
<ActionForm action=do_something_action class="form">
|
||||
<label>Should error: <input type="checkbox" name="should_error"/></label>
|
||||
<button type="submit">Submit</button>
|
||||
</ActionForm>
|
||||
</ErrorBoundary>
|
||||
@@ -77,5 +91,7 @@ fn NotFound() -> impl IntoView {
|
||||
resp.set_status(actix_web::http::StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
view! { <h1>"Not Found"</h1> }
|
||||
view! {
|
||||
<h1>"Not Found"</h1>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
pub mod app;
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||
pub fn hydrate() {
|
||||
use app::*;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "hydrate")] {
|
||||
|
||||
console_error_panic_hook::set_once();
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
leptos::mount::hydrate_body(App);
|
||||
#[wasm_bindgen]
|
||||
pub fn hydrate() {
|
||||
use app::*;
|
||||
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
leptos::mount_to_body(App);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,47 +4,25 @@ async fn main() -> std::io::Result<()> {
|
||||
use action_form_error_handling::app::*;
|
||||
use actix_files::Files;
|
||||
use actix_web::*;
|
||||
use leptos::prelude::*;
|
||||
use leptos::*;
|
||||
use leptos_actix::{generate_route_list, LeptosRoutes};
|
||||
use leptos_meta::MetaTags;
|
||||
|
||||
// Generate the list of routes in your Leptos App
|
||||
let conf = get_configuration(None).unwrap();
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
let addr = conf.leptos_options.site_addr;
|
||||
// Generate the list of routes in your Leptos App
|
||||
let routes = generate_route_list(App);
|
||||
println!("listening on http://{}", &addr);
|
||||
|
||||
HttpServer::new(move || {
|
||||
// Generate the list of routes in your Leptos App
|
||||
let routes = generate_route_list(App);
|
||||
let leptos_options = &conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
|
||||
App::new()
|
||||
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||
// serve JS/WASM/CSS from `pkg`
|
||||
.service(Files::new("/pkg", format!("{site_root}/pkg")))
|
||||
.leptos_routes(routes, {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || {
|
||||
use leptos::prelude::*;
|
||||
|
||||
view! {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1"
|
||||
/>
|
||||
<AutoReload options=leptos_options.clone()/>
|
||||
<HydrationScripts options=leptos_options.clone()/>
|
||||
<MetaTags/>
|
||||
</head>
|
||||
<body>
|
||||
<App/>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
}})
|
||||
.leptos_routes(leptos_options.to_owned(), routes.to_owned(), App)
|
||||
.app_data(web::Data::new(leptos_options.to_owned()))
|
||||
//.wrap(middleware::Compress::default())
|
||||
})
|
||||
.bind(&addr)?
|
||||
@@ -52,10 +30,24 @@ async fn main() -> std::io::Result<()> {
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
#[cfg(not(any(feature = "ssr", feature = "csr")))]
|
||||
pub fn main() {
|
||||
// no client-side main function
|
||||
// unless we want this to work with e.g., Trunk for pure client-side testing
|
||||
// see lib.rs for hydration function instead
|
||||
// see optional feature `csr` instead
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "ssr"), feature = "csr"))]
|
||||
pub fn main() {
|
||||
// a client-side main function is required for using `trunk serve`
|
||||
// prefer using `cargo leptos serve` instead
|
||||
// to run: `trunk serve --open --features csr`
|
||||
use action_form_error_handling::app::*;
|
||||
use leptos::*;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
leptos::mount_to_body(App);
|
||||
}
|
||||
|
||||
14
examples/animated_show/Cargo.toml
Normal file
14
examples/animated_show/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "animated-show"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
@@ -1,4 +1,4 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/cargo-leptos.toml" },
|
||||
{ path = "../cargo-make/trunk_server.toml" },
|
||||
]
|
||||
14
examples/animated_show/README.md
Normal file
14
examples/animated_show/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Animated Show Example
|
||||
|
||||
This is a very simple example of the `<AnimatedShow>` component.
|
||||
|
||||
The `<AnimatedShow>` component is an extension for the `<Show>` component and it will not take in a fallback, but it will unmount the component from the DOM after a given duration. This makes it possible to have really easy unmount animations with just
|
||||
CSS.
|
||||
|
||||
## Getting Started
|
||||
|
||||
See the [Examples README](../README.md) for setup and run instructions.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Run `trunk serve --open` to run this example.
|
||||
42
examples/animated_show/index.html
Normal file
42
examples/animated_show/index.html
Normal file
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z"/>
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
|
||||
<style>
|
||||
.hover-me {
|
||||
width: 100px;
|
||||
margin: 1rem;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border: 1px solid grey;
|
||||
}
|
||||
.here-i-am {
|
||||
width: 100px;
|
||||
margin: 1rem;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
background: black;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@keyframes fade-out {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
.fade-in-1000 {
|
||||
animation: 1000ms fade-in forwards;
|
||||
}
|
||||
.fade-out-1000 {
|
||||
animation: 1000ms fade-out forwards;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user