Compare commits
84 Commits
ci-disk-sp
...
correct-ax
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5df89b0d25 | ||
|
|
c050456a47 | ||
|
|
f6622448e9 | ||
|
|
78825401c5 | ||
|
|
f2b7ad6244 | ||
|
|
43f107d9bd | ||
|
|
a2612ca1fc | ||
|
|
a000c84e1a | ||
|
|
1377b823e2 | ||
|
|
4d21f5ac63 | ||
|
|
c56806713e | ||
|
|
2f6aa6753d | ||
|
|
2544687acd | ||
|
|
3d25e86c23 | ||
|
|
28ec3a6cda | ||
|
|
8b92a561a3 | ||
|
|
e490c0423f | ||
|
|
b6579a040a | ||
|
|
01e024b726 | ||
|
|
6603c44ce2 | ||
|
|
977f11b180 | ||
|
|
fb34b29ccf | ||
|
|
6576d8eda1 | ||
|
|
6b729f9131 | ||
|
|
dc60c35b58 | ||
|
|
32ec9cc57e | ||
|
|
35601d8284 | ||
|
|
49bc7d2a27 | ||
|
|
aa7c7367dc | ||
|
|
70808c5262 | ||
|
|
67503a108d | ||
|
|
ef52a01838 | ||
|
|
4cacfe98d8 | ||
|
|
52e653316e | ||
|
|
bf5b6ca9c2 | ||
|
|
8875939a27 | ||
|
|
b9a83277d9 | ||
|
|
dad84b5867 | ||
|
|
1f29d29947 | ||
|
|
1b8175e2fa | ||
|
|
dbe3ec015c | ||
|
|
492fa6c6d3 | ||
|
|
343e8c8abe | ||
|
|
656d20cb65 | ||
|
|
a0a66b75dd | ||
|
|
085ba3506c | ||
|
|
63f3780eda | ||
|
|
c41cf879d1 | ||
|
|
b34f2070d3 | ||
|
|
7fa21defa6 | ||
|
|
bdd9abc04d | ||
|
|
1d25134213 | ||
|
|
de73622949 | ||
|
|
5d3cfc6483 | ||
|
|
6fbbd09000 | ||
|
|
f2842cf14e | ||
|
|
a6b6864bc5 | ||
|
|
063b946cd4 | ||
|
|
5a2c9ea345 | ||
|
|
808d87598b | ||
|
|
0956c48b1e | ||
|
|
8915e2615b | ||
|
|
7f47134058 | ||
|
|
af7b93fa1e | ||
|
|
ed940f577a | ||
|
|
916f30a07b | ||
|
|
e01c565de1 | ||
|
|
dffe195cdc | ||
|
|
a5e2587555 | ||
|
|
af8889fab2 | ||
|
|
267c1cfc34 | ||
|
|
3498378e60 | ||
|
|
f8c680d14d | ||
|
|
b852e459a9 | ||
|
|
681f10ec8d | ||
|
|
1d480791a1 | ||
|
|
7acc309f66 | ||
|
|
9527de15ed | ||
|
|
aeb25a715a | ||
|
|
46e91a538c | ||
|
|
1fe526c99c | ||
|
|
6b05918807 | ||
|
|
677e4f2540 | ||
|
|
63b1837315 |
15
Cargo.toml
@@ -23,10 +23,23 @@ members = [
|
||||
]
|
||||
exclude = ["benchmarks", "examples"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0-beta"
|
||||
|
||||
[workspace.dependencies]
|
||||
leptos = { path = "./leptos", default-features = false, version = "0.1.0-beta" }
|
||||
leptos_dom = { path = "./leptos_dom", default-features = false, version = "0.1.0-beta" }
|
||||
leptos_macro = { path = "./leptos_macro", default-features = false, version = "0.1.0-beta" }
|
||||
leptos_reactive = { path = "./leptos_reactive", default-features = false, version = "0.1.0-beta" }
|
||||
leptos_server = { path = "./leptos_server", default-features = false, version = "0.1.0-beta" }
|
||||
leptos_config = { path = "./leptos_config", default-features = false, version = "0.1.0-beta" }
|
||||
leptos_router = { path = "./router", version = "0.1.0-beta" }
|
||||
leptos_meta = { path = "./meta", default-feature = false, version = "0.1.0-beta" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
opt-level = 'z'
|
||||
|
||||
[workspace.metadata.cargo-all-features]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
**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;">
|
||||
<img src="https://raw.githubusercontent.com/gbj/leptos/main/docs/logos/Leptos_logo_RGB.png" alt="Leptos Logo" style="width: 400px; height: auto">
|
||||
|
||||
[](https://crates.io/crates/leptos)
|
||||
[](https://docs.rs/leptos)
|
||||
@@ -63,7 +63,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 |
@@ -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]
|
||||
|
||||
@@ -209,8 +209,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()
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -15,7 +15,7 @@ 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"/>
|
||||
<Meta name="description" content="Leptos implementation of a HackerNews demo."/>
|
||||
<Router>
|
||||
<Nav />
|
||||
@@ -23,7 +23,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,6 +8,7 @@ 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 {
|
||||
@@ -18,18 +19,18 @@ cfg_if! {
|
||||
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)
|
||||
.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)?
|
||||
|
||||
@@ -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>
|
||||
|
||||
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,7 @@ pub fn App(cx: Scope) -> impl IntoView {
|
||||
view! {
|
||||
cx,
|
||||
<>
|
||||
<Stylesheet id="leptos" href="./target/site/pkg/hackernews_axum.css"/>
|
||||
<Stylesheet id="leptos" href="/pkg/hackernews_axum.css"/>
|
||||
<Meta name="description" content="Leptos implementation of a HackerNews demo."/>
|
||||
<Router>
|
||||
<Nav />
|
||||
@@ -24,7 +25,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,11 @@ cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
Router,
|
||||
error_handling::HandleError,
|
||||
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 +18,16 @@ 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/> }));
|
||||
.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>
|
||||
|
||||
@@ -7,11 +7,11 @@ edition = "2021"
|
||||
console_log = "0.2"
|
||||
log = "0.4"
|
||||
leptos = { path = "../../leptos" }
|
||||
leptos_router = { path = "../../router", features=["csr"] }
|
||||
leptos_router = { path = "../../router", features = ["csr"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
futures = "0.3"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
leptos_meta = { path = "../../meta", default-features = false }
|
||||
leptos_meta = { path = "../../meta", features = ["csr"] }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.0"
|
||||
|
||||
@@ -87,7 +87,7 @@ 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"
|
||||
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
|
||||
|
||||
@@ -1,28 +1,36 @@
|
||||
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"/>
|
||||
<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;
|
||||
|
||||
@@ -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,10 @@ 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"/>
|
||||
<Stylesheet id="leptos" href="/pkg/todo_app_sqlite.css"/>
|
||||
<Router>
|
||||
<header>
|
||||
<h1>"My Tasks"</h1>
|
||||
|
||||
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,10 @@ 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"/>
|
||||
<Stylesheet id="leptos" href="./pkg/todo_app_sqlite_axum.css"/>
|
||||
<Router>
|
||||
<header>
|
||||
<h1>"My Tasks"</h1>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_actix"
|
||||
version = "0.1.0-beta"
|
||||
version.workspace = true
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
@@ -10,13 +10,8 @@ description = "Actix integrations for the Leptos web framework."
|
||||
[dependencies]
|
||||
actix-web = "4"
|
||||
futures = "0.3"
|
||||
leptos = { path = "../../leptos", default-features = false, version = "0.1.0-alpha", features = [
|
||||
"ssr",
|
||||
] }
|
||||
leptos_meta = { path = "../../meta", default-features = false, version = "0.1.0-alpha", features = [
|
||||
"ssr",
|
||||
] }
|
||||
leptos_router = { path = "../../router", default-features = false, version = "0.1.0-alpha", features = [
|
||||
"ssr",
|
||||
] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
leptos = { workspace = true, features = ["ssr"] }
|
||||
leptos_meta = { workspace = true, features = ["ssr"] }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
regex = "1.7.0"
|
||||
tokio = "1.24.1"
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
use actix_web::{http::header, web::Bytes, *};
|
||||
use futures::{StreamExt};
|
||||
|
||||
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 +21,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 +45,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 +67,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 +142,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 +194,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 +249,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,8 +280,6 @@ 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
|
||||
// Otherwise we need to add _bg because wasm_pack always does. This is not the same as options.output_name, which is set regardless
|
||||
@@ -279,20 +290,10 @@ where IV: IntoView
|
||||
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 +327,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}
|
||||
"#
|
||||
);
|
||||
@@ -358,7 +359,7 @@ where IV: IntoView
|
||||
|
||||
let res_options = res_options.0.read().await;
|
||||
|
||||
let (status, mut headers) = (res_options.status.clone(), res_options.headers.clone());
|
||||
let (status, mut headers) = (res_options.status, res_options.headers.clone());
|
||||
let status = status.unwrap_or_default();
|
||||
|
||||
let complete_stream =
|
||||
@@ -382,3 +383,70 @@ 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();
|
||||
routes
|
||||
.iter()
|
||||
.map(|s| wildcard_re.replace_all(s, "{tail:.*}").to_string())
|
||||
.map(|s| capture_re.replace_all(&s, "{$1}").to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_axum"
|
||||
version = "0.1.0-beta"
|
||||
version.workspace = true
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
@@ -8,20 +8,14 @@ repository = "https://github.com/gbj/leptos"
|
||||
description = "Axum integrations for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
axum = {version="0.6", features=["macros"]}
|
||||
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 = { path = "../../leptos", default-features = false, version = "0.1.0-beta", features = [
|
||||
"ssr",
|
||||
] }
|
||||
leptos_meta = { path = "../../meta", default-features = false, version = "0.1.0-beta", features = [
|
||||
"ssr",
|
||||
] }
|
||||
leptos_router = { path = "../../router", default-features = false, version = "0.1.0-beta", features = [
|
||||
"ssr",
|
||||
] }
|
||||
leptos_config = { path = "../../leptos_config", default-features = false, version = "0.1.0-beta" }
|
||||
leptos = { workspace = true, features = ["ssr"] }
|
||||
leptos_meta = { workspace = true, features = ["ssr"] }
|
||||
leptos_router = { workspace = true, features = ["ssr"] }
|
||||
leptos_config.workspace = true
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
|
||||
@@ -3,6 +3,7 @@ use axum::{
|
||||
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 +12,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
|
||||
@@ -97,7 +98,7 @@ pub async fn generate_request_parts(req: Request<Body>) -> RequestParts {
|
||||
uri: parts.uri,
|
||||
headers: parts.headers,
|
||||
version: parts.version,
|
||||
body: body.clone(),
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +140,7 @@ pub async fn handle_server_fns(
|
||||
req: Request<Body>,
|
||||
) -> impl IntoResponse {
|
||||
// Axum Path extractor doesn't remove the first slash from the path, while Actix does
|
||||
let fn_name: String = match fn_name.strip_prefix("/") {
|
||||
let fn_name: String = match fn_name.strip_prefix('/') {
|
||||
Some(path) => path.to_string(),
|
||||
None => fn_name,
|
||||
};
|
||||
@@ -180,15 +181,12 @@ pub async fn handle_server_fns(
|
||||
let res_options_outer = res_options.unwrap().0;
|
||||
let res_options_inner = res_options_outer.read().await;
|
||||
let (status, mut res_headers) = (
|
||||
res_options_inner.status.clone(),
|
||||
res_options_inner.status,
|
||||
res_options_inner.headers.clone(),
|
||||
);
|
||||
|
||||
match res.headers_mut() {
|
||||
Some(header_ref) => {
|
||||
header_ref.extend(res_headers.drain());
|
||||
}
|
||||
None => (),
|
||||
if let Some(header_ref) = res.headers_mut() {
|
||||
header_ref.extend(res_headers.drain());
|
||||
};
|
||||
|
||||
if accept_header == Some("application/json")
|
||||
@@ -237,9 +235,9 @@ pub async fn handle_server_fns(
|
||||
Response::builder()
|
||||
.status(StatusCode::BAD_REQUEST)
|
||||
.body(Full::from(
|
||||
format!("Could not find a server function at the route {:?}. \
|
||||
format!("Could not find a server function at the route {fn_name}. \
|
||||
\n\nIt's likely that you need to call ServerFn::register() on the \
|
||||
server function type, somewhere in your `main` function.", fn_name)
|
||||
server function type, somewhere in your `main` function." )
|
||||
))
|
||||
}
|
||||
.expect("could not build Response");
|
||||
@@ -322,26 +320,10 @@ 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
|
||||
@@ -388,9 +370,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}
|
||||
"#
|
||||
);
|
||||
@@ -487,10 +469,9 @@ where
|
||||
Box::pin(complete_stream) as PinnedHtmlStream
|
||||
));
|
||||
|
||||
match res_options.status {
|
||||
Some(status) => *res.status_mut() = status,
|
||||
None => (),
|
||||
};
|
||||
if let Some(status) = res_options.status {
|
||||
*res.status_mut() = status
|
||||
}
|
||||
let mut res_headers = res_options.headers.clone();
|
||||
res.headers_mut().extend(res_headers.drain());
|
||||
|
||||
@@ -499,3 +480,79 @@ 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 ""
|
||||
routes
|
||||
.iter()
|
||||
.map(|s| {
|
||||
if s.is_empty() {
|
||||
return "/".to_string();
|
||||
}
|
||||
s.to_string()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 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,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos"
|
||||
version = "0.1.0-beta"
|
||||
version.workspace = true
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
@@ -10,11 +10,11 @@ readme = "../README.md"
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1"
|
||||
leptos_config = { path = "../leptos_config", default-features = false, version = "0.1.0-beta" }
|
||||
leptos_dom = { path = "../leptos_dom", default-features = false, version = "0.1.0-beta" }
|
||||
leptos_macro = { path = "../leptos_macro", default-features = false, version = "0.1.0-beta" }
|
||||
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.1.0-beta" }
|
||||
leptos_server = { path = "../leptos_server", default-features = false, version = "0.1.0-beta" }
|
||||
leptos_dom.workspace = true
|
||||
leptos_macro.workspace = true
|
||||
leptos_reactive.workspace = true
|
||||
leptos_server.workspace = true
|
||||
leptos_config.workspace = true
|
||||
tracing = "0.1"
|
||||
typed-builder = "0.11"
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos_macro::component;
|
||||
use std::rc::Rc;
|
||||
use leptos_dom::{DynChild, Fragment, IntoView, Component};
|
||||
use leptos_reactive::{provide_context, Scope, SuspenseContext};
|
||||
use leptos_dom::{Component, DynChild, Fragment, IntoView};
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
use leptos_dom::{HydrationCtx, HydrationKey};
|
||||
use leptos_macro::component;
|
||||
use leptos_reactive::{provide_context, Scope, SuspenseContext};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// If any [Resources](leptos_reactive::Resource) are read in the `children` of this
|
||||
/// component, it will show the `fallback` while they are loading. Once all are resolved,
|
||||
@@ -88,8 +88,8 @@ where
|
||||
} else {
|
||||
// run the child; we'll probably throw this away, but it will register resource reads
|
||||
let child = orig_child(cx).into_view(cx);
|
||||
|
||||
let initial = {
|
||||
|
||||
let initial = {
|
||||
// no resources were read under this, so just return the child
|
||||
if context.pending_resources.get() == 0 {
|
||||
child.clone()
|
||||
@@ -97,10 +97,10 @@ where
|
||||
// show the fallback, but also prepare to stream HTML
|
||||
else {
|
||||
let orig_child = Rc::clone(&orig_child);
|
||||
|
||||
|
||||
cx.register_suspense(
|
||||
context,
|
||||
&id_before_suspense.to_string(),
|
||||
&id_before_suspense.to_string(),
|
||||
¤t_id.to_string(),
|
||||
{
|
||||
let current_id = current_id.clone();
|
||||
@@ -117,17 +117,17 @@ where
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// return the fallback for now, wrapped in fragment identifer
|
||||
fallback().into_view(cx)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
HydrationCtx::continue_from(current_id.clone());
|
||||
|
||||
|
||||
initial
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use leptos_dom::{Fragment, IntoView, View};
|
||||
use leptos_macro::component;
|
||||
use leptos_reactive::{ Scope, SignalSetter};
|
||||
use leptos_reactive::{Scope, SignalSetter};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
/// If any [Resource](leptos_reactive::Resource)s are read in the `children` of this
|
||||
@@ -66,7 +66,7 @@ pub fn Transition<F, E>(
|
||||
#[prop(optional)]
|
||||
set_pending: Option<SignalSetter<bool>>,
|
||||
/// Will be displayed once all resources have resolved.
|
||||
children: Box<dyn Fn(Scope) -> Fragment>
|
||||
children: Box<dyn Fn(Scope) -> Fragment>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
F: Fn() -> E + 'static,
|
||||
@@ -78,7 +78,6 @@ where
|
||||
crate::SuspenseProps::builder()
|
||||
.fallback({
|
||||
let prev_child = Rc::clone(&prev_children);
|
||||
let set_pending = set_pending.clone();
|
||||
move || {
|
||||
if let Some(set_pending) = &set_pending {
|
||||
set_pending.set(true);
|
||||
@@ -98,6 +97,6 @@ where
|
||||
}
|
||||
frag
|
||||
}))
|
||||
.build()
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ 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,
|
||||
}
|
||||
@@ -18,13 +18,13 @@ pub struct ConfFile {
|
||||
/// 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())]
|
||||
#[builder(setter(into), default="target/site".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`.
|
||||
@@ -119,8 +119,7 @@ impl TryFrom<String> for Env {
|
||||
"prod" => Ok(Self::PROD),
|
||||
"production" => Ok(Self::PROD),
|
||||
other => Err(format!(
|
||||
"{} is not a supported environment. Use either `dev` or `production`.",
|
||||
other
|
||||
"{other} is not a supported environment. Use either `dev` or `production`."
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_dom"
|
||||
version = "0.1.0-beta"
|
||||
version.workspace = true
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
@@ -17,7 +17,7 @@ html-escape = "0.2"
|
||||
indexmap = "1.9"
|
||||
itertools = "0.10"
|
||||
js-sys = "0.3"
|
||||
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.1.0-beta" }
|
||||
leptos_reactive.workspace = true
|
||||
once_cell = "1"
|
||||
pad-adapter = "0.1"
|
||||
paste = "1"
|
||||
|
||||
@@ -43,6 +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");
|
||||
if is_a() {
|
||||
set_b(a());
|
||||
|
||||
@@ -54,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) />
|
||||
@@ -73,23 +77,26 @@ 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"
|
||||
</button>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ where
|
||||
children_fn,
|
||||
} = self;
|
||||
|
||||
let mut repr = ComponentRepr::new_with_id(name.clone(), id);
|
||||
let mut repr = ComponentRepr::new_with_id(name, id);
|
||||
|
||||
// disposed automatically when the parent scope is disposed
|
||||
let (child, _) =
|
||||
|
||||
@@ -154,10 +154,7 @@ where
|
||||
let component = DynChildRepr::new();
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
let (frag, closing) = (
|
||||
component.document_fragment.clone(),
|
||||
component.closing.node.clone(),
|
||||
);
|
||||
let closing = component.closing.node.clone();
|
||||
|
||||
let child = component.child.clone();
|
||||
|
||||
@@ -189,7 +186,9 @@ where
|
||||
// or to reuse it in the case of a text node
|
||||
|
||||
// TODO check does this still detect moves correctly?
|
||||
let was_child_moved = prev_t.is_none() && child.get_closing_node().next_sibling().as_ref() != Some(&closing);
|
||||
let was_child_moved = prev_t.is_none()
|
||||
&& child.get_closing_node().next_sibling().as_ref()
|
||||
!= Some(&closing);
|
||||
|
||||
// If the previous child was a text node, we would like to
|
||||
// make use of it again if our current child is also a text
|
||||
|
||||
@@ -16,10 +16,20 @@ thread_local! {
|
||||
pub fn add_event_listener<E>(
|
||||
target: &web_sys::Element,
|
||||
event_name: Cow<'static, str>,
|
||||
cb: impl FnMut(E) + 'static,
|
||||
mut cb: impl FnMut(E) + 'static,
|
||||
) where
|
||||
E: FromWasmAbi + 'static,
|
||||
{
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move |e| {
|
||||
let _guard = span.enter();
|
||||
cb(e);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let cb = Closure::wrap(Box::new(cb) as Box<dyn FnMut(E)>).into_js_value();
|
||||
let key = event_delegation_key(&event_name);
|
||||
_ = js_sys::Reflect::set(target, &JsValue::from_str(&key), &cb);
|
||||
@@ -31,10 +41,20 @@ pub fn add_event_listener<E>(
|
||||
pub fn add_event_listener_undelegated<E>(
|
||||
target: &web_sys::Element,
|
||||
event_name: &str,
|
||||
cb: impl FnMut(E) + 'static,
|
||||
mut cb: impl FnMut(E) + 'static,
|
||||
) where
|
||||
E: FromWasmAbi + 'static,
|
||||
{
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move |e| {
|
||||
let _guard = span.enter();
|
||||
cb(e);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let event_name = intern(event_name);
|
||||
let cb = Closure::wrap(Box::new(cb) as Box<dyn FnMut(E)>).into_js_value();
|
||||
_ = target.add_event_listener_with_callback(event_name, cb.unchecked_ref());
|
||||
@@ -97,7 +117,20 @@ pub(crate) fn add_delegated_event_listener(event_name: Cow<'static, str>) {
|
||||
}
|
||||
};
|
||||
|
||||
crate::window_event_listener(&event_name, handler);
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let handler = move |e| {
|
||||
let _guard = span.enter();
|
||||
handler(e);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let handler = Box::new(handler) as Box<dyn FnMut(web_sys::Event)>;
|
||||
let handler = Closure::wrap(handler).into_js_value();
|
||||
_ = crate::window()
|
||||
.add_event_listener_with_callback(&event_name, handler.unchecked_ref());
|
||||
|
||||
// register that we've created handler
|
||||
events.insert(event_name);
|
||||
|
||||
@@ -71,21 +71,57 @@ pub fn event_target_checked(ev: &web_sys::Event) -> bool {
|
||||
|
||||
/// Runs the given function between the next repaint
|
||||
/// using [`Window.requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame).
|
||||
#[cfg_attr(debug_assertions, instrument(level = "trace", skip_all))]
|
||||
pub fn request_animation_frame(cb: impl FnOnce() + 'static) {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move || {
|
||||
let _guard = span.enter();
|
||||
cb();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let cb = Closure::once_into_js(cb);
|
||||
_ = window().request_animation_frame(cb.as_ref().unchecked_ref());
|
||||
}
|
||||
|
||||
/// Queues the given function during an idle period
|
||||
/// using [`Window.requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestIdleCallback).
|
||||
#[cfg_attr(debug_assertions, instrument(level = "trace", skip_all))]
|
||||
pub fn request_idle_callback(cb: impl Fn() + 'static) {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move || {
|
||||
let _guard = span.enter();
|
||||
cb();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let cb = Closure::wrap(Box::new(cb) as Box<dyn Fn()>).into_js_value();
|
||||
_ = window().request_idle_callback(cb.as_ref().unchecked_ref());
|
||||
}
|
||||
|
||||
/// Executes the given function after the given duration of time has passed.
|
||||
/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
)]
|
||||
pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move || {
|
||||
let _guard = span.enter();
|
||||
cb();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let cb = Closure::once_into_js(Box::new(cb) as Box<dyn FnOnce()>);
|
||||
_ = window().set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
cb.as_ref().unchecked_ref(),
|
||||
@@ -107,10 +143,24 @@ impl IntervalHandle {
|
||||
|
||||
/// Repeatedly calls the given function, with a delay of the given duration between calls.
|
||||
/// See [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval).
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
)]
|
||||
pub fn set_interval(
|
||||
cb: impl Fn() + 'static,
|
||||
duration: Duration,
|
||||
) -> Result<IntervalHandle, JsValue> {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move || {
|
||||
let _guard = span.enter();
|
||||
cb();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let cb = Closure::wrap(Box::new(cb) as Box<dyn Fn()>).into_js_value();
|
||||
let handle = window()
|
||||
.set_interval_with_callback_and_timeout_and_arguments_0(
|
||||
@@ -121,10 +171,24 @@ pub fn set_interval(
|
||||
}
|
||||
|
||||
/// Adds an event listener to the `Window`.
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(level = "trace", skip_all, fields(event_name = %event_name))
|
||||
)]
|
||||
pub fn window_event_listener(
|
||||
event_name: &str,
|
||||
cb: impl Fn(web_sys::Event) + 'static,
|
||||
) {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move |e| {
|
||||
let _guard = span.enter();
|
||||
cb(e);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if !is_server() {
|
||||
let handler = Box::new(cb) as Box<dyn FnMut(web_sys::Event)>;
|
||||
|
||||
|
||||
@@ -90,7 +90,12 @@ where
|
||||
element: el,
|
||||
};
|
||||
|
||||
HtmlElement { cx, element }
|
||||
HtmlElement {
|
||||
cx,
|
||||
element,
|
||||
#[cfg(debug_assertions)]
|
||||
span: ::tracing::Span::current(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
@@ -192,6 +197,8 @@ cfg_if! {
|
||||
/// Represents an HTML element.
|
||||
#[derive(Clone)]
|
||||
pub struct HtmlElement<El: ElementDescriptor> {
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) span: ::tracing::Span,
|
||||
pub(crate) cx: Scope,
|
||||
pub(crate) element: El,
|
||||
}
|
||||
@@ -236,6 +243,8 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
Self {
|
||||
cx,
|
||||
element,
|
||||
#[cfg(debug_assertions)]
|
||||
span: ::tracing::Span::current()
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
@@ -272,6 +281,8 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
let Self {
|
||||
cx,
|
||||
element,
|
||||
#[cfg(debug_assertions)]
|
||||
span
|
||||
} = self;
|
||||
|
||||
HtmlElement {
|
||||
@@ -281,6 +292,8 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
element: element.as_ref().clone(),
|
||||
is_void: element.is_void(),
|
||||
},
|
||||
#[cfg(debug_assertions)]
|
||||
span
|
||||
}
|
||||
} else {
|
||||
let Self {
|
||||
@@ -571,10 +584,22 @@ impl<El: ElementDescriptor + 'static> HtmlElement<El> {
|
||||
pub fn on<E: EventDescriptor + 'static>(
|
||||
self,
|
||||
event: E,
|
||||
event_handler: impl FnMut(E::EventType) + 'static,
|
||||
#[allow(unused_mut)] // used for tracing in debug
|
||||
mut event_handler: impl FnMut(E::EventType) + 'static,
|
||||
) -> 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();
|
||||
}
|
||||
}
|
||||
let event_name = event.name();
|
||||
|
||||
if event.bubbles() {
|
||||
@@ -607,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);
|
||||
}
|
||||
|
||||
@@ -683,9 +712,9 @@ impl<El: ElementDescriptor> IntoView for HtmlElement<El> {
|
||||
let children = children;
|
||||
|
||||
if attrs.iter_mut().any(|(name, _)| name == "id") {
|
||||
attrs.push(("leptos-hk".into(), format!("_{}", id).into()));
|
||||
attrs.push(("leptos-hk".into(), format!("_{id}").into()));
|
||||
} else {
|
||||
attrs.push(("id".into(), format!("_{}", id).into()));
|
||||
attrs.push(("id".into(), format!("_{id}").into()));
|
||||
}
|
||||
|
||||
element.attrs = attrs;
|
||||
@@ -865,6 +894,17 @@ macro_rules! generate_html_tags {
|
||||
}
|
||||
|
||||
#[$meta]
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(
|
||||
level = "trace",
|
||||
name = "HtmlElement",
|
||||
skip_all,
|
||||
fields(
|
||||
tag = %format!("<{}/>", stringify!($tag))
|
||||
)
|
||||
)
|
||||
)]
|
||||
pub fn $tag(cx: Scope) -> HtmlElement<[<$tag:camel $($trailing_)?>]> {
|
||||
HtmlElement::new(cx, [<$tag:camel $($trailing_)?>]::default())
|
||||
}
|
||||
@@ -879,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
|
||||
|
||||
@@ -3,9 +3,9 @@ use std::{cell::RefCell, fmt::Display};
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
use once_cell::unsync::Lazy as LazyCell;
|
||||
|
||||
/// We can tell if we start in hydration mode by checking to see if the
|
||||
/// id "_0" is present in the DOM. If it is, we know we are hydrating from
|
||||
/// the server, if not, we are starting off in CSR
|
||||
// We can tell if we start in hydration mode by checking to see if the
|
||||
// id "_0-0-0" is present in the DOM. If it is, we know we are hydrating from
|
||||
// the server, if not, we are starting off in CSR
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
thread_local! {
|
||||
static IS_HYDRATING: RefCell<LazyCell<bool>> = RefCell::new(LazyCell::new(|| {
|
||||
|
||||
@@ -206,7 +206,12 @@ impl Element {
|
||||
is_void: false,
|
||||
};
|
||||
|
||||
HtmlElement { cx, element }
|
||||
HtmlElement {
|
||||
cx,
|
||||
element,
|
||||
#[cfg(debug_assertions)]
|
||||
span: ::tracing::Span::current(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
@@ -542,8 +547,19 @@ impl View {
|
||||
pub fn on<E: ev::EventDescriptor + 'static>(
|
||||
self,
|
||||
event: E,
|
||||
event_handler: impl FnMut(E::EventType) + 'static,
|
||||
mut event_handler: impl FnMut(E::EventType) + 'static,
|
||||
) -> Self {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
trace!("calling on() {}", event.name());
|
||||
let span = ::tracing::Span::current();
|
||||
let event_handler = move |e| {
|
||||
let _guard = span.enter();
|
||||
event_handler(e);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
self.on_impl(event, Box::new(event_handler))
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ macro_rules! debug_warn {
|
||||
/// or via `println!()` (if not in the browser).
|
||||
pub fn console_log(s: &str) {
|
||||
if is_server() {
|
||||
println!("{}", s);
|
||||
println!("{s}");
|
||||
} else {
|
||||
web_sys::console::log_1(&JsValue::from_str(s));
|
||||
}
|
||||
@@ -55,7 +55,7 @@ pub fn console_log(s: &str) {
|
||||
/// or via `println!()` (if not in the browser).
|
||||
pub fn console_warn(s: &str) {
|
||||
if is_server() {
|
||||
eprintln!("{}", s);
|
||||
eprintln!("{s}");
|
||||
} else {
|
||||
web_sys::console::warn_1(&JsValue::from_str(s));
|
||||
}
|
||||
@@ -65,7 +65,7 @@ pub fn console_warn(s: &str) {
|
||||
/// or via `println!()` (if not in the browser).
|
||||
pub fn console_error(s: &str) {
|
||||
if is_server() {
|
||||
eprintln!("{}", s);
|
||||
eprintln!("{s}");
|
||||
} else {
|
||||
web_sys::console::warn_1(&JsValue::from_str(s));
|
||||
}
|
||||
@@ -77,7 +77,7 @@ pub fn console_debug_warn(s: &str) {
|
||||
cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
if is_server() {
|
||||
eprintln!("{}", s);
|
||||
eprintln!("{s}");
|
||||
} else {
|
||||
web_sys::console::warn_1(&JsValue::from_str(s));
|
||||
}
|
||||
|
||||
@@ -449,4 +449,4 @@ fn to_kebab_case(name: &str) -> String {
|
||||
}
|
||||
|
||||
new_name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_macro"
|
||||
version = "0.1.0-beta"
|
||||
version.workspace = true
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
@@ -22,15 +22,15 @@ quote = "1"
|
||||
syn = { version = "1", features = ["full"] }
|
||||
syn-rsx = "0.9"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
leptos_dom = { path = "../leptos_dom", version = "0.1.0-beta" }
|
||||
leptos_reactive = { path = "../leptos_reactive", version = "0.1.0-beta" }
|
||||
leptos_server = { path = "../leptos_server", version = "0.1.0-beta" }
|
||||
leptos_dom.workspace = true
|
||||
leptos_reactive.workspace = true
|
||||
leptos_server.workspace = true
|
||||
lazy_static = "1.4"
|
||||
|
||||
[dev-dependencies]
|
||||
log = "0.4"
|
||||
typed-builder = "0.10"
|
||||
leptos = { path = "../leptos" }
|
||||
leptos.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["ssr"]
|
||||
|
||||
@@ -116,6 +116,7 @@ impl ToTokens for Model {
|
||||
let body_name = body.sig.ident.clone();
|
||||
|
||||
let (_, generics, where_clause) = body.sig.generics.split_for_impl();
|
||||
let lifetimes = body.sig.generics.lifetimes();
|
||||
|
||||
let props_name = format_ident!("{name}Props");
|
||||
let trace_name = format!("<{name} />");
|
||||
@@ -187,7 +188,7 @@ impl ToTokens for Model {
|
||||
#[allow(unused_variables)]
|
||||
#scope_name: Scope,
|
||||
props: #props_name #generics
|
||||
) #ret
|
||||
) #ret #(+ #lifetimes)*
|
||||
#where_clause
|
||||
{
|
||||
#body
|
||||
@@ -377,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,
|
||||
}
|
||||
@@ -389,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) {
|
||||
@@ -400,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) {
|
||||
@@ -417,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(),
|
||||
)
|
||||
@@ -436,6 +453,7 @@ impl PropOpt {
|
||||
|
||||
struct TypedBuilderOpts {
|
||||
default: bool,
|
||||
default_with_value: Option<syn::Lit>,
|
||||
strip_option: bool,
|
||||
into: bool,
|
||||
}
|
||||
@@ -444,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),
|
||||
@@ -453,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! {}
|
||||
|
||||
@@ -176,6 +176,25 @@ mod server;
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// However, you can pass arbitrary class names using the syntax `class=("name", value)`.
|
||||
/// ```rust
|
||||
/// # use leptos::*;
|
||||
/// # run_scope(create_runtime(), |cx| {
|
||||
/// # if !cfg!(any(feature = "csr", feature = "hydrate")) {
|
||||
/// let (count, set_count) = create_signal(cx, 2);
|
||||
/// // this allows you to use CSS frameworks that include complex class names
|
||||
/// view! { cx,
|
||||
/// <div
|
||||
/// class=("is-[this_-_really]-necessary-42", move || count() < 3)
|
||||
/// >
|
||||
/// "Now you see me, now you don’t."
|
||||
/// </div>
|
||||
/// }
|
||||
/// # ;
|
||||
/// # }
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// 8. You can use the `_ref` attribute to store a reference to its DOM element in a
|
||||
/// [NodeRef](leptos_reactive::NodeRef) to use later.
|
||||
/// ```rust
|
||||
@@ -358,12 +377,12 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
||||
/// ```
|
||||
///
|
||||
/// 5. You can access the children passed into the component with the `children` property, which takes
|
||||
/// an argument of the form `Box<dyn Fn(Scope) -> Fragment>`.
|
||||
/// an argument of the form `Box<dyn FnOnce(Scope) -> Fragment>`.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
/// #[component]
|
||||
/// fn ComponentWithChildren(cx: Scope, children: Box<dyn Fn(Scope) -> Fragment>) -> impl IntoView {
|
||||
/// fn ComponentWithChildren(cx: Scope, children: Box<dyn FnOnce(Scope) -> Fragment>) -> impl IntoView {
|
||||
/// view! {
|
||||
/// cx,
|
||||
/// <ul>
|
||||
|
||||
@@ -105,11 +105,11 @@ mod struct_info {
|
||||
builder_attr,
|
||||
builder_name: syn::Ident::new(&builder_name, proc_macro2::Span::call_site()),
|
||||
conversion_helper_trait_name: syn::Ident::new(
|
||||
&format!("{}_Optional", builder_name),
|
||||
&format!("{builder_name}_Optional"),
|
||||
proc_macro2::Span::call_site(),
|
||||
),
|
||||
core: syn::Ident::new(
|
||||
&format!("{}_core", builder_name),
|
||||
&format!("{builder_name}_core"),
|
||||
proc_macro2::Span::call_site(),
|
||||
),
|
||||
})
|
||||
@@ -188,8 +188,7 @@ mod struct_info {
|
||||
Some(ref doc) => quote!(#[doc = #doc]),
|
||||
None => {
|
||||
let doc = format!(
|
||||
"Builder for [`{name}`] instances.\n\nSee [`{name}::builder()`] for more info.",
|
||||
name = name
|
||||
"Builder for [`{name}`] instances.\n\nSee [`{name}::builder()`] for more info."
|
||||
);
|
||||
quote!(#[doc = #doc])
|
||||
}
|
||||
@@ -282,7 +281,7 @@ mod struct_info {
|
||||
});
|
||||
let reconstructing = self.included_fields().map(|f| f.name);
|
||||
|
||||
let &FieldInfo {
|
||||
let FieldInfo {
|
||||
name: ref field_name,
|
||||
ty: ref field_type,
|
||||
..
|
||||
@@ -391,7 +390,7 @@ mod struct_info {
|
||||
),
|
||||
proc_macro2::Span::call_site(),
|
||||
);
|
||||
let repeated_fields_error_message = format!("Repeated field {}", field_name);
|
||||
let repeated_fields_error_message = format!("Repeated field {field_name}");
|
||||
|
||||
Ok(quote! {
|
||||
#[allow(dead_code, non_camel_case_types, missing_docs)]
|
||||
@@ -513,7 +512,7 @@ mod struct_info {
|
||||
),
|
||||
proc_macro2::Span::call_site(),
|
||||
);
|
||||
let early_build_error_message = format!("Missing required field {}", field_name);
|
||||
let early_build_error_message = format!("Missing required field {field_name}");
|
||||
|
||||
Ok(quote! {
|
||||
#[doc(hidden)]
|
||||
@@ -622,7 +621,7 @@ mod struct_info {
|
||||
// I'd prefer “a” or “an” to “its”, but determining which is grammatically
|
||||
// correct is roughly impossible.
|
||||
let doc =
|
||||
format!("Finalise the builder and create its [`{}`] instance", name);
|
||||
format!("Finalise the builder and create its [`{name}`] instance");
|
||||
quote!(#[doc = #doc])
|
||||
}
|
||||
}
|
||||
@@ -718,7 +717,7 @@ mod struct_info {
|
||||
}
|
||||
_ => Err(Error::new_spanned(
|
||||
&assign,
|
||||
format!("Unknown parameter {:?}", name),
|
||||
format!("Unknown parameter {name:?}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -732,7 +731,7 @@ mod struct_info {
|
||||
}
|
||||
_ => Err(Error::new_spanned(
|
||||
&path,
|
||||
format!("Unknown parameter {:?}", name),
|
||||
format!("Unknown parameter {name:?}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -747,7 +746,7 @@ mod struct_info {
|
||||
let call_func = quote!(#call_func);
|
||||
Error::new_spanned(
|
||||
&call.func,
|
||||
format!("Illegal builder setting group {}", call_func),
|
||||
format!("Illegal builder setting group {call_func}"),
|
||||
)
|
||||
})?;
|
||||
match subsetting_name.as_str() {
|
||||
@@ -759,7 +758,7 @@ mod struct_info {
|
||||
}
|
||||
_ => Err(Error::new_spanned(
|
||||
&call.func,
|
||||
format!("Illegal builder setting group name {}", subsetting_name),
|
||||
format!("Illegal builder setting group name {subsetting_name}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -924,7 +923,7 @@ mod field_info {
|
||||
let tokenized_code = TokenStream::from_str(&code.value())?;
|
||||
self.default = Some(
|
||||
syn::parse(tokenized_code.into())
|
||||
.map_err(|e| Error::new_spanned(code, format!("{}", e)))?,
|
||||
.map_err(|e| Error::new_spanned(code, format!("{e}")))?,
|
||||
);
|
||||
} else {
|
||||
return Err(Error::new_spanned(assign.right, "Expected string"));
|
||||
@@ -933,7 +932,7 @@ mod field_info {
|
||||
}
|
||||
_ => Err(Error::new_spanned(
|
||||
&assign,
|
||||
format!("Unknown parameter {:?}", name),
|
||||
format!("Unknown parameter {name:?}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -950,7 +949,7 @@ mod field_info {
|
||||
}
|
||||
_ => Err(Error::new_spanned(
|
||||
&path,
|
||||
format!("Unknown parameter {:?}", name),
|
||||
format!("Unknown parameter {name:?}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -965,7 +964,7 @@ mod field_info {
|
||||
let call_func = quote!(#call_func);
|
||||
Error::new_spanned(
|
||||
&call.func,
|
||||
format!("Illegal builder setting group {}", call_func),
|
||||
format!("Illegal builder setting group {call_func}"),
|
||||
)
|
||||
})?;
|
||||
match subsetting_name.as_ref() {
|
||||
@@ -977,7 +976,7 @@ mod field_info {
|
||||
}
|
||||
_ => Err(Error::new_spanned(
|
||||
&call.func,
|
||||
format!("Illegal builder setting group name {}", subsetting_name),
|
||||
format!("Illegal builder setting group name {subsetting_name}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -1047,7 +1046,7 @@ mod field_info {
|
||||
}
|
||||
_ => Err(Error::new_spanned(
|
||||
&assign,
|
||||
format!("Unknown parameter {:?}", name),
|
||||
format!("Unknown parameter {name:?}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ pub fn server_macro_impl(args: proc_macro::TokenStream, s: TokenStream2) -> Resu
|
||||
use proc_macro::Span;
|
||||
let span = Span::call_site();
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let url = format!("{}/{}", span.source_file().path().to_string_lossy(), fn_name_as_str).replace("/", "-");
|
||||
let url = format!("{}/{}", span.source_file().path().to_string_lossy(), fn_name_as_str).replace('/', "-");
|
||||
#[cfg(target_os = "windows")]
|
||||
let url = format!("{}/{}", span.source_file().path().to_string_lossy(), fn_name_as_str).replace("\\", "-");
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use syn::{spanned::Spanned, ExprPath};
|
||||
use syn::{spanned::Spanned, Expr, ExprLit, ExprPath, Lit};
|
||||
use syn_rsx::{Node, NodeAttribute, NodeElement, NodeName};
|
||||
|
||||
use crate::{is_component_node, Mode};
|
||||
@@ -409,7 +409,9 @@ 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 {
|
||||
Some((a.key.span(), &a.value))
|
||||
@@ -429,6 +431,14 @@ fn set_class_attribute_ssr(
|
||||
.filter_map(|node| {
|
||||
if let Node::Attribute(node) = node {
|
||||
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))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
if name.starts_with("class:") || name.starts_with("class-") {
|
||||
let name = if name.starts_with("class:") {
|
||||
name.replacen("class:", "", 1)
|
||||
@@ -557,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)
|
||||
}
|
||||
@@ -752,6 +763,12 @@ 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;
|
||||
}
|
||||
|
||||
// all other attributes
|
||||
let value = match node.value.as_ref() {
|
||||
Some(value) => {
|
||||
let value = value.as_ref();
|
||||
@@ -866,7 +883,11 @@ fn ident_from_tag_name(tag_name: &NodeName) -> Ident {
|
||||
.last()
|
||||
.map(|segment| segment.ident.clone())
|
||||
.expect("element needs to have a name"),
|
||||
NodeName::Block(_) => panic!("blocks not allowed in tag-name position"),
|
||||
NodeName::Block(_) => {
|
||||
let span = tag_name.span();
|
||||
proc_macro_error::emit_error!(span, "blocks not allowed in tag-name position");
|
||||
Ident::new("", span)
|
||||
}
|
||||
_ => Ident::new(
|
||||
&tag_name.to_string().replace(['-', ':'], "_"),
|
||||
tag_name.span(),
|
||||
@@ -1044,3 +1065,51 @@ fn parse_event(event_name: &str) -> (&str, bool) {
|
||||
(event_name, false)
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
if let syn::Expr::Tuple(tuple) = expr.as_ref() {
|
||||
if tuple.elems.len() == 2 {
|
||||
let span = node.key.span();
|
||||
let class = quote_spanned! {
|
||||
span => .class
|
||||
};
|
||||
let class_name = &tuple.elems[0];
|
||||
let class_name = if let Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(s), ..
|
||||
}) = class_name
|
||||
{
|
||||
s.value()
|
||||
} else {
|
||||
proc_macro_error::emit_error!(
|
||||
class_name.span(),
|
||||
"class name must be a string literal"
|
||||
);
|
||||
Default::default()
|
||||
};
|
||||
let value = &tuple.elems[1];
|
||||
return Some((
|
||||
quote! {
|
||||
#class(#class_name, (#cx, #value))
|
||||
},
|
||||
class_name,
|
||||
value,
|
||||
));
|
||||
} else {
|
||||
proc_macro_error::emit_error!(
|
||||
tuple.span(),
|
||||
"class tuples must have two elements."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_reactive"
|
||||
version = "0.1.0-beta"
|
||||
version.workspace = true
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
||||
@@ -53,7 +53,7 @@ use std::fmt::Debug;
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
scope = %format!("{:?}", cx.id),
|
||||
scope = ?cx.id,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -107,7 +107,7 @@ where
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
scope = %format!("{:?}", cx.id),
|
||||
scope = ?cx.id,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -128,7 +128,7 @@ where
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
scope = %format!("{:?}", cx.id),
|
||||
scope = ?cx.id,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -172,9 +172,9 @@ where
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
ty = %std::any::type_name::<T>()
|
||||
id = ?id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
)]
|
||||
@@ -220,7 +220,7 @@ impl EffectId {
|
||||
level = "debug",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self),
|
||||
id = ?self,
|
||||
)
|
||||
)
|
||||
)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{ResourceId, runtime::PinnedFuture};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use crate::{runtime::PinnedFuture, ResourceId};
|
||||
use cfg_if::cfg_if;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub struct SharedContext {
|
||||
pub events: Vec<()>,
|
||||
@@ -40,16 +40,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,
|
||||
|
||||
@@ -60,7 +60,7 @@ use std::fmt::Debug;
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
cx = %format!("{:?}", cx.id),
|
||||
cx = ?cx.id,
|
||||
)
|
||||
)
|
||||
)]
|
||||
@@ -155,8 +155,8 @@ impl<T> UntrackedGettableSignal<T> for Memo<T> {
|
||||
name = "Memo::get_untracked()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.0.id),
|
||||
defined_at = %format!("{:?}", self.1),
|
||||
id = ?self.0.id,
|
||||
defined_at = %self.1,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -177,8 +177,8 @@ impl<T> UntrackedGettableSignal<T> for Memo<T> {
|
||||
name = "Memo::with_untracked()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.0.id),
|
||||
defined_at = %format!("{:?}", self.1),
|
||||
id = ?self.0.id,
|
||||
defined_at = %self.1,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -217,8 +217,8 @@ where
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.0.id),
|
||||
defined_at = %format!("{:?}", self.1)
|
||||
id = ?self.0.id,
|
||||
defined_at = %self.1
|
||||
)
|
||||
)
|
||||
)]
|
||||
@@ -256,8 +256,8 @@ where
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.0.id),
|
||||
defined_at = %format!("{:?}", self.1),
|
||||
id = ?self.0.id,
|
||||
defined_at = %self.1,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
|
||||
@@ -89,7 +89,7 @@ where
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
scope = %format!("{:?}", cx.id),
|
||||
scope = ?cx.id,
|
||||
ty = %std::any::type_name::<T>(),
|
||||
signal_ty = %std::any::type_name::<S>(),
|
||||
)
|
||||
@@ -208,7 +208,7 @@ where
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
scope = %format!("{:?}", cx.id),
|
||||
scope = ?cx.id,
|
||||
ty = %std::any::type_name::<T>(),
|
||||
signal_ty = %std::any::type_name::<S>(),
|
||||
)
|
||||
|
||||
@@ -129,14 +129,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 +156,7 @@ impl RuntimeId {
|
||||
id,
|
||||
ty: PhantomData,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at: std::panic::Location::caller()
|
||||
defined_at: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,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 +206,7 @@ impl RuntimeId {
|
||||
Memo(
|
||||
read,
|
||||
#[cfg(debug_assertions)]
|
||||
defined_at
|
||||
defined_at,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -322,10 +322,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,8 +1,9 @@
|
||||
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;
|
||||
@@ -51,7 +52,7 @@ use thiserror::Error;
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
scope = %format!("{:?}", cx.id),
|
||||
scope = ?cx.id,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -63,32 +64,46 @@ pub fn create_signal<T>(cx: Scope, value: T) -> (ReadSignal<T>, WriteSignal<T>)
|
||||
s
|
||||
}
|
||||
|
||||
/// Creates a signal that always contains the most recent value emitted by a [Stream].
|
||||
/// Creates a signal that always contains the most recent value emitted by a
|
||||
/// [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(
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
scope = %format!("{:?}", cx.id),
|
||||
scope = ?cx.id,
|
||||
)
|
||||
)
|
||||
)]
|
||||
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.
|
||||
@@ -154,8 +169,8 @@ impl<T> UntrackedGettableSignal<T> for ReadSignal<T> {
|
||||
name = "ReadSignal::get_untracked()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -174,8 +189,8 @@ impl<T> UntrackedGettableSignal<T> for ReadSignal<T> {
|
||||
name = "ReadSignal::with_untracked()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -214,8 +229,8 @@ where
|
||||
name = "ReadSignal::with()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -254,8 +269,8 @@ where
|
||||
name = "ReadSignal::get()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -391,8 +406,8 @@ where
|
||||
name = "WriteSignal::set_untracked()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -409,8 +424,8 @@ where
|
||||
name = "WriteSignal::updated_untracked()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -426,8 +441,8 @@ where
|
||||
name = "WriteSignal::update_returning_untracked()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -468,8 +483,8 @@ where
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -506,8 +521,8 @@ where
|
||||
name = "WriteSignal::update_returning()"
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -542,8 +557,8 @@ where
|
||||
name = "WriteSignal::set()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -691,8 +706,8 @@ impl<T> UntrackedGettableSignal<T> for RwSignal<T> {
|
||||
name = "RwSignal::get_untracked()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -712,8 +727,8 @@ impl<T> UntrackedGettableSignal<T> for RwSignal<T> {
|
||||
name = "RwSignal::with_untracked()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -731,8 +746,8 @@ impl<T> UntrackedSettableSignal<T> for RwSignal<T> {
|
||||
name = "RwSignal::set_untracked()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -749,8 +764,8 @@ impl<T> UntrackedSettableSignal<T> for RwSignal<T> {
|
||||
name = "RwSignal::update_untracked()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -766,8 +781,8 @@ impl<T> UntrackedSettableSignal<T> for RwSignal<T> {
|
||||
name = "RwSignal::update_returning_untracked()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -807,8 +822,8 @@ where
|
||||
name = "RwSignal::with()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -838,8 +853,8 @@ where
|
||||
name = "RwSignal::get()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -875,8 +890,8 @@ where
|
||||
name = "RwSignal::update()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -911,8 +926,8 @@ where
|
||||
name = "RwSignal::update_returning()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -942,8 +957,8 @@ where
|
||||
name = "RwSignal::set()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -975,8 +990,8 @@ where
|
||||
name = "RwSignal::read_only()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -1013,8 +1028,8 @@ where
|
||||
name = "RwSignal::write_only()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -1050,8 +1065,8 @@ where
|
||||
name = "RwSignal::split()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -1084,8 +1099,8 @@ where
|
||||
name = "RwSignal::to_stream()",
|
||||
skip_all,
|
||||
fields(
|
||||
id = %format!("{:?}", self.id),
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
id = ?self.id,
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
use crate::{store_value, Memo, ReadSignal, RwSignal, Scope, StoredValue, UntrackedGettableSignal};
|
||||
|
||||
/// Helper trait for converting `Fn() -> T` closures into
|
||||
/// [`Signal<T>`].
|
||||
pub trait IntoSignal<T>: Sized {
|
||||
/// Consumes `self`, returning a [`Signal<T>`].
|
||||
fn derive_signal(self, cx: Scope) -> Signal<T>;
|
||||
}
|
||||
|
||||
impl<F, T> IntoSignal<T> for F
|
||||
where
|
||||
F: Fn() -> T + 'static,
|
||||
{
|
||||
fn derive_signal(self, cx: Scope) -> Signal<T> {
|
||||
Signal::derive(cx, self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper for any kind of readable reactive signal: a [ReadSignal](crate::ReadSignal),
|
||||
/// [Memo](crate::Memo), [RwSignal](crate::RwSignal), or derived signal closure.
|
||||
///
|
||||
@@ -109,7 +125,7 @@ where
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
cx = %format!("{:?}", cx.id)
|
||||
cx = ?cx.id
|
||||
)
|
||||
)
|
||||
)]
|
||||
@@ -163,7 +179,7 @@ where
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -204,7 +220,7 @@ where
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
@@ -425,6 +441,18 @@ where
|
||||
/// assert_eq!(above_3(&double_count), true);
|
||||
/// # });
|
||||
/// ```
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(
|
||||
level = "trace",
|
||||
name = "MaybeSignal::derive()",
|
||||
skip_all,
|
||||
fields(
|
||||
cx = ?cx.id,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
)]
|
||||
pub fn derive(cx: Scope, derived_signal: impl Fn() -> T + 'static) -> Self {
|
||||
Self::Dynamic(Signal::derive(cx, derived_signal))
|
||||
}
|
||||
@@ -461,6 +489,15 @@ where
|
||||
/// assert_eq!(static_value(), "Bob");
|
||||
/// });
|
||||
/// ```
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(
|
||||
level = "trace",
|
||||
name = "MaybeSignal::derive()",
|
||||
skip_all,
|
||||
fields(ty = %std::any::type_name::<T>())
|
||||
)
|
||||
)]
|
||||
pub fn with<U>(&self, f: impl FnOnce(&T) -> U) -> U {
|
||||
match &self {
|
||||
Self::Static(value) => f(value),
|
||||
@@ -492,6 +529,15 @@ where
|
||||
/// assert_eq!(above_3(&static_value.into()), true);
|
||||
/// # });
|
||||
/// ```
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(
|
||||
level = "trace",
|
||||
name = "MaybeSignal::derive()",
|
||||
skip_all,
|
||||
fields(ty = %std::any::type_name::<T>())
|
||||
)
|
||||
)]
|
||||
pub fn get(&self) -> T
|
||||
where
|
||||
T: Clone,
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
use crate::{store_value, RwSignal, Scope, StoredValue, WriteSignal};
|
||||
|
||||
/// Helper trait for converting `Fn(T)` into [`SignalSetter<T>`].
|
||||
pub trait IntoSignalSetter<T>: Sized {
|
||||
/// Consumes `self`, returning [`SignalSetter<T>`].
|
||||
fn mapped_signal_setter(self, cx: Scope) -> SignalSetter<T>;
|
||||
}
|
||||
|
||||
impl<F, T> IntoSignalSetter<T> for F
|
||||
where
|
||||
F: Fn(T) + 'static,
|
||||
{
|
||||
fn mapped_signal_setter(self, cx: Scope) -> SignalSetter<T> {
|
||||
SignalSetter::map(cx, self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper for any kind of settable reactive signal: a [WriteSignal](crate::WriteSignal),
|
||||
/// [RwSignal](crate::RwSignal), or closure that receives a value and sets a signal depending
|
||||
/// on it.
|
||||
@@ -92,7 +107,7 @@ where
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
cx = %format!("{:?}", cx.id),
|
||||
cx = ?cx.id,
|
||||
)
|
||||
)
|
||||
)]
|
||||
@@ -130,7 +145,7 @@ where
|
||||
level = "trace",
|
||||
skip_all,
|
||||
fields(
|
||||
defined_at = %format!("{:?}", self.defined_at),
|
||||
defined_at = %self.defined_at,
|
||||
ty = %std::any::type_name::<T>()
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_server"
|
||||
version = "0.1.0-beta"
|
||||
version.workspace = true
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
@@ -8,8 +8,8 @@ repository = "https://github.com/gbj/leptos"
|
||||
description = "RPC for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
leptos_dom = { path = "../leptos_dom", default-features = false, version = "0.1.0-beta" }
|
||||
leptos_reactive = { path = "../leptos_reactive", default-features = false, version = "0.1.0-beta" }
|
||||
leptos_dom.workspace = true
|
||||
leptos_reactive.workspace = true
|
||||
form_urlencoded = "1"
|
||||
gloo-net = "0.2"
|
||||
lazy_static = "1"
|
||||
@@ -26,7 +26,7 @@ proc-macro2 = "1.0.47"
|
||||
ciborium = "0.2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
leptos = { path = "../leptos", default-features = false }
|
||||
leptos.workspace = true
|
||||
|
||||
[features]
|
||||
csr = [
|
||||
|
||||
@@ -9,7 +9,7 @@ description = "Tools to set HTML metadata in the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1"
|
||||
leptos = { path = "../leptos", version = "0.1.0-beta", default-features = false }
|
||||
leptos.workspace = true
|
||||
tracing = "0.1"
|
||||
typed-builder = "0.11"
|
||||
|
||||
@@ -18,7 +18,7 @@ version = "0.3"
|
||||
features = ["HtmlLinkElement", "HtmlMetaElement", "HtmlTitleElement"]
|
||||
|
||||
[features]
|
||||
default = ["csr"]
|
||||
default = []
|
||||
csr = ["leptos/csr", "leptos/tracing"]
|
||||
hydrate = ["leptos/hydrate", "leptos/tracing"]
|
||||
ssr = ["leptos/ssr", "leptos/tracing"]
|
||||
|
||||
@@ -80,10 +80,6 @@ pub(crate) struct MetaTagsContext {
|
||||
impl MetaTagsContext {
|
||||
#[cfg(feature = "ssr")]
|
||||
pub fn as_string(&self) -> String {
|
||||
println!(
|
||||
"\n\nrendering {} elements to strings\n\n",
|
||||
self.els.borrow().len()
|
||||
);
|
||||
self.els
|
||||
.borrow()
|
||||
.iter()
|
||||
|
||||
@@ -8,7 +8,7 @@ repository = "https://github.com/gbj/leptos"
|
||||
description = "Router for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
leptos = { path = "../leptos", version = "0.1.0-beta", default-features = false }
|
||||
leptos.workspace = true
|
||||
cfg-if = "1"
|
||||
common_macros = "0.1"
|
||||
gloo-net = "0.2"
|
||||
@@ -53,7 +53,7 @@ features = [
|
||||
]
|
||||
|
||||
[features]
|
||||
default = ["csr"]
|
||||
default = []
|
||||
csr = ["leptos/csr"]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = ["leptos/ssr", "dep:url", "dep:regex"]
|
||||
|
||||
@@ -36,7 +36,7 @@ pub fn Form<A>(
|
||||
#[prop(optional)]
|
||||
on_response: Option<Rc<dyn Fn(&web_sys::Response)>>,
|
||||
/// Component children; should include the HTML of the form elements.
|
||||
children: Box<dyn Fn(Scope) -> Fragment>,
|
||||
children: Box<dyn FnOnce(Scope) -> Fragment>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
A: ToHref + 'static,
|
||||
@@ -134,7 +134,7 @@ pub fn ActionForm<I, O>(
|
||||
/// manually using [leptos_server::Action::using_server_fn].
|
||||
action: Action<I, Result<O, ServerFnError>>,
|
||||
/// Component children; should include the HTML of the form elements.
|
||||
children: Box<dyn Fn(Scope) -> Fragment>,
|
||||
children: Box<dyn FnOnce(Scope) -> Fragment>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
I: Clone + ServerFn + 'static,
|
||||
@@ -208,7 +208,7 @@ pub fn MultiActionForm<I, O>(
|
||||
/// manually using [leptos_server::Action::using_server_fn].
|
||||
action: MultiAction<I, Result<O, ServerFnError>>,
|
||||
/// Component children; should include the HTML of the form elements.
|
||||
children: Box<dyn Fn(Scope) -> Fragment>,
|
||||
children: Box<dyn FnOnce(Scope) -> Fragment>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
I: Clone + ServerFn + 'static,
|
||||
|
||||
@@ -58,7 +58,7 @@ pub fn A<H>(
|
||||
#[prop(optional, into)]
|
||||
class: Option<MaybeSignal<String>>,
|
||||
/// The nodes or elements to be shown inside the link.
|
||||
children: Box<dyn Fn(Scope) -> Fragment>,
|
||||
children: Box<dyn FnOnce(Scope) -> Fragment>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
H: ToHref + 'static,
|
||||
|
||||
@@ -9,10 +9,13 @@ use leptos::*;
|
||||
pub fn Outlet(cx: Scope) -> impl IntoView {
|
||||
let route = use_route(cx);
|
||||
let is_showing = Rc::new(Cell::new(None::<(usize, Scope)>));
|
||||
let (outlet, set_outlet) = create_signal(cx, None);
|
||||
let (outlet, set_outlet) = create_signal(cx, None::<View>);
|
||||
create_isomorphic_effect(cx, move |_| {
|
||||
match (route.child(), &is_showing.get()) {
|
||||
(None, _) => {
|
||||
(None, prev) => {
|
||||
if let Some(prev_scope) = prev.map(|(_, scope)| scope) {
|
||||
prev_scope.dispose();
|
||||
}
|
||||
set_outlet.set(None);
|
||||
}
|
||||
(Some(child), Some((is_showing_val, _))) if child.id() == *is_showing_val => {
|
||||
|
||||
@@ -26,7 +26,7 @@ pub fn Route<E, F, P>(
|
||||
view: F,
|
||||
/// `children` may be empty or include nested routes.
|
||||
#[prop(optional)]
|
||||
children: Option<Box<dyn Fn(Scope) -> Fragment>>,
|
||||
children: Option<Box<dyn FnOnce(Scope) -> Fragment>>,
|
||||
) -> impl IntoView
|
||||
where
|
||||
E: IntoView,
|
||||
|
||||
@@ -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"))]
|
||||
@@ -32,7 +32,7 @@ pub fn Router(
|
||||
/// The `<Router/>` should usually wrap your whole page. It can contain
|
||||
/// any elements, and should include a [Routes](crate::Routes) component somewhere
|
||||
/// to define and display [Route](crate::Route)s.
|
||||
children: Box<dyn Fn(Scope) -> Fragment>,
|
||||
children: Box<dyn FnOnce(Scope) -> Fragment>,
|
||||
) -> impl IntoView {
|
||||
// create a new RouterContext and provide it to every component beneath the router
|
||||
let router = RouterContext::new(cx, base, fallback);
|
||||
@@ -49,6 +49,7 @@ pub struct RouterContext {
|
||||
pub(crate) struct RouterContextInner {
|
||||
pub location: Location,
|
||||
pub base: RouteContext,
|
||||
pub possible_routes: RefCell<Option<Vec<Branch>>>,
|
||||
base_path: String,
|
||||
history: Box<dyn History>,
|
||||
cx: Scope,
|
||||
@@ -104,10 +105,10 @@ impl RouterContext {
|
||||
value: base_path.to_string(),
|
||||
replace: true,
|
||||
scroll: false,
|
||||
state: State(None)
|
||||
state: State(None),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the current URL
|
||||
let (reference, set_reference) = create_signal(cx, source.with(|s| s.value.clone()));
|
||||
@@ -153,6 +154,7 @@ impl RouterContext {
|
||||
referrers,
|
||||
state,
|
||||
set_state,
|
||||
possible_routes: Default::default(),
|
||||
});
|
||||
|
||||
// handle all click events on anchor tags
|
||||
@@ -175,6 +177,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 {
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::{
|
||||
expand_optionals, get_route_matches, join_paths, Branch, Matcher, RouteDefinition,
|
||||
RouteMatch,
|
||||
},
|
||||
RouteContext, RouterContext,
|
||||
PossibleBranchContext, RouteContext, RouterContext,
|
||||
};
|
||||
|
||||
/// Contains route definitions and manages the actual routing process.
|
||||
@@ -22,7 +22,7 @@ use crate::{
|
||||
pub fn Routes(
|
||||
cx: Scope,
|
||||
#[prop(optional)] base: Option<String>,
|
||||
children: Box<dyn Fn(Scope) -> Fragment>,
|
||||
children: Box<dyn FnOnce(Scope) -> Fragment>,
|
||||
) -> impl IntoView {
|
||||
let router = use_context::<RouterContext>(cx).unwrap_or_else(|| {
|
||||
log::warn!("<Routes/> component should be nested within a <Router/>.");
|
||||
@@ -42,6 +42,7 @@ pub fn Routes(
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
create_branches(
|
||||
&children,
|
||||
&base.unwrap_or_default(),
|
||||
@@ -49,6 +50,10 @@ pub fn Routes(
|
||||
&mut branches,
|
||||
);
|
||||
|
||||
if let Some(context) = use_context::<PossibleBranchContext>(cx) {
|
||||
*context.0.borrow_mut() = branches.clone();
|
||||
}
|
||||
|
||||
// whenever path changes, update matches
|
||||
let matches = create_memo(cx, {
|
||||
let router = router.clone();
|
||||
@@ -138,10 +143,9 @@ pub fn Routes(
|
||||
}
|
||||
});
|
||||
|
||||
if disposers.borrow().len() > i + 1 {
|
||||
if disposers.borrow().len() > i {
|
||||
let mut disposers = disposers.borrow_mut();
|
||||
let old_route_disposer =
|
||||
std::mem::replace(&mut disposers[i + 1], disposer);
|
||||
let old_route_disposer = std::mem::replace(&mut disposers[i], disposer);
|
||||
old_route_disposer.dispose();
|
||||
} else {
|
||||
disposers.borrow_mut().push(disposer);
|
||||
|
||||
36
router/src/extract_routes.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
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
|
||||
#[cfg(feature = "ssr")]
|
||||
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()
|
||||
})
|
||||
}
|
||||
@@ -118,7 +118,7 @@ where
|
||||
Some(value) => match T::from_str(value) {
|
||||
Ok(value) => Ok(Some(value)),
|
||||
Err(e) => {
|
||||
eprintln!("{}", e);
|
||||
eprintln!("{e}");
|
||||
Err(ParamsError::Params(Rc::new(e)))
|
||||
}
|
||||
},
|
||||
|
||||
@@ -184,12 +184,14 @@
|
||||
#![cfg_attr(not(feature = "stable"), feature(type_name_of_val))]
|
||||
|
||||
mod components;
|
||||
mod extract_routes;
|
||||
mod history;
|
||||
mod hooks;
|
||||
#[doc(hidden)]
|
||||
pub mod matching;
|
||||
|
||||
pub use components::*;
|
||||
pub use extract_routes::*;
|
||||
pub use history::*;
|
||||
pub use hooks::*;
|
||||
pub use matching::*;
|
||||
|
||||