mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 18:22:34 -05:00
Compare commits
445 Commits
2182
...
0.7.0-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8c036ab51 | ||
|
|
d31c48b115 | ||
|
|
46376bbd0c | ||
|
|
0d83042621 | ||
|
|
a951bad070 | ||
|
|
0bf9063a76 | ||
|
|
78581d87cb | ||
|
|
af1525d4ea | ||
|
|
61a9d45060 | ||
|
|
1bf2982d03 | ||
|
|
5d7dfd360e | ||
|
|
704b66ec31 | ||
|
|
3ff20c3708 | ||
|
|
7c2df473c2 | ||
|
|
a75edf3526 | ||
|
|
f0e259f42f | ||
|
|
e3e9ad54c6 | ||
|
|
d344f96138 | ||
|
|
876ce79c2a | ||
|
|
336e702b86 | ||
|
|
09bdeb7286 | ||
|
|
6aac824207 | ||
|
|
baf11b30b3 | ||
|
|
6e6d16ffe5 | ||
|
|
cadd3cdd09 | ||
|
|
548e5acb39 | ||
|
|
d135dbf337 | ||
|
|
c1458fd061 | ||
|
|
57170beca6 | ||
|
|
72492f3102 | ||
|
|
ea22cd3757 | ||
|
|
0dded850ab | ||
|
|
c189fbef6c | ||
|
|
ee8ac29496 | ||
|
|
645c4abee3 | ||
|
|
532ee6daea | ||
|
|
4f65c4440a | ||
|
|
c7fd25d164 | ||
|
|
5f944f997f | ||
|
|
3a633be215 | ||
|
|
7581348bcd | ||
|
|
c6e8058b82 | ||
|
|
dd6e05559f | ||
|
|
d9d80344fb | ||
|
|
c82661b5b1 | ||
|
|
262df175dd | ||
|
|
8d13b3e679 | ||
|
|
bd92397ac7 | ||
|
|
b464d5c842 | ||
|
|
beecba5532 | ||
|
|
c880250729 | ||
|
|
463f5f4440 | ||
|
|
bc2cd0c7c1 | ||
|
|
db467fb153 | ||
|
|
da37908d81 | ||
|
|
df31ffb00a | ||
|
|
53778936fa | ||
|
|
897751de40 | ||
|
|
1c024b7464 | ||
|
|
9f2963c4a9 | ||
|
|
e4a2344110 | ||
|
|
68c4038fe5 | ||
|
|
057b449ba2 | ||
|
|
4b323829f0 | ||
|
|
e58d4f8fa8 | ||
|
|
85cf4bfde8 | ||
|
|
fdf8250770 | ||
|
|
947079ef99 | ||
|
|
618f649a4c | ||
|
|
da31d321ec | ||
|
|
a62b154ec8 | ||
|
|
30544ca05b | ||
|
|
eaa039efcc | ||
|
|
f1f5e19136 | ||
|
|
f14702c39f | ||
|
|
602b36ee60 | ||
|
|
8605e3f0cb | ||
|
|
f45dbf25ab | ||
|
|
9be2bad400 | ||
|
|
0600a646be | ||
|
|
722bedee63 | ||
|
|
597eb4fc04 | ||
|
|
5ec26aeada | ||
|
|
c84e189a61 | ||
|
|
88831c5543 | ||
|
|
f455c55b85 | ||
|
|
dec1b02579 | ||
|
|
c4ccc306ca | ||
|
|
330c2928fc | ||
|
|
464c42f314 | ||
|
|
6d71383637 | ||
|
|
2a99996e53 | ||
|
|
21292c059f | ||
|
|
36b9354869 | ||
|
|
c2cbfd50a3 | ||
|
|
499b7ea4fc | ||
|
|
54595e0ef8 | ||
|
|
3eb6b14da7 | ||
|
|
1d0865bee7 | ||
|
|
7145efc813 | ||
|
|
db99714f66 | ||
|
|
e184438b2c | ||
|
|
0a0d1803b8 | ||
|
|
812ad7c59f | ||
|
|
8105a27b13 | ||
|
|
eb0fa2cc9e | ||
|
|
fec59ff13f | ||
|
|
134a9d1593 | ||
|
|
506e5a7470 | ||
|
|
08bfcdb0ff | ||
|
|
97c1a32200 | ||
|
|
3bf17f9ff7 | ||
|
|
aff6939f79 | ||
|
|
4c7223136c | ||
|
|
0379a3c2ff | ||
|
|
8dacdb5917 | ||
|
|
6fa4b0cd4c | ||
|
|
17442f3e94 | ||
|
|
f9100a6752 | ||
|
|
bffe2607d8 | ||
|
|
e8f2c5a931 | ||
|
|
382ece1686 | ||
|
|
4be21eb298 | ||
|
|
6c9f3a9c2d | ||
|
|
04d1ed3371 | ||
|
|
a9552411fe | ||
|
|
ffbaa482b9 | ||
|
|
fc670fb473 | ||
|
|
d911638221 | ||
|
|
90513e8156 | ||
|
|
abc53662f8 | ||
|
|
914db2da00 | ||
|
|
68273127c2 | ||
|
|
b277e8322e | ||
|
|
a4f68d8061 | ||
|
|
d9ed2caf5a | ||
|
|
df4189f7bf | ||
|
|
944329fdba | ||
|
|
136f96b090 | ||
|
|
869fc2fb01 | ||
|
|
a28042e863 | ||
|
|
1dda2fb1dd | ||
|
|
3f094e76ae | ||
|
|
5e8ff1f222 | ||
|
|
8a9466acda | ||
|
|
c4c491935b | ||
|
|
ddbbb68255 | ||
|
|
b9be78bfc9 | ||
|
|
324a3e5e37 | ||
|
|
32666b97fe | ||
|
|
a03870315e | ||
|
|
ddc68b9698 | ||
|
|
4437e9cc75 | ||
|
|
e800108c46 | ||
|
|
136be456b2 | ||
|
|
a8f75f9678 | ||
|
|
0e4398bc86 | ||
|
|
fb38f51f82 | ||
|
|
7a7bc394df | ||
|
|
c78608d1d0 | ||
|
|
b7d3a628a1 | ||
|
|
6556a0b6a5 | ||
|
|
5a7ddeb9af | ||
|
|
2a9ac08d6e | ||
|
|
b896e4d5bf | ||
|
|
757435b218 | ||
|
|
65b2dc4136 | ||
|
|
c83435812d | ||
|
|
e10d872c02 | ||
|
|
c335203205 | ||
|
|
98ccf27909 | ||
|
|
521a591ee4 | ||
|
|
7dbfb0a86e | ||
|
|
0894dbff58 | ||
|
|
cce4b0aef0 | ||
|
|
dea4a2a91a | ||
|
|
828ea30c86 | ||
|
|
fb583262f4 | ||
|
|
e92711166d | ||
|
|
db134bce48 | ||
|
|
7d54ecf4a1 | ||
|
|
37112d9cc2 | ||
|
|
dd21e7e8b7 | ||
|
|
63e120f147 | ||
|
|
70b3a7b259 | ||
|
|
2b5321dcdd | ||
|
|
6a6770ccee | ||
|
|
5d9eab2e03 | ||
|
|
6e24177139 | ||
|
|
51720368ce | ||
|
|
906bb15c13 | ||
|
|
39154d917a | ||
|
|
e7d3d4ecf2 | ||
|
|
a88375204d | ||
|
|
7442147737 | ||
|
|
d20b8b52a0 | ||
|
|
bd26c611e5 | ||
|
|
679799f9c8 | ||
|
|
a3efe5a228 | ||
|
|
5c7a5654eb | ||
|
|
d481c1b3c8 | ||
|
|
e9d86a549f | ||
|
|
9290a1a66e | ||
|
|
89b15e784b | ||
|
|
f1c1401da6 | ||
|
|
3eca539423 | ||
|
|
6c09456f5a | ||
|
|
3190ebc971 | ||
|
|
d82215c919 | ||
|
|
a35b9f283c | ||
|
|
0eca06e5a2 | ||
|
|
aa32552f1a | ||
|
|
472674a841 | ||
|
|
86aa1a019e | ||
|
|
0734af4cb1 | ||
|
|
ced6510e7d | ||
|
|
df0e59bf8a | ||
|
|
717c99a1c5 | ||
|
|
4d28b134d4 | ||
|
|
a74fe75e10 | ||
|
|
a38b410587 | ||
|
|
23256f4d9d | ||
|
|
16d6110e75 | ||
|
|
c79852cea5 | ||
|
|
97c5a7ca9d | ||
|
|
dac4549117 | ||
|
|
ff4e3338e0 | ||
|
|
1f4de404ee | ||
|
|
adf5fe83b1 | ||
|
|
88feadecc9 | ||
|
|
9ddebd2413 | ||
|
|
6e97cb30a1 | ||
|
|
1dfb34413c | ||
|
|
92b2d72bfb | ||
|
|
ebbf1e9118 | ||
|
|
9e84e1f57c | ||
|
|
8be0f499b9 | ||
|
|
481c0c8e19 | ||
|
|
9515840c9a | ||
|
|
83cd043ac7 | ||
|
|
e103b10a02 | ||
|
|
1ca0039f91 | ||
|
|
2826b5d252 | ||
|
|
cb3fb964d9 | ||
|
|
06ea212d40 | ||
|
|
4cf0a6782c | ||
|
|
1d11836c23 | ||
|
|
6c6a009281 | ||
|
|
bc66faea1c | ||
|
|
ada87a4b4d | ||
|
|
0fc64a432f | ||
|
|
13b22c7623 | ||
|
|
d481dee95f | ||
|
|
65c6527527 | ||
|
|
f96b8b24ec | ||
|
|
079cb4d675 | ||
|
|
2f39ac5c33 | ||
|
|
dca7c857ae | ||
|
|
ef3b95fca0 | ||
|
|
f5d95b2f73 | ||
|
|
b906625a83 | ||
|
|
cfd369e5ff | ||
|
|
36ab0e8ead | ||
|
|
440e397bb2 | ||
|
|
c5520dca48 | ||
|
|
2aacf81380 | ||
|
|
43f70983ac | ||
|
|
386665d6b6 | ||
|
|
38b84b3c61 | ||
|
|
b1ee80ef9e | ||
|
|
3586f772a4 | ||
|
|
b2920ea32a | ||
|
|
0f5cb43d26 | ||
|
|
4731bb0716 | ||
|
|
7d8e51f81a | ||
|
|
38d7acc7d0 | ||
|
|
393cd6577b | ||
|
|
db1de18549 | ||
|
|
8225697a50 | ||
|
|
6e77f3a25f | ||
|
|
fcd5f682e2 | ||
|
|
7427c3417f | ||
|
|
436bd3f743 | ||
|
|
c11055cb0d | ||
|
|
816deeba86 | ||
|
|
8fa9c2a210 | ||
|
|
cfc5bb6c82 | ||
|
|
9fb0b021c1 | ||
|
|
a9e9832b10 | ||
|
|
e2a47e979b | ||
|
|
5fc03b8f54 | ||
|
|
4b805e1c72 | ||
|
|
4478444734 | ||
|
|
243e88cd35 | ||
|
|
e775a64bb9 | ||
|
|
9c2c9c4eca | ||
|
|
a347f25959 | ||
|
|
2cb146f6af | ||
|
|
0a611c9448 | ||
|
|
d76b7a3bf3 | ||
|
|
1775060fb0 | ||
|
|
f576babd6f | ||
|
|
07eaa15902 | ||
|
|
ce83ca2504 | ||
|
|
5584f3fc37 | ||
|
|
d470de5a4c | ||
|
|
74faa51022 | ||
|
|
65a400e084 | ||
|
|
35dc9b5e25 | ||
|
|
3c63069aaf | ||
|
|
1199d5afff | ||
|
|
56f9df9188 | ||
|
|
732cfca287 | ||
|
|
23546f55dd | ||
|
|
18bd20b7fb | ||
|
|
df3b714b6a | ||
|
|
406bc24f13 | ||
|
|
136b08a1fb | ||
|
|
5e6274d338 | ||
|
|
db98405215 | ||
|
|
262d0ccc3e | ||
|
|
86f8e5e903 | ||
|
|
8f47cb7a20 | ||
|
|
32b4defa36 | ||
|
|
51117f52c3 | ||
|
|
27445ce1f7 | ||
|
|
a355ef0201 | ||
|
|
caf51420f9 | ||
|
|
077ea24f05 | ||
|
|
1166d69d3a | ||
|
|
0541e8ce90 | ||
|
|
98f891d45c | ||
|
|
075235f8dd | ||
|
|
69db634da1 | ||
|
|
147ba5b7e6 | ||
|
|
d3192c9839 | ||
|
|
86de41c068 | ||
|
|
9d31d885c5 | ||
|
|
e07227f1c4 | ||
|
|
97321c58f0 | ||
|
|
8e247f421e | ||
|
|
f45cb64cd9 | ||
|
|
5df6bdbabd | ||
|
|
30fa0ac7d3 | ||
|
|
e09267bde6 | ||
|
|
cb1f75551f | ||
|
|
f42ab13332 | ||
|
|
b479fd4913 | ||
|
|
3aacbd7bba | ||
|
|
ffe82dff78 | ||
|
|
0edf5686fb | ||
|
|
f7c5f62d20 | ||
|
|
1857c6b614 | ||
|
|
df856a7b52 | ||
|
|
4f9510f145 | ||
|
|
3b8ebd88a7 | ||
|
|
89ff803672 | ||
|
|
b1241408ac | ||
|
|
521d9aa27f | ||
|
|
98474eff66 | ||
|
|
e7e58c063b | ||
|
|
720934741d | ||
|
|
16e934bf88 | ||
|
|
2de66a34cf | ||
|
|
ec44cdc123 | ||
|
|
40849d65d1 | ||
|
|
6b34d0287f | ||
|
|
1c9b955e9b | ||
|
|
2595ddcfe6 | ||
|
|
5df260c419 | ||
|
|
a7997ea1d5 | ||
|
|
c6b097c664 | ||
|
|
dcdcaaf01c | ||
|
|
d971a86ee0 | ||
|
|
099ba87216 | ||
|
|
746e39b295 | ||
|
|
1e0a3006cf | ||
|
|
0cae2ca4d1 | ||
|
|
d2da5c6a60 | ||
|
|
263be1d726 | ||
|
|
f942d334c2 | ||
|
|
cf56b6c623 | ||
|
|
2fdf87d416 | ||
|
|
c0cb79a396 | ||
|
|
096a1e48f2 | ||
|
|
e1a2650277 | ||
|
|
d4c1b07fec | ||
|
|
8abbff3060 | ||
|
|
fcd5162ee2 | ||
|
|
85def69017 | ||
|
|
4152a5f852 | ||
|
|
c0c3ec8348 | ||
|
|
b710c629db | ||
|
|
a73ac0236e | ||
|
|
16eb8e6162 | ||
|
|
cd9d899c62 | ||
|
|
991b94291a | ||
|
|
8910b3d36c | ||
|
|
bb37f3b64b | ||
|
|
a710cc4468 | ||
|
|
d8e7ade42a | ||
|
|
c19817ea0d | ||
|
|
b7c2e45a39 | ||
|
|
cf38ac3e6f | ||
|
|
29bbd926e8 | ||
|
|
f19424bcc0 | ||
|
|
4e73a1ec92 | ||
|
|
da96fdaa57 | ||
|
|
c317048196 | ||
|
|
01a7a71059 | ||
|
|
111d6bc389 | ||
|
|
bafb1efcff | ||
|
|
e81f73ae8b | ||
|
|
028f0a5152 | ||
|
|
db597d047d | ||
|
|
d1074d44c6 | ||
|
|
464ec943ee | ||
|
|
0d7186ca0c | ||
|
|
4ef7b64f66 | ||
|
|
87bdc5b47a | ||
|
|
a341f99355 | ||
|
|
f2eec684a8 | ||
|
|
cbee62973a | ||
|
|
14682c1caf | ||
|
|
1cbea396ea | ||
|
|
9c57f6a218 | ||
|
|
e052230343 | ||
|
|
85e26e19ac | ||
|
|
31cb6aaa05 | ||
|
|
7965302b89 | ||
|
|
62ac27dbeb | ||
|
|
9ee801b57b | ||
|
|
6e3b6d5d61 | ||
|
|
01f2977b9d | ||
|
|
c557b1236a | ||
|
|
0ae77d0b5a | ||
|
|
18f73b1482 | ||
|
|
6f96a67d08 | ||
|
|
222b7a17c9 | ||
|
|
45e7847452 | ||
|
|
087a1c332b | ||
|
|
37dae6ae90 | ||
|
|
5a8821c336 | ||
|
|
132dcced68 | ||
|
|
79cb763d82 |
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-semver.yml
vendored
6
.github/workflows/ci-semver.yml
vendored
@@ -14,8 +14,8 @@ jobs:
|
||||
|
||||
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' && github.event.pull_request.labels[0].name != 'breaking'
|
||||
name: Run semver check (nightly-2024-04-14)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -25,4 +25,4 @@ jobs:
|
||||
- name: Semver Checks
|
||||
uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
with:
|
||||
rust-toolchain: nightly-2024-08-01
|
||||
rust-toolchain: nightly-2024-04-14
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -49,4 +49,4 @@ jobs:
|
||||
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@v44
|
||||
uses: tj-actions/changed-files@v41
|
||||
with:
|
||||
dir_names: true
|
||||
dir_names_max_depth: "2"
|
||||
|
||||
2
.github/workflows/get-example-changed.yml
vendored
2
.github/workflows/get-example-changed.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
- name: Get example files that changed
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v44
|
||||
uses: tj-actions/changed-files@v43
|
||||
with:
|
||||
files: |
|
||||
examples/**
|
||||
|
||||
2
.github/workflows/get-leptos-changed.yml
vendored
2
.github/workflows/get-leptos-changed.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
- name: Get source files that changed
|
||||
id: changed-source
|
||||
uses: tj-actions/changed-files@v44
|
||||
uses: tj-actions/changed-files@v43
|
||||
with:
|
||||
files: |
|
||||
any_error/**
|
||||
|
||||
2
.github/workflows/run-cargo-make-task.yml
vendored
2
.github/workflows/run-cargo-make-task.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@v3
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
|
||||
56
Cargo.toml
56
Cargo.toml
@@ -19,8 +19,6 @@ members = [
|
||||
"leptos_macro",
|
||||
"leptos_server",
|
||||
"reactive_graph",
|
||||
"reactive_stores",
|
||||
"reactive_stores_macro",
|
||||
"server_fn",
|
||||
"server_fn_macro",
|
||||
"server_fn/server_fn_macro_default",
|
||||
@@ -37,39 +35,35 @@ members = [
|
||||
"router_macro",
|
||||
"any_error",
|
||||
]
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
exclude = ["benchmarks", "examples"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.7.0-beta2"
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
version = "0.7.0-alpha"
|
||||
rust-version = "1.75"
|
||||
|
||||
[workspace.dependencies]
|
||||
throw_error = { path = "./any_error/", version = "0.2.0-beta2" }
|
||||
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-beta2" }
|
||||
leptos = { path = "./leptos", version = "0.7.0-beta2" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.0-beta2" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.0-beta2" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-beta2" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-beta2" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.0-beta2" }
|
||||
leptos_router = { path = "./router", version = "0.7.0-beta2" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.0-beta2" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.0-beta2" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.0-beta2" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0-beta2" }
|
||||
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-beta2" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.0-beta2" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0-beta2" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.0-beta2" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-beta2" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-beta2" }
|
||||
tachys = { path = "./tachys", version = "0.1.0-beta2" }
|
||||
throw_error = { path = "./any_error/", version = "0.1" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.1" }
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
|
||||
either_of = { path = "./either_of/", version = "0.1" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0-alpha" }
|
||||
leptos = { path = "./leptos", version = "0.7.0-alpha" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.0-alpha" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.0-alpha" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-alpha" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-alpha" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.0-alpha" }
|
||||
leptos_router = { path = "./router", version = "0.7.0-alpha" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.0-alpha" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.0-alpha" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0-alpha" }
|
||||
oco_ref = { path = "./oco", version = "0.2" }
|
||||
or_poisoned = { path = "./or_poisoned", version = "0.1" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.0-alpha" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.0-alpha" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-alpha" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-alpha" }
|
||||
tachys = { path = "./tachys", version = "0.1.0-alpha" }
|
||||
|
||||
[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
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "throw_error"
|
||||
version = "0.2.0-beta2"
|
||||
edition = "2021"
|
||||
version = "0.1.0"
|
||||
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"
|
||||
pin-project-lite = "0.2"
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::{
|
||||
error,
|
||||
fmt::{self, Display},
|
||||
future::Future,
|
||||
mem, ops,
|
||||
ops,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
@@ -92,25 +92,9 @@ 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))),
|
||||
)
|
||||
pub fn set_error_hook(hook: Arc<dyn ErrorHook>) {
|
||||
ERROR_HOOK.with_borrow_mut(|this| *this = Some(hook))
|
||||
}
|
||||
|
||||
/// Invokes the error hook set by [`set_error_hook`] with the given error.
|
||||
@@ -156,10 +140,9 @@ where
|
||||
|
||||
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)));
|
||||
if let Some(hook) = &this.hook {
|
||||
set_error_hook(Arc::clone(hook))
|
||||
}
|
||||
this.inner.poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
[package]
|
||||
name = "any_spawner"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
version = "0.1.0"
|
||||
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]
|
||||
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",
|
||||
futures = "0.3"
|
||||
glib = { version = "0.19", optional = true }
|
||||
thiserror = "1"
|
||||
tokio = { version = "1", optional = true, default-features = false, features = [
|
||||
"rt",
|
||||
] }
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4.42", optional = true }
|
||||
tracing = { version = "0.1", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4", optional = true }
|
||||
|
||||
[features]
|
||||
tracing = ["dep:tracing"]
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
use std::{future::Future, pin::Pin, sync::OnceLock};
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -6,31 +6,31 @@ 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"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[package]
|
||||
name = "const_str_slice_concat"
|
||||
edition = "2021"
|
||||
version = "0.1.0"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
@@ -7,6 +8,5 @@ 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,5 +1,6 @@
|
||||
[package]
|
||||
name = "either_of"
|
||||
edition = "2021"
|
||||
version = "0.1.0"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
@@ -7,7 +8,6 @@ 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"
|
||||
pin-project-lite = "0.2"
|
||||
|
||||
@@ -7,18 +7,19 @@ 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"
|
||||
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]
|
||||
csr = ["leptos/csr"]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:actix-files",
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -52,10 +52,23 @@ 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::prelude::*;
|
||||
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
mount_to_body(App);
|
||||
}
|
||||
|
||||
@@ -10,12 +10,11 @@ lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
console_log = "1.0"
|
||||
log = "0.4.22"
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
gloo-timers = { version = "0.3.0", features = ["futures"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
web-sys = "0.3.70"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
web-sys = "0.3"
|
||||
|
||||
@@ -11,34 +11,34 @@ codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
actix-files = { version = "0.6.6", optional = true }
|
||||
actix-web = { version = "4.8", optional = true, features = ["macros"] }
|
||||
broadcaster = "1.0"
|
||||
console_log = "1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.30"
|
||||
lazy_static = "1.5"
|
||||
actix-files = { version = "0.6", optional = true }
|
||||
actix-web = { version = "4", optional = true, features = ["macros"] }
|
||||
broadcaster = "1"
|
||||
console_log = "1"
|
||||
console_error_panic_hook = "0.1"
|
||||
futures = "0.3"
|
||||
lazy_static = "1"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
log = "0.4.22"
|
||||
once_cell = "1.19"
|
||||
gloo-net = { version = "0.6.0" }
|
||||
wasm-bindgen = "0.2.93"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
simple_logger = "5.0"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
log = "0.4"
|
||||
once_cell = "1.18"
|
||||
gloo-net = { git = "https://github.com/rustwasm/gloo" }
|
||||
wasm-bindgen = "0.2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
simple_logger = "4.3"
|
||||
tracing = { version = "0.1", optional = true }
|
||||
send_wrapper = "0.6.0"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
"dep:tracing",
|
||||
"leptos/ssr",
|
||||
"leptos_actix",
|
||||
"leptos_router/ssr",
|
||||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
"dep:tracing",
|
||||
"leptos/ssr",
|
||||
"leptos_actix",
|
||||
"leptos_router/ssr",
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
|
||||
@@ -76,9 +76,18 @@ pub fn Counters() -> impl IntoView {
|
||||
</nav>
|
||||
<main>
|
||||
<FlatRoutes fallback=|| "Not found.">
|
||||
<Route path=StaticSegment("") view=Counter/>
|
||||
<Route path=StaticSegment("form") view=FormCounter/>
|
||||
<Route path=StaticSegment("multi") view=MultiuserCounter/>
|
||||
<Route
|
||||
path=StaticSegment("")
|
||||
view=Counter
|
||||
/>
|
||||
<Route
|
||||
path=StaticSegment("form")
|
||||
view=FormCounter
|
||||
/>
|
||||
<Route
|
||||
path=StaticSegment("multi")
|
||||
view=MultiuserCounter
|
||||
/>
|
||||
</FlatRoutes>
|
||||
</main>
|
||||
</Router>
|
||||
@@ -113,10 +122,13 @@ pub fn Counter() -> impl IntoView {
|
||||
</p>
|
||||
<ErrorBoundary fallback=|errors| move || format!("Error: {:#?}", errors.get())>
|
||||
<div>
|
||||
<button on:click=move |_| { clear.dispatch(()); }>"Clear"</button>
|
||||
<button on:click=move |_| { dec.dispatch(()); }>"-1"</button>
|
||||
<span>"Value: " <Suspense>{counter} "!"</Suspense></span>
|
||||
<button on:click=move |_| { inc.dispatch(()); }>"+1"</button>
|
||||
<button on:click=move |_| clear.dispatch(())>"Clear"</button>
|
||||
<button on:click=move |_| dec.dispatch(())>"-1"</button>
|
||||
<span>
|
||||
"Value: "
|
||||
<Suspense>{counter} "!" </Suspense>
|
||||
</span>
|
||||
<button on:click=move |_| inc.dispatch(())>"+1"</button>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
@@ -224,12 +236,12 @@ pub fn MultiuserCounter() -> impl IntoView {
|
||||
"This one uses server-sent events (SSE) to live-update when other users make changes."
|
||||
</p>
|
||||
<div>
|
||||
<button on:click=move |_| { clear.dispatch(()); }>"Clear"</button>
|
||||
<button on:click=move |_| { dec.dispatch(()); }>"-1"</button>
|
||||
<button on:click=move |_| clear.dispatch(())>"Clear"</button>
|
||||
<button on:click=move |_| dec.dispatch(())>"-1"</button>
|
||||
<span>
|
||||
"Multiplayer Value: " {move || multiplayer_value.get().unwrap_or_default()}
|
||||
</span>
|
||||
<button on:click=move |_| { inc.dispatch(()); }>"+1"</button>
|
||||
<button on:click=move |_| inc.dispatch(())>"+1"</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -13,6 +13,6 @@ leptos_router = { path = "../../router", features = [] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
web-sys = "0.3.70"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
web-sys = "0.3"
|
||||
|
||||
@@ -14,10 +14,10 @@ console_error_panic_hook = "0.1.7"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
pretty_assertions = "1.4"
|
||||
rstest = "0.22.0"
|
||||
wasm-bindgen-test = "0.3.34"
|
||||
pretty_assertions = "1.3.0"
|
||||
rstest = "0.17.0"
|
||||
|
||||
[dev-dependencies.web-sys]
|
||||
features = ["HtmlElement", "XPathResult"]
|
||||
version = "0.3.70"
|
||||
version = "0.3.61"
|
||||
|
||||
@@ -4,10 +4,10 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
wasm-bindgen = "0.2.93"
|
||||
web-sys = "0.3.70"
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
wasm-bindgen = "0.2"
|
||||
web-sys = "0.3"
|
||||
|
||||
@@ -23,7 +23,7 @@ async fn inc() {
|
||||
div.inner_html(),
|
||||
"<button>Add Counter</button><button>Add 1000 \
|
||||
Counters</button><button>Clear Counters</button><p>Total: \
|
||||
<span>0</span> from <span>0</span> counters.</p><ul><!----></ul>"
|
||||
<span>0</span> from <span>0</span> counters.</p><ul></ul>"
|
||||
);
|
||||
|
||||
// add 3 counters
|
||||
@@ -45,7 +45,7 @@ async fn inc() {
|
||||
type=\"text\"><span>0</span><button>+1</button><button>x</button></\
|
||||
li><li><button>-1</button><input \
|
||||
type=\"text\"><span>0</span><button>+1</button><button>x</button></\
|
||||
li><!----></ul>"
|
||||
li></ul>"
|
||||
);
|
||||
|
||||
let counters = div
|
||||
@@ -87,7 +87,7 @@ async fn inc() {
|
||||
type=\"text\"><span>2</span><button>+1</button><button>x</button></\
|
||||
li><li><button>-1</button><input \
|
||||
type=\"text\"><span>3</span><button>+1</button><button>x</button></\
|
||||
li><!----></ul>"
|
||||
li></ul>"
|
||||
);
|
||||
|
||||
// remove the first counter
|
||||
@@ -110,6 +110,6 @@ async fn inc() {
|
||||
type=\"text\"><span>2</span><button>+1</button><button>x</button></\
|
||||
li><li><button>-1</button><input \
|
||||
type=\"text\"><span>3</span><button>+1</button><button>x</button></\
|
||||
li><!----></ul>"
|
||||
li></ul>"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
log = "0.4.22"
|
||||
console_log = "1.0"
|
||||
log = "0.4"
|
||||
console_log = "1"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
web-sys = { version = "0.3.70", features = ["Clipboard", "Navigator"] }
|
||||
web-sys = { version = "0.3", features = ["Clipboard", "Navigator"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
wasm-bindgen = "0.2.93"
|
||||
web-sys = { version = "0.3.70", features = ["NodeList"] }
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
wasm-bindgen = "0.2"
|
||||
web-sys = { version = "0.3", features = ["NodeList"] }
|
||||
|
||||
@@ -24,7 +24,11 @@ pub fn copy_to_clipboard(el: Element, content: &str) {
|
||||
evt.prevent_default();
|
||||
evt.stop_propagation();
|
||||
|
||||
let _ = window().navigator().clipboard().write_text(&content);
|
||||
let _ = window()
|
||||
.navigator()
|
||||
.clipboard()
|
||||
.expect("navigator.clipboard to be available")
|
||||
.write_text(&content);
|
||||
|
||||
el.set_inner_html(&format!("Copied \"{}\"", &content));
|
||||
});
|
||||
@@ -49,6 +53,7 @@ impl From<()> for Amount {
|
||||
}
|
||||
}
|
||||
|
||||
// .into() will automatically be called on the parameter
|
||||
pub fn add_dot(el: Element, amount: Amount) {
|
||||
use leptos::wasm_bindgen::JsCast;
|
||||
let el = el.unchecked_into::<web_sys::HtmlElement>();
|
||||
@@ -77,17 +82,12 @@ pub fn App() -> impl IntoView {
|
||||
let data = "Hello World!";
|
||||
|
||||
view! {
|
||||
<a href="#" use:copy_to_clipboard=data>
|
||||
"Copy \""
|
||||
{data}
|
||||
"\" to clipboard"
|
||||
</a>
|
||||
<a href="#" use:copy_to_clipboard=data>"Copy \"" {data} "\" to clipboard"</a>
|
||||
// automatically applies the directive to every root element in `SomeComponent`
|
||||
<SomeComponent use:highlight/>
|
||||
<SomeComponent use:highlight />
|
||||
// no value will default to `().into()`
|
||||
<button use:add_dot>"Add a dot"</button>
|
||||
// can manually call `.into()` to convert to the correct type
|
||||
// (automatically calling `.into()` prevents using generics in directive functions)
|
||||
<button use:add_dot=5.into()>"Add 5 dots"</button>
|
||||
// `5.into()` automatically called
|
||||
<button use:add_dot=5>"Add 5 dots"</button>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@ lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
console_log = "1.0"
|
||||
log = "0.4.22"
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
@@ -7,19 +7,19 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
console_error_panic_hook = "0.1"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
http = { version = "1.1" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
axum = { version = "0.7", optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
http = { version = "1.0" }
|
||||
thiserror = "1.0"
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen = "0.2"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
|
||||
@@ -9,16 +9,16 @@ lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr", "tracing"] }
|
||||
reqwasm = "0.5.0"
|
||||
gloo-timers = { version = "0.3.0", features = ["futures"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
log = "0.4.22"
|
||||
console_log = "1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
thiserror = "1.0"
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = "0.3.18"
|
||||
tracing-subscriber-wasm = "0.1.0"
|
||||
reqwasm = "0.5"
|
||||
gloo-timers = { version = "0.3", features = ["futures"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
log = "0.4"
|
||||
console_log = "1"
|
||||
console_error_panic_hook = "0.1"
|
||||
thiserror = "1"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
tracing-subscriber-wasm = "0.1"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
wasm-bindgen-test = "0.3"
|
||||
|
||||
@@ -85,7 +85,7 @@ pub fn fetch_example() -> impl IntoView {
|
||||
<Transition fallback=|| view! { <div>"Loading..."</div> } {..spreadable}>
|
||||
<ErrorBoundary fallback>
|
||||
<ul>
|
||||
{move || Suspend::new(async move {
|
||||
{move || Suspend(async move {
|
||||
cats.await
|
||||
.map(|cats| {
|
||||
cats.iter()
|
||||
|
||||
@@ -4,15 +4,15 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
throw_error = { path = "../../any_error/" }
|
||||
|
||||
# these are used to build the integration
|
||||
gtk = { version = "0.9.0", package = "gtk4" }
|
||||
any_spawner = { path = "../../any_spawner/" }
|
||||
next_tuple = { path = "../../next_tuple/" }
|
||||
paste = "1.0"
|
||||
gtk = { version = "0.8.0", package = "gtk4", optional = true }
|
||||
paste = "1.0.14"
|
||||
|
||||
# we want to support using glib for the reactive runtime event loop
|
||||
any_spawner = { path = "../../any_spawner/", features = ["glib"] }
|
||||
# yes, we want effects to run: this is a "frontend," not a backend
|
||||
reactive_graph = { path = "../../reactive_graph", features = ["effects"] }
|
||||
console_error_panic_hook = { version = "0.1", optional = true }
|
||||
|
||||
[features]
|
||||
gtk = ["dep:gtk", "any_spawner/glib"]
|
||||
wasm = ["any_spawner/wasm-bindgen", "dep:console_error_panic_hook"]
|
||||
|
||||
@@ -56,12 +56,11 @@ impl Mountable<LeptosGtk> for Element {
|
||||
.insert_before(&parent.0, marker.as_ref().map(|m| &m.0));
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable<LeptosGtk>) -> bool {
|
||||
if let Some(parent) = self.0.parent() {
|
||||
child.mount(&Element(parent), Some(self));
|
||||
return true;
|
||||
}
|
||||
false
|
||||
fn insert_before_this(&self,
|
||||
child: &mut dyn Mountable<LeptosGtk>,
|
||||
) -> bool {
|
||||
child.mount(parent, Some(self.as_ref()));
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,8 +79,11 @@ impl Mountable<LeptosGtk> for Text {
|
||||
.insert_before(&parent.0, marker.as_ref().map(|m| &m.0));
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable<LeptosGtk>) -> bool {
|
||||
self.0.insert_before_this(child)
|
||||
fn insert_before_this(&self,
|
||||
child: &mut dyn Mountable<LeptosGtk>,
|
||||
) -> bool {
|
||||
child.mount(parent, Some(self.as_ref()));
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,12 +332,16 @@ where
|
||||
parent: &<LeptosGtk as Renderer>::Element,
|
||||
marker: Option<&<LeptosGtk as Renderer>::Node>,
|
||||
) {
|
||||
println!("mounting {}", std::any::type_name::<Widg>());
|
||||
self.children.mount(&self.widget, None);
|
||||
LeptosGtk::insert_node(parent, &self.widget, marker);
|
||||
}
|
||||
|
||||
fn insert_before_this(&self, child: &mut dyn Mountable<LeptosGtk>) -> bool {
|
||||
self.widget.insert_before_this(child)
|
||||
fn insert_before_this(&self,
|
||||
child: &mut dyn Mountable<LeptosGtk>,
|
||||
) -> bool {
|
||||
child.mount(parent, Some(self.widget.as_ref()));
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
use any_spawner::Executor;
|
||||
use gtk::{prelude::*, Application, ApplicationWindow, Orientation};
|
||||
use leptos::prelude::*;
|
||||
use leptos_gtk::LeptosGtk;
|
||||
#[cfg(feature = "gtk")]
|
||||
use gtk::{
|
||||
glib::Value, prelude::*, Application, ApplicationWindow, Orientation,
|
||||
Widget,
|
||||
};
|
||||
#[cfg(feature = "wasm")]
|
||||
use leptos::tachys::{dom::body, html::element, html::event as ev};
|
||||
use leptos::{
|
||||
logging,
|
||||
prelude::*,
|
||||
reactive_graph::{effect::Effect, owner::Owner, signal::RwSignal},
|
||||
Executor, For, ForProps,
|
||||
};
|
||||
#[cfg(feature = "gtk")]
|
||||
use leptos_gtk::{Element, LGtkWidget, LeptosGtk};
|
||||
use std::{mem, thread, time::Duration};
|
||||
#[cfg(feature = "gtk")]
|
||||
mod leptos_gtk;
|
||||
|
||||
const APP_ID: &str = "dev.leptos.Counter";
|
||||
@@ -10,39 +22,59 @@ const APP_ID: &str = "dev.leptos.Counter";
|
||||
// Basic GTK app setup from https://gtk-rs.org/gtk4-rs/stable/latest/book/hello_world.html
|
||||
fn main() {
|
||||
// use the glib event loop to power the reactive system
|
||||
_ = Executor::init_glib();
|
||||
let app = Application::builder().application_id(APP_ID).build();
|
||||
#[cfg(feature = "gtk")]
|
||||
{
|
||||
_ = Executor::init_glib();
|
||||
let app = Application::builder().application_id(APP_ID).build();
|
||||
|
||||
app.connect_startup(|_| load_css());
|
||||
app.connect_startup(|_| load_css());
|
||||
|
||||
app.connect_activate(|app| {
|
||||
// Connect to "activate" signal of `app`
|
||||
app.connect_activate(|app| {
|
||||
// Connect to "activate" signal of `app`
|
||||
let owner = Owner::new();
|
||||
let view = owner.with(ui);
|
||||
let (root, state) = leptos_gtk::root(view);
|
||||
|
||||
let window = ApplicationWindow::builder()
|
||||
.application(app)
|
||||
.title("TachyGTK")
|
||||
.child(&root)
|
||||
.build();
|
||||
// Present window
|
||||
window.present();
|
||||
mem::forget((owner, state));
|
||||
});
|
||||
|
||||
app.run();
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "wasm", not(feature = "gtk")))]
|
||||
{
|
||||
console_error_panic_hook::set_once();
|
||||
_ = Executor::init_wasm_bindgen();
|
||||
let owner = Owner::new();
|
||||
let view = owner.with(ui);
|
||||
let (root, state) = leptos_gtk::root(view);
|
||||
|
||||
let window = ApplicationWindow::builder()
|
||||
.application(app)
|
||||
.title("TachyGTK")
|
||||
.child(&root)
|
||||
.build();
|
||||
// Present window
|
||||
window.present();
|
||||
let mut state = view.build();
|
||||
state.mount(&body().into(), None);
|
||||
mem::forget((owner, state));
|
||||
});
|
||||
|
||||
app.run();
|
||||
}
|
||||
}
|
||||
|
||||
fn ui() -> impl Render<LeptosGtk> {
|
||||
#[cfg(feature = "gtk")]
|
||||
type Rndr = LeptosGtk;
|
||||
#[cfg(all(feature = "wasm", not(feature = "gtk")))]
|
||||
type Rndr = Dom;
|
||||
|
||||
fn ui() -> impl Render<Rndr> {
|
||||
let value = RwSignal::new(0);
|
||||
let rows = RwSignal::new(vec![1, 2, 3, 4, 5]);
|
||||
|
||||
Effect::new(move |_| {
|
||||
println!("value = {}", value.get());
|
||||
logging::log!("value = {}", value.get());
|
||||
});
|
||||
|
||||
// just an example of multithreaded reactivity
|
||||
#[cfg(feature = "gtk")]
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_millis(250));
|
||||
value.update(|n| *n += 1);
|
||||
@@ -50,10 +82,7 @@ fn ui() -> impl Render<LeptosGtk> {
|
||||
|
||||
vstack((
|
||||
hstack((
|
||||
button("-1", move || {
|
||||
println!("clicked -1");
|
||||
value.update(|n| *n -= 1);
|
||||
}),
|
||||
button("-1", move || value.update(|n| *n -= 1)),
|
||||
move || value.get().to_string(),
|
||||
button("+1", move || value.update(|n| *n += 1)),
|
||||
)),
|
||||
@@ -62,36 +91,75 @@ fn ui() -> impl Render<LeptosGtk> {
|
||||
items.swap(1, 3);
|
||||
})
|
||||
}),
|
||||
hstack(rows),
|
||||
hstack(For(ForProps::builder()
|
||||
.each(move || rows.get())
|
||||
.key(|k| *k)
|
||||
.children(|v| v)
|
||||
.build())),
|
||||
))
|
||||
}
|
||||
|
||||
fn button(
|
||||
label: impl Render<LeptosGtk>,
|
||||
label: impl Render<Rndr>,
|
||||
callback: impl Fn() + Send + Sync + 'static,
|
||||
) -> impl Render<LeptosGtk> {
|
||||
leptos_gtk::button()
|
||||
.child(label)
|
||||
.connect("clicked", move |_| {
|
||||
callback();
|
||||
None
|
||||
})
|
||||
) -> impl Render<Rndr> {
|
||||
#[cfg(feature = "gtk")]
|
||||
{
|
||||
leptos_gtk::button()
|
||||
.child(label)
|
||||
.connect("clicked", move |_| {
|
||||
callback();
|
||||
None
|
||||
})
|
||||
}
|
||||
#[cfg(all(feature = "wasm", not(feature = "gtk")))]
|
||||
{
|
||||
element::button()
|
||||
.on(ev::click, move |_| callback())
|
||||
.child(label)
|
||||
}
|
||||
}
|
||||
|
||||
fn vstack(children: impl Render<LeptosGtk>) -> impl Render<LeptosGtk> {
|
||||
leptos_gtk::r#box()
|
||||
.orientation(Orientation::Vertical)
|
||||
.spacing(12)
|
||||
.child(children)
|
||||
fn vstack(children: impl Render<Rndr>) -> impl Render<Rndr> {
|
||||
#[cfg(feature = "gtk")]
|
||||
{
|
||||
leptos_gtk::r#box()
|
||||
.orientation(Orientation::Vertical)
|
||||
.spacing(12)
|
||||
.child(children)
|
||||
}
|
||||
#[cfg(all(feature = "wasm", not(feature = "gtk")))]
|
||||
{
|
||||
element::div()
|
||||
.style(("display", "flex"))
|
||||
.style(("flex-direction", "column"))
|
||||
.style(("align-items", "center"))
|
||||
.style(("justify-content", "center"))
|
||||
.style(("margin", "1rem"))
|
||||
.child(children)
|
||||
}
|
||||
}
|
||||
|
||||
fn hstack(children: impl Render<LeptosGtk>) -> impl Render<LeptosGtk> {
|
||||
leptos_gtk::r#box()
|
||||
.orientation(Orientation::Horizontal)
|
||||
.spacing(12)
|
||||
.child(children)
|
||||
fn hstack(children: impl Render<Rndr>) -> impl Render<Rndr> {
|
||||
#[cfg(feature = "gtk")]
|
||||
{
|
||||
leptos_gtk::r#box()
|
||||
.orientation(Orientation::Horizontal)
|
||||
.spacing(12)
|
||||
.child(children)
|
||||
}
|
||||
#[cfg(all(feature = "wasm", not(feature = "gtk")))]
|
||||
{
|
||||
element::div()
|
||||
.style(("display", "flex"))
|
||||
.style(("align-items", "center"))
|
||||
.style(("justify-content", "center"))
|
||||
.style(("margin", "1rem"))
|
||||
.child(children)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "gtk")]
|
||||
fn load_css() {
|
||||
use gtk::{gdk::Display, CssProvider};
|
||||
|
||||
|
||||
@@ -13,27 +13,31 @@ panic = "abort"
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
actix-files = { version = "0.6.6", optional = true }
|
||||
actix-web = { version = "4.8", optional = true, features = ["macros"] }
|
||||
console_log = "1.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
actix-files = { version = "0.6", optional = true }
|
||||
actix-web = { version = "4", optional = true, features = ["macros"] }
|
||||
console_log = "1"
|
||||
console_error_panic_hook = "0.1"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
wasm-bindgen = "0.2.93"
|
||||
web-sys = { version = "0.3.70", features = ["AbortController", "AbortSignal"] }
|
||||
log = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
gloo-net = { version = "0.2", features = ["http"] }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
wasm-bindgen = "0.2"
|
||||
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
|
||||
send_wrapper = "0.6.0"
|
||||
|
||||
[features]
|
||||
default = ["csr"]
|
||||
csr = ["leptos/csr"]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = ["dep:actix-files", "dep:actix-web", "dep:leptos_actix", "leptos/ssr"]
|
||||
ssr = [
|
||||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
"dep:leptos_actix",
|
||||
"leptos/ssr",
|
||||
]
|
||||
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
|
||||
@@ -20,7 +20,7 @@ pub fn Story() -> impl IntoView {
|
||||
},
|
||||
);
|
||||
|
||||
Suspense(SuspenseProps::builder().fallback(|| "Loading...").children(ToChildren::to_children(move || Suspend::new(async move {
|
||||
Suspense(SuspenseProps::builder().fallback(|| "Loading...").children(ToChildren::to_children(move || Suspend(async move {
|
||||
match story.await.clone() {
|
||||
None => Either::Left("Story not found."),
|
||||
Some(story) => {
|
||||
|
||||
@@ -19,7 +19,7 @@ pub fn User() -> impl IntoView {
|
||||
view! {
|
||||
<div class="user-view">
|
||||
<Suspense fallback=|| view! { "Loading..." }>
|
||||
{move || Suspend::new(async move { match user.await.clone() {
|
||||
{move || Suspend(async move { match user.await.clone() {
|
||||
None => Either::Left(view! { <h1>"User not found."</h1> }),
|
||||
Some(user) => Either::Right(view! {
|
||||
<div>
|
||||
|
||||
@@ -11,22 +11,22 @@ codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
console_error_panic_hook = "0.1"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tracing = "0.1.40"
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
http = { version = "1.1", optional = true }
|
||||
web-sys = { version = "0.3.70", features = ["AbortController", "AbortSignal"] }
|
||||
wasm-bindgen = "0.2.93"
|
||||
tracing = "0.1"
|
||||
gloo-net = { version = "0.4", features = ["http"] }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
axum = { version = "0.7", optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
http = { version = "1.0", optional = true }
|
||||
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
|
||||
wasm-bindgen = "0.2"
|
||||
send_wrapper = { version = "0.6.0", features = ["futures"] }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -20,7 +20,7 @@ pub fn Story() -> impl IntoView {
|
||||
},
|
||||
);
|
||||
|
||||
Suspense(SuspenseProps::builder().fallback(|| "Loading...").children(ToChildren::to_children(move || Suspend::new(async move {
|
||||
Suspense(SuspenseProps::builder().fallback(|| "Loading...").children(ToChildren::to_children(move || Suspend(async move {
|
||||
match story.await.clone() {
|
||||
None => Either::Left("Story not found."),
|
||||
Some(story) => {
|
||||
|
||||
@@ -19,7 +19,7 @@ pub fn User() -> impl IntoView {
|
||||
view! {
|
||||
<div class="user-view">
|
||||
<Suspense fallback=|| view! { "Loading..." }>
|
||||
{move || Suspend::new(async move { match user.await.clone() {
|
||||
{move || Suspend(async move { match user.await.clone() {
|
||||
None => Either::Left(view! { <h1>"User not found."</h1> }),
|
||||
Some(user) => Either::Right(view! {
|
||||
<div>
|
||||
|
||||
@@ -11,35 +11,34 @@ codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
leptos = { path = "../../leptos", features = ["experimental-islands"] }
|
||||
console_error_panic_hook = "0.1"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"experimental-islands",
|
||||
] }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
leptos_router = { path = "../../router"}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tracing = "0.1.40"
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
axum = { version = "0.7.5", optional = true, features = ["http2"] }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = [
|
||||
tracing = "0.1"
|
||||
gloo-net = { version = "0.4", features = ["http"] }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
axum = { version = "0.7", optional = true, features = ["http2"] }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = [
|
||||
"fs",
|
||||
"compression-gzip",
|
||||
"compression-br",
|
||||
], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
http = { version = "1.1", optional = true }
|
||||
web-sys = { version = "0.3.70", features = ["AbortController", "AbortSignal"] }
|
||||
wasm-bindgen = "0.2.93"
|
||||
lazy_static = "1.5"
|
||||
rust-embed = { version = "8.5", features = [
|
||||
"axum",
|
||||
"mime_guess",
|
||||
"tokio",
|
||||
], optional = true }
|
||||
mime_guess = { version = "2.0", optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
http = { version = "1.0", optional = true }
|
||||
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
|
||||
wasm-bindgen = "0.2"
|
||||
lazy_static = "1.4.0"
|
||||
rust-embed = { version = "8", features = ["axum", "mime_guess", "tokio"], optional = true }
|
||||
mime_guess = { version = "2.0.4", optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
csr = ["leptos/csr"]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
|
||||
@@ -26,7 +26,7 @@ pub fn Story() -> impl IntoView {
|
||||
},
|
||||
);
|
||||
|
||||
Suspense(SuspenseProps::builder().fallback(|| "Loading...").children(ToChildren::to_children(move || Suspend::new(async move {
|
||||
Suspense(SuspenseProps::builder().fallback(|| "Loading...").children(ToChildren::to_children(move || Suspend(async move {
|
||||
match story.await.ok().flatten() {
|
||||
None => Either::Left("Story not found."),
|
||||
Some(story) => {
|
||||
|
||||
@@ -26,7 +26,7 @@ pub fn User() -> impl IntoView {
|
||||
view! {
|
||||
<div class="user-view">
|
||||
<Suspense fallback=|| view! { "Loading..." }>
|
||||
{move || Suspend::new(async move { match user.await.ok().flatten() {
|
||||
{move || Suspend(async move { match user.await.ok().flatten() {
|
||||
None => Either::Left(view! { <h1>"User not found."</h1> }),
|
||||
Some(user) => Either::Right(view! {
|
||||
<div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "hackernews_js_fetch"
|
||||
name = "hackernews-js-fetch"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
@@ -11,64 +11,63 @@ codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
console_log = "1.0.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
console_log = "1.0"
|
||||
log = "0.4.22"
|
||||
cfg-if = "1.0.0"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_axum = { path = "../../integrations/axum", default-features = false, optional = true }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
leptos_server = { path = "../../leptos_server", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tracing = "0.1.40"
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
axum = { version = "0.7.5", default-features = false, optional = true }
|
||||
log = "0.4.17"
|
||||
simple_logger = "4.0.0"
|
||||
serde = { version = "1.0.148", features = ["derive"] }
|
||||
tracing = "0.1"
|
||||
gloo-net = { version = "0.4.0", features = ["http"] }
|
||||
reqwest = { version = "0.11.13", features = ["json"] }
|
||||
axum = { version = "0.7", default-features = false, optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
http = { version = "1.1", optional = true }
|
||||
web-sys = { version = "0.3.70", features = [
|
||||
http = { version = "0.2.11", optional = true }
|
||||
web-sys = { version = "0.3", features = [
|
||||
"AbortController",
|
||||
"AbortSignal",
|
||||
"Request",
|
||||
"Response",
|
||||
] }
|
||||
getrandom = { version = "0.2.15", features = ["js"] }
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen-futures = { version = "0.4.42", features = [
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = { version = "0.4.37", features = [
|
||||
"futures-core-03-stream",
|
||||
], optional = true }
|
||||
axum-js-fetch = { git = "https://github.com/seanaye/axum-js-fetch", optional = true }
|
||||
send_wrapper = { version = "0.6.0", features = ["futures"] }
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:http",
|
||||
"dep:axum",
|
||||
"dep:wasm-bindgen-futures",
|
||||
"dep:axum-js-fetch",
|
||||
"leptos/ssr",
|
||||
"leptos_axum/wasm",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
"leptos_server/serde-wasm-bindgen",
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "http", "leptos_axum"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
skip_feature_sets = [["ssr", "hydrate"]]
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
output-name = "hackernews_js_fetch"
|
||||
output-name = "hackernews_axum"
|
||||
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
|
||||
site-root = "target/site"
|
||||
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
|
||||
# Defaults to pkg
|
||||
site-pkg-dir = "pkg"
|
||||
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
|
||||
style-file = "style.css"
|
||||
style-file = "./style.css"
|
||||
# [Optional] Files in the asset-dir will be copied to the site-root directory
|
||||
assets-dir = "public"
|
||||
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/deno-build.toml" },
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/deno-build.toml" },
|
||||
]
|
||||
|
||||
[env]
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
This example uses the basic Hacker News example as its basis, but shows how to run the server side as WASM running in a JS environment. In this example, Deno is used as the runtime.
|
||||
|
||||
**NOTE**: This example is slightly out of date pending an update to [`axum-js-fetch`](https://github.com/seanaye/axum-js-fetch/), which was waiting on a version of `gloo-net` that uses `http` 1.0. It still works with Leptos 0.5 and Axum 0.6, but not with the versions of Leptos (0.6 and later) that support Axum 1.0.
|
||||
|
||||
## Server Side Rendering with Deno
|
||||
|
||||
To run the Deno version, run
|
||||
|
||||
50
examples/hackernews_js_fetch/deno.lock
generated
50
examples/hackernews_js_fetch/deno.lock
generated
@@ -1,8 +1,5 @@
|
||||
{
|
||||
"version": "3",
|
||||
"redirects": {
|
||||
"https://deno.land/std/http/file_server.ts": "https://deno.land/std@0.224.0/http/file_server.ts"
|
||||
},
|
||||
"version": "2",
|
||||
"remote": {
|
||||
"https://deno.land/std@0.177.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462",
|
||||
"https://deno.land/std@0.177.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3",
|
||||
@@ -56,49 +53,6 @@
|
||||
"https://deno.land/std@0.177.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d",
|
||||
"https://deno.land/std@0.177.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1",
|
||||
"https://deno.land/std@0.177.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba",
|
||||
"https://deno.land/std@0.177.0/version.ts": "259c8866ec257c3511b437baa95205a86761abaef852a9b2199072accb2ef046",
|
||||
"https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834",
|
||||
"https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917",
|
||||
"https://deno.land/std@0.224.0/cli/parse_args.ts": "5250832fb7c544d9111e8a41ad272c016f5a53f975ef84d5a9fe5fcb70566ece",
|
||||
"https://deno.land/std@0.224.0/encoding/_util.ts": "beacef316c1255da9bc8e95afb1fa56ed69baef919c88dc06ae6cb7a6103d376",
|
||||
"https://deno.land/std@0.224.0/encoding/base64.ts": "dd59695391584c8ffc5a296ba82bcdba6dd8a84d41a6a539fbee8e5075286eaf",
|
||||
"https://deno.land/std@0.224.0/fmt/bytes.ts": "7b294a4b9cf0297efa55acb55d50610f3e116a0ac772d1df0ae00f0b833ccd4a",
|
||||
"https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5",
|
||||
"https://deno.land/std@0.224.0/http/etag.ts": "9ca56531be682f202e4239971931060b688ee5c362688e239eeaca39db9e72cb",
|
||||
"https://deno.land/std@0.224.0/http/file_server.ts": "2a5392195b8e7713288f274d071711b705bb5b3220294d76cce495d456c61a93",
|
||||
"https://deno.land/std@0.224.0/http/status.ts": "ed61b4882af2514a81aefd3245e8df4c47b9a8e54929a903577643d2d1ebf514",
|
||||
"https://deno.land/std@0.224.0/media_types/_db.ts": "19563a2491cd81b53b9c1c6ffd1a9145c355042d4a854c52f6e1424f73ff3923",
|
||||
"https://deno.land/std@0.224.0/media_types/_util.ts": "e0b8da0c7d8ad2015cf27ac16ddf0809ac984b2f3ec79f7fa4206659d4f10deb",
|
||||
"https://deno.land/std@0.224.0/media_types/content_type.ts": "ed3f2e1f243b418ad3f441edc95fd92efbadb0f9bde36219c7564c67f9639513",
|
||||
"https://deno.land/std@0.224.0/media_types/format_media_type.ts": "ffef4718afa2489530cb94021bb865a466eb02037609f7e82899c017959d288a",
|
||||
"https://deno.land/std@0.224.0/media_types/get_charset.ts": "277ebfceb205bd34e616fe6764ef03fb277b77f040706272bea8680806ae3f11",
|
||||
"https://deno.land/std@0.224.0/media_types/parse_media_type.ts": "487f000a38c230ccbac25420a50f600862e06796d0eee19d19631b9e84ee9654",
|
||||
"https://deno.land/std@0.224.0/media_types/type_by_extension.ts": "bf4e3f5d6b58b624d5daa01cbb8b1e86d9939940a77e7c26e796a075b60ec73b",
|
||||
"https://deno.land/std@0.224.0/media_types/vendor/mime-db.v1.52.0.ts": "0218d2c7d900e8cd6fa4a866e0c387712af4af9a1bae55d6b2546c73d273a1e6",
|
||||
"https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8",
|
||||
"https://deno.land/std@0.224.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c",
|
||||
"https://deno.land/std@0.224.0/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8",
|
||||
"https://deno.land/std@0.224.0/path/_common/normalize_string.ts": "33edef773c2a8e242761f731adeb2bd6d683e9c69e4e3d0092985bede74f4ac3",
|
||||
"https://deno.land/std@0.224.0/path/_common/relative.ts": "faa2753d9b32320ed4ada0733261e3357c186e5705678d9dd08b97527deae607",
|
||||
"https://deno.land/std@0.224.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15",
|
||||
"https://deno.land/std@0.224.0/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36",
|
||||
"https://deno.land/std@0.224.0/path/extname.ts": "593303db8ae8c865cbd9ceec6e55d4b9ac5410c1e276bfd3131916591b954441",
|
||||
"https://deno.land/std@0.224.0/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a",
|
||||
"https://deno.land/std@0.224.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d",
|
||||
"https://deno.land/std@0.224.0/path/posix/extname.ts": "e398c1d9d1908d3756a7ed94199fcd169e79466dd88feffd2f47ce0abf9d61d2",
|
||||
"https://deno.land/std@0.224.0/path/posix/join.ts": "7fc2cb3716aa1b863e990baf30b101d768db479e70b7313b4866a088db016f63",
|
||||
"https://deno.land/std@0.224.0/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91",
|
||||
"https://deno.land/std@0.224.0/path/posix/relative.ts": "3907d6eda41f0ff723d336125a1ad4349112cd4d48f693859980314d5b9da31c",
|
||||
"https://deno.land/std@0.224.0/path/posix/resolve.ts": "08b699cfeee10cb6857ccab38fa4b2ec703b0ea33e8e69964f29d02a2d5257cf",
|
||||
"https://deno.land/std@0.224.0/path/relative.ts": "ab739d727180ed8727e34ed71d976912461d98e2b76de3d3de834c1066667add",
|
||||
"https://deno.land/std@0.224.0/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d",
|
||||
"https://deno.land/std@0.224.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808",
|
||||
"https://deno.land/std@0.224.0/path/windows/extname.ts": "165a61b00d781257fda1e9606a48c78b06815385e7d703232548dbfc95346bef",
|
||||
"https://deno.land/std@0.224.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf",
|
||||
"https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780",
|
||||
"https://deno.land/std@0.224.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7",
|
||||
"https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972",
|
||||
"https://deno.land/std@0.224.0/streams/byte_slice_stream.ts": "5bbdcadb118390affa9b3d0a0f73ef8e83754f59bb89df349add669dd9369713",
|
||||
"https://deno.land/std@0.224.0/version.ts": "f6a28c9704d82d1c095988777e30e6172eb674a6570974a0d27a653be769bbbe"
|
||||
"https://deno.land/std@0.177.0/version.ts": "259c8866ec257c3511b437baa95205a86761abaef852a9b2199072accb2ef046"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use leptos::logging;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use leptos::Serializable;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn story(path: &str) -> String {
|
||||
format!("https://node-hnapi.herokuapp.com/{path}")
|
||||
@@ -10,51 +10,46 @@ pub fn user(path: &str) -> String {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub fn fetch_api<T>(
|
||||
path: &str,
|
||||
) -> impl std::future::Future<Output = Option<T>> + Send + '_
|
||||
pub async fn fetch_api<T>(path: &str) -> Option<T>
|
||||
where
|
||||
T: Serialize + DeserializeOwned,
|
||||
T: Serializable,
|
||||
{
|
||||
use leptos::prelude::on_cleanup;
|
||||
use send_wrapper::SendWrapper;
|
||||
let abort_controller = web_sys::AbortController::new().ok();
|
||||
let abort_signal = abort_controller.as_ref().map(|a| a.signal());
|
||||
|
||||
SendWrapper::new(async move {
|
||||
let abort_controller =
|
||||
SendWrapper::new(web_sys::AbortController::new().ok());
|
||||
let abort_signal = abort_controller.as_ref().map(|a| a.signal());
|
||||
// abort in-flight requests if, e.g., we've navigated away from this page
|
||||
leptos::on_cleanup(move || {
|
||||
if let Some(abort_controller) = abort_controller {
|
||||
abort_controller.abort()
|
||||
}
|
||||
});
|
||||
|
||||
// abort in-flight requests if, e.g., we've navigated away from this page
|
||||
on_cleanup(move || {
|
||||
if let Some(abort_controller) = abort_controller.take() {
|
||||
abort_controller.abort()
|
||||
}
|
||||
});
|
||||
let json = gloo_net::http::Request::get(path)
|
||||
.abort_signal(abort_signal.as_ref())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| log::error!("{e}"))
|
||||
.ok()?
|
||||
.text()
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
gloo_net::http::Request::get(path)
|
||||
.abort_signal(abort_signal.as_ref())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| logging::error!("{e}"))
|
||||
.ok()?
|
||||
.json()
|
||||
.await
|
||||
.ok()
|
||||
})
|
||||
T::de(&json).ok()
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
pub async fn fetch_api<T>(path: &str) -> Option<T>
|
||||
where
|
||||
T: Serialize + DeserializeOwned,
|
||||
T: Serializable,
|
||||
{
|
||||
reqwest::get(path)
|
||||
let json = reqwest::get(path)
|
||||
.await
|
||||
.map_err(|e| logging::error!("{e}"))
|
||||
.map_err(|e| log::error!("{e}"))
|
||||
.ok()?
|
||||
.json()
|
||||
.text()
|
||||
.await
|
||||
.ok()
|
||||
.ok()?;
|
||||
T::de(&json).map_err(|e| log::error!("{e}")).ok()
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
|
||||
|
||||
28
examples/hackernews_js_fetch/src/error_template.rs
Normal file
28
examples/hackernews_js_fetch/src/error_template.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use leptos::{view, Errors, For, IntoView, RwSignal, SignalGet, View};
|
||||
|
||||
// A basic function to display errors served by the error boundaries. Feel free to do more complicated things
|
||||
// here than just displaying them
|
||||
pub fn error_template(errors: Option<RwSignal<Errors>>) -> View {
|
||||
let Some(errors) = errors else {
|
||||
panic!("No Errors found and we expected errors!");
|
||||
};
|
||||
|
||||
view! {
|
||||
<h1>"Errors"</h1>
|
||||
<For
|
||||
// a function that returns the items we're iterating over; a signal is fine
|
||||
each=move || errors.get()
|
||||
// a unique key for each item as a reference
|
||||
key=|(key, _)| key.clone()
|
||||
// renders each item to a view
|
||||
children= move |(_, error)| {
|
||||
let error_string = error.to_string();
|
||||
view! {
|
||||
|
||||
<p>"Error: " {error_string}</p>
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
.into_view()
|
||||
}
|
||||
43
examples/hackernews_js_fetch/src/fallback.rs
Normal file
43
examples/hackernews_js_fetch/src/fallback.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use crate::error_template::error_template;
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::State,
|
||||
http::{Request, Response, StatusCode, Uri},
|
||||
response::{IntoResponse, Response as AxumResponse},
|
||||
};
|
||||
//use tower::ServiceExt;
|
||||
use leptos::LeptosOptions;
|
||||
|
||||
pub async fn file_and_error_handler(
|
||||
uri: Uri,
|
||||
State(options): State<LeptosOptions>,
|
||||
req: Request<Body>,
|
||||
) -> AxumResponse {
|
||||
let root = options.site_root.clone();
|
||||
let res = get_static_file(uri.clone(), &root).await.unwrap();
|
||||
|
||||
if res.status() == StatusCode::OK {
|
||||
res.into_response()
|
||||
} else {
|
||||
let handler =
|
||||
leptos_axum::render_app_to_stream(options.to_owned(), || {
|
||||
error_template(None)
|
||||
});
|
||||
handler(req).await.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_static_file(
|
||||
uri: Uri,
|
||||
root: &str,
|
||||
) -> Result<Response<Body>, (StatusCode, String)> {
|
||||
let req = Request::builder()
|
||||
.uri(uri.clone())
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
|
||||
// This path is relative to the cargo root
|
||||
_ = req;
|
||||
_ = root;
|
||||
todo!()
|
||||
}
|
||||
@@ -1,71 +1,46 @@
|
||||
use leptos::prelude::*;
|
||||
use leptos::{component, view, IntoView};
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
mod api;
|
||||
pub mod error_template;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub mod fallback;
|
||||
mod routes;
|
||||
use leptos_meta::{provide_meta_context, Link, Meta, MetaTags, Stylesheet};
|
||||
use leptos_router::{
|
||||
components::{FlatRoutes, Route, Router, RoutingProgress},
|
||||
ParamSegment, StaticSegment,
|
||||
};
|
||||
use routes::{nav::*, stories::*, story::*, users::*};
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
view! {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<AutoReload options=options.clone()/>
|
||||
<HydrationScripts options/>
|
||||
<MetaTags/>
|
||||
</head>
|
||||
<body>
|
||||
<App/>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
}
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
provide_meta_context();
|
||||
let (is_routing, set_is_routing) = signal(false);
|
||||
|
||||
view! {
|
||||
<Stylesheet id="leptos" href="/public/style.css"/>
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/public/favicon.ico"/>
|
||||
<Stylesheet id="leptos" href="/public/style.css"/>
|
||||
<Meta name="description" content="Leptos implementation of a HackerNews demo."/>
|
||||
<Router set_is_routing>
|
||||
// shows a progress bar while async data are loading
|
||||
<div class="routing-progress">
|
||||
<RoutingProgress is_routing max_time=Duration::from_millis(250)/>
|
||||
</div>
|
||||
<Nav/>
|
||||
<Router>
|
||||
<Nav />
|
||||
<main>
|
||||
<FlatRoutes fallback=|| "Not found.">
|
||||
<Route path=(StaticSegment("users"), ParamSegment("id")) view=User/>
|
||||
<Route path=(StaticSegment("stories"), ParamSegment("id")) view=Story/>
|
||||
<Route path=ParamSegment("stories") view=Stories/>
|
||||
// TODO allow optional params without duplication
|
||||
<Route path=StaticSegment("") view=Stories/>
|
||||
</FlatRoutes>
|
||||
<Routes>
|
||||
<Route path="users/:id" view=User/>
|
||||
<Route path="stories/:id" view=Story/>
|
||||
<Route path=":stories?" view=Stories/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||
#[wasm_bindgen]
|
||||
pub fn hydrate() {
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
leptos::mount::hydrate_body(App);
|
||||
leptos::mount_to_body(App);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
mod ssr_imports {
|
||||
use crate::{shell, App};
|
||||
use axum::Router;
|
||||
use crate::App;
|
||||
use axum::{routing::post, Router};
|
||||
use leptos::prelude::*;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use log::{info, Level};
|
||||
@@ -84,15 +59,12 @@ mod ssr_imports {
|
||||
.output_name("client")
|
||||
.site_pkg_dir("pkg")
|
||||
.build();
|
||||
|
||||
let routes = generate_route_list(App);
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.leptos_routes(&leptos_options, routes, {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
})
|
||||
let app: axum::Router<()> = Router::new()
|
||||
.leptos_routes(&leptos_options, routes, App)
|
||||
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
|
||||
.with_state(leptos_options);
|
||||
|
||||
info!("creating handler instance");
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use leptos::prelude::*;
|
||||
use leptos_router::components::A;
|
||||
use leptos::{component, view, IntoView};
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn Nav() -> impl IntoView {
|
||||
view! {
|
||||
<header class="header">
|
||||
<nav class="inner">
|
||||
<A href="/home">
|
||||
<A href="/">
|
||||
<strong>"HN"</strong>
|
||||
</A>
|
||||
<A href="/new">
|
||||
@@ -21,12 +21,7 @@ pub fn Nav() -> impl IntoView {
|
||||
<A href="/job">
|
||||
<strong>"Jobs"</strong>
|
||||
</A>
|
||||
<a
|
||||
class="github"
|
||||
href="http://github.com/leptos-rs/leptos"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<a class="github" href="http://github.com/leptos-rs/leptos" target="_blank" rel="noreferrer">
|
||||
"Built with Leptos"
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
use crate::api;
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use leptos_router::{
|
||||
components::A,
|
||||
hooks::{use_params_map, use_query_map},
|
||||
};
|
||||
use send_wrapper::SendWrapper;
|
||||
use leptos::prelude::*;
|
||||
use leptos_router::*;
|
||||
|
||||
fn category(from: &str) -> &'static str {
|
||||
match from {
|
||||
@@ -22,67 +18,57 @@ pub fn Stories() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let page = move || {
|
||||
query
|
||||
.read()
|
||||
.get("page")
|
||||
.and_then(|page| page.parse::<usize>().ok())
|
||||
.with(|q| q.get("page").and_then(|page| page.parse::<usize>().ok()))
|
||||
.unwrap_or(1)
|
||||
};
|
||||
let story_type = move || {
|
||||
params
|
||||
.read()
|
||||
.get("stories")
|
||||
.with(|p| p.get("stories").cloned())
|
||||
.unwrap_or_else(|| "top".to_string())
|
||||
};
|
||||
let stories = Resource::new(
|
||||
let stories = create_resource(
|
||||
move || (page(), story_type()),
|
||||
move |(page, story_type)| {
|
||||
SendWrapper::new(async move {
|
||||
let path = format!("{}?page={}", category(&story_type), page);
|
||||
api::fetch_api::<Vec<api::Story>>(&api::story(&path)).await
|
||||
})
|
||||
move |(page, story_type)| async move {
|
||||
let path = format!("{}?page={}", category(&story_type), page);
|
||||
api::fetch_api::<Vec<api::Story>>(&api::story(&path)).await
|
||||
},
|
||||
);
|
||||
let (pending, set_pending) = signal(false);
|
||||
let (pending, set_pending) = create_signal(false);
|
||||
|
||||
let hide_more_link = move || match &*stories.read() {
|
||||
Some(Some(stories)) => stories.len() < 28,
|
||||
_ => true
|
||||
} || pending.get();
|
||||
let hide_more_link = move || {
|
||||
stories.get().unwrap_or(None).unwrap_or_default().len() < 28
|
||||
|| pending.get()
|
||||
};
|
||||
|
||||
view! {
|
||||
<div class="news-view">
|
||||
<div class="news-list-nav">
|
||||
<span>
|
||||
{move || {
|
||||
if page() > 1 {
|
||||
Either::Left(
|
||||
view! {
|
||||
<a
|
||||
class="page-link"
|
||||
href=move || {
|
||||
format!("/{}?page={}", story_type(), page() - 1)
|
||||
}
|
||||
aria-label="Previous Page"
|
||||
>
|
||||
"< prev"
|
||||
</a>
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Either::Right(
|
||||
view! {
|
||||
<span class="page-link disabled" aria-hidden="true">
|
||||
"< prev"
|
||||
</span>
|
||||
},
|
||||
)
|
||||
}
|
||||
{move || if page() > 1 {
|
||||
view! {
|
||||
|
||||
<a class="page-link"
|
||||
href=move || format!("/{}?page={}", story_type(), page() - 1)
|
||||
attr:aria_label="Previous Page"
|
||||
>
|
||||
"< prev"
|
||||
</a>
|
||||
}.into_any()
|
||||
} else {
|
||||
view! {
|
||||
|
||||
<span class="page-link disabled" aria-hidden="true">
|
||||
"< prev"
|
||||
</span>
|
||||
}.into_any()
|
||||
}}
|
||||
</span>
|
||||
<span>"page " {page}</span>
|
||||
<span class="page-link" class:disabled=hide_more_link aria-hidden=hide_more_link>
|
||||
<a
|
||||
href=move || format!("/{}?page={}", story_type(), page() + 1)
|
||||
<span class="page-link"
|
||||
class:disabled=hide_more_link
|
||||
aria-hidden=hide_more_link
|
||||
>
|
||||
<a href=move || format!("/{}?page={}", story_type(), page() + 1)
|
||||
aria-label="Next Page"
|
||||
>
|
||||
"more >"
|
||||
@@ -91,19 +77,27 @@ pub fn Stories() -> impl IntoView {
|
||||
</div>
|
||||
<main class="news-list">
|
||||
<div>
|
||||
<Transition fallback=move || view! { <p>"Loading..."</p> } set_pending>
|
||||
<Show when=move || {
|
||||
stories.read().as_ref().map(Option::is_none).unwrap_or(false)
|
||||
}>> <p>"Error loading stories."</p></Show>
|
||||
<ul>
|
||||
<For
|
||||
each=move || stories.get().unwrap_or_default().unwrap_or_default()
|
||||
key=|story| story.id
|
||||
let:story
|
||||
>
|
||||
<Story story/>
|
||||
</For>
|
||||
</ul>
|
||||
<Transition
|
||||
fallback=move || view! { <p>"Loading..."</p> }
|
||||
set_pending
|
||||
>
|
||||
{move || match stories.get() {
|
||||
None => None,
|
||||
Some(None) => Some(view! { <p>"Error loading stories."</p> }.into_any()),
|
||||
Some(Some(stories)) => {
|
||||
Some(view! {
|
||||
<ul>
|
||||
<For
|
||||
each=move || stories.clone()
|
||||
key=|story| story.id
|
||||
let:story
|
||||
>
|
||||
<Story story/>
|
||||
</For>
|
||||
</ul>
|
||||
}.into_any())
|
||||
}
|
||||
}}
|
||||
</Transition>
|
||||
</div>
|
||||
</main>
|
||||
@@ -114,67 +108,49 @@ pub fn Stories() -> impl IntoView {
|
||||
#[component]
|
||||
fn Story(story: api::Story) -> impl IntoView {
|
||||
view! {
|
||||
<li class="news-item">
|
||||
<li class="news-item">
|
||||
<span class="score">{story.points}</span>
|
||||
<span class="title">
|
||||
{if !story.url.starts_with("item?id=") {
|
||||
Either::Left(
|
||||
view! {
|
||||
<span>
|
||||
<a href=story.url target="_blank" rel="noreferrer">
|
||||
{story.title.clone()}
|
||||
</a>
|
||||
<span class="host">"("{story.domain}")"</span>
|
||||
</span>
|
||||
},
|
||||
)
|
||||
view! {
|
||||
<span>
|
||||
<a href=story.url target="_blank" rel="noreferrer">
|
||||
{story.title.clone()}
|
||||
</a>
|
||||
<span class="host">"("{story.domain}")"</span>
|
||||
</span>
|
||||
}.into_view()
|
||||
} else {
|
||||
let title = story.title.clone();
|
||||
Either::Right(view! { <A href=format!("/stories/{}", story.id)>{title}</A> })
|
||||
view! { <A href=format!("/stories/{}", story.id)>{title.clone()}</A> }.into_view()
|
||||
}}
|
||||
</span>
|
||||
<br/>
|
||||
<br />
|
||||
<span class="meta">
|
||||
{if story.story_type != "job" {
|
||||
Either::Left(
|
||||
view! {
|
||||
<span>
|
||||
{"by "}
|
||||
{story
|
||||
.user
|
||||
.map(|user| {
|
||||
view! {
|
||||
<A href=format!("/users/{user}")>{user.clone()}</A>
|
||||
}
|
||||
})} {format!(" {} | ", story.time_ago)}
|
||||
<A href=format!(
|
||||
"/stories/{}",
|
||||
story.id,
|
||||
)>
|
||||
{if story.comments_count.unwrap_or_default() > 0 {
|
||||
format!(
|
||||
"{} comments",
|
||||
story.comments_count.unwrap_or_default(),
|
||||
)
|
||||
} else {
|
||||
"discuss".into()
|
||||
}}
|
||||
</A>
|
||||
</span>
|
||||
},
|
||||
)
|
||||
view! {
|
||||
<span>
|
||||
{"by "}
|
||||
{story.user.map(|user| view ! { <A href=format!("/users/{user}")>{user.clone()}</A>})}
|
||||
{format!(" {} | ", story.time_ago)}
|
||||
<A href=format!("/stories/{}", story.id)>
|
||||
{if story.comments_count.unwrap_or_default() > 0 {
|
||||
format!("{} comments", story.comments_count.unwrap_or_default())
|
||||
} else {
|
||||
"discuss".into()
|
||||
}}
|
||||
</A>
|
||||
</span>
|
||||
}.into_view()
|
||||
} else {
|
||||
let title = story.title.clone();
|
||||
Either::Right(view! { <A href=format!("/item/{}", story.id)>{title}</A> })
|
||||
view! { <A href=format!("/item/{}", story.id)>{title.clone()}</A> }.into_view()
|
||||
}}
|
||||
</span>
|
||||
{(story.story_type != "link")
|
||||
.then(|| {
|
||||
view! {
|
||||
" "
|
||||
<span class="label">{story.story_type}</span>
|
||||
}
|
||||
})}
|
||||
{(story.story_type != "link").then(|| view! {
|
||||
" "
|
||||
<span class="label">{story.story_type}</span>
|
||||
})}
|
||||
</li>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,139 +1,119 @@
|
||||
use crate::api;
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use leptos_meta::Meta;
|
||||
use leptos_router::{components::A, hooks::use_params_map};
|
||||
use send_wrapper::SendWrapper;
|
||||
use leptos::prelude::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn Story() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let story = Resource::new(
|
||||
move || params.read().get("id").unwrap_or_default(),
|
||||
move |id| {
|
||||
SendWrapper::new(async move {
|
||||
if id.is_empty() {
|
||||
None
|
||||
} else {
|
||||
api::fetch_api::<api::Story>(&api::story(&format!(
|
||||
"item/{id}"
|
||||
)))
|
||||
let story = create_resource(
|
||||
move || params.get().get("id").cloned().unwrap_or_default(),
|
||||
move |id| async move {
|
||||
if id.is_empty() {
|
||||
None
|
||||
} else {
|
||||
api::fetch_api::<api::Story>(&api::story(&format!("item/{id}")))
|
||||
.await
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
);
|
||||
let meta_description = move || {
|
||||
story
|
||||
.get()
|
||||
.and_then(|story| story.map(|story| story.title))
|
||||
.unwrap_or_else(|| "Loading story...".to_string())
|
||||
};
|
||||
|
||||
Suspense(SuspenseProps::builder().fallback(|| "Loading...").children(ToChildren::to_children(move || Suspend::new(async move {
|
||||
match story.await.clone() {
|
||||
None => Either::Left("Story not found."),
|
||||
Some(story) => {
|
||||
Either::Right(view! {
|
||||
<Meta name="description" content=story.title.clone()/>
|
||||
view! {
|
||||
<Suspense fallback=|| view! { "Loading..." }>
|
||||
<Meta name="description" content=meta_description/>
|
||||
{move || story.get().map(|story| match story {
|
||||
None => view! { <div class="item-view">"Error loading this story."</div> },
|
||||
Some(story) => view! {
|
||||
<div class="item-view">
|
||||
<div class="item-view-header">
|
||||
<a href=story.url target="_blank">
|
||||
<h1>{story.title}</h1>
|
||||
</a>
|
||||
<span class="host">"("{story.domain}")"</span>
|
||||
{story
|
||||
.user
|
||||
.map(|user| {
|
||||
view! {
|
||||
<p class="meta">
|
||||
{story.points} " points | by "
|
||||
<A href=format!("/users/{user}")>{user.clone()}</A>
|
||||
{format!(" {}", story.time_ago)}
|
||||
</p>
|
||||
}
|
||||
})}
|
||||
<a href=story.url target="_blank">
|
||||
<h1>{story.title}</h1>
|
||||
</a>
|
||||
<span class="host">
|
||||
"("{story.domain}")"
|
||||
</span>
|
||||
{story.user.map(|user| view! { <p class="meta">
|
||||
{story.points}
|
||||
" points | by "
|
||||
<A href=format!("/users/{user}")>{user.clone()}</A>
|
||||
{format!(" {}", story.time_ago)}
|
||||
</p>})}
|
||||
</div>
|
||||
<div class="item-view-comments">
|
||||
<p class="item-view-comments-header">
|
||||
{if story.comments_count.unwrap_or_default() > 0 {
|
||||
format!("{} comments", story.comments_count.unwrap_or_default())
|
||||
} else {
|
||||
"No comments yet.".into()
|
||||
}}
|
||||
</p>
|
||||
<ul class="comment-children">
|
||||
<For
|
||||
each=move || story.comments.clone().unwrap_or_default()
|
||||
key=|comment| comment.id
|
||||
let:comment
|
||||
>
|
||||
<Comment comment/>
|
||||
</For>
|
||||
</ul>
|
||||
</div>
|
||||
<p class="item-view-comments-header">
|
||||
{if story.comments_count.unwrap_or_default() > 0 {
|
||||
format!("{} comments", story.comments_count.unwrap_or_default())
|
||||
} else {
|
||||
"No comments yet.".into()
|
||||
}}
|
||||
</p>
|
||||
<ul class="comment-children">
|
||||
<For
|
||||
each=move || story.comments.clone().unwrap_or_default()
|
||||
key=|comment| comment.id
|
||||
let:comment
|
||||
>
|
||||
<Comment comment />
|
||||
</For>
|
||||
</ul>
|
||||
</div>
|
||||
})
|
||||
}
|
||||
}
|
||||
}))).build())
|
||||
</div>
|
||||
}})}
|
||||
</Suspense>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Comment(comment: api::Comment) -> impl IntoView {
|
||||
let (open, set_open) = signal(true);
|
||||
let (open, set_open) = create_signal(true);
|
||||
|
||||
view! {
|
||||
<li class="comment">
|
||||
<div class="by">
|
||||
<A href=format!(
|
||||
"/users/{}",
|
||||
comment.user.clone().unwrap_or_default(),
|
||||
)>{comment.user.clone()}</A>
|
||||
{format!(" {}", comment.time_ago)}
|
||||
</div>
|
||||
<div class="text" inner_html=comment.content></div>
|
||||
{(!comment.comments.is_empty())
|
||||
.then(|| {
|
||||
view! {
|
||||
<div>
|
||||
<div class="toggle" class:open=open>
|
||||
<a on:click=move |_| {
|
||||
set_open.update(|n| *n = !*n)
|
||||
}>
|
||||
{
|
||||
let comments_len = comment.comments.len();
|
||||
move || {
|
||||
if open.get() {
|
||||
"[-]".into()
|
||||
} else {
|
||||
format!(
|
||||
"[+] {}{} collapsed",
|
||||
comments_len,
|
||||
pluralize(comments_len),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
{move || {
|
||||
open.get()
|
||||
.then({
|
||||
let comments = comment.comments.clone();
|
||||
move || {
|
||||
view! {
|
||||
<ul class="comment-children">
|
||||
<For
|
||||
each=move || comments.clone()
|
||||
key=|comment| comment.id
|
||||
let:comment
|
||||
>
|
||||
<Comment comment/>
|
||||
</For>
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
})}
|
||||
<div class="by">
|
||||
<A href=format!("/users/{}", comment.user.clone().unwrap_or_default())>{comment.user.clone()}</A>
|
||||
{format!(" {}", comment.time_ago)}
|
||||
</div>
|
||||
<div class="text" inner_html=comment.content></div>
|
||||
{(!comment.comments.is_empty()).then(|| {
|
||||
view! {
|
||||
<div>
|
||||
<div class="toggle" class:open=open>
|
||||
<a on:click=move |_| set_open.update(|n| *n = !*n)>
|
||||
{
|
||||
let comments_len = comment.comments.len();
|
||||
move || if open.get() {
|
||||
"[-]".into()
|
||||
} else {
|
||||
format!("[+] {}{} collapsed", comments_len, pluralize(comments_len))
|
||||
}
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
{move || open.get().then({
|
||||
let comments = comment.comments.clone();
|
||||
move || view! {
|
||||
<ul class="comment-children">
|
||||
<For
|
||||
each=move || comments.clone()
|
||||
key=|comment| comment.id
|
||||
let:comment
|
||||
>
|
||||
<Comment comment />
|
||||
</For>
|
||||
</ul>
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
})}
|
||||
</li>
|
||||
}.into_any()
|
||||
}
|
||||
}
|
||||
|
||||
fn pluralize(n: usize) -> &'static str {
|
||||
|
||||
@@ -1,63 +1,44 @@
|
||||
use crate::api::{self, User};
|
||||
use leptos::{either::Either, prelude::*, server::Resource};
|
||||
use leptos_router::hooks::use_params_map;
|
||||
use send_wrapper::SendWrapper;
|
||||
use leptos::prelude::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn User() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let user = Resource::new(
|
||||
move || params.read().get("id").unwrap_or_default(),
|
||||
move |id| {
|
||||
SendWrapper::new(async move {
|
||||
if id.is_empty() {
|
||||
None
|
||||
} else {
|
||||
api::fetch_api::<User>(&api::user(&id)).await
|
||||
}
|
||||
})
|
||||
let user = create_resource(
|
||||
move || params.get().get("id").cloned().unwrap_or_default(),
|
||||
move |id| async move {
|
||||
if id.is_empty() {
|
||||
None
|
||||
} else {
|
||||
api::fetch_api::<User>(&api::user(&id)).await
|
||||
}
|
||||
},
|
||||
);
|
||||
view! {
|
||||
<div class="user-view">
|
||||
<Suspense fallback=|| {
|
||||
view! { "Loading..." }
|
||||
}>
|
||||
{move || Suspend::new(async move {
|
||||
match user.await.clone() {
|
||||
None => Either::Left(view! { <h1>"User not found."</h1> }),
|
||||
Some(user) => {
|
||||
Either::Right(
|
||||
view! {
|
||||
<div>
|
||||
<h1>"User: " {user.id.clone()}</h1>
|
||||
<ul class="meta">
|
||||
<li>
|
||||
<span class="label">"Created: "</span>
|
||||
{user.created}
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">"Karma: "</span>
|
||||
{user.karma}
|
||||
</li>
|
||||
<li inner_html=user.about class="about"></li>
|
||||
</ul>
|
||||
<p class="links">
|
||||
<a href=format!(
|
||||
"https://news.ycombinator.com/submitted?id={}",
|
||||
user.id,
|
||||
)>"submissions"</a>
|
||||
" | "
|
||||
<a href=format!(
|
||||
"https://news.ycombinator.com/threads?id={}",
|
||||
user.id,
|
||||
)>"comments"</a>
|
||||
</p>
|
||||
</div>
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
<Suspense fallback=|| view! { "Loading..." }>
|
||||
{move || user.get().map(|user| match user {
|
||||
None => view! { <h1>"User not found."</h1> }.into_any(),
|
||||
Some(user) => view! {
|
||||
<div>
|
||||
<h1>"User: " {&user.id}</h1>
|
||||
<ul class="meta">
|
||||
<li>
|
||||
<span class="label">"Created: "</span> {user.created}
|
||||
</li>
|
||||
<li>
|
||||
<span class="label">"Karma: "</span> {user.karma}
|
||||
</li>
|
||||
{user.about.as_ref().map(|about| view! { <li inner_html=about class="about"></li> })}
|
||||
</ul>
|
||||
<p class="links">
|
||||
<a href=format!("https://news.ycombinator.com/submitted?id={}", user.id)>"submissions"</a>
|
||||
" | "
|
||||
<a href=format!("https://news.ycombinator.com/threads?id={}", user.id)>"comments"</a>
|
||||
</p>
|
||||
</div>
|
||||
}.into_any()
|
||||
})}
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
@@ -7,32 +7,32 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.30"
|
||||
http = "1.1"
|
||||
console_error_panic_hook = "0.1"
|
||||
futures = "0.3"
|
||||
http = "1.0"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"tracing",
|
||||
"experimental-islands",
|
||||
"tracing",
|
||||
"experimental-islands",
|
||||
] }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
wasm-bindgen = "0.2.93"
|
||||
log = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
axum = { version = "0.7", optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
wasm-bindgen = "0.2"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"dep:leptos_axum",
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"dep:leptos_axum",
|
||||
]
|
||||
|
||||
[profile.wasm-release]
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
[package]
|
||||
name = "islands"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.30"
|
||||
http = "1.1"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"tracing",
|
||||
"experimental-islands",
|
||||
] }
|
||||
leptos_router = { path = "../../router" }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
|
||||
leptos_axum = { path = "../../integrations/axum", features = [
|
||||
"islands-router",
|
||||
], optional = true }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
wasm-bindgen = "0.2.93"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"dep:leptos_axum",
|
||||
]
|
||||
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
opt-level = 'z'
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "tower-http", "tokio", "sqlx", "leptos_axum"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
output-name = "islands"
|
||||
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
|
||||
site-root = "target/site"
|
||||
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
|
||||
# Defaults to pkg
|
||||
site-pkg-dir = "pkg"
|
||||
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
|
||||
style-file = "./style.css"
|
||||
# [Optional] Files in the asset-dir will be copied to the site-root directory
|
||||
assets-dir = "public"
|
||||
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
|
||||
site-addr = "127.0.0.1:3000"
|
||||
# The port to use for automatic reload monitoring
|
||||
reload-port = 3001
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
browserquery = "defaults"
|
||||
# Set by cargo-leptos watch when building with that tool. Controls whether autoreload JS will be included in the head
|
||||
watch = false
|
||||
# The environment Leptos will run in, usually either "DEV" or "PROD"
|
||||
env = "DEV"
|
||||
# The features to use when compiling the bin target
|
||||
#
|
||||
# Optional. Can be over-ridden with the command line parameter --bin-features
|
||||
bin-features = ["ssr"]
|
||||
|
||||
# If the --no-default-features flag should be used when compiling the bin target
|
||||
#
|
||||
# Optional. Defaults to false.
|
||||
bin-default-features = false
|
||||
|
||||
# The features to use when compiling the lib target
|
||||
#
|
||||
# Optional. Can be over-ridden with the command line parameter --lib-features
|
||||
lib-features = ["hydrate"]
|
||||
|
||||
# If the --no-default-features flag should be used when compiling the lib target
|
||||
#
|
||||
# Optional. Defaults to false.
|
||||
lib-default-features = false
|
||||
|
||||
lib-profile-release = "wasm-release"
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Greg Johnston
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,4 +0,0 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/cargo-leptos.toml" },
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
# Leptos Todo App Sqlite with Axum
|
||||
|
||||
This example creates a basic todo app with an Axum backend that uses Leptos' server functions to call sqlx from the client and seamlessly run it on the server.
|
||||
|
||||
## Getting Started
|
||||
|
||||
See the [Examples README](../README.md) for setup and run instructions.
|
||||
|
||||
## E2E Testing
|
||||
|
||||
See the [E2E README](./e2e/README.md) for more information about the testing strategy.
|
||||
|
||||
## Rendering
|
||||
|
||||
See the [SSR Notes](../SSR_NOTES.md) for more information about Server Side Rendering.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Run `cargo leptos watch` to run this example.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,135 +0,0 @@
|
||||
window.addEventListener("click", async (ev) => {
|
||||
// confirm that this is an <a> that meets our requirements
|
||||
if (
|
||||
ev.defaultPrevented ||
|
||||
ev.button !== 0 ||
|
||||
ev.metaKey ||
|
||||
ev.altKey ||
|
||||
ev.ctrlKey ||
|
||||
ev.shiftKey
|
||||
)
|
||||
return;
|
||||
|
||||
/** @type HTMLAnchorElement | undefined;*/
|
||||
const a = ev
|
||||
.composedPath()
|
||||
.find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
|
||||
|
||||
if (!a) return;
|
||||
|
||||
const svg = a.namespaceURI === "http://www.w3.org/2000/svg";
|
||||
const href = svg ? a.href.baseVal : a.href;
|
||||
const target = svg ? a.target.baseVal : a.target;
|
||||
if (target || (!href && !a.hasAttribute("state"))) return;
|
||||
|
||||
const rel = (a.getAttribute("rel") || "").split(/\s+/);
|
||||
if (a.hasAttribute("download") || (rel && rel.includes("external"))) return;
|
||||
|
||||
const url = svg ? new URL(href, document.baseURI) : new URL(href);
|
||||
if (
|
||||
url.origin !== window.location.origin // ||
|
||||
// TODO base
|
||||
//(basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase()))
|
||||
)
|
||||
return;
|
||||
|
||||
ev.preventDefault();
|
||||
|
||||
// fetch the new page
|
||||
const resp = await fetch(url);
|
||||
const htmlString = await resp.text();
|
||||
|
||||
// Use DOMParser to parse the HTML string
|
||||
const parser = new DOMParser();
|
||||
// TODO parse from the request stream instead?
|
||||
const doc = parser.parseFromString(htmlString, 'text/html');
|
||||
|
||||
// The 'doc' variable now contains the parsed DOM
|
||||
const transition = document.startViewTransition(async () => {
|
||||
const oldDocWalker = document.createTreeWalker(document);
|
||||
const newDocWalker = doc.createTreeWalker(doc);
|
||||
let oldNode = oldDocWalker.currentNode;
|
||||
let newNode = newDocWalker.currentNode;
|
||||
while(oldDocWalker.nextNode() && newDocWalker.nextNode()) {
|
||||
oldNode = oldDocWalker.currentNode;
|
||||
newNode = newDocWalker.currentNode;
|
||||
// if the nodes are different, we need to replace the old with the new
|
||||
// because of the typed view tree, this should never actually happen
|
||||
if (oldNode.nodeType !== newNode.nodeType) {
|
||||
oldNode.replaceWith(newNode);
|
||||
}
|
||||
// if it's a text node, just update the text with the new text
|
||||
else if (oldNode.nodeType === Node.TEXT_NODE) {
|
||||
oldNode.textContent = newNode.textContent;
|
||||
}
|
||||
// if it's an element, replace if it's a different tag, or update attributes
|
||||
else if (oldNode.nodeType === Node.ELEMENT_NODE) {
|
||||
/** @type Element */
|
||||
const oldEl = oldNode;
|
||||
/** @type Element */
|
||||
const newEl = newNode;
|
||||
if (oldEl.tagName !== newEl.tagName) {
|
||||
oldEl.replaceWith(newEl);
|
||||
}
|
||||
else {
|
||||
for(const attr of newEl.attributes) {
|
||||
oldEl.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
// we use comment "branch marker" nodes to distinguish between different branches in the statically-typed view tree
|
||||
// if one of these marker is hit, then there are two options
|
||||
// 1) it's the same branch, and we just keep walking until the end
|
||||
// 2) it's a different branch, in which case the old can be replaced with the new wholesale
|
||||
else if (oldNode.nodeType === Node.COMMENT_NODE) {
|
||||
const oldText = oldNode.textContent;
|
||||
const newText = newNode.textContent;
|
||||
if(oldText.startsWith("bo") && newText !== oldText) {
|
||||
oldDocWalker.nextNode();
|
||||
newDocWalker.nextNode();
|
||||
const oldRange = new Range();
|
||||
const newRange = new Range();
|
||||
let oldBranches = 1;
|
||||
let newBranches = 1;
|
||||
while(oldBranches > 0 && newBranches > 0) {
|
||||
if(oldDocWalker.nextNode() && newDocWalker.nextNode()) {
|
||||
console.log(oldDocWalker.currentNode, newDocWalker.currentNode);
|
||||
if(oldDocWalker.currentNode.nodeType === Node.COMMENT_NODE) {
|
||||
if(oldDocWalker.currentNode.textContent.startsWith("bo")) {
|
||||
oldBranches += 1;
|
||||
} else if(oldDocWalker.currentNode.textContent.startsWith("bc")) {
|
||||
|
||||
oldBranches -= 1;
|
||||
}
|
||||
}
|
||||
if(newDocWalker.currentNode.nodeType === Node.COMMENT_NODE) {
|
||||
if(newDocWalker.currentNode.textContent.startsWith("bo")) {
|
||||
newBranches += 1;
|
||||
} else if(newDocWalker.currentNode.textContent.startsWith("bc")) {
|
||||
|
||||
newBranches -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
oldRange.setStartAfter(oldNode);
|
||||
oldRange.setEndBefore(oldDocWalker.currentNode);
|
||||
newRange.setStartAfter(newNode);
|
||||
newRange.setEndBefore(newDocWalker.currentNode);
|
||||
const newContents = newRange.extractContents();
|
||||
oldRange.deleteContents();
|
||||
oldRange.insertNode(newContents);
|
||||
oldNode.replaceWith(newNode);
|
||||
oldDocWalker.currentNode.replaceWith(newDocWalker.currentNode);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
} }
|
||||
}
|
||||
});
|
||||
await transition;
|
||||
window.history.pushState(undefined, null, url);
|
||||
});
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
@@ -1,59 +0,0 @@
|
||||
use leptos::prelude::*;
|
||||
use leptos_router::{
|
||||
components::{FlatRoutes, Route, Router},
|
||||
StaticSegment,
|
||||
};
|
||||
|
||||
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
view! {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<AutoReload options=options.clone()/>
|
||||
<HydrationScripts options=options islands=true/>
|
||||
<link rel="stylesheet" id="leptos" href="/pkg/islands.css"/>
|
||||
<link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
|
||||
</head>
|
||||
<body>
|
||||
<App/>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
view! {
|
||||
<script src="/routing.js"></script>
|
||||
<Router>
|
||||
<header>
|
||||
<h1>"My Application"</h1>
|
||||
</header>
|
||||
<nav>
|
||||
<a href="/">"Page A"</a>
|
||||
<a href="/b">"Page B"</a>
|
||||
</nav>
|
||||
<main>
|
||||
<p>
|
||||
<label>"Home Checkbox" <input type="checkbox"/></label>
|
||||
</p>
|
||||
<FlatRoutes fallback=|| "Not found.">
|
||||
<Route path=StaticSegment("") view=PageA/>
|
||||
<Route path=StaticSegment("b") view=PageB/>
|
||||
</FlatRoutes>
|
||||
</main>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn PageA() -> impl IntoView {
|
||||
view! { <label>"Page A" <input type="checkbox"/></label> }
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn PageB() -> impl IntoView {
|
||||
view! { <label>"Page B" <input type="checkbox"/></label> }
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
pub mod app;
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||
pub fn hydrate() {
|
||||
console_error_panic_hook::set_once();
|
||||
leptos::mount::hydrate_islands();
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
use axum::Router;
|
||||
use islands::app::{shell, App};
|
||||
use leptos::prelude::*;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars
|
||||
let conf = get_configuration(None).unwrap();
|
||||
let leptos_options = conf.leptos_options;
|
||||
let addr = leptos_options.site_addr;
|
||||
let routes = generate_route_list(App);
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.leptos_routes(&leptos_options, routes, {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
})
|
||||
.fallback(leptos_axum::file_and_error_handler(shell))
|
||||
.with_state(leptos_options);
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||
println!("listening on http://{}", &addr);
|
||||
axum::serve(listener, app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.pending {
|
||||
color: purple;
|
||||
}
|
||||
@@ -8,13 +8,13 @@ codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] } # for actual benchmarking, add `nightly` and `delegation` features
|
||||
leptos = { path = "../../leptos", features = ["csr"] } # for actual benchmarking, add `nightly` and `event-delegation` features
|
||||
# used in rand, but we need to enable js feature
|
||||
getrandom = { version = "0.2.15", features = ["js"] }
|
||||
getrandom = { version = "0.2.7", features = ["js"] }
|
||||
rand = { version = "0.8.5", features = ["small_rng"] }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
web-sys = "0.3"
|
||||
|
||||
@@ -9,7 +9,7 @@ lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
console_log = "1.0"
|
||||
log = "0.4.22"
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
web-sys = "0.3.70"
|
||||
web-sys = "0.3"
|
||||
|
||||
@@ -5,12 +5,12 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
log = "0.4.22"
|
||||
console_log = "1.0"
|
||||
log = "0.4"
|
||||
console_log = "1"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
wasm-bindgen = "0.2.93"
|
||||
web-sys = "0.3.70"
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
wasm-bindgen = "0.2"
|
||||
web-sys = "0.3"
|
||||
|
||||
@@ -10,16 +10,15 @@ codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[dependencies]
|
||||
console_log = "1.0"
|
||||
console_log = "1"
|
||||
leptos = { path = "../../leptos", features = ["csr", "tracing"] }
|
||||
leptos_router = { path = "../../router" } #, features = ["tracing"] }
|
||||
leptos_router_macro = { path = "../../router_macro" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
futures = "0.3.30"
|
||||
leptos_router = { path = "../../router" } #, features = ["tracing"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
futures = "0.3"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
tracing-subscriber = "0.3.18"
|
||||
tracing-subscriber-wasm = "0.1.0"
|
||||
tracing = "0.1.40"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
|
||||
@@ -4,14 +4,12 @@ use leptos::either::Either;
|
||||
use leptos::prelude::*;
|
||||
use leptos_router::{
|
||||
components::{
|
||||
Form, Outlet, ParentRoute, ProtectedRoute, Redirect, Route, Router,
|
||||
Routes, A,
|
||||
Outlet, ParentRoute, ProtectedRoute, Redirect, Route, Router, Routes, A,
|
||||
},
|
||||
hooks::{use_navigate, use_params, use_query_map},
|
||||
params::Params,
|
||||
MatchNestedRoutes,
|
||||
MatchNestedRoutes, ParamSegment, StaticSegment,
|
||||
};
|
||||
use leptos_router_macro::path;
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
@@ -39,22 +37,27 @@ pub fn RouterExample() -> impl IntoView {
|
||||
<A href="/about">"About"</A>
|
||||
<A href="/settings">"Settings"</A>
|
||||
<A href="/redirect-home">"Redirect to Home"</A>
|
||||
<button on:click=move |_| {
|
||||
set_logged_in.update(|n| *n = !*n)
|
||||
}>{move || if logged_in.get() { "Log Out" } else { "Log In" }}</button>
|
||||
<button on:click=move |_| set_logged_in.update(|n| *n = !*n)>
|
||||
{move || if logged_in.get() {
|
||||
"Log Out"
|
||||
} else {
|
||||
"Log In"
|
||||
}}
|
||||
</button>
|
||||
</nav>
|
||||
<main>
|
||||
<Routes fallback=|| "This page could not be found.">
|
||||
// paths can be created using the path!() macro, or provided as types like
|
||||
// StaticSegment("about")
|
||||
<Route path=path!("about") view=About/>
|
||||
<Route path=StaticSegment("about") view=About/>
|
||||
<ProtectedRoute
|
||||
path=path!("settings")
|
||||
path=StaticSegment("settings")
|
||||
condition=move || Some(logged_in.get())
|
||||
redirect_path=|| "/"
|
||||
view=Settings
|
||||
/>
|
||||
<Route path=path!("redirect-home") view=|| view! { <Redirect path="/"/> }/>
|
||||
<Route
|
||||
path=StaticSegment("redirect-home")
|
||||
view=|| view! { <Redirect path="/"/> }
|
||||
/>
|
||||
<ContactRoutes/>
|
||||
</Routes>
|
||||
</main>
|
||||
@@ -67,12 +70,11 @@ pub fn RouterExample() -> impl IntoView {
|
||||
#[component]
|
||||
pub fn ContactRoutes() -> impl MatchNestedRoutes<Dom> + Clone {
|
||||
view! {
|
||||
<ParentRoute path=path!("") view=ContactList>
|
||||
<Route path=path!("/") view=|| "Select a contact."/>
|
||||
<Route path=path!("/:id") view=Contact/>
|
||||
<ParentRoute path=StaticSegment("") view=ContactList>
|
||||
<Route path=StaticSegment("") view=|| "Select a contact."/>
|
||||
<Route path=ParamSegment("id") view=Contact/>
|
||||
</ParentRoute>
|
||||
}
|
||||
.into_inner()
|
||||
}
|
||||
|
||||
#[component]
|
||||
@@ -93,7 +95,7 @@ pub fn ContactList() -> impl IntoView {
|
||||
get_contacts(search.get())
|
||||
});
|
||||
let contacts = move || {
|
||||
Suspend::new(async move {
|
||||
Suspend(async move {
|
||||
// this data doesn't change frequently so we can use .map().collect() instead of a keyed <For/>
|
||||
contacts.await
|
||||
.into_iter()
|
||||
@@ -152,7 +154,7 @@ pub fn Contact() -> impl IntoView {
|
||||
});
|
||||
|
||||
let contact_display = move || {
|
||||
Suspend::new(async move {
|
||||
Suspend(async move {
|
||||
match contact.await {
|
||||
None => Either::Left(
|
||||
view! { <p>"No contact with this ID was found."</p> },
|
||||
@@ -215,19 +217,13 @@ pub fn Settings() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<h1>"Settings"</h1>
|
||||
<Form action="">
|
||||
<form>
|
||||
<fieldset>
|
||||
<legend>"Name"</legend>
|
||||
<input type="text" name="first_name" placeholder="First"/>
|
||||
<input type="text" name="last_name" placeholder="Last"/>
|
||||
</fieldset>
|
||||
<input type="submit"/>
|
||||
<p>
|
||||
"This uses the " <code>"<Form/>"</code>
|
||||
" component, which enhances forms by using client-side navigation for "
|
||||
<code>"GET"</code> " requests, and client-side requests for " <code>"POST"</code>
|
||||
" requests, without requiring a full page reload."
|
||||
</p>
|
||||
</Form>
|
||||
<pre>"This page is just a placeholder."</pre>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,52 +7,45 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.30"
|
||||
http = "1.1"
|
||||
console_error_panic_hook = "0.1"
|
||||
futures = "0.3"
|
||||
http = "1.0"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
server_fn = { path = "../../server_fn", features = [
|
||||
"serde-lite",
|
||||
"rkyv",
|
||||
"multipart",
|
||||
] }
|
||||
log = "0.4.22"
|
||||
simple_logger = "5.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = [
|
||||
"fs",
|
||||
"tracing",
|
||||
"trace",
|
||||
], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite", "rkyv", "multipart"] }
|
||||
log = "0.4"
|
||||
simple_logger = "4.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
axum = { version = "0.7", optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs", "tracing", "trace"], optional = true }
|
||||
tokio = { version = "1", features = ["full"], optional = true }
|
||||
thiserror = "1.0"
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen = "0.2"
|
||||
serde_toml = "0.0.1"
|
||||
toml = "0.8.19"
|
||||
web-sys = { version = "0.3.70", features = ["FileList", "File"] }
|
||||
strum = { version = "0.26.3", features = ["strum_macros", "derive"] }
|
||||
notify = { version = "6.1", optional = true }
|
||||
pin-project-lite = "0.2.14"
|
||||
dashmap = { version = "6.0", optional = true }
|
||||
once_cell = { version = "1.19", optional = true }
|
||||
async-broadcast = { version = "0.7.1", optional = true }
|
||||
toml = "0.8.8"
|
||||
web-sys = { version = "0.3.67", features = ["FileList", "File"] }
|
||||
strum = { version = "0.25.0", features = ["strum_macros", "derive"] }
|
||||
notify = { version = "6.1.1", optional = true }
|
||||
pin-project-lite = "0.2.13"
|
||||
dashmap = { version = "5.5.3", optional = true }
|
||||
once_cell = { version = "1.19.0", optional = true }
|
||||
async-broadcast = { version = "0.6.0", optional = true }
|
||||
send_wrapper = "0.6.0"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"dep:leptos_axum",
|
||||
"dep:notify",
|
||||
"dep:dashmap",
|
||||
"dep:once_cell",
|
||||
"dep:async-broadcast",
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"dep:leptos_axum",
|
||||
"dep:notify",
|
||||
"dep:dashmap",
|
||||
"dep:once_cell",
|
||||
"dep:async-broadcast",
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
|
||||
@@ -28,7 +28,7 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<AutoReload options=options.clone()/>
|
||||
<AutoReload options=options.clone() />
|
||||
<HydrationScripts options/>
|
||||
<meta name="color-scheme" content="dark light"/>
|
||||
<link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
|
||||
@@ -101,19 +101,19 @@ pub fn SpawnLocal() -> impl IntoView {
|
||||
view! {
|
||||
<h3>Using <code>spawn_local</code></h3>
|
||||
<p>
|
||||
"You can call a server function by using " <code>"spawn_local"</code>
|
||||
" in an event listener. "
|
||||
"You can call a server function by using "<code>"spawn_local"</code> " in an event listener. "
|
||||
"Clicking this button should alert with the uppercase version of the input."
|
||||
</p>
|
||||
<input node_ref=input_ref placeholder="Type something here."/>
|
||||
<button on:click=move |_| {
|
||||
let value = input_ref.get().unwrap().value();
|
||||
spawn_local(async move {
|
||||
let uppercase_text = shouting_text(value).await.unwrap_or_else(|e| e.to_string());
|
||||
set_shout_result.set(uppercase_text);
|
||||
});
|
||||
}>
|
||||
|
||||
<button
|
||||
on:click=move |_| {
|
||||
let value = input_ref.get().unwrap().value();
|
||||
spawn_local(async move {
|
||||
let uppercase_text = shouting_text(value).await.unwrap_or_else(|e| e.to_string());
|
||||
set_shout_result.set(uppercase_text);
|
||||
});
|
||||
}
|
||||
>
|
||||
{shout_result}
|
||||
</button>
|
||||
}
|
||||
@@ -185,11 +185,17 @@ pub fn WithAnAction() -> impl IntoView {
|
||||
"These often work well as actions."
|
||||
</p>
|
||||
<input node_ref=input_ref placeholder="Type something here."/>
|
||||
<button on:click=move |_| {
|
||||
let text = input_ref.get().unwrap().value();
|
||||
action.dispatch(text.into());
|
||||
}>
|
||||
|
||||
<button
|
||||
on:click=move |_| {
|
||||
let text = input_ref.get().unwrap().value();
|
||||
action.dispatch(text.into());
|
||||
// note: technically, this `action` takes `AddRow` (the server fn type) as its
|
||||
// argument
|
||||
//
|
||||
// however, for any one-argument server functions, `From<_>` is implemented between
|
||||
// the server function type and the type of this single argument
|
||||
}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
<p>You submitted: {move || format!("{:?}", action.input().get())}</p>
|
||||
@@ -215,9 +221,7 @@ pub fn WithActionForm() -> impl IntoView {
|
||||
view! {
|
||||
<h3>Using <code>"<ActionForm/>"</code></h3>
|
||||
<p>
|
||||
<code>"<ActionForm/>"</code>
|
||||
"lets you use an HTML "
|
||||
<code>"<form>"</code>
|
||||
<code>"<ActionForm/>"</code> "lets you use an HTML " <code>"<form>"</code>
|
||||
"to call a server function in a way that gracefully degrades."
|
||||
</p>
|
||||
<ActionForm action>
|
||||
@@ -230,8 +234,7 @@ pub fn WithActionForm() -> impl IntoView {
|
||||
</ActionForm>
|
||||
<p>You submitted: {move || format!("{:?}", action.input().get())}</p>
|
||||
<p>The result was: {move || format!("{:?}", action.value().get())}</p>
|
||||
<Transition>
|
||||
archive underaligned: need alignment 4 but have alignment 1
|
||||
<Transition>archive underaligned: need alignment 4 but have alignment 1
|
||||
<p>Total rows: {row_count}</p>
|
||||
</Transition>
|
||||
}
|
||||
@@ -270,21 +273,24 @@ pub fn ServerFnArgumentExample() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<h3>Custom arguments to the <code>#[server]</code> " macro"</h3>
|
||||
<p>This example shows how to specify additional behavior, including:</p>
|
||||
<ul>
|
||||
<li>Specific server function <strong>paths</strong></li>
|
||||
<li>Mixing and matching input and output <strong>encodings</strong></li>
|
||||
<li>Adding custom <strong>middleware</strong> on a per-server-fn basis</li>
|
||||
</ul>
|
||||
<p>
|
||||
This example shows how to specify additional behavior including
|
||||
<ul>
|
||||
<li>Specific server function <strong>paths</strong></li>
|
||||
<li>Mixing and matching input and output <strong>encodings</strong></li>
|
||||
<li>Adding custom <strong>middleware</strong> on a per-server-fn basis</li>
|
||||
</ul>
|
||||
</p>
|
||||
<input node_ref=input_ref placeholder="Type something here."/>
|
||||
<button on:click=move |_| {
|
||||
let value = input_ref.get().unwrap().value();
|
||||
spawn_local(async move {
|
||||
let length = length_of_input(value).await.unwrap_or(0);
|
||||
set_result.set(length);
|
||||
});
|
||||
}>
|
||||
|
||||
<button
|
||||
on:click=move |_| {
|
||||
let value = input_ref.get().unwrap().value();
|
||||
spawn_local(async move {
|
||||
let length = length_of_input(value).await.unwrap_or(0);
|
||||
set_result.set(length);
|
||||
});
|
||||
}
|
||||
>
|
||||
Click to see length
|
||||
</button>
|
||||
<p>Length is {result}</p>
|
||||
@@ -316,15 +322,18 @@ pub fn RkyvExample() -> impl IntoView {
|
||||
view! {
|
||||
<h3>Using <code>rkyv</code> encoding</h3>
|
||||
<input node_ref=input_ref placeholder="Type something here."/>
|
||||
<button on:click=move |_| {
|
||||
let value = input_ref.get().unwrap().value();
|
||||
set_input.set(value);
|
||||
}>
|
||||
|
||||
<button
|
||||
on:click=move |_| {
|
||||
let value = input_ref.get().unwrap().value();
|
||||
set_input.set(value);
|
||||
}
|
||||
>
|
||||
Click to capitalize
|
||||
</button>
|
||||
<p>{input}</p>
|
||||
<Transition>{rkyv_result}</Transition>
|
||||
<Transition>
|
||||
{rkyv_result}
|
||||
</Transition>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,9 +370,10 @@ pub fn FileUpload() -> impl IntoView {
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
let upload_action = Action::new_local(|data: &FormData| {
|
||||
let upload_action = Action::new_unsync(|data: &FormData| {
|
||||
let data = data.to_owned();
|
||||
// `MultipartData` implements `From<FormData>`
|
||||
file_length(data.clone().into())
|
||||
file_length(data.into())
|
||||
});
|
||||
|
||||
view! {
|
||||
@@ -373,25 +383,21 @@ pub fn FileUpload() -> impl IntoView {
|
||||
ev.prevent_default();
|
||||
let target = ev.target().unwrap().unchecked_into::<HtmlFormElement>();
|
||||
let form_data = FormData::new_with_form(&target).unwrap();
|
||||
upload_action.dispatch_local(form_data);
|
||||
upload_action.dispatch_unsync(form_data);
|
||||
}>
|
||||
<input type="file" name="file_to_upload"/>
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
<p>
|
||||
{move || {
|
||||
if upload_action.input_local().read().is_none() && upload_action.value().read().is_none()
|
||||
{
|
||||
"Upload a file.".to_string()
|
||||
} else if upload_action.pending().get() {
|
||||
"Uploading...".to_string()
|
||||
} else if let Some(Ok(value)) = upload_action.value().get() {
|
||||
value.to_string()
|
||||
} else {
|
||||
format!("{:?}", upload_action.value().get())
|
||||
}
|
||||
{move || if upload_action.input().get().is_none() && upload_action.value().get().is_none() {
|
||||
"Upload a file.".to_string()
|
||||
} else if upload_action.pending().get() {
|
||||
"Uploading...".to_string()
|
||||
} else if let Some(Ok(value)) = upload_action.value().get() {
|
||||
value.to_string()
|
||||
} else {
|
||||
format!("{:?}", upload_action.value().get())
|
||||
}}
|
||||
|
||||
</p>
|
||||
}
|
||||
}
|
||||
@@ -553,17 +559,9 @@ pub fn FileUploadWithProgress() -> impl IntoView {
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
{move || filename.get().map(|filename| view! { <p>Uploading {filename}</p> })}
|
||||
{move || {
|
||||
max.get()
|
||||
.map(|max| {
|
||||
view! {
|
||||
<progress
|
||||
max=max
|
||||
value=move || current.get().unwrap_or_default()
|
||||
></progress>
|
||||
}
|
||||
})
|
||||
}}
|
||||
{move || max.get().map(|max| view! {
|
||||
<progress max=max value=move || current.get().unwrap_or_default()/>
|
||||
})}
|
||||
}
|
||||
}
|
||||
#[component]
|
||||
@@ -618,27 +616,9 @@ pub fn FileWatcher() -> impl IntoView {
|
||||
<h3>Watching files and returning a streaming response</h3>
|
||||
<p>Files changed since you loaded the page:</p>
|
||||
<ul>
|
||||
{move || {
|
||||
files
|
||||
.get()
|
||||
.into_iter()
|
||||
.map(|file| {
|
||||
view! {
|
||||
<li>
|
||||
<code>{file}</code>
|
||||
</li>
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}}
|
||||
|
||||
{move || files.get().into_iter().map(|file| view! { <li><code>{file}</code></li> }).collect::<Vec<_>>()}
|
||||
</ul>
|
||||
<p>
|
||||
<em>
|
||||
Add or remove some text files in the <code>watched_files</code>
|
||||
directory and see the list of changes here.
|
||||
</em>
|
||||
</p>
|
||||
<p><em>Add or remove some text files in the <code>watched_files</code> directory and see the list of changes here.</em></p>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -688,17 +668,20 @@ pub fn CustomErrorTypes() -> impl IntoView {
|
||||
the rules!"
|
||||
</p>
|
||||
<input node_ref=input_ref placeholder="Type something here."/>
|
||||
<button on:click=move |_| {
|
||||
let value = input_ref.get().unwrap().value();
|
||||
spawn_local(async move {
|
||||
let data = ascii_uppercase(value).await;
|
||||
set_result.set(Some(data));
|
||||
});
|
||||
}>
|
||||
|
||||
<button
|
||||
on:click=move |_| {
|
||||
let value = input_ref.get().unwrap().value();
|
||||
spawn_local(async move {
|
||||
let data = ascii_uppercase(value).await;
|
||||
set_result.set(Some(data));
|
||||
});
|
||||
}
|
||||
>
|
||||
"Submit"
|
||||
</button>
|
||||
<p>{move || format!("{:?}", result.get())}</p>
|
||||
<p>
|
||||
{move || format!("{:?}", result.get())}
|
||||
</p>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,14 +792,15 @@ pub fn CustomEncoding() -> impl IntoView {
|
||||
"This example creates a custom encoding that sends server fn data using TOML. Why? Well... why not?"
|
||||
</p>
|
||||
<input node_ref=input_ref placeholder="Type something here."/>
|
||||
<button on:click=move |_| {
|
||||
let value = input_ref.get().unwrap().value();
|
||||
spawn_local(async move {
|
||||
let new_value = why_not(value, ", but in TOML!!!".to_string()).await.unwrap();
|
||||
set_result.set(new_value.0.modified);
|
||||
});
|
||||
}>
|
||||
|
||||
<button
|
||||
on:click=move |_| {
|
||||
let value = input_ref.get().unwrap().value();
|
||||
spawn_local(async move {
|
||||
let new_value = why_not(value, ", but in TOML!!!".to_string()).await.unwrap();
|
||||
set_result.set(new_value.0.modified);
|
||||
});
|
||||
}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
<p>{result}</p>
|
||||
@@ -869,14 +853,8 @@ pub fn CustomClientExample() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<h3>Custom clients</h3>
|
||||
<p>
|
||||
You can define a custom server function client to do something like adding a header to every request.
|
||||
</p>
|
||||
<p>
|
||||
Check the network request in your browser devtools to see how this client adds a custom header.
|
||||
</p>
|
||||
<button on:click=|_| spawn_local(async {
|
||||
fn_with_custom_client().await.unwrap()
|
||||
})>Click me</button>
|
||||
<p>You can define a custom server function client to do something like adding a header to every request.</p>
|
||||
<p>Check the network request in your browser devtools to see how this client adds a custom header.</p>
|
||||
<button on:click=|_| spawn_local(async { fn_with_custom_client().await.unwrap() })>Click me</button>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,21 +37,21 @@ pub fn ErrorTemplate(
|
||||
}
|
||||
|
||||
view! {
|
||||
<h1>"Errors"</h1>
|
||||
<For
|
||||
// a function that returns the items we're iterating over; a signal is fine
|
||||
each=move || { errors.clone().into_iter().enumerate() }
|
||||
// a unique key for each item as a reference
|
||||
key=|(index, _error)| *index
|
||||
// renders each item to a view
|
||||
children=move |error| {
|
||||
let error_string = error.1.to_string();
|
||||
let error_code = error.1.status_code();
|
||||
view! {
|
||||
<h2>{error_code.to_string()}</h2>
|
||||
<p>"Error: " {error_string}</p>
|
||||
}
|
||||
}
|
||||
/>
|
||||
<h1>"Errors"</h1>
|
||||
<For
|
||||
// a function that returns the items we're iterating over; a signal is fine
|
||||
each= move || {errors.clone().into_iter().enumerate()}
|
||||
// a unique key for each item as a reference
|
||||
key=|(index, _error)| *index
|
||||
// renders each item to a view
|
||||
children=move |error| {
|
||||
let error_string = error.1.to_string();
|
||||
let error_code= error.1.status_code();
|
||||
view! {
|
||||
<h2>{error_code.to_string()}</h2>
|
||||
<p>"Error: " {error_string}</p>
|
||||
}
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@ lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
console_log = "1.0"
|
||||
log = "0.4.22"
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
@@ -7,20 +7,20 @@ 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"
|
||||
console_log = "1.0"
|
||||
lazy_static = "1.5"
|
||||
leptos = { path = "../../leptos" }
|
||||
actix-files = { version = "0.6", optional = true }
|
||||
actix-web = { version = "4", optional = true, features = ["macros"] }
|
||||
console_error_panic_hook = "0.1"
|
||||
console_log = "1"
|
||||
lazy_static = "1"
|
||||
leptos = { path = "../../leptos"}
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.39", features = ["time"] }
|
||||
wasm-bindgen = "0.2.93"
|
||||
log = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
thiserror = "1"
|
||||
tokio = { version = "1", features = ["time"] }
|
||||
wasm-bindgen = "0.2"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
|
||||
@@ -64,7 +64,7 @@ fn HomePage() -> impl IntoView {
|
||||
view! {
|
||||
<h1>"My Great Blog"</h1>
|
||||
<Suspense fallback=move || view! { <p>"Loading posts..."</p> }>
|
||||
<p>"number of posts: " {Suspend::new(async move { posts2.await })}</p>
|
||||
<p>"number of posts: " {Suspend(async move { posts2.await })}</p>
|
||||
</Suspense>
|
||||
<Suspense fallback=move || view! { <p>"Loading posts..."</p> }>
|
||||
<ul>
|
||||
@@ -105,7 +105,7 @@ fn Post() -> impl IntoView {
|
||||
}
|
||||
});
|
||||
|
||||
let post_view = Suspend::new(async move {
|
||||
let post_view = Suspend(async move {
|
||||
match post_resource.await.to_owned() {
|
||||
Ok(Ok(post)) => Ok(view! {
|
||||
<h1>{post.title.clone()}</h1>
|
||||
|
||||
@@ -7,39 +7,37 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
console_log = "1.0"
|
||||
lazy_static = "1.5"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"hydration",
|
||||
] } #"nightly", "hydration"] }
|
||||
console_error_panic_hook = "0.1"
|
||||
console_log = "1"
|
||||
lazy_static = "1"
|
||||
leptos = { path = "../../leptos", features = ["hydration" ] } #"nightly", "hydration"] }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = [
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
"time",
|
||||
log = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
thiserror = "1"
|
||||
axum = { version = "0.7", optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
tokio = { version = "1", features = [
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
"time",
|
||||
], optional = true }
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen = "0.2"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"dep:leptos_axum",
|
||||
"leptos_router/ssr",
|
||||
"dep:axum",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:tokio",
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"dep:leptos_axum",
|
||||
"leptos_router/ssr",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -20,7 +20,7 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<AutoReload options=options.clone()/>
|
||||
<AutoReload options=options.clone() />
|
||||
<HydrationScripts options/>
|
||||
<MetaTags/>
|
||||
</head>
|
||||
@@ -64,24 +64,15 @@ pub fn App() -> impl IntoView {
|
||||
<a href="/admin">"Admin"</a>
|
||||
<Transition>
|
||||
<ActionForm action=toggle_admin>
|
||||
<input
|
||||
type="hidden"
|
||||
name="is_admin"
|
||||
value=move || {
|
||||
(!is_admin.get().and_then(|n| n.ok()).unwrap_or_default())
|
||||
.to_string()
|
||||
}
|
||||
<input type="hidden" name="is_admin"
|
||||
value=move || (!is_admin.get().and_then(|n| n.ok()).unwrap_or_default()).to_string()
|
||||
/>
|
||||
|
||||
<button>
|
||||
{move || {
|
||||
if is_admin.get().and_then(Result::ok).unwrap_or_default() {
|
||||
"Log Out"
|
||||
} else {
|
||||
"Log In"
|
||||
}
|
||||
{move || if is_admin.get().and_then(Result::ok).unwrap_or_default() {
|
||||
"Log Out"
|
||||
} else {
|
||||
"Log In"
|
||||
}}
|
||||
|
||||
</button>
|
||||
</ActionForm>
|
||||
</Transition>
|
||||
@@ -103,10 +94,6 @@ pub fn App() -> impl IntoView {
|
||||
view=Post
|
||||
ssr=SsrMode::InOrder
|
||||
/>
|
||||
<Route
|
||||
path=(StaticSegment("post_partially_blocked"), ParamSegment("id"))
|
||||
view=Post
|
||||
/>
|
||||
<ProtectedRoute
|
||||
path=StaticSegment("admin")
|
||||
view=Admin
|
||||
@@ -140,7 +127,7 @@ fn HomePage() -> impl IntoView {
|
||||
view! {
|
||||
<h1>"My Great Blog"</h1>
|
||||
<Suspense fallback=move || view! { <p>"Loading posts..."</p> }>
|
||||
<p>"number of posts: " {Suspend::new(async move { posts2.await })}</p>
|
||||
<p>"number of posts: " {Suspend(async move { posts2.await })}</p>
|
||||
</Suspense>
|
||||
<Suspense fallback=move || view! { <p>"Loading posts..."</p> }>
|
||||
<ul>
|
||||
@@ -148,15 +135,7 @@ fn HomePage() -> impl IntoView {
|
||||
<li>
|
||||
<a href=format!("/post/{}", post.id)>{post.title.clone()}</a>
|
||||
"|"
|
||||
<a href=format!(
|
||||
"/post_in_order/{}",
|
||||
post.id,
|
||||
)>{post.title.clone()} "(in order)"</a>
|
||||
"|"
|
||||
<a href=format!(
|
||||
"/post_partially_blocked/{}",
|
||||
post.id,
|
||||
)>{post.title} "(partially blocked)"</a>
|
||||
<a href=format!("/post_in_order/{}", post.id)>{post.title} "(in order)"</a>
|
||||
</li>
|
||||
</For>
|
||||
</ul>
|
||||
@@ -179,7 +158,7 @@ fn Post() -> impl IntoView {
|
||||
.map_err(|_| PostError::InvalidId)
|
||||
})
|
||||
};
|
||||
let post_resource = Resource::new_blocking(id, |id| async move {
|
||||
let post_resource = Resource::new(id, |id| async move {
|
||||
match id {
|
||||
Err(e) => Err(e),
|
||||
Ok(id) => get_post(id)
|
||||
@@ -188,43 +167,18 @@ fn Post() -> impl IntoView {
|
||||
.map_err(|_| PostError::ServerError),
|
||||
}
|
||||
});
|
||||
let comments_resource = Resource::new(id, |id| async move {
|
||||
match id {
|
||||
Err(e) => Err(e),
|
||||
Ok(id) => {
|
||||
get_comments(id).await.map_err(|_| PostError::ServerError)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let post_view = Suspend::new(async move {
|
||||
match post_resource.await {
|
||||
Ok(Ok(post)) => {
|
||||
Ok(view! {
|
||||
<h1>{post.title.clone()}</h1>
|
||||
<p>{post.content.clone()}</p>
|
||||
let post_view = Suspend(async move {
|
||||
match post_resource.await.to_owned() {
|
||||
Ok(Ok(post)) => Ok(view! {
|
||||
<h1>{post.title.clone()}</h1>
|
||||
<p>{post.content.clone()}</p>
|
||||
|
||||
// since we're using async rendering for this page,
|
||||
// this metadata should be included in the actual HTML <head>
|
||||
// when it's first served
|
||||
<Title text=post.title/>
|
||||
<Meta name="description" content=post.content/>
|
||||
})
|
||||
}
|
||||
_ => Err(PostError::ServerError),
|
||||
}
|
||||
});
|
||||
let comments_view = Suspend::new(async move {
|
||||
match comments_resource.await {
|
||||
Ok(comments) => Ok(view! {
|
||||
<h1>"Comments"</h1>
|
||||
<ul>
|
||||
{comments
|
||||
.into_iter()
|
||||
.map(|comment| view! { <li>{comment}</li> })
|
||||
.collect_view()}
|
||||
|
||||
</ul>
|
||||
// since we're using async rendering for this page,
|
||||
// this metadata should be included in the actual HTML <head>
|
||||
// when it's first served
|
||||
<Title text=post.title/>
|
||||
<Meta name="description" content=post.content/>
|
||||
}),
|
||||
_ => Err(PostError::ServerError),
|
||||
}
|
||||
@@ -251,13 +205,14 @@ fn Post() -> impl IntoView {
|
||||
}
|
||||
}>{post_view}</ErrorBoundary>
|
||||
</Suspense>
|
||||
<Suspense fallback=move || view! { <p>"Loading comments..."</p> }>{comments_view}</Suspense>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Admin() -> impl IntoView {
|
||||
view! { <p>"You can only see this page if you're logged in."</p> }
|
||||
view! {
|
||||
<p>"You can only see this page if you're logged in."</p>
|
||||
}
|
||||
}
|
||||
|
||||
// Dummy API
|
||||
@@ -321,10 +276,3 @@ pub async fn get_post(id: usize) -> Result<Option<Post>, ServerFnError> {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
Ok(POSTS.iter().find(|post| post.id == id).cloned())
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn get_comments(id: usize) -> Result<Vec<String>, ServerFnError> {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||
_ = id;
|
||||
Ok(vec!["Some comment".into(), "Some other comment".into()])
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "stores"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 'z'
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
reactive_stores = { path = "../../reactive_stores" }
|
||||
reactive_stores_macro = { path = "../../reactive_stores_macro" }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
web-sys = "0.3.70"
|
||||
@@ -1,3 +0,0 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
]
|
||||
@@ -1,11 +0,0 @@
|
||||
# Leptos Counter Example
|
||||
|
||||
This example creates a simple counter in a client side rendered app with Rust and WASM!
|
||||
|
||||
## Getting Started
|
||||
|
||||
See the [Examples README](../README.md) for setup and run instructions.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Run `trunk serve --open` to run this example.
|
||||
@@ -1,8 +0,0 @@
|
||||
<!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"/>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,2 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
@@ -1,130 +0,0 @@
|
||||
use leptos::prelude::*;
|
||||
use reactive_stores::{Field, Store, StoreFieldIterator};
|
||||
use reactive_stores_macro::Store;
|
||||
|
||||
#[derive(Debug, Store)]
|
||||
struct Todos {
|
||||
user: String,
|
||||
todos: Vec<Todo>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Store)]
|
||||
struct Todo {
|
||||
label: String,
|
||||
completed: bool,
|
||||
}
|
||||
|
||||
impl Todo {
|
||||
pub fn new(label: impl ToString) -> Self {
|
||||
Self {
|
||||
label: label.to_string(),
|
||||
completed: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn data() -> Todos {
|
||||
Todos {
|
||||
user: "Bob".to_string(),
|
||||
todos: vec![
|
||||
Todo {
|
||||
label: "Create reactive store".to_string(),
|
||||
completed: true,
|
||||
},
|
||||
Todo {
|
||||
label: "???".to_string(),
|
||||
completed: false,
|
||||
},
|
||||
Todo {
|
||||
label: "Profit".to_string(),
|
||||
completed: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
let store = Store::new(data());
|
||||
|
||||
let input_ref = NodeRef::new();
|
||||
|
||||
let rows = move || {
|
||||
store
|
||||
.todos()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, todo)| view! { <TodoRow store idx todo/> })
|
||||
.collect_view()
|
||||
};
|
||||
|
||||
view! {
|
||||
<p>"Hello, " {move || store.user().get()}</p>
|
||||
<form on:submit=move |ev| {
|
||||
ev.prevent_default();
|
||||
store.todos().write().push(Todo::new(input_ref.get().unwrap().value()));
|
||||
}>
|
||||
<label>"Add a Todo" <input type="text" node_ref=input_ref/></label>
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
<ol>{rows}</ol>
|
||||
<div style="display: flex"></div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn TodoRow(
|
||||
store: Store<Todos>,
|
||||
idx: usize,
|
||||
#[prop(into)] todo: Field<Todo>,
|
||||
) -> impl IntoView {
|
||||
let completed = todo.completed();
|
||||
let title = todo.label();
|
||||
|
||||
let editing = RwSignal::new(false);
|
||||
|
||||
view! {
|
||||
<li
|
||||
style:text-decoration=move || {
|
||||
completed.get().then_some("line-through").unwrap_or_default()
|
||||
}
|
||||
|
||||
class:foo=move || completed.get()
|
||||
>
|
||||
<p
|
||||
class:hidden=move || editing.get()
|
||||
on:click=move |_| {
|
||||
editing.update(|n| *n = !*n);
|
||||
}
|
||||
>
|
||||
|
||||
{move || title.get()}
|
||||
</p>
|
||||
<input
|
||||
class:hidden=move || !(editing.get())
|
||||
type="text"
|
||||
prop:value=move || title.get()
|
||||
on:change=move |ev| {
|
||||
title.set(event_target_value(&ev));
|
||||
editing.set(false);
|
||||
}
|
||||
|
||||
on:blur=move |_| editing.set(false)
|
||||
autofocus
|
||||
/>
|
||||
<input
|
||||
type="checkbox"
|
||||
prop:checked=move || completed.get()
|
||||
on:click=move |_| { completed.update(|n| *n = !*n) }
|
||||
/>
|
||||
|
||||
<button on:click=move |_| {
|
||||
store
|
||||
.todos()
|
||||
.update(|todos| {
|
||||
todos.remove(idx);
|
||||
});
|
||||
}>"X"</button>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
use leptos::prelude::*;
|
||||
use stores::App;
|
||||
|
||||
pub fn main() {
|
||||
console_error_panic_hook::set_once();
|
||||
mount_to_body(App)
|
||||
}
|
||||
@@ -7,26 +7,26 @@ 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"
|
||||
actix-files = { version = "0.6", optional = true }
|
||||
actix-web = { version = "4", optional = true, features = ["macros"] }
|
||||
console_error_panic_hook = "0.1"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
log = "0.4.22"
|
||||
wasm-bindgen = "0.2.93"
|
||||
serde = "1.0"
|
||||
tokio = { version = "1.39", features = ["time", "rt"], optional = true }
|
||||
log = "0.4"
|
||||
wasm-bindgen = "0.2"
|
||||
serde = "1.0.159"
|
||||
tokio = { version = "1.29", features = ["time", "rt"], optional = true }
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
"dep:leptos_actix",
|
||||
"leptos/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:tokio",
|
||||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
"dep:leptos_actix",
|
||||
"leptos/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:tokio",
|
||||
]
|
||||
|
||||
[package.metadata.leptos]
|
||||
|
||||
@@ -4,14 +4,14 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0"
|
||||
async-trait = "0.1.81"
|
||||
cucumber = "0.21.1"
|
||||
fantoccini = "0.21.1"
|
||||
pretty_assertions = "1.4"
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1.39", features = ["macros", "rt-multi-thread", "time"] }
|
||||
url = "2.5"
|
||||
anyhow = "1.0.72"
|
||||
async-trait = "0.1.72"
|
||||
cucumber = "0.19.1"
|
||||
fantoccini = "0.19.3"
|
||||
pretty_assertions = "1.4.0"
|
||||
serde_json = "1.0.104"
|
||||
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread", "time"] }
|
||||
url = "2.4.0"
|
||||
|
||||
[[test]]
|
||||
name = "app_suite"
|
||||
|
||||
@@ -8,8 +8,8 @@ This example demonstrates e2e testing with Rust using executable requirements.
|
||||
|---|---|---|
|
||||
| [Cucumber](https://github.com/cucumber-rs/cucumber/tree/main) | Test Runner | Run [Gherkin](https://cucumber.io/docs/gherkin/reference/) specifications as Rust tests |
|
||||
| [Fantoccini](https://github.com/jonhoo/fantoccini/tree/main) | Browser Client | Interact with web pages through WebDriver |
|
||||
| [Cargo Leptos](https://github.com/leptos-rs/cargo-leptos) | Build Tool | Compile example and start the server and end-2-end tests |
|
||||
| [chromedriver](https://chromedriver.chromium.org/downloads) | WebDriver | Provide WebDriver for Chrome |
|
||||
| [Cargo Leptos ](https://github.com/leptos-rs/cargo-leptos) | Build Tool | Compile example and start the server and end-2-end tests |
|
||||
| [chromedriver](https://chromedriver.chromium.org/downloads) | WebDriver | Provide WebDriver for Chrome
|
||||
|
||||
## Testing Organization
|
||||
|
||||
|
||||
@@ -51,63 +51,51 @@ pub fn App() -> impl IntoView {
|
||||
// out-of-order
|
||||
<ParentRoute
|
||||
path=StaticSegment("out-of-order")
|
||||
view=|| {
|
||||
view! {
|
||||
<SecondaryNav/>
|
||||
<h1>"Out-of-Order"</h1>
|
||||
<Outlet/>
|
||||
}
|
||||
view=|| view! {
|
||||
<SecondaryNav/>
|
||||
<h1>"Out-of-Order"</h1>
|
||||
<Outlet/>
|
||||
}
|
||||
>
|
||||
|
||||
<Route path=StaticSegment("") view=Nested/>
|
||||
<Route path=StaticSegment("inside") view=NestedResourceInside/>
|
||||
<Route path=StaticSegment("single") view=Single/>
|
||||
<Route path=StaticSegment("parallel") view=Parallel/>
|
||||
<Route path=StaticSegment("inside-component") view=InsideComponent/>
|
||||
<Route path=StaticSegment("local") view=LocalResource/>
|
||||
<Route path=StaticSegment("none") view=None/>
|
||||
</ParentRoute>
|
||||
// in-order
|
||||
<ParentRoute
|
||||
path=StaticSegment("in-order")
|
||||
ssr=SsrMode::InOrder
|
||||
view=|| {
|
||||
view! {
|
||||
<SecondaryNav/>
|
||||
<h1>"In-Order"</h1>
|
||||
<Outlet/>
|
||||
}
|
||||
view=|| view! {
|
||||
<SecondaryNav/>
|
||||
<h1>"In-Order"</h1>
|
||||
<Outlet/>
|
||||
}
|
||||
>
|
||||
|
||||
<Route path=StaticSegment("") view=Nested/>
|
||||
<Route path=StaticSegment("inside") view=NestedResourceInside/>
|
||||
<Route path=StaticSegment("single") view=Single/>
|
||||
<Route path=StaticSegment("parallel") view=Parallel/>
|
||||
<Route path=StaticSegment("inside-component") view=InsideComponent/>
|
||||
<Route path=StaticSegment("local") view=LocalResource/>
|
||||
<Route path=StaticSegment("none") view=None/>
|
||||
</ParentRoute>
|
||||
// async
|
||||
<ParentRoute
|
||||
path=StaticSegment("async")
|
||||
ssr=SsrMode::Async
|
||||
view=|| {
|
||||
view! {
|
||||
<SecondaryNav/>
|
||||
<h1>"Async"</h1>
|
||||
<Outlet/>
|
||||
}
|
||||
view=|| view! {
|
||||
<SecondaryNav/>
|
||||
<h1>"Async"</h1>
|
||||
<Outlet/>
|
||||
}
|
||||
>
|
||||
|
||||
<Route path=StaticSegment("") view=Nested/>
|
||||
<Route path=StaticSegment("inside") view=NestedResourceInside/>
|
||||
<Route path=StaticSegment("single") view=Single/>
|
||||
<Route path=StaticSegment("parallel") view=Parallel/>
|
||||
<Route path=StaticSegment("inside-component") view=InsideComponent/>
|
||||
<Route path=StaticSegment("local") view=LocalResource/>
|
||||
<Route path=StaticSegment("none") view=None/>
|
||||
</ParentRoute>
|
||||
</Routes>
|
||||
@@ -120,16 +108,11 @@ pub fn App() -> impl IntoView {
|
||||
fn SecondaryNav() -> impl IntoView {
|
||||
view! {
|
||||
<nav>
|
||||
<A href="" exact=true>
|
||||
"Nested"
|
||||
</A>
|
||||
<A href="inside" exact=true>
|
||||
"Nested (resource created inside)"
|
||||
</A>
|
||||
<A href="" exact=true>"Nested"</A>
|
||||
<A href="inside" exact=true>"Nested (resource created inside)"</A>
|
||||
<A href="single">"Single"</A>
|
||||
<A href="parallel">"Parallel"</A>
|
||||
<A href="inside-component">"Inside Component"</A>
|
||||
<A href="local">"Local Resource"</A>
|
||||
<A href="none">"No Resources"</A>
|
||||
</nav>
|
||||
}
|
||||
@@ -143,28 +126,21 @@ fn Nested() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<Suspense fallback=|| {
|
||||
"Loading 1..."
|
||||
}>
|
||||
<Suspense fallback=|| "Loading 1...">
|
||||
{move || {
|
||||
one_second.get().map(|_| view! { <p id="loaded-1">"One Second: Loaded 1!"</p> })
|
||||
one_second.get().map(|_| view! {
|
||||
<p id="loaded-1">"One Second: Loaded 1!"</p>
|
||||
})
|
||||
}}
|
||||
<Suspense fallback=|| {
|
||||
"Loading 2..."
|
||||
}>
|
||||
<Suspense fallback=|| "Loading 2...">
|
||||
{move || {
|
||||
two_second
|
||||
.get()
|
||||
.map(|_| {
|
||||
view! {
|
||||
<p id="loaded-2">"Two Second: Loaded 2!"</p>
|
||||
<button on:click=move |_| {
|
||||
set_count.update(|n| *n += 1)
|
||||
}>{count}</button>
|
||||
}
|
||||
})
|
||||
two_second.get().map(|_| view! {
|
||||
<p id="loaded-2">"Two Second: Loaded 2!"</p>
|
||||
<button on:click=move |_| set_count.update(|n| *n += 1)>
|
||||
{count}
|
||||
</button>
|
||||
})
|
||||
}}
|
||||
|
||||
</Suspense>
|
||||
</Suspense>
|
||||
</div>
|
||||
@@ -178,27 +154,25 @@ fn NestedResourceInside() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<Suspense fallback=|| {
|
||||
"Loading 1..."
|
||||
}>
|
||||
{Suspend::new(async move {
|
||||
<Suspense fallback=|| "Loading 1...">
|
||||
{Suspend(async move {
|
||||
_ = one_second.await;
|
||||
let two_second = Resource::new(
|
||||
|| (),
|
||||
move |_| async move { second_wait_fn(WAIT_TWO_SECONDS).await },
|
||||
);
|
||||
let two_second = Resource::new(|| (), move |_| async move {
|
||||
second_wait_fn(WAIT_TWO_SECONDS).await
|
||||
});
|
||||
view! {
|
||||
<p id="loaded-1">"One Second: Loaded 1!"</p>
|
||||
<Suspense fallback=|| "Loading 2...">
|
||||
<span id="loaded-2">
|
||||
"Loaded 2 (created inside first suspense)!: "
|
||||
{Suspend::new(async move { format!("{:?}", two_second.await) })}
|
||||
{Suspend(async move { format!("{:?}", two_second.await)})}
|
||||
</span>
|
||||
<button on:click=move |_| set_count.update(|n| *n += 1)>{count}</button>
|
||||
<button on:click=move |_| set_count.update(|n| *n += 1)>
|
||||
{count}
|
||||
</button>
|
||||
</Suspense>
|
||||
}
|
||||
})}
|
||||
|
||||
</Suspense>
|
||||
</div>
|
||||
}
|
||||
@@ -212,42 +186,25 @@ fn Parallel() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<Suspense fallback=|| {
|
||||
"Loading 1..."
|
||||
}>
|
||||
<Suspense fallback=|| "Loading 1...">
|
||||
{move || {
|
||||
one_second
|
||||
.get()
|
||||
.map(move |_| {
|
||||
view! {
|
||||
<p id="loaded-1">"One Second: Loaded 1!"</p>
|
||||
<button on:click=move |_| {
|
||||
set_count.update(|n| *n += 1)
|
||||
}>{count}</button>
|
||||
}
|
||||
})
|
||||
one_second.get().map(move |_| view! {
|
||||
<p id="loaded-1">"One Second: Loaded 1!"</p>
|
||||
<button on:click=move |_| set_count.update(|n| *n += 1)>
|
||||
{count}
|
||||
</button>
|
||||
})
|
||||
}}
|
||||
|
||||
</Suspense>
|
||||
<Suspense fallback=|| {
|
||||
"Loading 2..."
|
||||
}>
|
||||
<Suspense fallback=|| "Loading 2...">
|
||||
{move || {
|
||||
two_second
|
||||
.get()
|
||||
.map(move |_| {
|
||||
view! {
|
||||
<p id="loaded-2">"Two Second: Loaded 2!"</p>
|
||||
<button
|
||||
id="second-count"
|
||||
on:click=move |_| set_count.update(|n| *n += 1)
|
||||
>
|
||||
{count}
|
||||
</button>
|
||||
}
|
||||
})
|
||||
two_second.get().map(move |_| view! {
|
||||
<p id="loaded-2">"Two Second: Loaded 2!"</p>
|
||||
<button id="second-count" on:click=move |_| set_count.update(|n| *n += 1)>
|
||||
{count}
|
||||
</button>
|
||||
})
|
||||
}}
|
||||
|
||||
</Suspense>
|
||||
</div>
|
||||
}
|
||||
@@ -260,17 +217,18 @@ fn Single() -> impl IntoView {
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<Suspense fallback=|| {
|
||||
"Loading 1..."
|
||||
}>
|
||||
{move || {
|
||||
one_second.get().map(|_| view! { <p id="loaded-1">"One Second: Loaded 1!"</p> })
|
||||
}}
|
||||
|
||||
<Suspense fallback=|| "Loading 1...">
|
||||
{move || {
|
||||
one_second.get().map(|_| view! {
|
||||
<p id="loaded-1">"One Second: Loaded 1!"</p>
|
||||
})
|
||||
}}
|
||||
</Suspense>
|
||||
<p id="following-message">"Children following Suspense should hydrate properly."</p>
|
||||
<div>
|
||||
<button on:click=move |_| set_count.update(|n| *n += 1)>{count}</button>
|
||||
<button on:click=move |_| set_count.update(|n| *n += 1)>
|
||||
{count}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -286,7 +244,9 @@ fn InsideComponent() -> impl IntoView {
|
||||
<InsideComponentChild/>
|
||||
<p id="following-message">"Children following Suspense should hydrate properly."</p>
|
||||
<div>
|
||||
<button on:click=move |_| set_count.update(|n| *n += 1)>{count}</button>
|
||||
<button on:click=move |_| set_count.update(|n| *n += 1)>
|
||||
{count}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -296,47 +256,16 @@ fn InsideComponent() -> impl IntoView {
|
||||
fn InsideComponentChild() -> impl IntoView {
|
||||
let one_second = Resource::new(|| WAIT_ONE_SECOND, first_wait_fn);
|
||||
view! {
|
||||
<Suspense fallback=|| {
|
||||
"Loading 1..."
|
||||
}>
|
||||
{move || {
|
||||
one_second.get().map(|_| view! { <p id="loaded-1">"One Second: Loaded 1!"</p> })
|
||||
}}
|
||||
|
||||
<Suspense fallback=|| "Loading 1...">
|
||||
{move || {
|
||||
one_second.get().map(|_| view! {
|
||||
<p id="loaded-1">"One Second: Loaded 1!"</p>
|
||||
})
|
||||
}}
|
||||
</Suspense>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn LocalResource() -> impl IntoView {
|
||||
let one_second = Resource::new(|| WAIT_ONE_SECOND, first_wait_fn);
|
||||
let local = LocalResource::new(|| first_wait_fn(WAIT_ONE_SECOND));
|
||||
let (count, set_count) = signal(0);
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<Suspense fallback=|| {
|
||||
"Loading 1..."
|
||||
}>
|
||||
{move || {
|
||||
one_second.get().map(|_| view! { <p id="loaded-1">"One Second: Loaded 1!"</p> })
|
||||
}}
|
||||
{move || {
|
||||
Suspend::new(async move {
|
||||
let value = local.await;
|
||||
view! { <p id="loaded-2">"One Second: Local Loaded " {value} "!"</p> }
|
||||
})
|
||||
}}
|
||||
|
||||
</Suspense>
|
||||
<p id="following-message">"Children following Suspense should hydrate properly."</p>
|
||||
<div>
|
||||
<button on:click=move |_| set_count.update(|n| *n += 1)>{count}</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn None() -> impl IntoView {
|
||||
let (count, set_count) = signal(0);
|
||||
@@ -345,7 +274,9 @@ fn None() -> impl IntoView {
|
||||
<div>
|
||||
<Suspense fallback=|| "Loading 1...">
|
||||
<p id="inside-message">"Children inside Suspense should hydrate properly."</p>
|
||||
<button on:click=move |_| set_count.update(|n| *n += 1)>{count}</button>
|
||||
<button on:click=move |_| set_count.update(|n| *n += 1)>
|
||||
{count}
|
||||
</button>
|
||||
</Suspense>
|
||||
<p id="following-message">"Children following Suspense should hydrate properly."</p>
|
||||
<div>
|
||||
|
||||
@@ -13,15 +13,19 @@ leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
|
||||
# dependencies for browser (enable when hydrate set)
|
||||
console_error_panic_hook = { version = "0.1.7", optional = true }
|
||||
wasm-bindgen = { version = "0.2.93", optional = true }
|
||||
console_error_panic_hook = { version = "0.1", optional = true }
|
||||
wasm-bindgen = { version = "0.2", optional = true }
|
||||
|
||||
# dependencies for server (enable when ssr set)
|
||||
actix-files = { version = "0.6.6", optional = true }
|
||||
actix-web = { version = "4.8", features = ["macros"], optional = true }
|
||||
actix-files = { version = "0.6", optional = true }
|
||||
actix-web = { version = "4", features = ["macros"], optional = true }
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate", "dep:wasm-bindgen", "dep:console_error_panic_hook"]
|
||||
hydrate = [
|
||||
"leptos/hydrate",
|
||||
"dep:wasm-bindgen",
|
||||
"dep:console_error_panic_hook",
|
||||
]
|
||||
ssr = [
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
|
||||
@@ -7,35 +7,32 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
axum = { version = "0.7", optional = true }
|
||||
console_error_panic_hook = "0.1"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
tokio = { version = "1.39", features = [
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
], optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
wasm-bindgen = "0.2.93"
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"], optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
wasm-bindgen = "0.2"
|
||||
thiserror = "1.0"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
http = "1.1"
|
||||
tracing = { version = "0.1", optional = true }
|
||||
http = "1.0"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tokio",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:leptos_axum",
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:tracing",
|
||||
"dep:axum",
|
||||
"dep:tokio",
|
||||
"dep:tower",
|
||||
"dep:tower-http",
|
||||
"dep:leptos_axum",
|
||||
"leptos/ssr",
|
||||
"leptos_meta/ssr",
|
||||
"leptos_router/ssr",
|
||||
"dep:tracing",
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
|
||||
@@ -7,5 +7,5 @@ edition = "2021"
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
gloo-net = { version = "0.5", features = ["http"] }
|
||||
console_error_panic_hook = { version = "0.1" }
|
||||
|
||||
@@ -8,15 +8,15 @@ codegen-units = 1
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../../leptos", features = ["csr"] }
|
||||
console_log = "1.0"
|
||||
log = "0.4.22"
|
||||
leptos = { path = "../../leptos" }
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
wasm-bindgen = "0.2.93"
|
||||
wasm-bindgen = "0.2"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.70"
|
||||
version = "0.3"
|
||||
features = ["Window"]
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.42"
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
|
||||
@@ -7,36 +7,36 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
actix-files = { version = "0.6.6", optional = true }
|
||||
actix-web = { version = "4.8", optional = true, features = ["macros"] }
|
||||
anyhow = "1.0"
|
||||
broadcaster = "1.0"
|
||||
console_log = "1.0"
|
||||
actix-files = { version = "0.6.2", optional = true }
|
||||
actix-web = { version = "4.2.1", optional = true, features = ["macros"] }
|
||||
anyhow = "1.0.68"
|
||||
broadcaster = "1.0.0"
|
||||
console_log = "1.0.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
futures = "0.3.30"
|
||||
leptos = { path = "../../leptos" }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
futures = "0.3.25"
|
||||
leptos = { path = "../../leptos"}
|
||||
leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
log = "0.4.22"
|
||||
simple_logger = "5.0"
|
||||
log = "0.4.17"
|
||||
simple_logger = "4.0.0"
|
||||
gloo = { git = "https://github.com/rustwasm/gloo" }
|
||||
sqlx = { version = "0.8.0", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
sqlx = { version = "0.6.2", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
], optional = true }
|
||||
wasm-bindgen = "0.2.93"
|
||||
tokio = { version = "1.39", features = ["rt", "time"], optional = true }
|
||||
wasm-bindgen = "0.2"
|
||||
tokio = { version = "1", features = ["rt", "time"], optional = true }
|
||||
server_fn = { path = "../../server_fn", features = ["cbor"] }
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = [
|
||||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
"dep:sqlx",
|
||||
"leptos/ssr",
|
||||
"leptos_actix",
|
||||
"dep:tokio",
|
||||
"dep:actix-files",
|
||||
"dep:actix-web",
|
||||
"dep:sqlx",
|
||||
"leptos/ssr",
|
||||
"leptos_actix",
|
||||
"dep:tokio",
|
||||
]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
|
||||
@@ -4,14 +4,14 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0"
|
||||
async-trait = "0.1.81"
|
||||
cucumber = "0.21.1"
|
||||
fantoccini = "0.21.1"
|
||||
pretty_assertions = "1.4"
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1.39", features = ["macros", "rt-multi-thread", "time"] }
|
||||
url = "2.5"
|
||||
anyhow = "1.0.72"
|
||||
async-trait = "0.1.72"
|
||||
cucumber = "0.19.1"
|
||||
fantoccini = "0.19.3"
|
||||
pretty_assertions = "1.4.0"
|
||||
serde_json = "1.0.104"
|
||||
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread", "time"] }
|
||||
url = "2.4.0"
|
||||
|
||||
[[test]]
|
||||
name = "app_suite"
|
||||
|
||||
@@ -113,7 +113,7 @@ pub fn Todos() -> impl IntoView {
|
||||
);
|
||||
|
||||
let existing_todos = move || {
|
||||
Suspend::new(async move {
|
||||
Suspend(async move {
|
||||
todos
|
||||
.await
|
||||
.map(|todos| {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user