Compare commits
67 Commits
outlet-rer
...
router-fea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e83acfe63 | ||
|
|
46254a18f3 | ||
|
|
7d7a96d9bc | ||
|
|
ecc24fa65d | ||
|
|
1925c5bbe5 | ||
|
|
1613616008 | ||
|
|
8a01880ade | ||
|
|
180ab87ff9 | ||
|
|
e324fb6e76 | ||
|
|
0547b4f846 | ||
|
|
75659ce674 | ||
|
|
190cb162ad | ||
|
|
1f556cefb0 | ||
|
|
6a68ef67f3 | ||
|
|
23bbd90c81 | ||
|
|
27b8553076 | ||
|
|
5bfeb93e3d | ||
|
|
dd9ae1b7b1 | ||
|
|
ad34a5d9c6 | ||
|
|
ace5e7cbba | ||
|
|
c8f0988e53 | ||
|
|
64f0f8879b | ||
|
|
b8cafeb650 | ||
|
|
992b218ffe | ||
|
|
5df89b0d25 | ||
|
|
c050456a47 | ||
|
|
8a8d7cbe1b | ||
|
|
f5f345e623 | ||
|
|
4df3687463 | ||
|
|
f6622448e9 | ||
|
|
78825401c5 | ||
|
|
f2b7ad6244 | ||
|
|
43f107d9bd | ||
|
|
a2612ca1fc | ||
|
|
ee647cba1c | ||
|
|
1377b823e2 | ||
|
|
4d21f5ac63 | ||
|
|
1ec603ee58 | ||
|
|
c56806713e | ||
|
|
2f6aa6753d | ||
|
|
2544687acd | ||
|
|
3d25e86c23 | ||
|
|
28ec3a6cda | ||
|
|
8b92a561a3 | ||
|
|
e490c0423f | ||
|
|
b6579a040a | ||
|
|
01e024b726 | ||
|
|
6603c44ce2 | ||
|
|
977f11b180 | ||
|
|
fb34b29ccf | ||
|
|
6576d8eda1 | ||
|
|
6b729f9131 | ||
|
|
dc60c35b58 | ||
|
|
32ec9cc57e | ||
|
|
35601d8284 | ||
|
|
49bc7d2a27 | ||
|
|
dad84b5867 | ||
|
|
1b8175e2fa | ||
|
|
c41cf879d1 | ||
|
|
b34f2070d3 | ||
|
|
7fa21defa6 | ||
|
|
bdd9abc04d | ||
|
|
1d25134213 | ||
|
|
de73622949 | ||
|
|
5d3cfc6483 | ||
|
|
677e4f2540 | ||
|
|
63b1837315 |
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: gbj
|
||||
4
.github/workflows/test.yml
vendored
@@ -28,6 +28,7 @@ jobs:
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt
|
||||
|
||||
- name: Setup cargo-make
|
||||
uses: davidB/rust-cargo-make@v1
|
||||
@@ -35,6 +36,9 @@ jobs:
|
||||
- name: Cargo generate-lockfile
|
||||
run: cargo generate-lockfile
|
||||
|
||||
- name: Run Rustfmt
|
||||
run: cargo fmt -- --check
|
||||
|
||||
- name: Cargo cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
**NOTE: We're in the middle of merging changes and making fixes to support our upcoming `0.1.0` release. Some of the examples may be in a broken state. You can continue using the `0.0` releases with no issues.**
|
||||
|
||||
<img src="https://raw.githubusercontent.com/gbj/leptos/main/docs/logos/logo.svg" alt="Leptos Logo" style="width: 100%; height: auto; display: block; margin: auto;">
|
||||
<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>
|
||||
|
||||
[](https://crates.io/crates/leptos)
|
||||
[](https://docs.rs/leptos)
|
||||
@@ -63,7 +66,6 @@ Here are some resources for learning more about Leptos:
|
||||
- [Common Bugs](https://github.com/gbj/leptos/tree/main/docs/COMMON_BUGS.md) (and how to fix them!)
|
||||
- Leptos Guide (in progress)
|
||||
|
||||
|
||||
## `nightly` Note
|
||||
|
||||
Most of the examples assume you’re using `nightly` Rust.
|
||||
|
||||
BIN
docs/logos/Leptos_logo_RGB.png
Executable file
|
After Width: | Height: | Size: 72 KiB |
64
docs/logos/Leptos_logo_RGB.svg
Executable file
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 437.4294 209.6185" style="enable-background:new 0 0 437.4294 209.6185;" xml:space="preserve">
|
||||
<path style="fill:none;" d="M130.0327,79.3931c-11.4854-0.23-22.52,9.3486-24.5034,21.0117l49.1157,0.0293
|
||||
c-2.1729-10.418-11.1821-21.0449-24.1987-21.0449C130.3081,79.3892,130.1714,79.3907,130.0327,79.3931z"/>
|
||||
<path style="fill:#181139;" d="M95.1109,128.1089H58.6797V65.6861c0-1.5234-0.8169-2.4331-2.1855-2.4331h-3.1187
|
||||
c-1.3159,0-2.2349,1.0005-2.2349,2.4331v67.4297c0,1.4521,0.8145,2.2852,2.2349,2.2852h41.7353c1.4844,0,2.4819-0.9375,2.4819-2.333
|
||||
v-2.7744C97.5928,128.9253,96.6651,128.1089,95.1109,128.1089z"/>
|
||||
<path style="fill:#181139;" d="M146.4561,77.1739c-4.8252-3.001-10.3037-4.5249-16.2837-4.5288c-0.0068,0-0.0137,0-0.0205,0
|
||||
c-5.7349,0-11.1377,1.4639-16.0566,4.3511c-4.916,2.8853-8.8721,6.8364-11.7593,11.7456
|
||||
c-2.8975,4.9248-4.3687,10.332-4.3721,16.0713c-0.0034,5.7188,1.4966,11.0654,4.4565,15.8887
|
||||
c2.9893,4.9209,6.8789,8.7334,11.8887,11.6514c4.8657,2.8633,10.2397,4.3174,15.9717,4.3203c0.0073,0,0.0146,0,0.022,0
|
||||
c8.123,0,14.7441-2.5869,21.4683-8.3906c0.5493-0.4805,0.8516-1.1201,0.8516-1.8008c0.001-0.6074-0.1743-1.1035-0.5205-1.4756
|
||||
l-1.3569-1.8428l-0.0732-0.0859c-0.2637-0.2637-0.6929-0.6152-1.3716-0.6152c-0.6421,0-1.2549,0.2217-1.7124,0.6143
|
||||
c-1.9346,1.585-3.5459,2.8008-4.7969,3.6182c-1.7979,1.208-5.8218,3.2314-12.5986,3.2314c-0.0073,0-0.0142,0-0.021,0
|
||||
c-0.1357,0.0029-0.269,0.0039-0.4043,0.0039c-12.2642,0-23.4736-10.3262-24.5088-22.4814l53.0127,0.0322c0.0015,0,0.0024,0,0.0034,0
|
||||
c2.2373,0,3.4697-1.1621,3.4712-3.2715c0.0034-5.2588-1.3574-10.3945-4.0464-15.2705
|
||||
C155.0015,84.0953,151.2188,80.1363,146.4561,77.1739z M154.6451,100.4341l-49.1157-0.0293
|
||||
c1.9834-11.6631,13.0181-21.2417,24.5034-21.0117c0.1387-0.0024,0.2754-0.0039,0.4136-0.0039
|
||||
C143.4629,79.3892,152.4722,90.0162,154.6451,100.4341z"/>
|
||||
<path style="fill:#181139;" d="M204.0386,136.6382c5.7319,0,11.1069-1.4502,15.9746-4.3115
|
||||
c4.938-2.9014,8.75-6.7129,11.6533-11.6533c2.8608-4.8672,4.311-10.2578,4.311-16.0244c0-5.7324-1.4502-11.1064-4.311-15.9746
|
||||
c-2.9019-4.9385-6.7134-8.75-11.6533-11.6533c-4.8687-2.8618-10.2437-4.3125-15.9746-4.3125
|
||||
c-9.938,0-19.2021,4.7583-24.3516,12.3174v-9.438c0-0.5946-0.1465-1.0788-0.411-1.4511c-0.3815-0.5369-1.0157-0.834-1.8727-0.834
|
||||
h-2.6738c-1.4521,0-2.2852,0.833-2.2852,2.2852v5.6964v46.4791v23.9676c0,1.2568,0.7808,2.0371,2.0371,2.0371h3.3667
|
||||
c0.9209,0,1.6421-0.6992,1.6421-1.5908v-17.098v-10.984C185.0884,131.8892,194.2749,136.6382,204.0386,136.6382z M186.6358,122.5591
|
||||
c-4.9346-4.9346-7.6831-11.4932-7.542-18.0254c-0.1367-6.3506,2.5439-12.751,7.3545-17.5605
|
||||
c4.8521-4.8521,11.3037-7.5547,17.7383-7.417c4.3691,0,8.4863,1.1465,12.2314,3.4043c3.7344,2.2979,6.7456,5.4053,8.9492,9.2354
|
||||
c2.1699,3.9072,3.2695,8.0967,3.2695,12.4697c0.1396,6.4619-2.5967,12.9844-7.5083,17.8955
|
||||
c-4.7617,4.7617-11.0469,7.3857-17.2544,7.2803C197.6856,129.9712,191.396,127.3208,186.6358,122.5591z"/>
|
||||
<path style="fill:#181139;" d="M241.8955,80.3975h7.5669v42.0259c0,6.8174,4.5674,12.1309,11.0825,12.9189
|
||||
c0.6836,0.1055,1.8379,0.1572,3.5303,0.1572c2.0078,0,3.0273-0.3535,3.0273-2.2842v-2.377c0-1.7891-1.334-2.0371-2.7568-2.0371
|
||||
c0,0-0.001,0-0.002,0l-1.7871-0.0488c-2.0117-0.0439-3.4883-0.7627-4.3896-2.1367c-0.9697-1.4805-1.4619-3.1738-1.4619-5.0352
|
||||
V80.3975h10.0928c1.3076,0,2.2852-1.3628,2.2852-2.5815v-1.9312c0-1.3999-0.8359-2.2354-2.2354-2.2354h-10.1426V60.6861
|
||||
c0-1.4619-0.7969-2.4829-1.9375-2.4829c-0.1865,0-0.4121,0-0.6392,0.0884l-2.6489,0.6865
|
||||
c-1.2109,0.3682-2.0171,0.9263-2.0171,2.4507v12.2207h-7.5669c-1.4185,0-2.335,0.897-2.335,2.2852v1.8813
|
||||
C239.5606,79.2393,240.6079,80.3975,241.8955,80.3975z"/>
|
||||
<path style="fill:#181139;" d="M379.1182,106.2691c-4.0488-2.9219-8.8545-5.0293-14.291-6.2646
|
||||
c-6.5049-1.3975-13.4473-5.2129-13.3203-10.3066c0-7.5225,6.6367-10.1914,12.3203-10.1914c5.3574,0,10.2207,3.002,13.001,8.0146
|
||||
c0.6729,1.2861,1.4785,1.9375,2.3955,1.9375c0.3311,0,0.7061-0.1113,0.9922-0.2832l2.2021-1.1523
|
||||
c0.5947-0.3408,0.9229-0.9414,0.9229-1.6924c0-0.5205-0.0908-0.9541-0.2617-1.292c-3.6367-8.2466-10.0967-12.4282-19.2021-12.4282
|
||||
c-11.7305,0-19.6123,6.9263-19.6123,17.2349c0,4.3125,1.8438,7.9746,5.4756,10.8809c3.4482,2.7979,7.9121,4.8623,13.2705,6.1377
|
||||
c4.5859,1.085,8.3193,2.5654,11.0977,4.4023c1.4159,0.9354,2.4412,2.0535,3.106,3.3672c0.6053,1.1962,0.9135,2.5535,0.9135,4.1005
|
||||
c0.0742,2.3857-0.79,4.5176-2.5684,6.3389c-3.1445,3.2178-8.4053,4.6689-12.0205,4.6689c-0.0361,0-0.0723,0-0.1074,0
|
||||
c-3.4268,0-6.4893-0.8438-9.1035-2.5068c-2.5918-1.6484-4.2363-3.8076-5.0293-6.6064c-0.3203-1.0996-0.751-2.1738-2.1553-2.1738
|
||||
c-0.0742,0-0.2109,0.0146-0.4062,0.0449c-0.1133,0.0166-0.2559,0.0381-0.5088,0.0742l-1.8818,0.4463l-0.1045,0.0332
|
||||
c-1.0244,0.4082-1.6113,1.1846-1.6113,2.1309c0,0.2285,0.0625,0.6592,0.2178,1.1094c1.9707,8.5801,10.2432,14.3447,20.5732,14.3447
|
||||
c0.125,0.002,0.249,0.002,0.374,0.002c6.5947,0,12.6748-2.3193,16.7275-6.3945c3.1895-3.208,4.8311-7.2363,4.748-11.6357
|
||||
c0-2.8187-0.6185-5.3109-1.8062-7.481C382.4437,109.2624,381.0062,107.631,379.1182,106.2691z"/>
|
||||
<path style="fill:#EF3939;" d="M348.9043,45.7325c0-6.3157-3.2826-11.8699-8.2238-15.0756
|
||||
c-2.811-1.8237-6.1537-2.8947-9.7469-2.8947c-9.9092,0-17.9707,8.0615-17.9707,17.9702c0,4.7659,1.8775,9.0925,4.9157,12.3123
|
||||
c-3.6619,4.3709-6.6334,9.3336-8.7663,14.7186c-1.5873-0.2422-3.2123-0.3683-4.8662-0.3683
|
||||
c-17.7158,0-32.1289,14.4131-32.1289,32.1289c0,14.6854,9.9077,27.0922,23.3869,30.9101
|
||||
c-6.7762,17.3461-23.6572,29.6719-43.3742,29.6719c-16.8195,0-31.583-8.9662-39.7656-22.369
|
||||
c-2.4778,0.5446-5.0429,0.8519-7.6721,0.9023c9.0226,16.99,26.8969,28.5917,47.4377,28.5917
|
||||
c23.2646,0,43.1121-14.8788,50.5461-35.6179c0.5204,0.0251,1.0435,0.0398,1.5701,0.0398c17.7158,0,32.1289-14.4131,32.1289-32.1289
|
||||
c0-13.557-8.4446-25.1712-20.3465-29.8811c1.9001-4.5678,4.5115-8.7646,7.6888-12.4641c0.9996,0.4404,2.0479,0.785,3.1324,1.0384
|
||||
c1.3144,0.3071,2.6773,0.486,4.0839,0.486C340.8428,63.7032,348.9043,55.6416,348.9043,45.7325z M304.2461,129.5279
|
||||
c-13.7871,0-25.0039-11.2168-25.0039-25.0039s11.2168-25.0039,25.0039-25.0039S329.25,90.7369,329.25,104.524
|
||||
S318.0332,129.5279,304.2461,129.5279z M330.9336,34.8872c0.645,0,1.2737,0.0671,1.8881,0.1755
|
||||
c5.0818,0.8974,8.9576,5.3347,8.9576,10.6697c0,5.9805-4.8652,10.8457-10.8457,10.8457s-10.8457-4.8652-10.8457-10.8457
|
||||
c0-1.3967,0.2746-2.7282,0.7576-3.9555C322.4306,37.7496,326.35,34.8872,330.9336,34.8872z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.5 KiB |
61
docs/logos/Leptos_logo_Solid_Black.svg
Executable file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 437.4294 209.6185" style="enable-background:new 0 0 437.4294 209.6185;" xml:space="preserve">
|
||||
<path d="M95.1109,128.1089H58.6797V65.6861c0-1.5234-0.8169-2.4331-2.1855-2.4331h-3.1187c-1.3159,0-2.2349,1.0005-2.2349,2.4331
|
||||
v67.4297c0,1.4521,0.8145,2.2852,2.2349,2.2852h41.7353c1.4844,0,2.4819-0.9375,2.4819-2.333v-2.7744
|
||||
C97.5928,128.9253,96.6651,128.1089,95.1109,128.1089z"/>
|
||||
<path d="M146.4561,77.1739c-4.8252-3.001-10.3037-4.5249-16.2837-4.5288c-0.0068,0-0.0137,0-0.0205,0
|
||||
c-5.7349,0-11.1377,1.4639-16.0566,4.3511c-4.916,2.8853-8.8721,6.8364-11.7593,11.7456
|
||||
c-2.8975,4.9248-4.3687,10.332-4.3721,16.0713c-0.0034,5.7188,1.4966,11.0654,4.4565,15.8887
|
||||
c2.9893,4.9209,6.8789,8.7334,11.8887,11.6514c4.8657,2.8633,10.2397,4.3174,15.9717,4.3203c0.0073,0,0.0146,0,0.022,0
|
||||
c8.123,0,14.7441-2.5869,21.4683-8.3906c0.5493-0.4805,0.8516-1.1201,0.8516-1.8008c0.001-0.6074-0.1743-1.1035-0.5205-1.4756
|
||||
l-1.3569-1.8428l-0.0732-0.0859c-0.2637-0.2637-0.6929-0.6152-1.3716-0.6152c-0.6421,0-1.2549,0.2217-1.7124,0.6143
|
||||
c-1.9346,1.585-3.5459,2.8008-4.7969,3.6182c-1.7979,1.208-5.8218,3.2314-12.5986,3.2314c-0.0073,0-0.0142,0-0.021,0
|
||||
c-0.1357,0.0029-0.269,0.0039-0.4043,0.0039c-12.2642,0-23.4736-10.3262-24.5088-22.4814l53.0127,0.0322c0.0015,0,0.0024,0,0.0034,0
|
||||
c2.2373,0,3.4697-1.1621,3.4712-3.2715c0.0034-5.2588-1.3574-10.3945-4.0464-15.2705
|
||||
C155.0015,84.0953,151.2188,80.1363,146.4561,77.1739z M154.6451,100.4341l-49.1157-0.0293
|
||||
c1.9834-11.6631,13.0181-21.2417,24.5034-21.0117c0.1387-0.0024,0.2754-0.0039,0.4136-0.0039
|
||||
C143.4629,79.3892,152.4722,90.0162,154.6451,100.4341z"/>
|
||||
<path d="M204.0386,136.6382c5.7319,0,11.1069-1.4502,15.9746-4.3115c4.938-2.9014,8.75-6.7129,11.6533-11.6533
|
||||
c2.8608-4.8672,4.311-10.2578,4.311-16.0244c0-5.7324-1.4502-11.1064-4.311-15.9746c-2.9019-4.9385-6.7134-8.75-11.6533-11.6533
|
||||
c-4.8687-2.8618-10.2437-4.3125-15.9746-4.3125c-9.938,0-19.2021,4.7583-24.3516,12.3174v-9.438
|
||||
c0-0.5946-0.1465-1.0788-0.411-1.4511c-0.3815-0.5369-1.0157-0.834-1.8727-0.834h-2.6738c-1.4521,0-2.2852,0.833-2.2852,2.2852
|
||||
v5.6964v46.4791v23.9676c0,1.2568,0.7808,2.0371,2.0371,2.0371h3.3667c0.9209,0,1.6421-0.6992,1.6421-1.5908v-17.098v-10.984
|
||||
C185.0884,131.8892,194.2749,136.6382,204.0386,136.6382z M186.6358,122.5591c-4.9346-4.9346-7.6831-11.4932-7.542-18.0254
|
||||
c-0.1367-6.3506,2.5439-12.751,7.3545-17.5605c4.8521-4.8521,11.3037-7.5547,17.7383-7.417c4.3691,0,8.4863,1.1465,12.2314,3.4043
|
||||
c3.7344,2.2979,6.7456,5.4053,8.9492,9.2354c2.1699,3.9072,3.2695,8.0967,3.2695,12.4697
|
||||
c0.1396,6.4619-2.5967,12.9844-7.5083,17.8955c-4.7617,4.7617-11.0469,7.3857-17.2544,7.2803
|
||||
C197.6856,129.9712,191.396,127.3208,186.6358,122.5591z"/>
|
||||
<path d="M241.8955,80.3975h7.5669v42.0259c0,6.8174,4.5674,12.1309,11.0825,12.9189c0.6836,0.1055,1.8379,0.1572,3.5303,0.1572
|
||||
c2.0078,0,3.0273-0.3535,3.0273-2.2842v-2.377c0-1.7891-1.334-2.0371-2.7568-2.0371c0,0-0.001,0-0.002,0l-1.7871-0.0488
|
||||
c-2.0117-0.0439-3.4883-0.7627-4.3896-2.1367c-0.9697-1.4805-1.4619-3.1738-1.4619-5.0352V80.3975h10.0928
|
||||
c1.3076,0,2.2852-1.3628,2.2852-2.5815v-1.9312c0-1.3999-0.8359-2.2354-2.2354-2.2354h-10.1426V60.6861
|
||||
c0-1.4619-0.7969-2.4829-1.9375-2.4829c-0.1865,0-0.4121,0-0.6392,0.0884l-2.6489,0.6865
|
||||
c-1.2109,0.3682-2.0171,0.9263-2.0171,2.4507v12.2207h-7.5669c-1.4185,0-2.335,0.897-2.335,2.2852v1.8813
|
||||
C239.5606,79.2393,240.6079,80.3975,241.8955,80.3975z"/>
|
||||
<path d="M379.1182,106.2691c-4.0488-2.9219-8.8545-5.0293-14.291-6.2646c-6.5049-1.3975-13.4473-5.2129-13.3203-10.3066
|
||||
c0-7.5225,6.6367-10.1914,12.3203-10.1914c5.3574,0,10.2207,3.002,13.001,8.0146c0.6729,1.2861,1.4785,1.9375,2.3955,1.9375
|
||||
c0.3311,0,0.7061-0.1113,0.9922-0.2832l2.2021-1.1523c0.5947-0.3408,0.9229-0.9414,0.9229-1.6924
|
||||
c0-0.5205-0.0908-0.9541-0.2617-1.292c-3.6367-8.2466-10.0967-12.4282-19.2021-12.4282c-11.7305,0-19.6123,6.9263-19.6123,17.2349
|
||||
c0,4.3125,1.8438,7.9746,5.4756,10.8809c3.4482,2.7979,7.9121,4.8623,13.2705,6.1377c4.5859,1.085,8.3193,2.5654,11.0977,4.4023
|
||||
c1.4159,0.9354,2.4412,2.0535,3.106,3.3672c0.6053,1.1962,0.9135,2.5535,0.9135,4.1005c0.0742,2.3857-0.79,4.5176-2.5684,6.3389
|
||||
c-3.1445,3.2178-8.4053,4.6689-12.0205,4.6689c-0.0361,0-0.0723,0-0.1074,0c-3.4268,0-6.4893-0.8438-9.1035-2.5068
|
||||
c-2.5918-1.6484-4.2363-3.8076-5.0293-6.6064c-0.3203-1.0996-0.751-2.1738-2.1553-2.1738c-0.0742,0-0.2109,0.0146-0.4062,0.0449
|
||||
c-0.1133,0.0166-0.2559,0.0381-0.5088,0.0742l-1.8818,0.4463l-0.1045,0.0332c-1.0244,0.4082-1.6113,1.1846-1.6113,2.1309
|
||||
c0,0.2285,0.0625,0.6592,0.2178,1.1094c1.9707,8.5801,10.2432,14.3447,20.5732,14.3447c0.125,0.002,0.249,0.002,0.374,0.002
|
||||
c6.5947,0,12.6748-2.3193,16.7275-6.3945c3.1895-3.208,4.8311-7.2363,4.748-11.6357c0-2.8187-0.6185-5.3109-1.8062-7.481
|
||||
C382.4437,109.2624,381.0062,107.631,379.1182,106.2691z"/>
|
||||
<path d="M348.9043,45.7325c0-6.3157-3.2826-11.8699-8.2238-15.0756c-2.811-1.8237-6.1537-2.8947-9.7469-2.8947
|
||||
c-9.9092,0-17.9707,8.0615-17.9707,17.9702c0,4.7659,1.8775,9.0925,4.9157,12.3123c-3.6619,4.3709-6.6334,9.3336-8.7663,14.7186
|
||||
c-1.5873-0.2422-3.2123-0.3683-4.8662-0.3683c-17.7158,0-32.1289,14.4131-32.1289,32.1289c0,14.6854,9.9077,27.0922,23.3869,30.9101
|
||||
c-6.7762,17.3461-23.6572,29.6719-43.3742,29.6719c-16.8195,0-31.583-8.9662-39.7656-22.369
|
||||
c-2.4778,0.5446-5.0429,0.8519-7.6721,0.9023c9.0226,16.99,26.8969,28.5917,47.4377,28.5917
|
||||
c23.2646,0,43.1121-14.8788,50.5461-35.6179c0.5204,0.0251,1.0435,0.0398,1.5701,0.0398c17.7158,0,32.1289-14.4131,32.1289-32.1289
|
||||
c0-13.557-8.4446-25.1712-20.3465-29.8811c1.9001-4.5678,4.5115-8.7646,7.6888-12.4641c0.9996,0.4404,2.0479,0.785,3.1324,1.0384
|
||||
c1.3144,0.3071,2.6773,0.486,4.0839,0.486C340.8428,63.7032,348.9043,55.6416,348.9043,45.7325z M304.2461,129.5279
|
||||
c-13.7871,0-25.0039-11.2168-25.0039-25.0039s11.2168-25.0039,25.0039-25.0039S329.25,90.7369,329.25,104.524
|
||||
S318.0332,129.5279,304.2461,129.5279z M330.9336,34.8872c0.645,0,1.2737,0.0671,1.8881,0.1755
|
||||
c5.0818,0.8974,8.9576,5.3347,8.9576,10.6697c0,5.9805-4.8652,10.8457-10.8457,10.8457s-10.8457-4.8652-10.8457-10.8457
|
||||
c0-1.3967,0.2746-2.7282,0.7576-3.9555C322.4306,37.7496,326.35,34.8872,330.9336,34.8872z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
62
docs/logos/Leptos_logo_Solid_White.svg
Executable file
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 437.4294 209.6185" style="enable-background:new 0 0 437.4294 209.6185;" xml:space="preserve">
|
||||
<path style="fill:#FFFFFF;" d="M95.1109,128.1089H58.6797V65.6861c0-1.5234-0.8169-2.4331-2.1855-2.4331h-3.1187
|
||||
c-1.3159,0-2.2349,1.0005-2.2349,2.4331v67.4297c0,1.4521,0.8145,2.2852,2.2349,2.2852h41.7353c1.4844,0,2.4819-0.9375,2.4819-2.333
|
||||
v-2.7744C97.5928,128.9253,96.6651,128.1089,95.1109,128.1089z"/>
|
||||
<path style="fill:#FFFFFF;" d="M146.4561,77.1739c-4.8252-3.001-10.3037-4.5249-16.2837-4.5288c-0.0068,0-0.0137,0-0.0205,0
|
||||
c-5.7349,0-11.1377,1.4639-16.0566,4.3511c-4.916,2.8853-8.8721,6.8364-11.7593,11.7456
|
||||
c-2.8975,4.9248-4.3687,10.332-4.3721,16.0713c-0.0034,5.7188,1.4966,11.0654,4.4565,15.8887
|
||||
c2.9893,4.9209,6.8789,8.7334,11.8887,11.6514c4.8657,2.8633,10.2397,4.3174,15.9717,4.3203c0.0073,0,0.0146,0,0.022,0
|
||||
c8.123,0,14.7441-2.5869,21.4683-8.3906c0.5493-0.4805,0.8516-1.1201,0.8516-1.8008c0.001-0.6074-0.1743-1.1035-0.5205-1.4756
|
||||
l-1.3569-1.8428l-0.0732-0.0859c-0.2637-0.2637-0.6929-0.6152-1.3716-0.6152c-0.6421,0-1.2549,0.2217-1.7124,0.6143
|
||||
c-1.9346,1.585-3.5459,2.8008-4.7969,3.6182c-1.7979,1.208-5.8218,3.2314-12.5986,3.2314c-0.0073,0-0.0142,0-0.021,0
|
||||
c-0.1357,0.0029-0.269,0.0039-0.4043,0.0039c-12.2642,0-23.4736-10.3262-24.5088-22.4814l53.0127,0.0322c0.0015,0,0.0024,0,0.0034,0
|
||||
c2.2373,0,3.4697-1.1621,3.4712-3.2715c0.0034-5.2588-1.3574-10.3945-4.0464-15.2705
|
||||
C155.0015,84.0953,151.2188,80.1363,146.4561,77.1739z M154.6451,100.4341l-49.1157-0.0293
|
||||
c1.9834-11.6631,13.0181-21.2417,24.5034-21.0117c0.1387-0.0024,0.2754-0.0039,0.4136-0.0039
|
||||
C143.4629,79.3892,152.4722,90.0162,154.6451,100.4341z"/>
|
||||
<path style="fill:#FFFFFF;" d="M204.0386,136.6382c5.7319,0,11.1069-1.4502,15.9746-4.3115
|
||||
c4.938-2.9014,8.75-6.7129,11.6533-11.6533c2.8608-4.8672,4.311-10.2578,4.311-16.0244c0-5.7324-1.4502-11.1064-4.311-15.9746
|
||||
c-2.9019-4.9385-6.7134-8.75-11.6533-11.6533c-4.8687-2.8618-10.2437-4.3125-15.9746-4.3125
|
||||
c-9.938,0-19.2021,4.7583-24.3516,12.3174v-9.438c0-0.5946-0.1465-1.0788-0.411-1.4511c-0.3815-0.5369-1.0157-0.834-1.8727-0.834
|
||||
h-2.6738c-1.4521,0-2.2852,0.833-2.2852,2.2852v5.6964v46.4791v23.9676c0,1.2568,0.7808,2.0371,2.0371,2.0371h3.3667
|
||||
c0.9209,0,1.6421-0.6992,1.6421-1.5908v-17.098v-10.984C185.0884,131.8892,194.2749,136.6382,204.0386,136.6382z M186.6358,122.5591
|
||||
c-4.9346-4.9346-7.6831-11.4932-7.542-18.0254c-0.1367-6.3506,2.5439-12.751,7.3545-17.5605
|
||||
c4.8521-4.8521,11.3037-7.5547,17.7383-7.417c4.3691,0,8.4863,1.1465,12.2314,3.4043c3.7344,2.2979,6.7456,5.4053,8.9492,9.2354
|
||||
c2.1699,3.9072,3.2695,8.0967,3.2695,12.4697c0.1396,6.4619-2.5967,12.9844-7.5083,17.8955
|
||||
c-4.7617,4.7617-11.0469,7.3857-17.2544,7.2803C197.6856,129.9712,191.396,127.3208,186.6358,122.5591z"/>
|
||||
<path style="fill:#FFFFFF;" d="M241.8955,80.3975h7.5669v42.0259c0,6.8174,4.5674,12.1309,11.0825,12.9189
|
||||
c0.6836,0.1055,1.8379,0.1572,3.5303,0.1572c2.0078,0,3.0273-0.3535,3.0273-2.2842v-2.377c0-1.7891-1.334-2.0371-2.7568-2.0371
|
||||
c0,0-0.001,0-0.002,0l-1.7871-0.0488c-2.0117-0.0439-3.4883-0.7627-4.3896-2.1367c-0.9697-1.4805-1.4619-3.1738-1.4619-5.0352
|
||||
V80.3975h10.0928c1.3076,0,2.2852-1.3628,2.2852-2.5815v-1.9312c0-1.3999-0.8359-2.2354-2.2354-2.2354h-10.1426V60.6861
|
||||
c0-1.4619-0.7969-2.4829-1.9375-2.4829c-0.1865,0-0.4121,0-0.6392,0.0884l-2.6489,0.6865
|
||||
c-1.2109,0.3682-2.0171,0.9263-2.0171,2.4507v12.2207h-7.5669c-1.4185,0-2.335,0.897-2.335,2.2852v1.8813
|
||||
C239.5606,79.2393,240.6079,80.3975,241.8955,80.3975z"/>
|
||||
<path style="fill:#FFFFFF;" d="M379.1182,106.2691c-4.0488-2.9219-8.8545-5.0293-14.291-6.2646
|
||||
c-6.5049-1.3975-13.4473-5.2129-13.3203-10.3066c0-7.5225,6.6367-10.1914,12.3203-10.1914c5.3574,0,10.2207,3.002,13.001,8.0146
|
||||
c0.6729,1.2861,1.4785,1.9375,2.3955,1.9375c0.3311,0,0.7061-0.1113,0.9922-0.2832l2.2021-1.1523
|
||||
c0.5947-0.3408,0.9229-0.9414,0.9229-1.6924c0-0.5205-0.0908-0.9541-0.2617-1.292c-3.6367-8.2466-10.0967-12.4282-19.2021-12.4282
|
||||
c-11.7305,0-19.6123,6.9263-19.6123,17.2349c0,4.3125,1.8438,7.9746,5.4756,10.8809c3.4482,2.7979,7.9121,4.8623,13.2705,6.1377
|
||||
c4.5859,1.085,8.3193,2.5654,11.0977,4.4023c1.4159,0.9354,2.4412,2.0535,3.106,3.3672c0.6053,1.1962,0.9135,2.5535,0.9135,4.1005
|
||||
c0.0742,2.3857-0.79,4.5176-2.5684,6.3389c-3.1445,3.2178-8.4053,4.6689-12.0205,4.6689c-0.0361,0-0.0723,0-0.1074,0
|
||||
c-3.4268,0-6.4893-0.8438-9.1035-2.5068c-2.5918-1.6484-4.2363-3.8076-5.0293-6.6064c-0.3203-1.0996-0.751-2.1738-2.1553-2.1738
|
||||
c-0.0742,0-0.2109,0.0146-0.4062,0.0449c-0.1133,0.0166-0.2559,0.0381-0.5088,0.0742l-1.8818,0.4463l-0.1045,0.0332
|
||||
c-1.0244,0.4082-1.6113,1.1846-1.6113,2.1309c0,0.2285,0.0625,0.6592,0.2178,1.1094c1.9707,8.5801,10.2432,14.3447,20.5732,14.3447
|
||||
c0.125,0.002,0.249,0.002,0.374,0.002c6.5947,0,12.6748-2.3193,16.7275-6.3945c3.1895-3.208,4.8311-7.2363,4.748-11.6357
|
||||
c0-2.8187-0.6185-5.3109-1.8062-7.481C382.4437,109.2624,381.0062,107.631,379.1182,106.2691z"/>
|
||||
<path style="fill:#FFFFFF;" d="M348.9043,45.7325c0-6.3157-3.2826-11.8699-8.2238-15.0756
|
||||
c-2.811-1.8237-6.1537-2.8947-9.7469-2.8947c-9.9092,0-17.9707,8.0615-17.9707,17.9702c0,4.7659,1.8775,9.0925,4.9157,12.3123
|
||||
c-3.6619,4.3709-6.6334,9.3336-8.7663,14.7186c-1.5873-0.2422-3.2123-0.3683-4.8662-0.3683
|
||||
c-17.7158,0-32.1289,14.4131-32.1289,32.1289c0,14.6854,9.9077,27.0922,23.3869,30.9101
|
||||
c-6.7762,17.3461-23.6572,29.6719-43.3742,29.6719c-16.8195,0-31.583-8.9662-39.7656-22.369
|
||||
c-2.4778,0.5446-5.0429,0.8519-7.6721,0.9023c9.0226,16.99,26.8969,28.5917,47.4377,28.5917
|
||||
c23.2646,0,43.1121-14.8788,50.5461-35.6179c0.5204,0.0251,1.0435,0.0398,1.5701,0.0398c17.7158,0,32.1289-14.4131,32.1289-32.1289
|
||||
c0-13.557-8.4446-25.1712-20.3465-29.8811c1.9001-4.5678,4.5115-8.7646,7.6888-12.4641c0.9996,0.4404,2.0479,0.785,3.1324,1.0384
|
||||
c1.3144,0.3071,2.6773,0.486,4.0839,0.486C340.8428,63.7032,348.9043,55.6416,348.9043,45.7325z M304.2461,129.5279
|
||||
c-13.7871,0-25.0039-11.2168-25.0039-25.0039s11.2168-25.0039,25.0039-25.0039S329.25,90.7369,329.25,104.524
|
||||
S318.0332,129.5279,304.2461,129.5279z M330.9336,34.8872c0.645,0,1.2737,0.0671,1.8881,0.1755
|
||||
c5.0818,0.8974,8.9576,5.3347,8.9576,10.6697c0,5.9805-4.8652,10.8457-10.8457,10.8457s-10.8457-4.8652-10.8457-10.8457
|
||||
c0-1.3967,0.2746-2.7282,0.7576-3.9555C322.4306,37.7496,326.35,34.8872,330.9336,34.8872z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.3 KiB |
BIN
docs/logos/Leptos_logo_abbreviation__circle_RGB.png
Executable file
|
After Width: | Height: | Size: 38 KiB |
37
docs/logos/Leptos_logo_abbreviation__circle_RGB.svg
Executable file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 115.9988 115.9988" style="enable-background:new 0 0 115.9988 115.9988;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#180D38;" d="M29.1281,108.2941c9.5736-4.5548,17.1531-12.6456,21.0335-22.5787
|
||||
c-12.0865-3.4232-20.9707-14.548-20.9707-27.7159c0-15.8849,12.9236-28.8085,28.8085-28.8085
|
||||
c1.4832,0,2.9404,0.113,4.3639,0.3303c1.9125-4.8287,4.5771-9.2786,7.8607-13.1979c-2.7243-2.8871-4.4077-6.7665-4.4077-11.0399
|
||||
c0-1.6191,0.2457-3.1808,0.6921-4.6562C63.7305,0.2186,60.8908,0,57.9995,0C25.9672,0,0,25.9672,0,57.9994
|
||||
C0,79.5165,11.7263,98.2828,29.1281,108.2941z"/>
|
||||
<path style="fill:#EF3939;" d="M81.9297,15.0082c3.6788,0,6.886-2.0536,8.5379-5.0742
|
||||
c-5.3185-3.5997-11.2684-6.3339-17.646-8.0151c-0.3903,1.0504-0.6168,2.1798-0.6168,3.3644
|
||||
C72.2049,10.6458,76.5673,15.0082,81.9297,15.0082z"/>
|
||||
<path style="fill:#180D38;" d="M95.5663,13.828c-2.8537,4.5375-7.8918,7.5688-13.6366,7.5688
|
||||
c-1.2614,0-2.4835-0.1604-3.6622-0.4359c-0.9722-0.2272-1.9121-0.5362-2.8083-0.931c-2.8492,3.3173-5.1907,7.0806-6.8945,11.1766
|
||||
c10.6715,4.2233,18.2432,14.6371,18.2432,26.7928c0,15.8849-12.9235,28.8085-28.8085,28.8085
|
||||
c-0.4718,0-0.9406-0.0131-1.4069-0.0357c-3.7532,10.4704-11.0354,19.2744-20.406,24.9696
|
||||
c6.7355,2.7367,14.0948,4.257,21.8129,4.257c32.0322,0,57.9994-25.9672,57.9994-57.9995
|
||||
C115.9988,40.3018,108.0628,24.4664,95.5663,13.828z"/>
|
||||
<circle style="fill:#EF3939;" cx="57.9994" cy="57.9994" r="22.4198"/>
|
||||
</g>
|
||||
<path style="fill:#FFFFFF;" d="M78.2676,20.961c1.1786,0.2755,2.4008,0.4359,3.6622,0.4359
|
||||
c5.7448,0,10.7829-3.0313,13.6366-7.5688c-1.6275-1.3855-3.3236-2.6925-5.0987-3.894c-1.6519,3.0206-4.8591,5.0742-8.5379,5.0742
|
||||
c-5.3624,0-9.7249-4.3624-9.7249-9.7249c0-1.1846,0.2264-2.3141,0.6168-3.3644c-2.062-0.5436-4.1682-0.9763-6.3133-1.2917
|
||||
c-0.4464,1.4753-0.6921,3.0371-0.6921,4.6562c0,4.2734,1.6834,8.1528,4.4077,11.0399c-3.2836,3.9193-5.9482,8.3692-7.8607,13.1979
|
||||
c-1.4235-0.2172-2.8807-0.3303-4.3639-0.3303c-15.8849,0-28.8085,12.9235-28.8085,28.8085
|
||||
c0,13.168,8.8842,24.2928,20.9707,27.7159c-3.8804,9.9332-11.4599,18.0239-21.0335,22.5787
|
||||
c2.2621,1.3013,4.6175,2.456,7.0584,3.4478c9.3706-5.6952,16.6528-14.4992,20.406-24.9696
|
||||
c0.4663,0.0226,0.9351,0.0357,1.4069,0.0357c15.8849,0,28.8085-12.9236,28.8085-28.8085c0-12.1557-7.5717-22.5695-18.2432-26.7928
|
||||
c1.7038-4.0959,4.0453-7.8593,6.8945-11.1766C76.3555,20.4248,77.2953,20.7338,78.2676,20.961z M80.4193,57.9994
|
||||
c0,12.3623-10.0576,22.4199-22.4198,22.4199c-12.3623,0-22.4199-10.0576-22.4199-22.4199
|
||||
c0-12.3622,10.0576-22.4199,22.4199-22.4199C70.3617,35.5795,80.4193,45.6371,80.4193,57.9994z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
BIN
docs/logos/Leptos_logo_abbreviation__square_RGB.png
Executable file
|
After Width: | Height: | Size: 27 KiB |
27
docs/logos/Leptos_logo_abbreviation__square_RGB.svg
Executable file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 115.5026 115.5026" style="enable-background:new 0 0 115.5026 115.5026;" xml:space="preserve">
|
||||
<path style="fill:#181139;" d="M115.5026,0h-13.957c0.0002,0.0315,0.0031,0.0623,0.0031,0.0938
|
||||
c0,9.718-7.9059,17.6239-17.6239,17.6239c-1.3796,0-2.7163-0.1754-4.0055-0.4767c-1.0634-0.2485-2.0913-0.5864-3.0715-1.0182
|
||||
c-3.1162,3.6283-5.6772,7.7443-7.5408,12.2242c11.6719,4.6192,19.9532,16.0091,19.9532,29.3043
|
||||
c0,17.374-14.1349,31.5089-31.5089,31.5089c-0.5161,0-1.0288-0.0143-1.5388-0.039c-3.8856,10.8397-11.2302,20.0454-20.6959,26.2814
|
||||
h79.986V0z"/>
|
||||
<circle style="fill:#EF3939;" cx="57.7513" cy="57.7513" r="24.5214"/>
|
||||
<path style="fill:#181139;" d="M49.1788,88.0652c-13.2195-3.744-22.9364-15.9116-22.9364-30.3139
|
||||
c0-17.374,14.1349-31.5089,31.5089-31.5089c1.6223,0,3.2161,0.1237,4.7729,0.3612c2.0918-5.2813,5.0061-10.1484,8.5975-14.4351
|
||||
c-2.9796-3.1577-4.8209-7.4008-4.8209-12.0747c0-0.0317,0.0046-0.0622,0.0048-0.0938H0v115.5026h18.8623
|
||||
C32.7495,111.6378,43.9877,101.3537,49.1788,88.0652z"/>
|
||||
<path style="fill:#EF3939;" d="M83.9248,10.7302c5.8651,0,10.6364-4.7714,10.6364-10.6364c0-0.0316-0.004-0.0623-0.0043-0.0938
|
||||
H73.293c-0.0003,0.0316-0.0046,0.0621-0.0046,0.0938C73.2884,5.9589,78.0598,10.7302,83.9248,10.7302z"/>
|
||||
<path style="fill:#FFFFFF;" d="M56.2125,89.2212c0.51,0.0247,1.0228,0.039,1.5388,0.039c17.374,0,31.5089-14.1349,31.5089-31.5089
|
||||
c0-13.2952-8.2814-24.6851-19.9532-29.3043c1.8635-4.4799,4.4246-8.5959,7.5408-12.2242c0.9802,0.4318,2.0082,0.7698,3.0715,1.0182
|
||||
c1.2892,0.3013,2.6259,0.4767,4.0055,0.4767c9.718,0,17.6239-7.9059,17.6239-17.6239c0-0.0315-0.0029-0.0623-0.0031-0.0938h-6.9887
|
||||
c0.0003,0.0316,0.0043,0.0622,0.0043,0.0938c0,5.8651-4.7714,10.6364-10.6364,10.6364S73.2884,5.9589,73.2884,0.0938
|
||||
c0-0.0317,0.0043-0.0622,0.0046-0.0938h-6.9874c-0.0002,0.0316-0.0048,0.0621-0.0048,0.0938c0,4.674,1.8413,8.9171,4.8209,12.0747
|
||||
c-3.5914,4.2867-6.5057,9.1537-8.5975,14.4351c-1.5569-0.2375-3.1507-0.3612-4.7729-0.3612
|
||||
c-17.374,0-31.5089,14.1349-31.5089,31.5089c0,14.4023,9.7169,26.5699,22.9364,30.3139
|
||||
c-5.1912,13.2885-16.4293,23.5726-30.3165,27.4374h16.6543C44.9824,109.2666,52.327,100.0609,56.2125,89.2212z M33.2299,57.7513
|
||||
c0-13.5211,11.0004-24.5214,24.5214-24.5214s24.5214,11.0004,24.5214,24.5214S71.2724,82.2727,57.7513,82.2727
|
||||
S33.2299,71.2723,33.2299,57.7513z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
@@ -2,6 +2,7 @@
|
||||
<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>
|
||||
BIN
examples/counter/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -23,7 +23,7 @@ leptos_actix = { path = "../../integrations/actix", optional = true }
|
||||
leptos_meta = { path = "../../meta", default-features = false }
|
||||
leptos_router = { path = "../../router", default-features = false }
|
||||
log = "0.4"
|
||||
simple_logger = "2"
|
||||
simple_logger = "4.0.0"
|
||||
gloo-net = { git = "https://github.com/rustwasm/gloo" }
|
||||
|
||||
[features]
|
||||
@@ -54,7 +54,7 @@ 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 = "src/styles/tailwind.css"
|
||||
# [Optional] Files in the asset-dir will be copied to the site-root directory
|
||||
# assets-dir = "static/assets"
|
||||
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-address = "127.0.0.1:3000"
|
||||
# The port to use for automatic reload monitoring
|
||||
|
||||
BIN
examples/counter_isomorphic/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -1,5 +1,6 @@
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
use leptos_meta::*;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
@@ -44,6 +45,7 @@ pub async fn clear_server_count() -> Result<i32, ServerFnError> {
|
||||
}
|
||||
#[component]
|
||||
pub fn Counters(cx: Scope) -> impl IntoView {
|
||||
provide_meta_context(cx);
|
||||
view! {
|
||||
cx,
|
||||
<Router>
|
||||
@@ -59,6 +61,7 @@ pub fn Counters(cx: Scope) -> impl IntoView {
|
||||
<li><A href="multi">"Multi-User"</A></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="" view=|cx| view! {
|
||||
@@ -209,8 +212,8 @@ pub fn MultiuserCounter(cx: Scope) -> impl IntoView {
|
||||
};
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
let multiplayer_value =
|
||||
create_signal_from_stream(cx, futures::stream::once(Box::pin(async { 0.to_string() })));
|
||||
let (multiplayer_value, _) =
|
||||
create_signal(cx, None::<i32>);
|
||||
|
||||
view! {
|
||||
cx,
|
||||
|
||||
@@ -9,6 +9,7 @@ cfg_if! {
|
||||
use actix_files::{Files};
|
||||
use actix_web::*;
|
||||
use crate::counters::*;
|
||||
use leptos_actix::{generate_route_list, LeptosRoutes};
|
||||
|
||||
#[get("/api/events")]
|
||||
async fn counter_events() -> impl Responder {
|
||||
@@ -29,23 +30,23 @@ cfg_if! {
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
|
||||
crate::counters::register_server_functions();
|
||||
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
let leptos_options = &conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
let pkg_dir = &leptos_options.site_pkg_dir;
|
||||
let bundle_path = format!("/{site_root}/{pkg_dir}");
|
||||
let addr = conf.leptos_options.site_address.clone();
|
||||
let routes = generate_route_list(|cx| view! { cx, <Counters/> });
|
||||
|
||||
HttpServer::new(move || {
|
||||
let leptos_options = &conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
|
||||
App::new()
|
||||
.service(Files::new("/pkg", "./pkg")) // used by wasm-pack and cargo run. Can be removed if using cargo-leptos
|
||||
.service(Files::new(&bundle_path, format!("./{bundle_path}"))) // used by cargo-leptos. Can be removed if using wasm-pack and cargo run.
|
||||
.service(counter_events)
|
||||
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||
.route("/{tail:.*}", leptos_actix::render_app_to_stream(leptos_options.to_owned(), |cx| view! { cx, <Counters/> }))
|
||||
//.wrap(middleware::Compress::default())
|
||||
.leptos_routes(leptos_options.to_owned(), routes.to_owned(), |cx| view! { cx, <Counters/> })
|
||||
.service(Files::new("/", &site_root))
|
||||
//.wrap(middleware::Compress::default())
|
||||
})
|
||||
.bind(&addr)?
|
||||
.run()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z"/>
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
|
||||
</head>
|
||||
<style>
|
||||
img {
|
||||
|
||||
BIN
examples/fetch/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -19,7 +19,7 @@ leptos_meta = { path = "../../meta", default-features = false }
|
||||
leptos_actix = { path = "../../integrations/actix", default-features = false, optional = true }
|
||||
leptos_router = { path = "../../router", default-features = false }
|
||||
log = "0.4"
|
||||
simple_logger = "2"
|
||||
simple_logger = "4.0.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
gloo-net = { version = "0.2", features = ["http"] }
|
||||
@@ -56,7 +56,7 @@ 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 = "static/assets"
|
||||
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-address = "127.0.0.1:3000"
|
||||
# The port to use for automatic reload monitoring
|
||||
|
||||
BIN
examples/hackernews/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -1,4 +1,4 @@
|
||||
use leptos::{on_cleanup, Scope, Serializable};
|
||||
use leptos::{Scope, Serializable};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn story(path: &str) -> String {
|
||||
@@ -29,7 +29,7 @@ where
|
||||
|
||||
// abort in-flight requests if the Scope is disposed
|
||||
// i.e., if we've navigated away from this page
|
||||
on_cleanup(cx, move || {
|
||||
leptos::on_cleanup(cx, move || {
|
||||
if let Some(abort_controller) = abort_controller {
|
||||
abort_controller.abort()
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
view! {
|
||||
cx,
|
||||
<>
|
||||
<Stylesheet id="leptos" href="/target/site/pkg/hackernews.css"/>
|
||||
<Stylesheet id="leptos" href="/pkg/hackernews.css"/>
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
|
||||
<Meta name="description" content="Leptos implementation of a HackerNews demo."/>
|
||||
<Router>
|
||||
<Nav />
|
||||
@@ -23,7 +24,7 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
<Routes>
|
||||
<Route path="users/:id" view=|cx| view! { cx, <User/> }/>
|
||||
<Route path="stories/:id" view=|cx| view! { cx, <Story/> }/>
|
||||
<Route path="*stories" view=|cx| view! { cx, <Stories/> }/>
|
||||
<Route path=":stories?" view=|cx| view! { cx, <Stories/> }/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
|
||||
@@ -8,28 +8,34 @@ cfg_if! {
|
||||
use actix_files::{Files};
|
||||
use actix_web::*;
|
||||
use hackernews::{App,AppProps};
|
||||
use leptos_actix::{LeptosRoutes, generate_route_list};
|
||||
|
||||
#[get("/style.css")]
|
||||
async fn css() -> impl Responder {
|
||||
actix_files::NamedFile::open_async("./style.css").await
|
||||
}
|
||||
#[get("/favicon.ico")]
|
||||
async fn favicon() -> impl Responder {
|
||||
actix_files::NamedFile::open_async("./target/site//favicon.ico").await
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
let addr = conf.leptos_options.site_address.clone();
|
||||
// Generate the list of routes in your Leptos App
|
||||
let routes = generate_route_list(|cx| view! { cx, <App/> });
|
||||
|
||||
HttpServer::new(move || {
|
||||
let leptos_options = &conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
let pkg_dir = &leptos_options.site_pkg_dir;
|
||||
let bundle_path = format!("/{site_root}/{pkg_dir}");
|
||||
|
||||
App::new()
|
||||
.service(Files::new("/pkg", "./pkg")) // used by wasm-pack and cargo run. Can be removed if using cargo-leptos
|
||||
.service(Files::new(&bundle_path, format!("./{bundle_path}"))) // used by cargo-leptos. Can be removed if using wasm-pack and cargo run.
|
||||
.service(css)
|
||||
.service(favicon)
|
||||
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||
.route("/{tail:.*}", leptos_actix::render_app_to_stream(leptos_options.to_owned(), |cx| view! { cx, <App/> }))
|
||||
.leptos_routes(leptos_options.to_owned(), routes.to_owned(), |cx| view! { cx, <App/> })
|
||||
.service(Files::new("/", &site_root))
|
||||
//.wrap(middleware::Compress::default())
|
||||
})
|
||||
.bind(&addr)?
|
||||
|
||||
@@ -48,12 +48,12 @@ pub fn Stories(cx: Scope) -> impl IntoView {
|
||||
{move || if page() > 1 {
|
||||
view! {
|
||||
cx,
|
||||
<html::a class="page-link"
|
||||
<a class="page-link"
|
||||
href=move || format!("/{}?page={}", story_type(), page() - 1)
|
||||
attr:aria_label="Previous Page"
|
||||
>
|
||||
"< prev"
|
||||
</html::a>
|
||||
</a>
|
||||
}.into_any()
|
||||
} else {
|
||||
view! {
|
||||
|
||||
@@ -64,7 +64,7 @@ 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 = "static/assets"
|
||||
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-address = "127.0.0.1:3000"
|
||||
# The port to use for automatic reload monitoring
|
||||
|
||||
BIN
examples/hackernews_axum/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
41
examples/hackernews_axum/src/file.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
body::{boxed, Body, BoxBody},
|
||||
extract::Extension,
|
||||
http::{Request, Response, StatusCode, Uri},
|
||||
};
|
||||
use tower::ServiceExt;
|
||||
use tower_http::services::ServeDir;
|
||||
use std::sync::Arc;
|
||||
use leptos::LeptosOptions;
|
||||
|
||||
pub async fn file_handler(uri: Uri, Extension(options): Extension<Arc<LeptosOptions>>) -> Result<Response<BoxBody>, (StatusCode, String)> {
|
||||
|
||||
let options = &*options;
|
||||
let root = options.site_root.clone();
|
||||
let res = get_static_file(uri.clone(), &root).await?;
|
||||
|
||||
match res.status() {
|
||||
StatusCode::OK => Ok(res),
|
||||
_ => Err((res.status(), "File Not Found".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_static_file(uri: Uri, root: &str) -> Result<Response<BoxBody>, (StatusCode, String)> {
|
||||
let req = Request::builder().uri(uri.clone()).body(Body::empty()).unwrap();
|
||||
let root_path = format!("{root}");
|
||||
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
|
||||
// This path is relative to the cargo root
|
||||
match ServeDir::new(&root_path).oneshot(req).await {
|
||||
Ok(res) => Ok(res.map(boxed)),
|
||||
Err(err) => Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {}", err),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use leptos::{component, view, IntoView, Scope};
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
mod api;
|
||||
pub mod file;
|
||||
pub mod handlers;
|
||||
mod routes;
|
||||
use routes::nav::*;
|
||||
@@ -16,7 +17,8 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
view! {
|
||||
cx,
|
||||
<>
|
||||
<Stylesheet id="leptos" href="./target/site/pkg/hackernews_axum.css"/>
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
|
||||
<Stylesheet id="leptos" href="/pkg/hackernews_axum.css"/>
|
||||
<Meta name="description" content="Leptos implementation of a HackerNews demo."/>
|
||||
<Router>
|
||||
<Nav />
|
||||
@@ -24,7 +26,7 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
<Routes>
|
||||
<Route path="users/:id" view=|cx| view! { cx, <User/> }/>
|
||||
<Route path="stories/:id" view=|cx| view! { cx, <Story/> }/>
|
||||
<Route path="*stories" view=|cx| view! { cx, <Stories/> }/>
|
||||
<Route path=":stories?" view=|cx| view! { cx, <Stories/> }/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
|
||||
@@ -6,10 +6,12 @@ cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
Router,
|
||||
error_handling::HandleError,
|
||||
routing::get,
|
||||
extract::Extension,
|
||||
};
|
||||
use http::StatusCode;
|
||||
use tower_http::services::ServeDir;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use std::sync::Arc;
|
||||
use hackernews_axum::file::file_handler;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
@@ -17,40 +19,17 @@ if #[cfg(feature = "ssr")] {
|
||||
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
let leptos_options = conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
let pkg_dir = &leptos_options.site_pkg_dir;
|
||||
|
||||
// The URL path of the generated JS/WASM bundle from cargo-leptos
|
||||
let bundle_path = format!("/{site_root}/{pkg_dir}");
|
||||
// The filesystem path of the generated JS/WASM bundle from cargo-leptos
|
||||
let bundle_filepath = format!("./{site_root}/{pkg_dir}");
|
||||
let addr = leptos_options.site_address.clone();
|
||||
log::debug!("serving at {addr}");
|
||||
let routes = generate_route_list(|cx| view! { cx, <App/> }).await;
|
||||
|
||||
simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
|
||||
|
||||
// These are Tower Services that will serve files from the static and pkg repos.
|
||||
// HandleError is needed as Axum requires services to implement Infallible Errors
|
||||
// because all Errors are converted into Responses
|
||||
let static_service = HandleError::new( ServeDir::new("./static"), handle_file_error);
|
||||
let pkg_service =HandleError::new( ServeDir::new("./pkg"), handle_file_error);
|
||||
let cargo_leptos_service = HandleError::new( ServeDir::new(&bundle_filepath), handle_file_error);
|
||||
|
||||
/// Convert the Errors from ServeDir to a type that implements IntoResponse
|
||||
async fn handle_file_error(err: std::io::Error) -> (StatusCode, String) {
|
||||
(
|
||||
StatusCode::NOT_FOUND,
|
||||
format!("File Not Found: {}", err),
|
||||
)
|
||||
}
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
// `GET /` goes to `root`
|
||||
.nest_service("/pkg", pkg_service) // Only need if using wasm-pack. Can be deleted if using cargo-leptos
|
||||
.nest_service(&bundle_path, cargo_leptos_service) // Only needed if using cargo-leptos. Can be deleted if using wasm-pack and cargo-run
|
||||
.nest_service("/static", static_service)
|
||||
.fallback(leptos_axum::render_app_to_stream(leptos_options, |cx| view! { cx, <App/> }));
|
||||
.route("/favicon.ico", get(file_handler))
|
||||
.leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, <App/> } )
|
||||
.fallback(file_handler)
|
||||
.layer(Extension(Arc::new(leptos_options)));
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
|
||||
@@ -134,7 +134,7 @@ fn Story(cx: Scope, story: api::Story) -> impl IntoView {
|
||||
view! { cx,
|
||||
<span>
|
||||
{"by "}
|
||||
{story.user.map(|user| view ! { cx, <A href=format!("/users/{}", user)>{user.clone()}</A>})}
|
||||
{story.user.map(|user| view ! { cx, <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 {
|
||||
|
||||
@@ -17,7 +17,7 @@ pub fn Story(cx: Scope) -> impl IntoView {
|
||||
}
|
||||
},
|
||||
);
|
||||
let meta_description = move || story.read().and_then(|story| story.map(|story| story.title.clone())).unwrap_or_else(|| "Loading story...".to_string());
|
||||
let meta_description = move || story.read().and_then(|story| story.map(|story| story.title)).unwrap_or_else(|| "Loading story...".to_string());
|
||||
|
||||
view! { cx,
|
||||
<>
|
||||
@@ -37,7 +37,7 @@ pub fn Story(cx: Scope) -> impl IntoView {
|
||||
{story.user.map(|user| view! { cx, <p class="meta">
|
||||
{story.points}
|
||||
" points | by "
|
||||
<A href=format!("/users/{}", user)>{user.clone()}</A>
|
||||
<A href=format!("/users/{user}")>{user.clone()}</A>
|
||||
{format!(" {}", story.time_ago)}
|
||||
</p>})}
|
||||
</div>
|
||||
|
||||
@@ -1,326 +0,0 @@
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
font-size: 15px;
|
||||
background-color: #f2f3f5;
|
||||
margin: 0;
|
||||
padding-top: 55px;
|
||||
color: #34495e;
|
||||
overflow-y: scroll
|
||||
}
|
||||
|
||||
a {
|
||||
color: #34495e;
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: #335d92;
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
height: 55px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0
|
||||
}
|
||||
|
||||
.header .inner {
|
||||
max-width: 800px;
|
||||
box-sizing: border-box;
|
||||
margin: 0 auto;
|
||||
padding: 15px 5px
|
||||
}
|
||||
|
||||
.header a {
|
||||
color: rgba(255, 255, 255, .8);
|
||||
line-height: 24px;
|
||||
transition: color .15s ease;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
font-weight: 300;
|
||||
letter-spacing: .075em;
|
||||
margin-right: 1.8em
|
||||
}
|
||||
|
||||
.header a:hover {
|
||||
color: #fff
|
||||
}
|
||||
|
||||
.header a.active {
|
||||
color: #fff;
|
||||
font-weight: 400
|
||||
}
|
||||
|
||||
.header a:nth-child(6) {
|
||||
margin-right: 0
|
||||
}
|
||||
|
||||
.header .github {
|
||||
color: #fff;
|
||||
font-size: .9em;
|
||||
margin: 0;
|
||||
float: right
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 24px;
|
||||
margin-right: 10px;
|
||||
display: inline-block;
|
||||
vertical-align: middle
|
||||
}
|
||||
|
||||
.view {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
position: relative
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-exit-active {
|
||||
transition: all .2s ease
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-exit-active {
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
@media (max-width:860px) {
|
||||
.header .inner {
|
||||
padding: 15px 30px
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:600px) {
|
||||
.header .inner {
|
||||
padding: 15px
|
||||
}
|
||||
|
||||
.header a {
|
||||
margin-right: 1em
|
||||
}
|
||||
|
||||
.header .github {
|
||||
display: none
|
||||
}
|
||||
}
|
||||
|
||||
.news-view {
|
||||
padding-top: 45px
|
||||
}
|
||||
|
||||
.news-list,
|
||||
.news-list-nav {
|
||||
background-color: #fff;
|
||||
border-radius: 2px
|
||||
}
|
||||
|
||||
.news-list-nav {
|
||||
padding: 15px 30px;
|
||||
position: fixed;
|
||||
text-align: center;
|
||||
top: 55px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 998;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .1)
|
||||
}
|
||||
|
||||
.news-list-nav .page-link {
|
||||
margin: 0 1em
|
||||
}
|
||||
|
||||
.news-list-nav .disabled {
|
||||
color: #aaa
|
||||
}
|
||||
|
||||
.news-list {
|
||||
position: absolute;
|
||||
margin: 30px 0;
|
||||
width: 100%;
|
||||
transition: all .5s cubic-bezier(.55, 0, .1, 1)
|
||||
}
|
||||
|
||||
.news-list ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0
|
||||
}
|
||||
|
||||
@media (max-width:600px) {
|
||||
.news-list {
|
||||
margin: 10px 0
|
||||
}
|
||||
}
|
||||
|
||||
.news-item {
|
||||
background-color: #fff;
|
||||
padding: 20px 30px 20px 80px;
|
||||
border-bottom: 1px solid #eee;
|
||||
position: relative;
|
||||
line-height: 20px
|
||||
}
|
||||
|
||||
.news-item .score {
|
||||
color: #335d92;
|
||||
font-size: 1.1em;
|
||||
font-weight: 700;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
margin-top: -10px
|
||||
}
|
||||
|
||||
.news-item .host,
|
||||
.news-item .meta {
|
||||
font-size: .85em;
|
||||
color: #626262
|
||||
}
|
||||
|
||||
.news-item .host a,
|
||||
.news-item .meta a {
|
||||
color: #626262;
|
||||
text-decoration: underline
|
||||
}
|
||||
|
||||
.news-item .host a:hover,
|
||||
.news-item .meta a:hover {
|
||||
color: #335d92
|
||||
}
|
||||
|
||||
.item-view-header {
|
||||
background-color: #fff;
|
||||
padding: 1.8em 2em 1em;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .1)
|
||||
}
|
||||
|
||||
.item-view-header h1 {
|
||||
display: inline;
|
||||
font-size: 1.5em;
|
||||
margin: 0;
|
||||
margin-right: .5em
|
||||
}
|
||||
|
||||
.item-view-header .host,
|
||||
.item-view-header .meta,
|
||||
.item-view-header .meta a {
|
||||
color: #626262
|
||||
}
|
||||
|
||||
.item-view-header .meta a {
|
||||
text-decoration: underline
|
||||
}
|
||||
|
||||
.item-view-comments {
|
||||
background-color: #fff;
|
||||
margin-top: 10px;
|
||||
padding: 0 2em .5em
|
||||
}
|
||||
|
||||
.item-view-comments-header {
|
||||
margin: 0;
|
||||
font-size: 1.1em;
|
||||
padding: 1em 0;
|
||||
position: relative
|
||||
}
|
||||
|
||||
.item-view-comments-header .spinner {
|
||||
display: inline-block;
|
||||
margin: -15px 0
|
||||
}
|
||||
|
||||
.comment-children {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0
|
||||
}
|
||||
|
||||
@media (max-width:600px) {
|
||||
.item-view-header h1 {
|
||||
font-size: 1.25em
|
||||
}
|
||||
}
|
||||
|
||||
.comment-children .comment-children {
|
||||
margin-left: 1.5em
|
||||
}
|
||||
|
||||
.comment {
|
||||
border-top: 1px solid #eee;
|
||||
position: relative
|
||||
}
|
||||
|
||||
.comment .by,
|
||||
.comment .text,
|
||||
.comment .toggle {
|
||||
font-size: .9em;
|
||||
margin: 1em 0
|
||||
}
|
||||
|
||||
.comment .by {
|
||||
color: #626262
|
||||
}
|
||||
|
||||
.comment .by a {
|
||||
color: #626262;
|
||||
text-decoration: underline
|
||||
}
|
||||
|
||||
.comment .text {
|
||||
overflow-wrap: break-word
|
||||
}
|
||||
|
||||
.comment .text a:hover {
|
||||
color: #335d92
|
||||
}
|
||||
|
||||
.comment .text pre {
|
||||
white-space: pre-wrap
|
||||
}
|
||||
|
||||
.comment .toggle {
|
||||
background-color: #fffbf2;
|
||||
padding: .3em .5em;
|
||||
border-radius: 4px
|
||||
}
|
||||
|
||||
.comment .toggle a {
|
||||
color: #626262;
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
.comment .toggle.open {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
margin-bottom: -.5em
|
||||
}
|
||||
|
||||
.user-view {
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
padding: 2em 3em
|
||||
}
|
||||
|
||||
.user-view h1 {
|
||||
margin: 0;
|
||||
font-size: 1.5em
|
||||
}
|
||||
|
||||
.user-view .meta {
|
||||
list-style-type: none;
|
||||
padding: 0
|
||||
}
|
||||
|
||||
.user-view .label {
|
||||
display: inline-block;
|
||||
min-width: 4em
|
||||
}
|
||||
|
||||
.user-view .about {
|
||||
margin: 1em 0
|
||||
}
|
||||
|
||||
.user-view .links a {
|
||||
text-decoration: underline
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z"/>
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
|
||||
<style>
|
||||
.red {
|
||||
color: red;
|
||||
|
||||
BIN
examples/parent_child/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z"/>
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
|
||||
<style>
|
||||
a[aria-current] {
|
||||
font-weight: bold;
|
||||
|
||||
BIN
examples/router/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -87,9 +87,9 @@ site-root = "target/site"
|
||||
# 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 = "src/styles/tailwind.css"
|
||||
style-file = "style/output.css"
|
||||
# [Optional] Files in the asset-dir will be copied to the site-root directory
|
||||
assets-dir = "assets"
|
||||
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-address = "127.0.0.1:3000"
|
||||
# The port to use for automatic reload monitoring
|
||||
|
||||
|
Before Width: | Height: | Size: 101 KiB |
BIN
examples/tailwind/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -1,28 +1,37 @@
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
|
||||
#[component]
|
||||
pub fn App(cx: Scope) -> impl IntoView {
|
||||
provide_meta_context(cx);
|
||||
|
||||
let (count, set_count) = create_signal(cx, 0);
|
||||
|
||||
|
||||
view! {
|
||||
cx,
|
||||
<main class="my-0 mx-auto max-w-3xl text-center">
|
||||
<Stylesheet id="leptos" href="/style.css"/>
|
||||
<h2 class="p-6 text-4xl">"Welcome to Leptos with Tailwind"</h2>
|
||||
<p class="px-10 pb-10 text-left">"Tailwind will scan your Rust files for Tailwind class names and compile them into a CSS file."</p>
|
||||
<button
|
||||
class="bg-sky-600 hover:bg-sky-700 px-5 py-3 text-white rounded-lg"
|
||||
on:click=move |_| set_count.update(|count| *count += 1)
|
||||
>
|
||||
{move || if count() == 0 {
|
||||
"Click me!".to_string()
|
||||
} else {
|
||||
count().to_string()
|
||||
}}
|
||||
</button>
|
||||
</main>
|
||||
<Stylesheet id="leptos" href="/pkg/tailwind.css"/>
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="" view= move |cx| view! {
|
||||
cx,
|
||||
<main class="my-0 mx-auto max-w-3xl text-center">
|
||||
<h2 class="p-6 text-4xl">"Welcome to Leptos with Tailwind"</h2>
|
||||
<p class="px-10 pb-10 text-left">"Tailwind will scan your Rust files for Tailwind class names and compile them into a CSS file."</p>
|
||||
<button
|
||||
class="bg-sky-600 hover:bg-sky-700 px-5 py-3 text-white rounded-lg"
|
||||
on:click=move |_| set_count.update(|count| *count += 1)
|
||||
>
|
||||
{move || if count() == 0 {
|
||||
"Click me!".to_string()
|
||||
} else {
|
||||
count().to_string()
|
||||
}}
|
||||
</button>
|
||||
</main>
|
||||
}/>
|
||||
</Routes>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ cfg_if! {
|
||||
use actix_web::*;
|
||||
use leptos::*;
|
||||
use crate::app::*;
|
||||
use leptos_actix::{generate_route_list, LeptosRoutes};
|
||||
|
||||
#[get("/style.css")]
|
||||
async fn css() -> impl Responder {
|
||||
@@ -15,20 +16,21 @@ cfg_if! {
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
let addr = conf.leptos_options.site_address.clone();
|
||||
let leptos_options = &conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
let pkg_dir = &leptos_options.site_pkg_dir;
|
||||
let bundle_path = format!("/{site_root}/{pkg_dir}");
|
||||
|
||||
// Generate the list of routes in your Leptos App
|
||||
let routes = generate_route_list(|cx| view! { cx, <App/> });
|
||||
|
||||
HttpServer::new(move || {
|
||||
let leptos_options = &conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
let routes = &routes;
|
||||
App::new()
|
||||
.service(css)
|
||||
.service(Files::new("/pkg", "./pkg")) // used by wasm-pack and cargo run. Can be removed if using cargo-leptos
|
||||
.service(Files::new(&bundle_path, format!("./{bundle_path}"))) // used by cargo-leptos. Can be removed if using wasm-pack and cargo run.
|
||||
.route("/{tail:.*}", leptos_actix::render_app_to_stream(leptos_options.to_owned(), |cx| view! { cx, <App/> }))
|
||||
.leptos_routes(leptos_options.to_owned(), routes.to_owned(), |cx| view! { cx, <App/> })
|
||||
.service(Files::new("/", &site_root))
|
||||
.wrap(middleware::Compress::default())
|
||||
})
|
||||
.bind(&addr)?
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com
|
||||
! tailwindcss v3.1.8 | MIT License | https://tailwindcss.com
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -30,7 +30,6 @@
|
||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||
3. Use a more readable tab size.
|
||||
4. Use the user's configured `sans` font-family by default.
|
||||
5. Use the user's configured `sans` font-feature-settings by default.
|
||||
*/
|
||||
|
||||
html {
|
||||
@@ -45,8 +44,6 @@ html {
|
||||
/* 3 */
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
/* 4 */
|
||||
font-feature-settings: normal;
|
||||
/* 5 */
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -413,13 +410,54 @@ video {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* Make elements with the HTML hidden attribute stay hidden by default */
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
*, ::before, ::after {
|
||||
--tw-border-spacing-x: 0;
|
||||
--tw-border-spacing-y: 0;
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-rotate: 0;
|
||||
--tw-skew-x: 0;
|
||||
--tw-skew-y: 0;
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
--tw-pan-x: ;
|
||||
--tw-pan-y: ;
|
||||
--tw-pinch-zoom: ;
|
||||
--tw-scroll-snap-strictness: proximity;
|
||||
--tw-ordinal: ;
|
||||
--tw-slashed-zero: ;
|
||||
--tw-numeric-figure: ;
|
||||
--tw-numeric-spacing: ;
|
||||
--tw-numeric-fraction: ;
|
||||
--tw-ring-inset: ;
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-ring-shadow: 0 0 #0000;
|
||||
--tw-shadow: 0 0 #0000;
|
||||
--tw-shadow-colored: 0 0 #0000;
|
||||
--tw-blur: ;
|
||||
--tw-brightness: ;
|
||||
--tw-contrast: ;
|
||||
--tw-grayscale: ;
|
||||
--tw-hue-rotate: ;
|
||||
--tw-invert: ;
|
||||
--tw-saturate: ;
|
||||
--tw-sepia: ;
|
||||
--tw-drop-shadow: ;
|
||||
--tw-backdrop-blur: ;
|
||||
--tw-backdrop-brightness: ;
|
||||
--tw-backdrop-contrast: ;
|
||||
--tw-backdrop-grayscale: ;
|
||||
--tw-backdrop-hue-rotate: ;
|
||||
--tw-backdrop-invert: ;
|
||||
--tw-backdrop-opacity: ;
|
||||
--tw-backdrop-saturate: ;
|
||||
--tw-backdrop-sepia: ;
|
||||
}
|
||||
|
||||
*, ::before, ::after {
|
||||
::-webkit-backdrop {
|
||||
--tw-border-spacing-x: 0;
|
||||
--tw-border-spacing-y: 0;
|
||||
--tw-translate-x: 0;
|
||||
|
||||
@@ -58,7 +58,7 @@ 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 = "static/assets"
|
||||
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-address = "127.0.0.1:3000"
|
||||
# The port to use for automatic reload monitoring
|
||||
|
||||
BIN
examples/todo_app_sqlite/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -9,6 +9,7 @@ cfg_if! {
|
||||
use actix_files::{Files};
|
||||
use actix_web::*;
|
||||
use crate::todo::*;
|
||||
use leptos_actix::{generate_route_list, LeptosRoutes};
|
||||
|
||||
#[get("/style.css")]
|
||||
async fn css() -> impl Responder {
|
||||
@@ -25,23 +26,23 @@ cfg_if! {
|
||||
|
||||
crate::todo::register_server_functions();
|
||||
|
||||
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
let addr = conf.leptos_options.site_address.clone();
|
||||
|
||||
// Generate the list of routes in your Leptos App
|
||||
let routes = generate_route_list(|cx| view! { cx, <TodoApp/> });
|
||||
|
||||
HttpServer::new(move || {
|
||||
let leptos_options = &conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
let pkg_dir = &leptos_options.site_pkg_dir;
|
||||
let bundle_path = format!("/{site_root}/{pkg_dir}");
|
||||
let routes = &routes;
|
||||
|
||||
App::new()
|
||||
.service(Files::new("/pkg", "./pkg")) // used by wasm-pack and cargo run. Can be removed if using cargo-leptos
|
||||
.service(Files::new(&bundle_path, format!("./{bundle_path}"))) // used by cargo-leptos. Can be removed if using wasm-pack and cargo run.
|
||||
.service(css)
|
||||
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
|
||||
.route("/{tail:.*}", leptos_actix::render_app_to_stream(leptos_options.to_owned(), |cx| view! { cx, <TodoApp/> }))
|
||||
//.wrap(middleware::Compress::default())
|
||||
.leptos_routes(leptos_options.to_owned(), routes.to_owned(), |cx| view! { cx, <TodoApp/> })
|
||||
.service(Files::new("/", &site_root))
|
||||
//.wrap(middleware::Compress::default())
|
||||
})
|
||||
.bind(addr)?
|
||||
.run()
|
||||
|
||||
@@ -38,9 +38,11 @@ cfg_if! {
|
||||
pub async fn get_todos(cx: Scope) -> Result<Vec<Todo>, ServerFnError> {
|
||||
// this is just an example of how to access server context injected in the handlers
|
||||
let req =
|
||||
use_context::<actix_web::HttpRequest>(cx).expect("couldn't get HttpRequest from context");
|
||||
println!("req.path = {:?}", req.path());
|
||||
|
||||
use_context::<actix_web::HttpRequest>(cx);
|
||||
|
||||
if let Some(req) = req{
|
||||
println!("req.path = {:#?}", req.path());
|
||||
}
|
||||
use futures::TryStreamExt;
|
||||
|
||||
let mut conn = db().await?;
|
||||
@@ -90,9 +92,11 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
|
||||
|
||||
#[component]
|
||||
pub fn TodoApp(cx: Scope) -> impl IntoView {
|
||||
provide_meta_context(cx);
|
||||
view! {
|
||||
cx,
|
||||
<Stylesheet id="leptos" href="./target/site/pkg/todo_app_sqlite.css"/>
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
|
||||
<Stylesheet id="leptos" href="/pkg/todo_app_sqlite.css"/>
|
||||
<Router>
|
||||
<header>
|
||||
<h1>"My Tasks"</h1>
|
||||
|
||||
@@ -72,7 +72,7 @@ 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 = "static/assets"
|
||||
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-address = "127.0.0.1:3000"
|
||||
# The port to use for automatic reload monitoring
|
||||
|
||||
BIN
examples/todo_app_sqlite_axum/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
41
examples/todo_app_sqlite_axum/src/file.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
body::{boxed, Body, BoxBody},
|
||||
extract::Extension,
|
||||
http::{Request, Response, StatusCode, Uri},
|
||||
};
|
||||
use tower::ServiceExt;
|
||||
use tower_http::services::ServeDir;
|
||||
use std::sync::Arc;
|
||||
use leptos::LeptosOptions;
|
||||
|
||||
pub async fn file_handler(uri: Uri, Extension(options): Extension<Arc<LeptosOptions>>) -> Result<Response<BoxBody>, (StatusCode, String)> {
|
||||
|
||||
let options = &*options;
|
||||
let root = options.site_root.clone();
|
||||
let res = get_static_file(uri.clone(), &root).await?;
|
||||
|
||||
match res.status() {
|
||||
StatusCode::OK => Ok(res),
|
||||
_ => Err((res.status(), "File Not Found".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_static_file(uri: Uri, root: &str) -> Result<Response<BoxBody>, (StatusCode, String)> {
|
||||
let req = Request::builder().uri(uri.clone()).body(Body::empty()).unwrap();
|
||||
let root_path = format!("{root}");
|
||||
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
|
||||
// This path is relative to the cargo root
|
||||
match ServeDir::new(&root_path).oneshot(req).await {
|
||||
Ok(res) => Ok(res.map(boxed)),
|
||||
Err(err) => Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {}", err),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
pub mod file;
|
||||
pub mod todo;
|
||||
|
||||
// Needs to be in lib.rs AFAIK because wasm-bindgen needs us to be compiling a lib. I may be wrong.
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
|
||||
// boilerplate to run in different modes
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
routing::{post},
|
||||
error_handling::HandleError,
|
||||
routing::post,
|
||||
extract::Extension,
|
||||
Router,
|
||||
};
|
||||
use crate::todo::*;
|
||||
use todo_app_sqlite_axum::*;
|
||||
use http::StatusCode;
|
||||
use tower_http::services::ServeDir;
|
||||
use crate::file::file_handler;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
|
||||
|
||||
let mut conn = db().await.expect("couldn't connect to DB");
|
||||
let conn = db().await.expect("couldn't connect to DB");
|
||||
/* sqlx::migrate!()
|
||||
.run(&mut conn)
|
||||
.await
|
||||
@@ -28,38 +28,15 @@ if #[cfg(feature = "ssr")] {
|
||||
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
let leptos_options = conf.leptos_options;
|
||||
let site_root = &leptos_options.site_root;
|
||||
let pkg_dir = &leptos_options.site_pkg_dir;
|
||||
|
||||
// The URL path of the generated JS/WASM bundle from cargo-leptos
|
||||
let bundle_path = format!("/{site_root}/{pkg_dir}");
|
||||
// The filesystem path of the generated JS/WASM bundle from cargo-leptos
|
||||
let bundle_filepath = format!("./{site_root}/{pkg_dir}");
|
||||
let addr = leptos_options.site_address.clone();
|
||||
log::debug!("serving at {addr}");
|
||||
|
||||
// These are Tower Services that will serve files from the static and pkg repos.
|
||||
// HandleError is needed as Axum requires services to implement Infallible Errors
|
||||
// because all Errors are converted into Responses
|
||||
let static_service = HandleError::new( ServeDir::new("./static"), handle_file_error);
|
||||
let pkg_service = HandleError::new( ServeDir::new("./pkg"), handle_file_error);
|
||||
let cargo_leptos_service = HandleError::new( ServeDir::new(&bundle_filepath), handle_file_error);
|
||||
|
||||
/// Convert the Errors from ServeDir to a type that implements IntoResponse
|
||||
async fn handle_file_error(err: std::io::Error) -> (StatusCode, String) {
|
||||
(
|
||||
StatusCode::NOT_FOUND,
|
||||
format!("File Not Found: {}", err),
|
||||
)
|
||||
}
|
||||
let routes = generate_route_list(|cx| view! { cx, <TodoApp/> }).await;
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
|
||||
.nest_service("/pkg", pkg_service) // Only need if using wasm-pack. Can be deleted if using cargo-leptos
|
||||
.nest_service(&bundle_path, cargo_leptos_service) // Only needed if using cargo-leptos. Can be deleted if using wasm-pack and cargo-run
|
||||
.nest_service("/static", static_service)
|
||||
.fallback(leptos_axum::render_app_to_stream(leptos_options, |cx| view! { cx, <TodoApp/> }));
|
||||
.leptos_routes(leptos_options.clone(), routes, |cx| view! { cx, <TodoApp/> } )
|
||||
.fallback(file_handler)
|
||||
.layer(Extension(Arc::new(leptos_options)));
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
|
||||
@@ -39,9 +39,11 @@ cfg_if! {
|
||||
pub async fn get_todos(cx: Scope) -> Result<Vec<Todo>, ServerFnError> {
|
||||
// this is just an example of how to access server context injected in the handlers
|
||||
// http::Request doesn't implement Clone, so more work will be needed to do use_context() on this
|
||||
let req_parts = use_context::<leptos_axum::RequestParts>(cx).unwrap();
|
||||
println!("\ncalling server fn");
|
||||
let req_parts = use_context::<leptos_axum::RequestParts>(cx);
|
||||
|
||||
if let Some(req_parts) = req_parts{
|
||||
println!("Uri = {:?}", req_parts.uri);
|
||||
}
|
||||
|
||||
use futures::TryStreamExt;
|
||||
|
||||
@@ -105,9 +107,11 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
|
||||
|
||||
#[component]
|
||||
pub fn TodoApp(cx: Scope) -> impl IntoView {
|
||||
provide_meta_context(cx);
|
||||
view! {
|
||||
cx,
|
||||
<Stylesheet id="leptos" href="./target/site/pkg/todo_app_sqlite_axum.css"/>
|
||||
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
|
||||
<Stylesheet id="leptos" href="/pkg/todo_app_sqlite_axum.css"/>
|
||||
<Router>
|
||||
<header>
|
||||
<h1>"My Tasks"</h1>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<link data-trunk rel="css" href="./node_modules/todomvc-app-css/index.css">
|
||||
<title>Leptos • TodoMVC</title>
|
||||
<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>
|
||||
BIN
examples/todomvc/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -13,4 +13,5 @@ futures = "0.3"
|
||||
leptos = { workspace = true, features = ["ssr"] }
|
||||
leptos_meta = { workspace = true, features = ["ssr"] }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
regex = "1.7.0"
|
||||
tokio = "1.24.1"
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
use actix_web::{http::header, web::Bytes, *};
|
||||
use futures::{StreamExt};
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use actix_web::{
|
||||
dev::{ServiceFactory, ServiceRequest},
|
||||
http::header,
|
||||
web::Bytes,
|
||||
*,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use http::StatusCode;
|
||||
use leptos::*;
|
||||
use leptos_meta::*;
|
||||
use leptos_router::*;
|
||||
use regex::Regex;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
@@ -16,13 +23,13 @@ pub struct ResponseParts {
|
||||
pub status: Option<StatusCode>,
|
||||
}
|
||||
|
||||
impl ResponseParts{
|
||||
impl ResponseParts {
|
||||
/// Insert a header, overwriting any previous value with the same key
|
||||
pub fn insert_header(&mut self, key: header::HeaderName, value: header::HeaderValue){
|
||||
pub fn insert_header(&mut self, key: header::HeaderName, value: header::HeaderValue) {
|
||||
self.headers.insert(key, value);
|
||||
}
|
||||
/// Append a header, leaving any header with the same key intact
|
||||
pub fn append_header(&mut self, key: header::HeaderName, value: header::HeaderValue){
|
||||
pub fn append_header(&mut self, key: header::HeaderName, value: header::HeaderValue) {
|
||||
self.headers.append(key, value);
|
||||
}
|
||||
}
|
||||
@@ -40,21 +47,21 @@ impl ResponseOptions {
|
||||
*writable = parts
|
||||
}
|
||||
/// Set the status of the returned Response
|
||||
pub async fn set_status(&self, status: StatusCode){
|
||||
pub async fn set_status(&self, status: StatusCode) {
|
||||
let mut writeable = self.0.write().await;
|
||||
let res_parts = &mut*writeable;
|
||||
let res_parts = &mut *writeable;
|
||||
res_parts.status = Some(status);
|
||||
}
|
||||
/// Insert a header, overwriting any previous value with the same key
|
||||
pub async fn insert_header(&self, key: header::HeaderName, value: header::HeaderValue){
|
||||
pub async fn insert_header(&self, key: header::HeaderName, value: header::HeaderValue) {
|
||||
let mut writeable = self.0.write().await;
|
||||
let res_parts = &mut*writeable;
|
||||
let res_parts = &mut *writeable;
|
||||
res_parts.headers.insert(key, value);
|
||||
}
|
||||
/// Append a header, leaving any header with the same key intact
|
||||
pub async fn append_header(&self, key: header::HeaderName, value: header::HeaderValue){
|
||||
pub async fn append_header(&self, key: header::HeaderName, value: header::HeaderValue) {
|
||||
let mut writeable = self.0.write().await;
|
||||
let res_parts = &mut*writeable;
|
||||
let res_parts = &mut *writeable;
|
||||
res_parts.headers.append(key, value);
|
||||
}
|
||||
}
|
||||
@@ -62,10 +69,15 @@ impl ResponseOptions {
|
||||
/// Provides an easy way to redirect the user from within a server function. Mimicing the Remix `redirect()`,
|
||||
/// it sets a StatusCode of 302 and a LOCATION header with the provided value.
|
||||
/// If looking to redirect from the client, `leptos_router::use_navigate()` should be used instead
|
||||
pub async fn redirect(cx: leptos::Scope, path: &str){
|
||||
pub async fn redirect(cx: leptos::Scope, path: &str) {
|
||||
let response_options = use_context::<ResponseOptions>(cx).unwrap();
|
||||
response_options.set_status(StatusCode::FOUND).await;
|
||||
response_options.insert_header(header::LOCATION, header::HeaderValue::from_str(path).expect("Failed to create HeaderValue")).await;
|
||||
response_options
|
||||
.insert_header(
|
||||
header::LOCATION,
|
||||
header::HeaderValue::from_str(path).expect("Failed to create HeaderValue"),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// An Actix [Route](actix_web::Route) that listens for a `POST` request with
|
||||
@@ -132,7 +144,7 @@ pub fn handle_server_fns() -> Route {
|
||||
|
||||
let mut res: HttpResponseBuilder;
|
||||
let mut res_parts = res_options.0.write().await;
|
||||
|
||||
|
||||
if accept_header == Some("application/json")
|
||||
|| accept_header == Some("application/x-www-form-urlencoded")
|
||||
|| accept_header == Some("application/cbor")
|
||||
@@ -184,10 +196,12 @@ pub fn handle_server_fns() -> Route {
|
||||
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
|
||||
}
|
||||
} else {
|
||||
HttpResponse::BadRequest()
|
||||
.body(format!("Could not find a server function at the route {:?}. \
|
||||
HttpResponse::BadRequest().body(format!(
|
||||
"Could not find a server function at the route {:?}. \
|
||||
\n\nIt's likely that you need to call ServerFn::register() on the \
|
||||
server function type, somewhere in your `main` function.", req.path()))
|
||||
server function type, somewhere in your `main` function.",
|
||||
req.path()
|
||||
))
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -237,7 +251,8 @@ pub fn render_app_to_stream<IV>(
|
||||
options: LeptosOptions,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + 'static,
|
||||
) -> Route
|
||||
where IV: IntoView
|
||||
where
|
||||
IV: IntoView,
|
||||
{
|
||||
web::get().to(move |req: HttpRequest| {
|
||||
let options = options.clone();
|
||||
@@ -267,32 +282,19 @@ where IV: IntoView
|
||||
}
|
||||
};
|
||||
|
||||
let site_root = &options.site_root;
|
||||
|
||||
// Because wasm-pack adds _bg to the end of the WASM filename, and we want to mantain compatibility with it's default options
|
||||
// we add _bg to the wasm files if cargo-leptos doesn't set the env var OUTPUT_NAME
|
||||
// we add _bg to the wasm files if cargo-leptos doesn't set the env var LEPTOS_OUTPUT_NAME
|
||||
// Otherwise we need to add _bg because wasm_pack always does. This is not the same as options.output_name, which is set regardless
|
||||
let output_name = &options.output_name;
|
||||
let mut wasm_output_name = output_name.clone();
|
||||
|
||||
if std::env::var("OUTPUT_NAME").is_err() {
|
||||
if std::env::var("LEPTOS_OUTPUT_NAME").is_err() {
|
||||
wasm_output_name.push_str("_bg");
|
||||
}
|
||||
|
||||
|
||||
let site_ip = &options.site_address.ip().to_string();
|
||||
let reload_port = options.reload_port;
|
||||
let site_root = &options.site_root;
|
||||
let pkg_path = &options.site_pkg_dir;
|
||||
|
||||
// We need to do some logic to check if the site_root is pkg
|
||||
// if it is, then we need to not add pkg_path. This would mean
|
||||
// the site was built with cargo run and not cargo-leptos
|
||||
let bundle_path = match site_root.as_ref() {
|
||||
"pkg" => "pkg".to_string(),
|
||||
_ => format!("{site_root}/{pkg_path}"),
|
||||
};
|
||||
|
||||
let leptos_autoreload = match std::env::var("LEPTOS_WATCH").is_ok() {
|
||||
true => format!(
|
||||
r#"
|
||||
@@ -326,9 +328,9 @@ where IV: IntoView
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<link rel="modulepreload" href="/{bundle_path}/{output_name}.js">
|
||||
<link rel="preload" href="/{bundle_path}/{wasm_output_name}.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||
<script type="module">import init, {{ hydrate }} from '/{bundle_path}/{output_name}.js'; init('/{bundle_path}/{wasm_output_name}.wasm').then(hydrate);</script>
|
||||
<link rel="modulepreload" href="/{pkg_path}/{output_name}.js">
|
||||
<link rel="preload" href="/{pkg_path}/{wasm_output_name}.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||
<script type="module">import init, {{ hydrate }} from '/{pkg_path}/{output_name}.js'; init('/{pkg_path}/{wasm_output_name}.wasm').then(hydrate);</script>
|
||||
{leptos_autoreload}
|
||||
"#
|
||||
);
|
||||
@@ -343,7 +345,7 @@ where IV: IntoView
|
||||
format!("{head}</head><body>").into()
|
||||
});
|
||||
|
||||
let mut stream = Box::pin(futures::stream::once(async move { head.clone() })
|
||||
let mut stream = Box::pin(futures::stream::once(async move { head.clone() })
|
||||
.chain(stream)
|
||||
.chain(futures::stream::once(async move {
|
||||
runtime.dispose();
|
||||
@@ -360,7 +362,7 @@ where IV: IntoView
|
||||
|
||||
let (status, mut headers) = (res_options.status, res_options.headers.clone());
|
||||
let status = status.unwrap_or_default();
|
||||
|
||||
|
||||
let complete_stream =
|
||||
futures::stream::iter([first_chunk.unwrap(), second_chunk.unwrap(), third_chunk.unwrap()])
|
||||
.chain(stream);
|
||||
@@ -382,3 +384,77 @@ where IV: IntoView
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically
|
||||
/// create routes in Actix's App without having to use wildcard matching or fallbacks. Takes in your root app Element
|
||||
/// as an argument so it can walk you app tree. This version is tailored to generated Actix compatible paths.
|
||||
pub fn generate_route_list<IV>(app_fn: impl FnOnce(leptos::Scope) -> IV + 'static) -> Vec<String>
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
let mut routes = leptos_router::generate_route_list_inner(app_fn);
|
||||
|
||||
// Empty strings screw with Actix pathing, they need to be "/"
|
||||
routes = routes
|
||||
.iter()
|
||||
.map(|s| {
|
||||
if s.is_empty() {
|
||||
return "/".to_string();
|
||||
}
|
||||
s.to_string()
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Actix's Router doesn't follow Leptos's
|
||||
// Match `*` or `*someword` to replace with replace it with "/{tail.*}
|
||||
let wildcard_re = Regex::new(r"\*.*").unwrap();
|
||||
// Match `:some_word` but only capture `some_word` in the groups to replace with `{some_word}`
|
||||
let capture_re = Regex::new(r":((?:[^.,/]+)+)[^/]?").unwrap();
|
||||
|
||||
let routes: Vec<String> = routes
|
||||
.iter()
|
||||
.map(|s| wildcard_re.replace_all(s, "{tail:.*}").to_string())
|
||||
.map(|s| capture_re.replace_all(&s, "{$1}").to_string())
|
||||
.collect();
|
||||
|
||||
if routes.is_empty() {
|
||||
vec!["/".to_string()]
|
||||
} else {
|
||||
routes
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait allows one to pass a list of routes and a render function to Axum's router, letting us avoid
|
||||
/// having to use wildcards or manually define all routes in multiple places.
|
||||
pub trait LeptosRoutes {
|
||||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<String>,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
IV: IntoView + 'static;
|
||||
}
|
||||
/// The default implementation of `LeptosRoutes` which takes in a list of paths, and dispatches GET requests
|
||||
/// to those paths to Leptos's renderer.
|
||||
impl<T> LeptosRoutes for actix_web::App<T>
|
||||
where
|
||||
T: ServiceFactory<ServiceRequest, Config = (), Error = Error, InitError = ()>,
|
||||
{
|
||||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<String>,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
let mut router = self;
|
||||
for path in paths.iter() {
|
||||
router = router.route(path, render_app_to_stream(options.clone(), app_fn.clone()));
|
||||
}
|
||||
router
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,9 @@ description = "Axum integrations for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.6", features = ["macros"] }
|
||||
derive_builder = "0.12.0"
|
||||
futures = "0.3"
|
||||
http = "0.2.8"
|
||||
hyper = "0.14.23"
|
||||
kdl = "4.6.0"
|
||||
leptos = { workspace = true, features = ["ssr"] }
|
||||
leptos_meta = { workspace = true, features = ["ssr"] }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use axum::{
|
||||
body::{Body, Bytes, Full, StreamBody},
|
||||
extract::Path,
|
||||
http::{header::HeaderName, header::HeaderValue, HeaderMap, Request, StatusCode},
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
};
|
||||
use futures::{Future, SinkExt, Stream, StreamExt};
|
||||
use http::{header, method::Method, uri::Uri, version::Version, Response};
|
||||
@@ -11,7 +14,7 @@ use leptos::*;
|
||||
use leptos_meta::MetaContext;
|
||||
use leptos_router::*;
|
||||
use std::{io, pin::Pin, sync::Arc};
|
||||
use tokio::{sync::RwLock, task::spawn_blocking};
|
||||
use tokio::{sync::RwLock, task::spawn_blocking, task::LocalSet};
|
||||
|
||||
/// A struct to hold the parts of the incoming Request. Since `http::Request` isn't cloneable, we're forced
|
||||
/// to construct this for Leptos to use in Axum
|
||||
@@ -319,33 +322,17 @@ where
|
||||
async move {
|
||||
// Need to get the path and query string of the Request
|
||||
let path = req.uri();
|
||||
let query = path.query();
|
||||
|
||||
let full_path;
|
||||
if let Some(query) = query {
|
||||
full_path = "http://leptos".to_string() + &path.to_string() + "?" + query
|
||||
} else {
|
||||
full_path = "http://leptos".to_string() + &path.to_string()
|
||||
}
|
||||
let full_path = format!("http://leptos.dev{path}");
|
||||
|
||||
let site_root = &options.site_root;
|
||||
let pkg_path = &options.site_pkg_dir;
|
||||
|
||||
// We need to do some logic to check if the site_root is pkg
|
||||
// if it is, then we need to not add pkg_path. This would mean
|
||||
// the site was built with cargo run and not cargo-leptos
|
||||
let bundle_path = match site_root.as_ref() {
|
||||
"pkg" => "pkg".to_string(),
|
||||
_ => format!("{site_root}/{pkg_path}"),
|
||||
};
|
||||
|
||||
let output_name = &options.output_name;
|
||||
|
||||
// Because wasm-pack adds _bg to the end of the WASM filename, and we want to mantain compatibility with it's default options
|
||||
// we add _bg to the wasm files if cargo-leptos doesn't set the env var OUTPUT_NAME
|
||||
// we add _bg to the wasm files if cargo-leptos doesn't set the env var LEPTOS_OUTPUT_NAME
|
||||
// Otherwise we need to add _bg because wasm_pack always does. This is not the same as options.output_name, which is set regardless
|
||||
let mut wasm_output_name = output_name.clone();
|
||||
if std::env::var("OUTPUT_NAME").is_err() {
|
||||
if std::env::var("LEPTOS_OUTPUT_NAME").is_err() {
|
||||
wasm_output_name.push_str("_bg");
|
||||
}
|
||||
|
||||
@@ -385,9 +372,9 @@ where
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<link rel="modulepreload" href="/{bundle_path}/{output_name}.js">
|
||||
<link rel="preload" href="/{bundle_path}/{wasm_output_name}.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||
<script type="module">import init, {{ hydrate }} from '/{bundle_path}/{output_name}.js'; init('/{bundle_path}/{wasm_output_name}.wasm').then(hydrate);</script>
|
||||
<link rel="modulepreload" href="/{pkg_path}/{output_name}.js">
|
||||
<link rel="preload" href="/{pkg_path}/{wasm_output_name}.wasm" as="fetch" type="application/wasm" crossorigin="">
|
||||
<script type="module">import init, {{ hydrate }} from '/{pkg_path}/{output_name}.js'; init('/{pkg_path}/{wasm_output_name}.wasm').then(hydrate);</script>
|
||||
{leptos_autoreload}
|
||||
"#
|
||||
);
|
||||
@@ -495,3 +482,85 @@ where
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a list of all routes defined in Leptos's Router in your app. We can then use this to automatically
|
||||
/// create routes in Axum's Router without having to use wildcard matching or fallbacks. Takes in your root app Element
|
||||
/// as an argument so it can walk you app tree. This version is tailored to generate Axum compatible paths.
|
||||
pub async fn generate_route_list<IV>(app_fn: impl FnOnce(Scope) -> IV + 'static) -> Vec<String>
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct Routes(pub Arc<RwLock<Vec<String>>>);
|
||||
|
||||
let routes = Routes::default();
|
||||
let routes_inner = routes.clone();
|
||||
|
||||
let local = LocalSet::new();
|
||||
// Run the local task set.
|
||||
|
||||
local
|
||||
.run_until(async move {
|
||||
tokio::task::spawn_local(async move {
|
||||
let routes = leptos_router::generate_route_list_inner(app_fn);
|
||||
let mut writable = routes_inner.0.write().await;
|
||||
*writable = routes;
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
})
|
||||
.await;
|
||||
|
||||
let routes = routes.0.read().await.to_owned();
|
||||
// Axum's Router defines Root routes as "/" not ""
|
||||
let routes: Vec<String> = routes
|
||||
.iter()
|
||||
.map(|s| {
|
||||
if s.is_empty() {
|
||||
return "/".to_string();
|
||||
}
|
||||
s.to_string()
|
||||
})
|
||||
.collect();
|
||||
|
||||
if routes.is_empty() {
|
||||
return vec!["/".to_string()];
|
||||
} else {
|
||||
return routes;
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait allows one to pass a list of routes and a render function to Axum's router, letting us avoid
|
||||
/// having to use wildcards or manually define all routes in multiple places.
|
||||
pub trait LeptosRoutes {
|
||||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<String>,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
IV: IntoView + 'static;
|
||||
}
|
||||
/// The default implementation of `LeptosRoutes` which takes in a list of paths, and dispatches GET requests
|
||||
/// to those paths to Leptos's renderer.
|
||||
impl LeptosRoutes for axum::Router {
|
||||
fn leptos_routes<IV>(
|
||||
self,
|
||||
options: LeptosOptions,
|
||||
paths: Vec<String>,
|
||||
app_fn: impl Fn(leptos::Scope) -> IV + Clone + Send + 'static,
|
||||
) -> Self
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
let mut router = self;
|
||||
for path in paths.iter() {
|
||||
router = router.route(
|
||||
path,
|
||||
get(render_app_to_stream(options.clone(), app_fn.clone())),
|
||||
);
|
||||
}
|
||||
router
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![deny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! # About Leptos
|
||||
//!
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0-beta"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/gbj/leptos"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Configuraiton for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::{net::AddrParseError, num::ParseIntError};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, Clone)]
|
||||
@@ -10,9 +12,23 @@ pub enum LeptosConfigError {
|
||||
EnvError,
|
||||
#[error("Config Error: {0}")]
|
||||
ConfigError(String),
|
||||
#[error("Config Error: {0}")]
|
||||
EnvVarError(String),
|
||||
}
|
||||
impl From<config::ConfigError> for LeptosConfigError {
|
||||
fn from(e: config::ConfigError) -> Self {
|
||||
Self::ConfigError(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for LeptosConfigError {
|
||||
fn from(e: ParseIntError) -> Self {
|
||||
Self::ConfigError(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AddrParseError> for LeptosConfigError {
|
||||
fn from(e: AddrParseError) -> Self {
|
||||
Self::ConfigError(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod errors;
|
||||
|
||||
use crate::errors::LeptosConfigError;
|
||||
@@ -10,21 +12,23 @@ use typed_builder::TypedBuilder;
|
||||
|
||||
/// A Struct to allow us to parse LeptosOptions from the file. Not really needed, most interactions should
|
||||
/// occur with LeptosOptions
|
||||
#[derive(Clone, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, serde::Deserialize)]
|
||||
pub struct ConfFile {
|
||||
pub leptos_options: LeptosOptions,
|
||||
}
|
||||
|
||||
/// This struct serves as a convenient place to store details used for configuring Leptos.
|
||||
/// It's used in our actix and axum integrations to generate the
|
||||
/// correct path for WASM, JS, and Websockets, as well as other configuration tasks.
|
||||
/// It shares keys with cargo-leptos, to allow for easy interoperability
|
||||
#[derive(TypedBuilder, Clone, serde::Deserialize)]
|
||||
#[derive(TypedBuilder, Debug, Clone, serde::Deserialize)]
|
||||
pub struct LeptosOptions {
|
||||
/// The name of the WASM and JS files generated by wasm-bindgen. Defaults to the crate name with underscores instead of dashes
|
||||
#[builder(setter(into))]
|
||||
pub output_name: String,
|
||||
/// The path of the all the files generated by cargo-leptos
|
||||
#[builder(setter(into), default="pkg".to_string())]
|
||||
/// The path of the all the files generated by cargo-leptos. This defaults to '.' for convenience when integrating with other
|
||||
/// tools.
|
||||
#[builder(setter(into), default=".".to_string())]
|
||||
pub site_root: String,
|
||||
/// The path of the WASM and JS files generated by wasm-bindgen from the root of your app
|
||||
/// By default, wasm-bindgen puts them in `pkg`.
|
||||
@@ -46,6 +50,28 @@ pub struct LeptosOptions {
|
||||
pub reload_port: u32,
|
||||
}
|
||||
|
||||
impl LeptosOptions {
|
||||
fn try_from_env() -> Result<Self, LeptosConfigError> {
|
||||
Ok(LeptosOptions {
|
||||
output_name: std::env::var("LEPTOS_OUTPUT_NAME")
|
||||
.map_err(|e| LeptosConfigError::EnvVarError(format!("LEPTOS_OUTPUT_NAME: {e}")))?,
|
||||
site_root: env_w_default("LEPTOS_SITE_ROOT", "target/site")?,
|
||||
site_pkg_dir: env_w_default("LEPTOS_SITE_PKG_DIR", "pkg")?,
|
||||
env: Env::default(),
|
||||
site_address: env_w_default("LEPTOS_SITE_ADDR", "127.0.0.1:3000")?.parse()?,
|
||||
reload_port: env_w_default("LEPTOS_RELOAD_PORT", "3001")?.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn env_w_default(key: &str, default: &str) -> Result<String, LeptosConfigError> {
|
||||
match std::env::var(key) {
|
||||
Ok(val) => Ok(val),
|
||||
Err(VarError::NotPresent) => Ok(default.to_string()),
|
||||
Err(e) => Err(LeptosConfigError::EnvVarError(format!("{key}: {e}"))),
|
||||
}
|
||||
}
|
||||
|
||||
/// An enum that can be used to define the environment Leptos is running in. Can be passed to [RenderOptions].
|
||||
/// Setting this to the `PROD` variant will not include the websockets code for `cargo-leptos` watch mode.
|
||||
/// Defaults to `DEV`.
|
||||
@@ -124,40 +150,40 @@ impl TryFrom<String> for Env {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads [LeptosOptions] from a Cargo.toml with layered overrides. If an env var is specified, like `LEPTOS_ENV`,
|
||||
/// it will override a setting in the file.
|
||||
pub async fn get_configuration(path: Option<&str>) -> Result<ConfFile, LeptosConfigError> {
|
||||
// Allow Cargo.toml path to be specified in case of workspace wonkiness
|
||||
let text = match path {
|
||||
Some(p) => fs::read_to_string(p).map_err(|_| LeptosConfigError::ConfigNotFound)?,
|
||||
None => fs::read_to_string("Cargo.toml").map_err(|_| LeptosConfigError::ConfigNotFound)?,
|
||||
};
|
||||
if let Some(path) = path {
|
||||
let text = fs::read_to_string(path).map_err(|_| LeptosConfigError::ConfigNotFound)?;
|
||||
|
||||
let re: Regex =
|
||||
Regex::new(r#"(?m)^\[package.metadata.leptos\]|(?m)^\[\[workspace.metadata.leptos\]\]"#)
|
||||
.unwrap();
|
||||
let start = match re.find(&text) {
|
||||
Some(found) => found.start(),
|
||||
None => return Err(LeptosConfigError::ConfigSectionNotFound),
|
||||
};
|
||||
let re: Regex = Regex::new(r#"(?m)^\[package.metadata.leptos\]"#).unwrap();
|
||||
let start = match re.find(&text) {
|
||||
Some(found) => found.start(),
|
||||
None => return Err(LeptosConfigError::ConfigSectionNotFound),
|
||||
};
|
||||
|
||||
// so that serde error messages have right line number
|
||||
let newlines = text[..start].matches('\n').count();
|
||||
let input = "\n".repeat(newlines) + &text[start..];
|
||||
let toml = input
|
||||
.replace("[package.metadata.leptos]", "[leptos_options]")
|
||||
.replace("[[workspace.metadata.leptos]]", "[leptos_options]")
|
||||
.replace('-', "_");
|
||||
let settings = Config::builder()
|
||||
// Read the "default" configuration file
|
||||
.add_source(File::from_str(&toml, FileFormat::Toml))
|
||||
// Layer on the environment-specific values.
|
||||
// Add in settings from environment variables (with a prefix of LEPTOS and '_' as separator)
|
||||
// E.g. `LEPTOS_RELOAD_PORT=5001 would set `LeptosOptions.reload_port`
|
||||
.add_source(config::Environment::with_prefix("LEPTOS").separator("_"))
|
||||
.build()?;
|
||||
// so that serde error messages have right line number
|
||||
let newlines = text[..start].matches('\n').count();
|
||||
let input = "\n".repeat(newlines) + &text[start..];
|
||||
let toml = input
|
||||
.replace("[package.metadata.leptos]", "[leptos_options]")
|
||||
.replace('-', "_");
|
||||
let settings = Config::builder()
|
||||
// Read the "default" configuration file
|
||||
.add_source(File::from_str(&toml, FileFormat::Toml))
|
||||
// Layer on the environment-specific values.
|
||||
// Add in settings from environment variables (with a prefix of LEPTOS and '_' as separator)
|
||||
// E.g. `LEPTOS_RELOAD_PORT=5001 would set `LeptosOptions.reload_port`
|
||||
.add_source(config::Environment::with_prefix("LEPTOS").separator("_"))
|
||||
.build()?;
|
||||
|
||||
settings
|
||||
.try_deserialize()
|
||||
.map_err(|e| LeptosConfigError::ConfigError(e.to_string()))
|
||||
settings
|
||||
.try_deserialize()
|
||||
.map_err(|e| LeptosConfigError::ConfigError(e.to_string()))
|
||||
} else {
|
||||
Ok(ConfFile {
|
||||
leptos_options: LeptosOptions::try_from_env()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ fn view_fn(cx: Scope) -> impl IntoView {
|
||||
let (is_a, set_is_a) = create_signal(cx, true);
|
||||
|
||||
let handle_toggle = move |_| {
|
||||
trace!("toggling");
|
||||
trace!("toggling");
|
||||
if is_a() {
|
||||
set_b(a());
|
||||
|
||||
@@ -55,11 +55,14 @@ fn view_fn(cx: Scope) -> impl IntoView {
|
||||
}
|
||||
};
|
||||
|
||||
let a_tag = view! { cx, <svg::a/> };
|
||||
|
||||
view! { cx,
|
||||
<>
|
||||
<div>
|
||||
<button on:click=handle_toggle>"Toggle"</button>
|
||||
</div>
|
||||
<svg>{a_tag}</svg>
|
||||
<Example/>
|
||||
<A child=Signal::from(a) />
|
||||
<A child=Signal::from(b) />
|
||||
@@ -74,23 +77,23 @@ fn A(cx: Scope, child: Signal<View>) -> impl IntoView {
|
||||
|
||||
#[component]
|
||||
fn Example(cx: Scope) -> impl IntoView {
|
||||
trace!("rendering <Example/>");
|
||||
|
||||
let (value, set_value) = create_signal(cx, 10);
|
||||
trace!("rendering <Example/>");
|
||||
|
||||
let memo = create_memo(cx, move |_| value() * 2);
|
||||
let derived = Signal::derive(cx, move || {
|
||||
value() * 3
|
||||
});
|
||||
let (value, set_value) = create_signal(cx, 10);
|
||||
|
||||
create_effect(cx, move |_| {
|
||||
trace!("logging value of derived..., {}", derived.get());
|
||||
});
|
||||
let memo = create_memo(cx, move |_| value() * 2);
|
||||
let derived = Signal::derive(cx, move || value() * 3);
|
||||
|
||||
create_effect(cx, move |_| {
|
||||
trace!("logging value of derived..., {}", derived.get());
|
||||
});
|
||||
|
||||
set_timeout(move || { set_value.update(|v| *v += 1)}, std::time::Duration::from_millis(50));
|
||||
set_timeout(
|
||||
move || set_value.update(|v| *v += 1),
|
||||
std::time::Duration::from_millis(50),
|
||||
);
|
||||
|
||||
view! { cx,
|
||||
view! { cx,
|
||||
<h1>"Example"</h1>
|
||||
<button on:click=move |_| set_value.update(|value| *value += 1)>
|
||||
"Click me"
|
||||
|
||||
@@ -15,6 +15,15 @@ pub fn set_property(
|
||||
};
|
||||
}
|
||||
|
||||
/// Gets the value of a property set on a DOM element.
|
||||
pub fn get_property(
|
||||
el: &web_sys::Element,
|
||||
prop_name: &str,
|
||||
) -> Result<JsValue, JsValue> {
|
||||
let key = JsValue::from_str(prop_name);
|
||||
js_sys::Reflect::get(el, &key)
|
||||
}
|
||||
|
||||
/// Returns the current [`window.location`](https://developer.mozilla.org/en-US/docs/Web/API/Window/location).
|
||||
pub fn location() -> web_sys::Location {
|
||||
window().location()
|
||||
|
||||
@@ -90,11 +90,11 @@ where
|
||||
element: el,
|
||||
};
|
||||
|
||||
HtmlElement {
|
||||
cx,
|
||||
element,
|
||||
#[cfg(debug_assertions)]
|
||||
span: ::tracing::Span::current()
|
||||
HtmlElement {
|
||||
cx,
|
||||
element,
|
||||
#[cfg(debug_assertions)]
|
||||
span: ::tracing::Span::current(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
cx,
|
||||
element,
|
||||
#[cfg(debug_assertions)]
|
||||
span
|
||||
span
|
||||
} = self;
|
||||
|
||||
HtmlElement {
|
||||
@@ -589,17 +589,17 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
) -> Self {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
{
|
||||
cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let onspan = ::tracing::span!(
|
||||
parent: &self.span,
|
||||
::tracing::Level::TRACE,
|
||||
"on",
|
||||
event = %event.name()
|
||||
);
|
||||
let _onguard = onspan.enter();
|
||||
}
|
||||
}
|
||||
cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let onspan = ::tracing::span!(
|
||||
parent: &self.span,
|
||||
::tracing::Level::TRACE,
|
||||
"on",
|
||||
event = %event.name()
|
||||
);
|
||||
let _onguard = onspan.enter();
|
||||
}
|
||||
}
|
||||
let event_name = event.name();
|
||||
|
||||
if event.bubbles() {
|
||||
@@ -632,6 +632,10 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
{
|
||||
if !HydrationCtx::is_hydrating() {
|
||||
// add a debug-only, run-time warning for the SVG <a> element
|
||||
#[cfg(debug_assertions)]
|
||||
warn_on_ambiguous_a(self.element.as_ref(), &child);
|
||||
|
||||
mount_child(MountKind::Append(self.element.as_ref()), &child);
|
||||
}
|
||||
|
||||
@@ -915,6 +919,23 @@ macro_rules! generate_html_tags {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(debug_assertions, target_arch = "wasm32", feature = "web"))]
|
||||
fn warn_on_ambiguous_a(parent: &web_sys::Element, child: &View) {
|
||||
if let View::Element(el) = &child {
|
||||
if el.name == "a" {
|
||||
if parent.namespace_uri() != el.element.namespace_uri() {
|
||||
crate::warn!(
|
||||
"Warning: you are appending an SVG <a/> to an HTML element, or an \
|
||||
HTML <a/> to an SVG. Typically, this occurs when you create an \
|
||||
<a/> with the `view` macro and append it to an SVG, but the \
|
||||
framework assumed it was HTML when you created it. To specify that \
|
||||
it is an SVG <a/>, use <svg::a/> in the view macro."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generate_html_tags![
|
||||
// ==========================
|
||||
// Main root
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![deny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
#![cfg_attr(not(feature = "stable"), feature(fn_traits))]
|
||||
#![cfg_attr(not(feature = "stable"), feature(unboxed_closures))]
|
||||
|
||||
@@ -207,10 +208,10 @@ impl Element {
|
||||
};
|
||||
|
||||
HtmlElement {
|
||||
cx,
|
||||
element,
|
||||
#[cfg(debug_assertions)]
|
||||
span: ::tracing::Span::current()
|
||||
cx,
|
||||
element,
|
||||
#[cfg(debug_assertions)]
|
||||
span: ::tracing::Span::current(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -449,4 +449,4 @@ fn to_kebab_case(name: &str) -> String {
|
||||
}
|
||||
|
||||
new_name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,10 +378,11 @@ impl Docs {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
enum PropOpt {
|
||||
Optional,
|
||||
OptionalNoStrip,
|
||||
OptionalWithDefault(syn::Lit),
|
||||
StripOption,
|
||||
Into,
|
||||
}
|
||||
@@ -390,7 +391,8 @@ impl PropOpt {
|
||||
fn from_attribute(attr: &Attribute) -> Option<HashSet<Self>> {
|
||||
const ABORT_OPT_MESSAGE: &str = "only `optional`, \
|
||||
`optional_no_strip`, \
|
||||
`strip_option`, and `into` are \
|
||||
`strip_option`, \
|
||||
`default` and `into` are \
|
||||
allowed as arguments to `#[prop()]`";
|
||||
|
||||
if attr.path != parse_quote!(prop) {
|
||||
@@ -401,8 +403,8 @@ impl PropOpt {
|
||||
Some(
|
||||
nested
|
||||
.iter()
|
||||
.map(|opt| {
|
||||
if let NestedMeta::Meta(Meta::Path(opt)) = opt {
|
||||
.map(|opt| match opt {
|
||||
NestedMeta::Meta(Meta::Path(opt)) => {
|
||||
if *opt == parse_quote!(optional) {
|
||||
PropOpt::Optional
|
||||
} else if *opt == parse_quote!(optional_no_strip) {
|
||||
@@ -418,9 +420,23 @@ impl PropOpt {
|
||||
help = ABORT_OPT_MESSAGE
|
||||
);
|
||||
}
|
||||
} else {
|
||||
abort!(opt, ABORT_OPT_MESSAGE,);
|
||||
}
|
||||
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
|
||||
path,
|
||||
eq_token: _,
|
||||
lit,
|
||||
})) => {
|
||||
if *path == parse_quote!(default) {
|
||||
PropOpt::OptionalWithDefault(lit.to_owned())
|
||||
} else {
|
||||
abort!(
|
||||
opt,
|
||||
"invalid prop option";
|
||||
help = ABORT_OPT_MESSAGE
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => abort!(opt, ABORT_OPT_MESSAGE,),
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
@@ -437,6 +453,7 @@ impl PropOpt {
|
||||
|
||||
struct TypedBuilderOpts {
|
||||
default: bool,
|
||||
default_with_value: Option<syn::Lit>,
|
||||
strip_option: bool,
|
||||
into: bool,
|
||||
}
|
||||
@@ -445,6 +462,10 @@ impl TypedBuilderOpts {
|
||||
fn from_opts(opts: &HashSet<PropOpt>, is_ty_option: bool) -> Self {
|
||||
Self {
|
||||
default: opts.contains(&PropOpt::Optional) || opts.contains(&PropOpt::OptionalNoStrip),
|
||||
default_with_value: opts.iter().find_map(|p| match p {
|
||||
PropOpt::OptionalWithDefault(v) => Some(v.to_owned()),
|
||||
_ => None,
|
||||
}),
|
||||
strip_option: opts.contains(&PropOpt::StripOption)
|
||||
|| (opts.contains(&PropOpt::Optional) && is_ty_option),
|
||||
into: opts.contains(&PropOpt::Into),
|
||||
@@ -454,7 +475,9 @@ impl TypedBuilderOpts {
|
||||
|
||||
impl ToTokens for TypedBuilderOpts {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let default = if self.default {
|
||||
let default = if let Some(v) = &self.default_with_value {
|
||||
quote! { default=#v, }
|
||||
} else if self.default {
|
||||
quote! { default, }
|
||||
} else {
|
||||
quote! {}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![cfg_attr(not(feature = "stable"), feature(proc_macro_span))]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate proc_macro_error;
|
||||
@@ -184,7 +185,7 @@ mod server;
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// // this allows you to use CSS frameworks that include complex class names
|
||||
/// view! { cx,
|
||||
/// <div
|
||||
/// <div
|
||||
/// class=("is-[this_-_really]-necessary-42", move || count() < 3)
|
||||
/// >
|
||||
/// "Now you see me, now you don’t."
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use syn::{spanned::Spanned, ExprPath, Expr, ExprLit, Lit};
|
||||
use syn::{spanned::Spanned, Expr, ExprLit, ExprPath, Lit};
|
||||
use syn_rsx::{Node, NodeAttribute, NodeElement, NodeName};
|
||||
|
||||
use crate::{is_component_node, Mode};
|
||||
@@ -409,14 +409,12 @@ fn set_class_attribute_ssr(
|
||||
.filter_map(|a| {
|
||||
if let Node::Attribute(a) = a {
|
||||
if a.key.to_string() == "class" {
|
||||
if a.value.as_ref().and_then(value_to_string).is_some() {
|
||||
if a.value.as_ref().and_then(value_to_string).is_some()
|
||||
|| fancy_class_name(&a.key.to_string(), cx, a).is_some()
|
||||
{
|
||||
None
|
||||
} else {
|
||||
if fancy_class_name(&a.key.to_string(), cx, a).is_some() {
|
||||
None
|
||||
} else {
|
||||
Some((a.key.span(), &a.value))
|
||||
}
|
||||
Some((a.key.span(), &a.value))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
@@ -435,11 +433,11 @@ fn set_class_attribute_ssr(
|
||||
let name = node.key.to_string();
|
||||
if name == "class" {
|
||||
return if let Some((_, name, value)) = fancy_class_name(&name, cx, node) {
|
||||
let span = node.key.span();
|
||||
Some((span, name, value))
|
||||
let span = node.key.span();
|
||||
Some((span, name, value))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None
|
||||
};
|
||||
}
|
||||
if name.starts_with("class:") || name.starts_with("class-") {
|
||||
let name = if name.starts_with("class:") {
|
||||
@@ -569,8 +567,9 @@ fn element_to_tokens(cx: &Ident, node: &NodeElement, mut parent_type: TagType) -
|
||||
let name = &node.name;
|
||||
match parent_type {
|
||||
TagType::Unknown => {
|
||||
proc_macro_error::emit_warning!(name.span(), "The view macro is assuming this is an HTML element, \
|
||||
but it is ambiguous; if it is an SVG or MathML element, prefix with svg:: or math::");
|
||||
// We decided this warning was too aggressive, but I'll leave it here in case we want it later
|
||||
/* proc_macro_error::emit_warning!(name.span(), "The view macro is assuming this is an HTML element, \
|
||||
but it is ambiguous; if it is an SVG or MathML element, prefix with svg:: or math::"); */
|
||||
quote! {
|
||||
leptos::leptos_dom::#name(#cx)
|
||||
}
|
||||
@@ -764,7 +763,7 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
|
||||
}
|
||||
} else {
|
||||
let name = name.replacen("attr:", "", 1);
|
||||
|
||||
|
||||
if let Some((fancy, _, _)) = fancy_class_name(&name, cx, node) {
|
||||
return fancy;
|
||||
}
|
||||
@@ -1067,8 +1066,12 @@ fn parse_event(event_name: &str) -> (&str, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
fn fancy_class_name<'a>(name: &str, cx: &Ident, node: &'a NodeAttribute) -> Option<(TokenStream, String, &'a Expr)> {
|
||||
// special case for complex class names:
|
||||
fn fancy_class_name<'a>(
|
||||
name: &str,
|
||||
cx: &Ident,
|
||||
node: &'a NodeAttribute,
|
||||
) -> Option<(TokenStream, String, &'a Expr)> {
|
||||
// special case for complex class names:
|
||||
// e.g., Tailwind `class=("mt-[calc(100vh_-_3rem)]", true)`
|
||||
if name == "class" {
|
||||
if let Some(expr) = node.value.as_ref() {
|
||||
@@ -1079,7 +1082,10 @@ fn fancy_class_name<'a>(name: &str, cx: &Ident, node: &'a NodeAttribute) -> Opti
|
||||
span => .class
|
||||
};
|
||||
let class_name = &tuple.elems[0];
|
||||
let class_name = if let Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) = class_name {
|
||||
let class_name = if let Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(s), ..
|
||||
}) = class_name
|
||||
{
|
||||
s.value()
|
||||
} else {
|
||||
proc_macro_error::emit_error!(
|
||||
@@ -1094,8 +1100,8 @@ fn fancy_class_name<'a>(name: &str, cx: &Ident, node: &'a NodeAttribute) -> Opti
|
||||
#class(#class_name, (#cx, #value))
|
||||
},
|
||||
class_name,
|
||||
value
|
||||
))
|
||||
value,
|
||||
));
|
||||
} else {
|
||||
proc_macro_error::emit_error!(
|
||||
tuple.span(),
|
||||
|
||||
@@ -4,7 +4,7 @@ version.workspace = true
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/gbj/leptos"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Reactive system for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![forbid(unsafe_code)]
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
collections::HashMap,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![forbid(unsafe_code)]
|
||||
use crate::runtime::{with_runtime, RuntimeId};
|
||||
use crate::{debug_warn, Runtime, Scope, ScopeProperty};
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{ResourceId, runtime::PinnedFuture};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
#![forbid(unsafe_code)]
|
||||
use crate::{runtime::PinnedFuture, ResourceId};
|
||||
use cfg_if::cfg_if;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub struct SharedContext {
|
||||
pub events: Vec<()>,
|
||||
@@ -40,16 +41,16 @@ impl Default for SharedContext {
|
||||
.map_err(|_| ())
|
||||
.and_then(|pr| serde_wasm_bindgen::from_value(pr).map_err(|_| ()))
|
||||
.unwrap_or_default();
|
||||
|
||||
|
||||
let resolved_resources = js_sys::Reflect::get(
|
||||
&web_sys::window().unwrap(),
|
||||
&wasm_bindgen::JsValue::from_str("__LEPTOS_RESOLVED_RESOURCES"),
|
||||
)
|
||||
.unwrap_or(wasm_bindgen::JsValue::NULL);
|
||||
|
||||
|
||||
let resolved_resources =
|
||||
serde_wasm_bindgen::from_value(resolved_resources).unwrap_or_default();
|
||||
|
||||
|
||||
Self {
|
||||
events: Default::default(),
|
||||
pending_resources,
|
||||
|
||||
@@ -82,6 +82,7 @@ mod signal;
|
||||
mod signal_wrappers_read;
|
||||
mod signal_wrappers_write;
|
||||
mod spawn;
|
||||
mod spawn_microtask;
|
||||
mod stored_value;
|
||||
mod suspense;
|
||||
|
||||
@@ -98,6 +99,7 @@ pub use signal::*;
|
||||
pub use signal_wrappers_read::*;
|
||||
pub use signal_wrappers_write::*;
|
||||
pub use spawn::*;
|
||||
pub use spawn_microtask::*;
|
||||
pub use stored_value::*;
|
||||
pub use suspense::*;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![forbid(unsafe_code)]
|
||||
use crate::{ReadSignal, Scope, SignalError, UntrackedGettableSignal};
|
||||
use std::fmt::Debug;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![forbid(unsafe_code)]
|
||||
use crate::{
|
||||
create_effect, create_isomorphic_effect, create_memo, create_signal, queue_microtask,
|
||||
runtime::{with_runtime, RuntimeId},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![forbid(unsafe_code)]
|
||||
use crate::{
|
||||
hydration::SharedContext, serialization::Serializable, AnyEffect, AnyResource, Effect,
|
||||
EffectId, Memo, ReadSignal, ResourceId, ResourceState, RwSignal, Scope, ScopeDisposer, ScopeId,
|
||||
@@ -129,14 +130,14 @@ impl RuntimeId {
|
||||
id,
|
||||
ty: PhantomData,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller()
|
||||
defined_at: std::panic::Location::caller(),
|
||||
},
|
||||
WriteSignal {
|
||||
runtime: self,
|
||||
id,
|
||||
ty: PhantomData,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller()
|
||||
defined_at: std::panic::Location::caller(),
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -156,7 +157,7 @@ impl RuntimeId {
|
||||
id,
|
||||
ty: PhantomData,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller()
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +174,7 @@ impl RuntimeId {
|
||||
f,
|
||||
value: RefCell::new(None),
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at
|
||||
defined_at,
|
||||
};
|
||||
let id = { runtime.effects.borrow_mut().insert(Rc::new(effect)) };
|
||||
id.run::<T>(self);
|
||||
@@ -206,7 +207,7 @@ impl RuntimeId {
|
||||
Memo(
|
||||
read,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at
|
||||
defined_at,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -322,10 +323,12 @@ impl Runtime {
|
||||
self.resources
|
||||
.borrow()
|
||||
.iter()
|
||||
.filter_map(|(resource_id, res)| if matches!(res, AnyResource::Serializable(_)) {
|
||||
Some(resource_id)
|
||||
} else {
|
||||
None
|
||||
.filter_map(|(resource_id, res)| {
|
||||
if matches!(res, AnyResource::Serializable(_)) {
|
||||
Some(resource_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![forbid(unsafe_code)]
|
||||
use crate::{
|
||||
runtime::{with_runtime, RuntimeId},
|
||||
EffectId, PinnedFuture, ResourceId, SignalId, SuspenseContext,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![forbid(unsafe_code)]
|
||||
use std::{cell::RefCell, collections::HashMap, fmt::Debug, hash::Hash, rc::Rc};
|
||||
|
||||
use crate::{create_isomorphic_effect, create_signal, ReadSignal, Scope, WriteSignal};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![forbid(unsafe_code)]
|
||||
use cfg_if::cfg_if;
|
||||
use std::rc::Rc;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#![forbid(unsafe_code)]
|
||||
use crate::{
|
||||
debug_warn,
|
||||
runtime::{with_runtime, RuntimeId},
|
||||
spawn_local, Runtime, Scope, ScopeProperty, UntrackedGettableSignal, UntrackedSettableSignal,
|
||||
Runtime, Scope, ScopeProperty, UntrackedGettableSignal, UntrackedSettableSignal,
|
||||
};
|
||||
use cfg_if::cfg_if;
|
||||
use futures::Stream;
|
||||
use std::{fmt::Debug, marker::PhantomData};
|
||||
use thiserror::Error;
|
||||
@@ -67,6 +69,9 @@ pub fn create_signal<T>(cx: Scope, value: T) -> (ReadSignal<T>, WriteSignal<T>)
|
||||
/// [Stream](futures::stream::Stream).
|
||||
/// If the stream has not yet emitted a value since the signal was created, the signal's
|
||||
/// value will be `None`.
|
||||
///
|
||||
/// **Note**: If used on the server side during server rendering, this will return `None`
|
||||
/// immediately and not begin driving the stream.
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(
|
||||
@@ -79,17 +84,27 @@ pub fn create_signal<T>(cx: Scope, value: T) -> (ReadSignal<T>, WriteSignal<T>)
|
||||
)]
|
||||
pub fn create_signal_from_stream<T>(
|
||||
cx: Scope,
|
||||
#[allow(unused_mut)] // allowed because needed for SSR
|
||||
mut stream: impl Stream<Item = T> + Unpin + 'static,
|
||||
) -> ReadSignal<Option<T>> {
|
||||
use futures::StreamExt;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
_ = stream;
|
||||
let (read, _) = create_signal(cx, None);
|
||||
read
|
||||
} else {
|
||||
use crate::spawn_local;
|
||||
use futures::StreamExt;
|
||||
|
||||
let (read, write) = create_signal(cx, None);
|
||||
spawn_local(async move {
|
||||
while let Some(value) = stream.next().await {
|
||||
write.set(Some(value));
|
||||
let (read, write) = create_signal(cx, None);
|
||||
spawn_local(async move {
|
||||
while let Some(value) = stream.next().await {
|
||||
write.set(Some(value));
|
||||
}
|
||||
});
|
||||
read
|
||||
}
|
||||
});
|
||||
read
|
||||
}
|
||||
}
|
||||
|
||||
/// The getter for a reactive signal.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![forbid(unsafe_code)]
|
||||
use crate::{store_value, Memo, ReadSignal, RwSignal, Scope, StoredValue, UntrackedGettableSignal};
|
||||
|
||||
/// Helper trait for converting `Fn() -> T` closures into
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#![forbid(unsafe_code)]
|
||||
use crate::{store_value, RwSignal, Scope, StoredValue, WriteSignal};
|
||||
|
||||
/// Helper trait for converting `Fn(T)` into [`SignalSetter<T>`].
|
||||
|
||||
@@ -1,31 +1,7 @@
|
||||
#![forbid(unsafe_code)]
|
||||
use cfg_if::cfg_if;
|
||||
use std::future::Future;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
/// Exposes the [queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) method
|
||||
/// in the browser, and simply runs the given function when on the server.
|
||||
pub fn queue_microtask(task: impl FnOnce() + 'static) {
|
||||
microtask(wasm_bindgen::closure::Closure::once_into_js(task));
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
#[wasm_bindgen::prelude::wasm_bindgen(
|
||||
inline_js = "export function microtask(f) { queueMicrotask(f); }"
|
||||
)]
|
||||
extern "C" {
|
||||
fn microtask(task: wasm_bindgen::JsValue);
|
||||
}
|
||||
} else {
|
||||
/// Exposes the [queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) method
|
||||
/// in the browser, and simply runs the given function when on the server.
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
pub fn queue_microtask(task: impl FnOnce()) {
|
||||
task();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns and runs a thread-local [std::future::Future] in a platform-independent way.
|
||||
///
|
||||
/// This can be used to interface with any `async` code.
|
||||
@@ -33,7 +9,7 @@ pub fn spawn_local<F>(fut: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
cfg_if::cfg_if! {
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
wasm_bindgen_futures::spawn_local(fut)
|
||||
}
|
||||
|
||||
31
leptos_reactive/src/spawn_microtask.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
// `queue_microtask` needs to be in its own module, which is the only thing
|
||||
// in this entire framework that requires "unsafe" code (because Rust seems to
|
||||
// that a `wasm_bindgen` imported function like this is unsafe)
|
||||
// this is stupid, and one day hopefully web_sys will add queue_microtask itself
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
/// Exposes the [queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) method
|
||||
/// in the browser, and simply runs the given function when on the server.
|
||||
pub fn queue_microtask(task: impl FnOnce() + 'static) {
|
||||
microtask(wasm_bindgen::closure::Closure::once_into_js(task));
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
#[wasm_bindgen::prelude::wasm_bindgen(
|
||||
inline_js = "export function microtask(f) { queueMicrotask(f); }"
|
||||
)]
|
||||
extern "C" {
|
||||
fn microtask(task: wasm_bindgen::JsValue);
|
||||
}
|
||||
} else {
|
||||
/// Exposes the [queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) method
|
||||
/// in the browser, and simply runs the given function when on the server.
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
pub fn queue_microtask(task: impl FnOnce()) {
|
||||
task();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
#![forbid(unsafe_code)]
|
||||
use crate::{create_rw_signal, RwSignal, Scope, UntrackedGettableSignal, UntrackedSettableSignal};
|
||||
|
||||
/// A **non-reactive** wrapper for any value, which can be created with [store_value].
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{create_signal, spawn::queue_microtask, ReadSignal, Scope, WriteSignal};
|
||||
#![forbid(unsafe_code)]
|
||||
use crate::{create_signal, queue_microtask, ReadSignal, Scope, WriteSignal};
|
||||
|
||||
/// Tracks [Resource](crate::Resource)s that are read under a suspense context,
|
||||
/// i.e., within a [`Suspense`](https://docs.rs/leptos_core/latest/leptos_core/fn.Suspense.html) component.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![deny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! # Leptos Server Functions
|
||||
//!
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#![deny(missing_docs)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! # Leptos Meta
|
||||
//!
|
||||
|
||||
@@ -4,7 +4,7 @@ version = "0.1.0-beta"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/gbj/leptos"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Router for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -30,10 +30,12 @@ pub fn Form<A>(
|
||||
error: Option<RwSignal<Option<Box<dyn Error>>>>,
|
||||
/// A callback will be called with the [FormData](web_sys::FormData) when the form is submitted.
|
||||
#[prop(optional)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
on_form_data: Option<Rc<dyn Fn(&web_sys::FormData)>>,
|
||||
/// A callback will be called with the [Response](web_sys::Response) the server sends in response
|
||||
/// to a form submission.
|
||||
#[prop(optional)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
on_response: Option<Rc<dyn Fn(&web_sys::Response)>>,
|
||||
/// Component children; should include the HTML of the form elements.
|
||||
children: Box<dyn FnOnce(Scope) -> Fragment>,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::leptos_dom::IntoView;
|
||||
use leptos::*;
|
||||
|
||||
@@ -83,29 +82,15 @@ where
|
||||
}
|
||||
});
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
view! { cx,
|
||||
<html::a
|
||||
href=move || href.get().unwrap_or_default()
|
||||
prop:state={state.map(|s| s.to_js_value())}
|
||||
prop:replace={replace}
|
||||
aria-current=move || if is_active.get() { Some("page") } else { None }
|
||||
class=move || class.as_ref().map(|class| class.get())
|
||||
>
|
||||
{children(cx)}
|
||||
</html::a>
|
||||
}
|
||||
} else {
|
||||
view! { cx,
|
||||
<html::a
|
||||
href=move || href.get().unwrap_or_default()
|
||||
aria-current=move || if is_active.get() { Some("page") } else { None }
|
||||
class=move || class.as_ref().map(|class| class.get())
|
||||
>
|
||||
{children(cx)}
|
||||
</html::a>
|
||||
}
|
||||
}
|
||||
view! { cx,
|
||||
<a
|
||||
href=move || href.get().unwrap_or_default()
|
||||
prop:state={state.map(|s| s.to_js_value())}
|
||||
prop:replace={replace}
|
||||
aria-current=move || if is_active.get() { Some("page") } else { None }
|
||||
class=move || class.as_ref().map(|class| class.get())
|
||||
>
|
||||
{children(cx)}
|
||||
</a>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ use wasm_bindgen::JsCast;
|
||||
use leptos_reactive::use_transition;
|
||||
|
||||
use crate::{
|
||||
create_location, matching::resolve_path, History, Location, LocationChange, RouteContext,
|
||||
RouterIntegrationContext, State,
|
||||
create_location, matching::resolve_path, Branch, History, Location, LocationChange,
|
||||
RouteContext, RouterIntegrationContext, State,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
@@ -49,6 +49,8 @@ pub struct RouterContext {
|
||||
pub(crate) struct RouterContextInner {
|
||||
pub location: Location,
|
||||
pub base: RouteContext,
|
||||
pub possible_routes: RefCell<Option<Vec<Branch>>>,
|
||||
#[allow(unused)] // used in CSR/hydrate
|
||||
base_path: String,
|
||||
history: Box<dyn History>,
|
||||
cx: Scope,
|
||||
@@ -85,7 +87,15 @@ impl RouterContext {
|
||||
let history = use_context::<RouterIntegrationContext>(cx)
|
||||
.unwrap_or_else(|| RouterIntegrationContext(Rc::new(crate::BrowserIntegration {})));
|
||||
} else {
|
||||
let history = use_context::<RouterIntegrationContext>(cx).expect("You must call provide_context::<RouterIntegrationContext>(cx, ...) somewhere above the <Router/>.");
|
||||
let history = use_context::<RouterIntegrationContext>(cx).unwrap_or_else(|| {
|
||||
let msg = "No router integration found.\n\nIf you are using this in the browser, \
|
||||
you should enable `feature = [\"csr\"]` or `feature = [\"hydrate\"] in your \
|
||||
`leptos_router` import.\n\nIf you are using this on the server without a \
|
||||
Leptos server integration, you must call provide_context::<RouterIntegrationContext>(cx, ...) \
|
||||
somewhere above the <Router/>.";
|
||||
leptos::debug_warn!("{}", msg);
|
||||
panic!("{}", msg);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -153,6 +163,7 @@ impl RouterContext {
|
||||
referrers,
|
||||
state,
|
||||
set_state,
|
||||
possible_routes: Default::default(),
|
||||
});
|
||||
|
||||
// handle all click events on anchor tags
|
||||
@@ -175,6 +186,15 @@ impl RouterContext {
|
||||
pub fn base(&self) -> RouteContext {
|
||||
self.inner.base.clone()
|
||||
}
|
||||
|
||||
/// A list of all possible routes this router can match.
|
||||
pub fn possible_branches(&self) -> Vec<Branch> {
|
||||
self.inner
|
||||
.possible_routes
|
||||
.borrow()
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl RouterContextInner {
|
||||
@@ -203,7 +223,6 @@ impl RouterContextInner {
|
||||
|
||||
if resolved_to != this.reference.get() || options.state != (this.state).get() {
|
||||
if cfg!(feature = "server") {
|
||||
// TODO server out
|
||||
self.history.navigate(&LocationChange {
|
||||
value: resolved_to,
|
||||
replace: options.replace,
|
||||
@@ -329,19 +348,29 @@ impl RouterContextInner {
|
||||
}
|
||||
|
||||
let to = path_name + &unescape(&url.search) + &unescape(&url.hash);
|
||||
// TODO "state" is set as a prop, not an attribute
|
||||
let state = a.get_attribute("state"); // TODO state
|
||||
let state = get_property(a.unchecked_ref(), "state")
|
||||
.ok()
|
||||
.and_then(|value| {
|
||||
if value == wasm_bindgen::JsValue::UNDEFINED {
|
||||
None
|
||||
} else {
|
||||
Some(value)
|
||||
}
|
||||
});
|
||||
|
||||
ev.prevent_default();
|
||||
|
||||
let replace = get_property(a.unchecked_ref(), "replace")
|
||||
.ok()
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or(false);
|
||||
if let Err(e) = self.navigate_from_route(
|
||||
&to,
|
||||
&NavigateOptions {
|
||||
resolve: false,
|
||||
// TODO "replace" is set as a prop, not an attribute
|
||||
replace: a.has_attribute("replace"),
|
||||
replace,
|
||||
scroll: !a.has_attribute("noscroll"),
|
||||
state: State(None), // TODO state
|
||||
state: State(state),
|
||||
},
|
||||
) {
|
||||
log::error!("{e:#?}");
|
||||
|
||||
@@ -42,6 +42,7 @@ pub fn Routes(
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
create_branches(
|
||||
&children,
|
||||
&base.unwrap_or_default(),
|
||||
@@ -49,6 +50,11 @@ pub fn Routes(
|
||||
&mut branches,
|
||||
);
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
if let Some(context) = use_context::<crate::PossibleBranchContext>(cx) {
|
||||
*context.0.borrow_mut() = branches.clone();
|
||||
}
|
||||
|
||||
// whenever path changes, update matches
|
||||
let matches = create_memo(cx, {
|
||||
let router = router.clone();
|
||||
|
||||
35
router/src/extract_routes.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use leptos::*;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::{Branch, RouterIntegrationContext, ServerIntegration};
|
||||
|
||||
/// Context to contain all possible routes.
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct PossibleBranchContext(pub(crate) Rc<RefCell<Vec<Branch>>>);
|
||||
|
||||
/// Generates a list of all routes this application could possibly serve. This returns the raw routes in the leptos_router
|
||||
/// format. Odds are you want `generate_route_list()` from either the actix or axum integrations if you want
|
||||
/// to work with their router
|
||||
pub fn generate_route_list_inner<IV>(app_fn: impl FnOnce(Scope) -> IV + 'static) -> Vec<String>
|
||||
where
|
||||
IV: IntoView + 'static,
|
||||
{
|
||||
let runtime = create_runtime();
|
||||
run_scope(runtime, move |cx| {
|
||||
let integration = ServerIntegration {
|
||||
path: "http://leptos.rs/".to_string(),
|
||||
};
|
||||
|
||||
provide_context(cx, RouterIntegrationContext::new(integration));
|
||||
let branches = PossibleBranchContext::default();
|
||||
provide_context(cx, branches.clone());
|
||||
|
||||
let _ = app_fn(cx).into_view(cx);
|
||||
|
||||
let branches = branches.0.borrow();
|
||||
branches
|
||||
.iter()
|
||||
.flat_map(|branch| branch.routes.last().map(|route| route.pattern.clone()))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! # Leptos Router
|
||||
//!
|
||||
//! Leptos Router is a router and state management tool for web applications
|
||||
//! written in Rust using the [Leptos](https://github.com/gbj/leptos) web framework.
|
||||
//! written in Rust using the [Leptos](https://github.com/leptos-rs/leptos) web framework.
|
||||
//! It is ”isomorphic,” i.e., it can be used for client-side applications/single-page
|
||||
//! apps (SPAs), server-side rendering/multi-page apps (MPAs), or to synchronize
|
||||
//! state between the two.
|
||||
@@ -184,12 +186,16 @@
|
||||
#![cfg_attr(not(feature = "stable"), feature(type_name_of_val))]
|
||||
|
||||
mod components;
|
||||
#[cfg(any(feature = "ssr", doc))]
|
||||
mod extract_routes;
|
||||
mod history;
|
||||
mod hooks;
|
||||
#[doc(hidden)]
|
||||
pub mod matching;
|
||||
|
||||
pub use components::*;
|
||||
#[cfg(any(feature = "ssr", doc))]
|
||||
pub use extract_routes::*;
|
||||
pub use history::*;
|
||||
pub use hooks::*;
|
||||
pub use matching::*;
|
||||
|
||||