Compare commits

...

626 Commits

Author SHA1 Message Date
Greg Johnston
3a5e3aea99 Small fixes to Tailwind example 2023-01-01 08:09:24 -05:00
Greg Johnston
e8eb55ca5c Make sure tag names are uppercased in debug assertions where they're uppercased 2023-01-01 08:03:26 -05:00
Greg Johnston
7946df8bfc Merge pull request #209 from benwis/cargo-leptos-release-testing
Update all examples to use cargo-leptos and patch integrations for latest cargo-leptos support
2023-01-01 07:56:41 -05:00
Ben Wishovich
827b787c91 Bugfixes, using cargo-leptos for CSS, and updating READMEs. 2022-12-31 18:39:05 -08:00
Greg Johnston
795270447b Merge pull request #208 from jquesada2016/207
fixes compiler recursion limit on `View::on`
2022-12-31 19:15:28 -05:00
Ben Wishovich
01c00eee6b Update SSR Readmes with new instructions 2022-12-31 16:06:54 -08:00
Ben Wishovich
f45d33db73 Move the examples out of the workspace, and standardize naming. All of the SSR examples now work with cargo-leptos 2022-12-31 15:52:19 -08:00
Jose Quesada
60e2f34456 changed View::on to not require manually boxing the closure 2022-12-31 09:59:48 -06:00
Jose Quesada
7ec82c8df3 boxing View::on closure to fix compiler recursion error 2022-12-31 09:54:13 -06:00
Greg Johnston
d5f8d3a9b7 Merge pull request #206 from jquesada2016/199
fixed components only rendering `<() />` on release
2022-12-31 09:22:09 -05:00
Jose Quesada
2a1b531bd2 fixed components only rendering <() /> on release 2022-12-31 08:08:14 -06:00
Greg Johnston
f5c476cfd5 Merge pull request #205 from gbj/meta-context-warning
Fix warning about MetaContext so it's less misleading.
2022-12-30 20:04:46 -05:00
Greg Johnston
26b28be436 Fix warning about MetaContext so it's less misleading. 2022-12-30 19:30:35 -05:00
Ben Wishovich
60f0bf23fd Merge branch 'main' into cargo-leptos-release-testing 2022-12-30 16:17:50 -08:00
Ben Wishovich
442dc1e041 More changes to the examples 2022-12-30 16:17:17 -08:00
Greg Johnston
c438b46eb1 Merge pull request #200 from gbj/duplicating-text-fix
Fixing duplicating-text-node issue in `DynChild`
2022-12-30 19:11:43 -05:00
Greg Johnston
2a399f05ac Merge pull request #202 from Gentle/detect_tag_type
ambiguous tags inherit the type of their parent (svg/mathml/html)
2022-12-30 19:11:25 -05:00
Greg Johnston
6e1bc42879 Merge pull request #204 from gbj/debug-shadowed-context
Give warning when shadowing a context in debug mode
2022-12-30 19:11:02 -05:00
Greg Johnston
7ad94cc520 Merge pull request #203 from gbj/stored-value
`store_value`
2022-12-30 19:10:25 -05:00
Ramon Klass
c3a7ef0357 ambiguous tags inherit the type of their parent 2022-12-30 23:38:51 +01:00
Greg Johnston
8ee521787e Give warning when shadowing a context in debug mode 2022-12-30 17:11:34 -05:00
Greg Johnston
04c85d6eb0 Fix Axum example 2022-12-30 16:55:50 -05:00
Greg Johnston
71d278927b Update examples to new action APIs 2022-12-30 15:44:25 -05:00
Greg Johnston
cc1d15989e Update router to new action APIs 2022-12-30 15:36:01 -05:00
Greg Johnston
2c614722f4 Make Action and MultiAction Copy by backing them with a StoredValue 2022-12-30 15:29:35 -05:00
Greg Johnston
98d151f5fb Make Signal and SignalSetter Copy by backing them with StoredValue when needed 2022-12-30 15:10:37 -05:00
Greg Johnston
5a9a681d8a Create store_value and StoredValue, allowing you to stash things inside the reactive system in exchange for a Copy + 'static wrapper. 2022-12-30 15:10:28 -05:00
Ben Wishovich
8eaa0b0c15 Merge branch 'main' into cargo-leptos-release-testing 2022-12-30 11:04:43 -08:00
Ben Wishovich
c3fbf13ef3 No leading slashes, and a working todo-app-sqlite example. Improved config section detection 2022-12-30 11:01:01 -08:00
Greg Johnston
54f666c957 Merge pull request #201 from gbj/custom-events-dont-bubble
Fixes issue #178
2022-12-30 12:20:48 -05:00
Greg Johnston
b318449ee7 Issue #178 2022-12-30 11:41:21 -05:00
Greg Johnston
59c291a1e5 Could it be this simple? 2022-12-30 10:47:01 -05:00
Greg Johnston
6c7b20ce77 Merge pull request #197 from luckynumberke7in/patch-2
Fix a few typos in README.md
2022-12-30 08:19:10 -05:00
Ke7in
9a00f7f492 Fix a few typos in README.md
Minor, but I noticed 1 while reading the file and decided to skim the rest of the file.
2022-12-29 23:23:43 -05:00
Greg Johnston
26e90d1959 Merge pull request #196 from gbj/cleanup
Clean up issues relating to `0.1.0` merge
2022-12-29 20:44:31 -05:00
Ben Wishovich
1f1d675d17 Basic cargo-leptos test 2022-12-29 16:42:05 -08:00
Greg Johnston
9bde885b9d Fix suspense in story page 2022-12-29 19:28:10 -05:00
Greg Johnston
383f8a409d Add <Suspense/> to story and user pages 2022-12-29 19:17:35 -05:00
Greg Johnston
b98bacdcab Remove logs 2022-12-29 18:51:39 -05:00
Greg Johnston
d3d71875da FIXME DynChild issue 2022-12-29 18:48:21 -05:00
Greg Johnston
e06946e5a4 stable support for router 2022-12-29 18:40:50 -05:00
Greg Johnston
c485a391ee Update README.md 2022-12-29 18:25:45 -05:00
Greg Johnston
cc48ff72ad Prep for stable support 2022-12-29 18:21:24 -05:00
Greg Johnston
b2cf953c07 Example fix 2022-12-29 18:21:18 -05:00
Greg Johnston
f8af065c0e Adjustments to README 2022-12-29 18:21:08 -05:00
Greg Johnston
333f60cfb7 Changes for stable in router and meta 2022-12-29 18:10:14 -05:00
Greg Johnston
cd9fe66fbb Debug bound 2022-12-29 18:01:47 -05:00
Greg Johnston
3bb9e93c69 Debug bounds 2022-12-29 18:01:01 -05:00
Greg Johnston
f9474def96 Relaxed Debug bounds 2022-12-29 17:43:17 -05:00
Greg Johnston
d7dba85f2d Missing changes re: docs 2022-12-29 17:43:06 -05:00
Greg Johnston
94af8f26ca Fix site-root 2022-12-29 17:39:08 -05:00
Greg Johnston
19dabb6b6a Clear up warnings in the example 2022-12-29 17:39:04 -05:00
Greg Johnston
15b5f7545a Use <Transition/> here for that silky-smooth optimistic UI update 2022-12-29 17:35:46 -05:00
Greg Johnston
9399fc7b4f Removed old/broken fix for hydration under <Suspense/> 2022-12-29 17:30:33 -05:00
Greg Johnston
5d8d5d9910 SocketAddr? I hardly know her! (missing import from merge) 2022-12-29 16:01:37 -05:00
Greg Johnston
e6a1255140 Merge pull request #119 from jquesada2016/leptos_dom_v2
leptos_dom v2
2022-12-29 12:21:13 -05:00
Greg Johnston
528a9d7a6f Fix merge 2022-12-29 12:13:52 -05:00
Greg Johnston
f28da0770f Fix leptos_config version 2022-12-29 12:13:45 -05:00
Jose Quesada
0145b01da5 impl IntoView for &View 2022-12-29 09:55:35 -06:00
Greg Johnston
725ea8a01e Merge branch 'jquesada2016-leptos_dom_v2' 2022-12-29 09:32:35 -05:00
Greg Johnston
70f6297277 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into jquesada2016-leptos_dom_v2 2022-12-29 09:32:12 -05:00
Greg Johnston
e595d35c8b Fix CSS location for hackernews-axum 2022-12-29 08:50:43 -05:00
Greg Johnston
c44693e0a4 Use #[component] macro for leptos_meta to generate docs 2022-12-29 08:46:41 -05:00
Greg Johnston
b86e7f33dc Bump versions for new cargo-leptos compatible integrations 2022-12-29 08:04:22 -05:00
Greg Johnston
a603531409 Typos 2022-12-29 08:02:56 -05:00
Greg Johnston
f47fad3ed5 Merge pull request #177 from benwis/cargo-leptos-beta
Changes to leptos, leptos_meta, leptos_actix/leptos_axum, and leptos_config to support cargo-leptos-beta
2022-12-29 07:48:38 -05:00
Greg Johnston
1cb03914ab Merge pull request #185 from snapbug/stable-build-fix
Fix build errors in `counter-isomorphic` when using `stable`
2022-12-29 07:44:56 -05:00
Jose Quesada
67c5eda099 removed clone bount on the type argument of SignalSetter 2022-12-28 21:14:10 -06:00
Jose Quesada
63e70db736 removed EventHandler trait 2022-12-28 17:49:10 -06:00
Jose Quesada
8acbc579e0 fixed broken undelegated type 2022-12-28 15:38:13 -06:00
Jose Quesada
28bb3f81aa made ev::undelegated lowercase to match the rest of the event names 2022-12-28 15:28:59 -06:00
Greg Johnston
e8424138ce Fix TOML 2022-12-28 15:06:52 -05:00
Greg Johnston
4b1fce4c9c Revert "Merge branch 'main' into pr/119"
This reverts commit 63f680f37d, reversing
changes made to 50ba796f49.
2022-12-28 15:06:46 -05:00
Greg Johnston
fd2a2bd5f4 Fixing merge issues 2022-12-28 14:51:01 -05:00
Greg Johnston
f09ded454d Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into pr/119 2022-12-28 14:48:03 -05:00
Greg Johnston
a2f85feb57 Update Readme 2022-12-28 14:41:19 -05:00
Greg Johnston
d64ca366fc Fix merge 2022-12-28 14:22:03 -05:00
Greg Johnston
63f680f37d Merge branch 'main' into pr/119 2022-12-28 14:21:54 -05:00
Greg Johnston
50ba796f49 Fix leptos_server tests 2022-12-28 13:26:10 -05:00
Jose Quesada
f3b62bcf88 impl HtmlElement::inner_html for SSR 2022-12-28 11:34:27 -06:00
Jose Quesada
57c72c038c impl HtmlElement::inner_html for web targets, SSR still TODO 2022-12-28 11:17:47 -06:00
Jose Quesada
4340fbfc78 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-28 10:52:06 -06:00
Jose Quesada
4e1753fc71 moved #[component] tracing support behind a tracing feature flag 2022-12-28 10:51:55 -06:00
Greg Johnston
f30310a64a Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-28 11:33:29 -05:00
Greg Johnston
e3c4e9f6a4 chores: fix failing tests, update docs, suppress warnings 2022-12-28 11:33:26 -05:00
Jose Quesada
494deef9b6 maybe possibly perhaps fixed broken tracing dep 2022-12-28 09:53:11 -06:00
Jose Quesada
0da8d0113c added clone: to components in view! macro to help with move dilemmaa 2022-12-28 09:24:52 -06:00
Greg Johnston
dac69b9802 Port hackernews-axum 2022-12-28 08:53:05 -05:00
Jose Quesada
3c1e1e12d2 component move fix would break scope continuity 2022-12-28 07:44:41 -06:00
Jose Quesada
0e179d0cb5 component move fix would break scope continuity
This reverts commit 4dd5768a66.
2022-12-28 07:36:05 -06:00
Greg Johnston
55b27f7aec Fix <Meta/> tag hydration lookup 2022-12-28 08:19:56 -05:00
Jose Quesada
4467d060b6 fixed duplicate imports on web 2022-12-27 20:18:46 -06:00
Jose Quesada
4dd5768a66 fixed move dilema on component children 2022-12-27 20:14:10 -06:00
Jose Quesada
a3f090c4df added LazyView to fix view! macro move dilema 2022-12-27 20:12:59 -06:00
Ben Wishovich
5729655657 Merge remote-tracking branch 'origin/cargo-leptos-beta' into cargo-leptos-beta 2022-12-27 13:02:13 -08:00
Ben Wishovich
f2ed521de8 Missing .is_ok() and more examples changes 2022-12-27 13:01:40 -08:00
Ben Wishovich
f8f0d9fae0 Merge branch 'main' into cargo-leptos-beta 2022-12-27 12:49:17 -08:00
Ben Wishovich
e23c05a1df Remove unused derives 2022-12-27 11:01:20 -08:00
Ben Wishovich
be94c1b846 Update examples to camelcase and add missing fields for feature flags. Should be working with cargo-leptos beta again 2022-12-27 10:58:05 -08:00
Jose Quesada
b3c4c77dee now unwrapping type when documenting a field which has #[prop(strip_option)] 2022-12-27 12:20:14 -06:00
Jose Quesada
8b81425b21 explicit handler type to help compiler type inference 2022-12-27 08:46:09 -06:00
Jose Quesada
04e3e7a9a6 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-27 08:33:22 -06:00
Jose Quesada
ab2d554dc3 now only adding event handlers Some(_) and FnMut; called cargo fmt which is why more files were edited 2022-12-27 08:33:09 -06:00
Greg Johnston
c712cc8937 chore: clippy 2022-12-26 17:49:32 -05:00
Greg Johnston
6077966cd7 Streaming SSR for <Suspense/> in release mode 2022-12-26 17:47:26 -05:00
Greg Johnston
3179b2a9e5 Remove duplicate 2022-12-26 17:25:18 -05:00
Greg Johnston
a68d276c90 Merge pull request #188 from tshepang/patch-1
readme: fix indentation of code block
2022-12-26 17:16:03 -05:00
Greg Johnston
bf3bba3794 Merge pull request #176 from ultrasaurus/nightly-readme
info about how to set up nightly -> README
2022-12-26 17:15:30 -05:00
Sarah Allen
17eb571ef3 remove --allow-downgrade option since not required 2022-12-26 11:00:27 -08:00
Greg Johnston
ebd7080149 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-26 11:38:27 -05:00
Greg Johnston
f1a148caf8 Remove errant Clone bound on Signal<T> by implementing explicitly 2022-12-26 11:38:25 -05:00
Jose Quesada
a5351dd33d fixed broken cfg 2022-12-26 09:51:36 -06:00
Jose Quesada
a15b3dd882 now using a shared marker to check if a Each has already been mounted, rather than calling out to JS 2022-12-26 09:42:37 -06:00
Jose Quesada
d42b79b261 now using a shared marker to check if a component has already been mounted, rather than calling out to JS 2022-12-26 09:37:32 -06:00
Jose Quesada
6cd136ec9b Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-26 08:59:11 -06:00
Jose Quesada
85b72f5b68 added support for moving views around in the DOM 2022-12-26 08:58:58 -06:00
Greg Johnston
2bd0c38304 Properly detect and namespace SVG/MathML 2022-12-26 08:21:36 -05:00
Greg Johnston
9dc30da3e9 Fix component children example in docs 2022-12-26 08:03:40 -05:00
Tshepang Mbambo
3436cf7fbf readme: fix indentation of code block 2022-12-26 06:29:12 +02:00
Greg Johnston
38ef93d862 Fix deps 2022-12-25 23:14:10 -05:00
Greg Johnston
0e437cac68 Update README 2022-12-25 23:13:27 -05:00
Greg Johnston
98e3f5a155 Remove dev-deps for publish 2022-12-25 23:11:08 -05:00
Greg Johnston
a55ce8f752 Fix deps 2022-12-25 23:08:00 -05:00
Greg Johnston
469a65ad7a Remove dev-deps for publish 2022-12-25 23:07:42 -05:00
Greg Johnston
8a8c00455e Remove version of dev-deps 2022-12-25 23:06:20 -05:00
Greg Johnston
2048e89109 Remove dev-dependency (for cargo publish reasons) 2022-12-25 23:03:43 -05:00
Greg Johnston
5540bb8e8c Bump version to 0.1.0-alpha 2022-12-25 22:58:07 -05:00
Greg Johnston
86df770dad chores: getting tests fixed, etc. 2022-12-25 22:53:52 -05:00
Greg Johnston
535bd69b2a Merge pull request #186 from luckynumberke7in/patch-1
Update COMMON_BUGS.md to fix typo
2022-12-25 20:48:40 -05:00
Ke7in
1b0200390b Update COMMON_BUGS.md to fix typo
```rust
let (b, set_a) = create_signal(cx, false); // should be set_b
```
2022-12-25 18:27:52 -05:00
Greg Johnston
e05778726b Update docs 2022-12-25 16:06:29 -05:00
Matt Crane
587a85baaf Fix erros in counter-isomorphic with stable 2022-12-24 12:13:35 -08:00
Ben Wishovich
ff0d058a3e leptos_watch is not a bool 2022-12-24 10:30:29 -08:00
Jose Quesada
623bb7cb3f Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-24 08:45:43 -06:00
Jose Quesada
fc062e6829 fixed the type of math elements 2022-12-24 08:45:36 -06:00
Greg Johnston
479c11e3f8 MUCH better solution to hydration mismatch when resources read not under Suspense 2022-12-24 08:24:56 -05:00
Greg Johnston
bf9587c349 Attempt at Transition 2022-12-24 07:39:43 -05:00
Greg Johnston
b3da8a5dba Fix SVG warning fix 2022-12-24 07:39:25 -05:00
Greg Johnston
d3f2cae07a Fix SVG warnings 2022-12-24 07:28:34 -05:00
Greg Johnston
8c4dcbeddc Fix paths for imports 2022-12-23 17:05:52 -05:00
Greg Johnston
a4747596fa Only stream Resources if they're under a Suspense to fix rendering issue 2022-12-23 17:01:22 -05:00
Greg Johnston
af68da0a9a Remove web by default 2022-12-23 17:01:06 -05:00
Greg Johnston
48e1d6cfab Recursive components allowed 2022-12-23 17:00:52 -05:00
Greg Johnston
a4740d6c06 Remove web by default 2022-12-23 17:00:42 -05:00
Jose Quesada
ae506fced6 fixed name mismatch 2022-12-23 14:51:27 -06:00
Jose Quesada
86394105dd fixed name collision within components so that recursion is possible 2022-12-23 14:47:57 -06:00
Jose Quesada
7028dd8b3d fixed math namespace 2022-12-23 14:34:35 -06:00
Jose Quesada
8092bf1962 untoggled wasm32 target 2022-12-23 14:22:16 -06:00
Jose Quesada
2d97790ab9 fixed imports on non-web 2022-12-23 14:17:54 -06:00
Jose Quesada
ef846e7b88 added all math elements 2022-12-23 14:09:46 -06:00
Jose Quesada
d78ee8c3c9 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-23 14:07:01 -06:00
Jose Quesada
d1ece97575 added all svg elements 2022-12-23 14:06:55 -06:00
Greg Johnston
c24958bec4 Fix paths in hackernews example 2022-12-23 14:35:02 -05:00
Greg Johnston
8e1c165427 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-23 14:32:26 -05:00
Greg Johnston
50c9c38b7d Fix JS path 2022-12-23 14:24:59 -05:00
Greg Johnston
232776f9a6 Add example to Cargo.toml 2022-12-23 14:23:52 -05:00
Greg Johnston
629ac01484 merge todo-app-sqlite-axum 2022-12-23 14:23:38 -05:00
Greg Johnston
f20c74fa98 SSR changes to support integrations 2022-12-23 14:23:06 -05:00
Greg Johnston
2499755a9e Merge main integrations in 2022-12-23 14:22:49 -05:00
Greg Johnston
f17f651986 Clean up example 2022-12-23 13:30:22 -05:00
Greg Johnston
c1d6ff51a6 Update meta and router versions 2022-12-23 13:19:51 -05:00
Greg Johnston
4839bfbb29 Tailwind example ported 2022-12-23 13:10:45 -05:00
Greg Johnston
391fe89542 0.0.21 2022-12-23 13:01:15 -05:00
Greg Johnston
f54ffab888 Update integration versions 2022-12-23 12:43:18 -05:00
Jose Quesada
7ee0c01594 fixed non-web builds 2022-12-23 11:19:39 -06:00
Jose Quesada
6ce90fa49d revert default build target 2022-12-23 11:16:28 -06:00
Jose Quesada
c96965ab64 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-23 11:10:01 -06:00
Jose Quesada
5def2a72bc impl View::on and renamed IntoElement to ElementDescriptor 2022-12-23 11:09:55 -06:00
Greg Johnston
a648f084c6 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-23 11:40:59 -05:00
Greg Johnston
590ec40e0c Final Suspense fix? 2022-12-23 11:40:57 -05:00
Greg Johnston
6354b79588 Use Transition in example 2022-12-23 11:34:20 -05:00
Greg Johnston
d95dc1858c Fix merged leptos_reactive so that resources are streamed properly 2022-12-23 11:33:17 -05:00
Greg Johnston
558b13dc0e Fix merged leptos_reactive so that resources are streamed properly 2022-12-23 11:33:05 -05:00
Greg Johnston
43bbd2f33e Merge branch 'main' of https://github.com/gbj/leptos 2022-12-23 10:25:51 -05:00
Greg Johnston
833eee6639 Create shared_context by default in SSR 2022-12-23 10:25:48 -05:00
Greg Johnston
feb7961bd0 Created shared_context by default in SSR 2022-12-23 10:24:44 -05:00
Greg Johnston
8aa05f8f3d Merge pull request #181 from gbj/relax-debug-trait-bounds
Relax the `Debug` trait bounds on various types in `leptos_reactive`
2022-12-23 10:05:49 -05:00
Jose Quesada
fa87bc6f19 added transparent to `<For /> 2022-12-23 08:39:06 -06:00
Greg Johnston
0f31e924a6 Transparent <For/> 2022-12-23 09:34:47 -05:00
Jose Quesada
c89930aed0 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-23 08:19:15 -06:00
Greg Johnston
3d160ed152 Merge in changes to leptos_reactive from main 2022-12-23 09:19:04 -05:00
Jose Quesada
8037915294 reintroduced panic in DynChild 2022-12-23 08:19:02 -06:00
Greg Johnston
b62568b8a4 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-23 08:59:23 -05:00
Jose Quesada
a5d7563a67 fixed DynChild panicking when expected text is not there 2022-12-23 07:54:29 -06:00
Ben Wishovich
fbacfc787c Add missing port to Websockets code 2022-12-22 20:56:47 -08:00
Greg Johnston
0bf52c95bb Make sure --debug is noted in examples so hydration matches 2022-12-22 21:24:36 -05:00
Greg Johnston
89aa02af19 Update counter example 2022-12-22 21:21:17 -05:00
Greg Johnston
21af940c61 Merge pull request #175 from ultrasaurus/counter-example
fix warning, set initial value of counter
2022-12-22 21:08:49 -05:00
Greg Johnston
46c939ba28 Relax all the Debug trait bounds on various types in leptos_reactive 2022-12-22 21:07:38 -05:00
Greg Johnston
d158c34d24 Transition back to Transition in hackernews 2022-12-22 20:55:38 -05:00
Greg Johnston
42eef284e6 Fix Transition in new hydration model 2022-12-22 20:55:17 -05:00
Greg Johnston
6cf5d0a403 <Route element=... => view=... 2022-12-22 20:54:52 -05:00
Greg Johnston
79ac501302 Make <Suspense/> properly reactive again (i.e., shows fallback between states) 2022-12-22 20:45:51 -05:00
Greg Johnston
fdf17af7ab Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-22 20:21:17 -05:00
Greg Johnston
ac16d34985 Fix scoping issue by manually creating Component in Suspense 2022-12-22 20:21:14 -05:00
Jose Quesada
aa9f8f24b0 renamed element to view on <Route /> and RouteDefinition 2022-12-22 17:41:01 -06:00
Jose Quesada
d2ba8f5d46 exporting everything from matching 2022-12-22 17:30:30 -06:00
Greg Johnston
7c25cd9200 Use #[component] macro for core components, deleting leptos_core package 2022-12-22 16:46:48 -05:00
Jose Quesada
34b4917837 updated component doc formatting 2022-12-22 13:10:41 -06:00
Ben Wishovich
ac489e7523 Who let me near the computer before coffee? 2022-12-22 08:42:16 -08:00
Ben Wishovich
0909f60e55 Remove redundant env check and add path option to get_configuration() 2022-12-22 08:39:04 -08:00
Ben Wishovich
5ec76682a7 Fix Websockets Code and re-enable optimizations in tailwind. Remove watch as a param 2022-12-21 23:54:48 -08:00
Ben Wishovich
428999fd14 Updated all the examples to use the new leptos_options, and make cargo-leptos porting easy. Refactored the Tailwind example to bring it closer to leptos norms 2022-12-21 23:08:39 -08:00
Greg Johnston
ce84632c39 Update NodeRef to be generic over typed HTML elements 2022-12-21 21:15:48 -05:00
Greg Johnston
351389c2bf Correct types in SSR 2022-12-21 20:47:30 -05:00
Greg Johnston
532f5c5b83 Add types for HTML elements 2022-12-21 20:37:15 -05:00
Ben Wishovich
0d314224c9 Make tests pass, and do small tweaks/cleanup 2022-12-21 11:51:29 -08:00
Ben Wishovich
b4897f7a61 Fix dumb typos and add an option for an id to Stylesheet 2022-12-21 10:15:42 -08:00
Sarah Allen
49e93278b5 info about how to set up nightly -> README 2022-12-21 07:19:43 -08:00
Sarah Allen
cd59bf5a10 fix warning, set initial value of counter 2022-12-21 07:11:15 -08:00
Greg Johnston
66ac7d2a9d Fix hydration of <Routes/> 2022-12-21 07:58:26 -05:00
Greg Johnston
ae82395100 Remove some hydration key element increments 2022-12-21 07:58:08 -05:00
Greg Johnston
e57b6e0ccf Tidy up <Suspense/> 2022-12-21 07:57:44 -05:00
Greg Johnston
2f218e7428 Small changes to examples 2022-12-21 07:56:33 -05:00
Ben Wishovich
f2e9d6f4c3 More small tweaks to support config 2022-12-20 17:56:38 -08:00
Ben Wishovich
ee379bb405 Add errors to leptos_config and more changes to support beta 2022-12-20 12:14:42 -08:00
Ben Wishovich
d5fbeb9474 WIP Implementation of expanded LeptosOptions, which interop with the cargo-leptos beta and allow configuration of Leptos. Adds id option to <Stylesheet/> 2022-12-20 12:14:05 -08:00
Greg Johnston
7ca131c5b8 Work on hydration examples 2022-12-19 22:45:12 -05:00
Greg Johnston
79712ac4ef Fixes for <Suspense/> hydration 2022-12-19 22:45:00 -05:00
Greg Johnston
065d6b3c19 Fix workspace 2022-12-19 22:44:41 -05:00
Greg Johnston
9a2035f1e1 Relocate view-tests into leptos_dom 2022-12-19 20:11:25 -05:00
Greg Johnston
df8e50e85a Update TodoMVC example 2022-12-19 20:10:15 -05:00
Greg Johnston
58748af63b Fix _ref API in macro 2022-12-19 20:10:09 -05:00
Greg Johnston
36099c19c3 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-19 16:25:26 -05:00
Greg Johnston
8be33ccd7c Updating todo-app-sqlite example (todo: fix hydration) 2022-12-19 16:25:24 -05:00
Greg Johnston
d6920847ca Fix tests in counter example 2022-12-19 16:14:46 -05:00
Jose Quesada
f2553c117b changed from using <template /> to using custom element names and fixed EachItem on release 2022-12-19 08:57:09 -06:00
Greg Johnston
c63c8728a7 Clear warnings in router example 2022-12-19 07:41:50 -05:00
Greg Johnston
557bd25e2c Clear warnings 2022-12-19 07:39:30 -05:00
Greg Johnston
5ee8b20770 Clear warnings 2022-12-19 07:35:18 -05:00
Greg Johnston
da4b46e359 Clear warnings and improve #[cfg] readability 2022-12-19 07:34:10 -05:00
Greg Johnston
51b0ec3204 Clear warnings in view macro 2022-12-19 07:26:31 -05:00
Greg Johnston
c103c8f05b Suppress warnings about unused Scope variable in components 2022-12-19 07:22:25 -05:00
Greg Johnston
83c8f8b0cb Clear warnings in #[component] macro 2022-12-19 07:20:55 -05:00
Jose Quesada
eccbc424a2 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-18 16:52:23 -06:00
Jose Quesada
9d370776d2 printing html as it is being streamed to client 2022-12-18 16:52:12 -06:00
Greg Johnston
fc8921445e Update parent-child example 2022-12-18 17:12:06 -05:00
Greg Johnston
c635adb426 Fix Suspense & Transition, hopefully for last time (lol) 2022-12-18 17:01:56 -05:00
Greg Johnston
a845bf12e6 Clear warnings 2022-12-18 16:27:19 -05:00
Greg Johnston
eb573bf242 Remove HydrationKey from leptos_reactive 2022-12-18 16:23:04 -05:00
Greg Johnston
e8aaa77160 Remove now-unused hydration stuff from Scope 2022-12-18 16:20:44 -05:00
Greg Johnston
c0a407b0cd Fix --release builds 2022-12-18 16:15:18 -05:00
Jose Quesada
d79d4c4f86 fixed Fragment::lazy that should take `FnOnce 2022-12-18 13:29:46 -06:00
Greg Johnston
3195ab4ffc Get Suspense/Transition hydration working 2022-12-18 07:38:51 -05:00
Greg Johnston
80287f7a61 Merge pull request #174 from nim65s/main
Fix example tailwind
2022-12-17 19:43:52 -05:00
Guilhem Saurel
218faed341 Fix example tailwind
fix:
╰─>$ cargo leptos watch
Error: at `…/cargo-leptos-0.0.9/src/main.rs@104:58`

Caused by:
    no css/sass/scss file found at: "style/output.css"
2022-12-17 23:33:18 +01:00
Jose Quesada
d071a7c1e0 added HydrationCtx helper methods for forcebly adding leptos-hk, only helpers though, still need to update component lookup logic as well as element and template creation to include these 2022-12-17 12:38:40 -06:00
Jose Quesada
234e1cda4e Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-17 11:57:25 -06:00
Jose Quesada
e140549755 now removing id and leptos-hk from elements 2022-12-17 11:57:14 -06:00
Greg Johnston
df41e4dbc2 FIxes for Suspense/Hydration 2022-12-17 08:46:09 -05:00
Greg Johnston
9c6aaed0a8 Fix issue of <Routes/> mismatch 2022-12-17 08:28:22 -05:00
Greg Johnston
eaf4bbb068 Merge pull request #173 from benwis/ssr-headers
Allow Headers and Status to be set by components and Server Fns during initial SSR Load
2022-12-17 07:04:32 -05:00
Ben Wishovich
186e2454b0 Fix DBs 2022-12-16 17:37:17 -08:00
Ben Wishovich
6fa15a5584 Cleanup of testing files 2022-12-16 17:35:32 -08:00
Jose Quesada
aa71e1f66c Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-16 16:38:06 -06:00
Jose Quesada
ae16c2f96d prepending :: to leptos imports 2022-12-16 16:37:47 -06:00
Greg Johnston
4e8ad641ec Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-16 17:35:57 -05:00
Greg Johnston
64c29b1787 Remove unused import 2022-12-16 16:43:07 -05:00
Greg Johnston
df517ea9bb Add transition support 2022-12-16 16:42:27 -05:00
Ben Wishovich
2d289dd2b6 Actix version, but the issue is the same 2022-12-16 13:18:35 -08:00
Greg Johnston
702a785ca0 Fix context tests 2022-12-16 15:28:10 -05:00
Jose Quesada
abc117b9bf annotated For with #[component] 2022-12-16 14:13:51 -06:00
Greg Johnston
4823e0eb8d Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-16 12:17:49 -05:00
Greg Johnston
d65fde1ed4 Fix Link in SSR 2022-12-16 12:17:41 -05:00
Jose Quesada
6cfd2ba04e added prop docs to props builder 2022-12-16 08:51:07 -06:00
Jose Quesada
cd178c5c85 using format_ident instead of Ident::new 2022-12-16 08:25:52 -06:00
Jose Quesada
5a44f9eb4b Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-16 07:57:00 -06:00
Jose Quesada
688b0a6b73 started fine-tunning view macro spans 2022-12-16 07:30:13 -06:00
Greg Johnston
ef84d77e12 Merge branch 'main' of https://github.com/gbj/leptos 2022-12-16 07:20:59 -05:00
Greg Johnston
924b632fd3 Improve docs for provide_context and use_context 2022-12-16 07:20:54 -05:00
Ben Wishovich
2658e158df HERE BE DRAGONS 2022-12-15 22:31:47 -08:00
Ben Wishovich
21274c08bf Hmmmm 2022-12-15 22:07:11 -08:00
Greg Johnston
b83cf4dc51 Clean up Suspense SSR 2022-12-15 21:58:11 -05:00
Greg Johnston
1826d4bab2 Remove extra logging 2022-12-15 21:56:11 -05:00
Greg Johnston
e6e71cb8e6 Remove extra logging 2022-12-15 21:55:21 -05:00
Greg Johnston
01252b841d Fix <Suspense/> SSR 2022-12-15 21:54:43 -05:00
Greg Johnston
c6d30a710a FIx issues I introduced to SSR while logging this 2022-12-15 21:04:55 -05:00
Greg Johnston
fefca82d16 Minimal tracing to show order of rendering 2022-12-15 20:56:52 -05:00
Greg Johnston
a253d224ac Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-15 20:18:44 -05:00
Greg Johnston
04f331d444 Fix missing HTML 2022-12-15 20:18:41 -05:00
Ben Wishovich
2fb3515e62 Cursed, it is 2022-12-15 16:03:07 -08:00
Ben Wishovich
7a8b08d149 Broken in a new way 2022-12-15 15:57:41 -08:00
Ben Wishovich
0bc29b5f26 Closer maybe? 2022-12-15 14:12:54 -08:00
Ben Wishovich
f9bda65dbe WIP impl of SSR Header/Status setting 2022-12-15 13:14:02 -08:00
Jose Quesada
a591d54a22 changed router to directly return children, rather than a closure to not break tracing continuity 2022-12-15 13:53:39 -06:00
Jose Quesada
3e508b16f3 impl #[component(transparent)] 2022-12-15 13:40:58 -06:00
Jose Quesada
5603fac109 ported Route 2022-12-15 13:07:09 -06:00
Jose Quesada
8adf9108c5 ported Routes 2022-12-15 13:00:53 -06:00
Jose Quesada
610e38a967 fixed Coutlet from being a double component 2022-12-15 12:52:46 -06:00
Jose Quesada
34697b74a0 fixed form components to take children(cx) directly instead of a closure 2022-12-15 12:51:43 -06:00
Jose Quesada
0a8b516182 ported A 2022-12-15 12:50:00 -06:00
Jose Quesada
5405dcd09d ported MultiActionForm 2022-12-15 12:40:41 -06:00
Jose Quesada
1aaacbaf5b ported ActionForm 2022-12-15 12:37:19 -06:00
Jose Quesada
ce9aec2520 started porting router to #[component] 2022-12-15 12:31:53 -06:00
Jose Quesada
422233eecf removed some HydrationCtx things and improved tracing for DynChild 2022-12-15 12:05:17 -06:00
Jose Quesada
3b99d2d4fd added tracing support to component macro 2022-12-15 12:01:16 -06:00
Jose Quesada
9eadac9f2c added HydrationCtx helpers and fixed a couple bugs with the hydration example 2022-12-15 10:04:44 -06:00
Greg Johnston
416e1a617b Change start to hydrate in example 2022-12-15 08:39:19 -05:00
Greg Johnston
1b0aa4d903 Disable macro SSR until I've added IDs 2022-12-15 08:36:46 -05:00
Greg Johnston
01013b00e5 Implement classes in SSR macro path 2022-12-15 07:56:31 -05:00
Greg Johnston
3ef64bd372 <Suspense/> streaming and hydration issue 2022-12-14 23:18:54 -05:00
Greg Johnston
c463579faa Round 1 of next_hydration_key() 2022-12-14 20:38:37 -05:00
Greg Johnston
67de5685bb Merge pull request #167 from benwis/axum-context
Give Axum access to Request and allow server functions to set status/headers/cookies
2022-12-14 15:47:54 -05:00
Ben Wishovich
8a85d4261a Fix Todo DB 2022-12-14 11:20:53 -08:00
Jose Quesada
218c4d3c90 moved body back inside itself to allow forwarding attributes 2022-12-14 12:57:43 -06:00
Ben Wishovich
90849cc6e3 Add working example of Actix/Axum Header and Status Setting 2022-12-14 10:55:05 -08:00
Jose Quesada
5f95776a08 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-14 12:24:16 -06:00
Jose Quesada
adee33a08e initial impl of #[prop()] 2022-12-14 12:15:41 -06:00
Greg Johnston
e3e0460371 Disable SSR macr until node ID generation is done 2022-12-14 10:57:37 -05:00
Greg Johnston
6e4448dae6 SSR work with missing node IDs 2022-12-14 10:56:48 -05:00
Greg Johnston
fee0f5490b Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-14 09:06:20 -05:00
Greg Johnston
e1836af6bd Fix .wasm location in integration 2022-12-14 09:06:10 -05:00
Jose Quesada
b37a36a003 allowing clippy lint for too many arguments on components 2022-12-14 07:34:31 -06:00
Greg Johnston
e865f609ee Merge pull request #166 from gbj/component-macro-docs
Add more entry-level docs for `#[component]` macro
2022-12-14 07:49:33 -05:00
Greg Johnston
621122adf7 Fix Suspense/Transition SSR 2022-12-14 07:12:43 -05:00
Greg Johnston
f6acecd3ad Fix Wasm import URL in integrations 2022-12-14 07:12:36 -05:00
Greg Johnston
f17c7fdb90 Remove duplicated window_event_listener 2022-12-14 07:00:56 -05:00
Greg Johnston
21178b1682 Correct definitions of is_browser and is_server 2022-12-14 06:57:35 -05:00
Greg Johnston
1c8b640855 Update #[component] docs 2022-12-14 06:44:14 -05:00
Ben Wishovich
9f97497e48 Working impl of Request access and Response cookies for Axum 2022-12-13 22:07:15 -08:00
Ben Wishovich
70f2b3b4d3 More fiddling, still no dice 2022-12-13 16:05:31 -08:00
Ben Wishovich
181a15cf66 WIP axum acces to request 2022-12-13 15:23:47 -08:00
Jose Quesada
a7a35857bb now tracking scope name 2022-12-13 14:33:34 -06:00
Jose Quesada
caa919b257 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-13 14:15:42 -06:00
Jose Quesada
4c123884de reverted to redeclaring the component fn within itself 2022-12-13 14:15:23 -06:00
Greg Johnston
5e06eb1a99 Correct use of cx 2022-12-13 15:14:41 -05:00
Greg Johnston
cc0bf20c9d Use the correct identifier for cx here 2022-12-13 15:06:21 -05:00
Jose Quesada
5aab8e40c2 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-13 13:24:06 -06:00
Jose Quesada
eeaccfc815 moved fn block into Component::new 2022-12-13 13:23:51 -06:00
Greg Johnston
cac1187346 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-13 14:21:06 -05:00
Greg Johnston
4134d2f924 Working on router example 2022-12-13 14:20:58 -05:00
Greg Johnston
621976c92c Add correct import for doctest 2022-12-13 14:14:04 -05:00
Jose Quesada
39e809f686 initial impl of component macro with inline doc comments and TypedBuilder overrides 2022-12-13 12:44:30 -06:00
Greg Johnston
b2d7ad2afd Fix a couple issues with intra-doc links 2022-12-13 13:10:04 -05:00
Greg Johnston
73b21487b9 Add more entry-level docs for #[component] macro 2022-12-13 13:06:37 -05:00
Greg Johnston
896812c8d6 Merge pull request #165 from Gentle/update_returning
update_returning handlers
2022-12-13 12:54:40 -05:00
Jose Quesada
286c95136f Merge branch 'leptos_dom_v2' into leptos_dom_v2_component 2022-12-13 07:38:51 -06:00
Greg Johnston
5ca169ac06 Properly handle Scope when creating component children 2022-12-13 07:42:14 -05:00
Greg Johnston
8efb28826f Properly set hydration key 2022-12-12 21:17:51 -05:00
Greg Johnston
10799c33b7 Properly dispose 2022-12-12 21:17:38 -05:00
Greg Johnston
657de9df33 Remove log 2022-12-12 20:57:18 -05:00
Greg Johnston
069fc88042 Update SSR support for Suspense/Transition 2022-12-12 20:56:19 -05:00
Greg Johnston
92335989b7 Don't overwrite MetaContext from integration 2022-12-12 20:55:57 -05:00
Greg Johnston
58d2ce113d Use Scope-based hydration key generation 2022-12-12 20:55:41 -05:00
Greg Johnston
e499c88288 Consolidating hydration key generation 2022-12-12 20:55:28 -05:00
Ramon Klass
fea462c90a update_returning handlers 2022-12-13 02:19:58 +01:00
Jose Quesada
a75cbee133 started working on component macro 2022-12-12 18:33:12 -06:00
Jose Quesada
99ff73c721 applied fix per gbj's suggested 2022-12-12 16:23:15 -06:00
Jose Quesada
2c9eff3659 impl IntoView for all std types that made sense 2022-12-12 14:45:10 -06:00
Jose Quesada
ce5355d73f impl IntoView for `Vec<impl IntoView> 2022-12-12 13:52:41 -06:00
Jose Quesada
582cd7d729 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-12 13:39:57 -06:00
Jose Quesada
e2ef293d19 added IntoFragment trait for all IntoIterator and also impl FromIterator for Fragment 2022-12-12 13:39:41 -06:00
Greg Johnston
b06a4ba805 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-12 13:40:13 -05:00
Greg Johnston
6b6c54e8ff Updating hackernews example 2022-12-12 13:40:05 -05:00
Greg Johnston
8a0e56aff5 Properly namespace Fragment when used 2022-12-12 13:39:50 -05:00
Greg Johnston
1804a65857 Merge in updates to meta package 2022-12-12 13:39:30 -05:00
Greg Johnston
035f929d3b Merge class prop back into A component 2022-12-12 13:39:19 -05:00
Jose Quesada
0b11a8dda6 fixed compilation on non-CSR targets 2022-12-12 12:26:11 -06:00
Jose Quesada
5881fb9064 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-12 12:18:29 -06:00
Jose Quesada
9dbbb26100 made is_server and helpers const fn and added View::into_html_element helper 2022-12-12 12:18:15 -06:00
Greg Johnston
9c0d813697 Resolve issue with scope disposal panicking because it can't find runtime (because runtime already dropped) 2022-12-12 10:25:20 -05:00
Greg Johnston
4a1d16b641 Properly reset ID in streaming 2022-12-12 10:24:55 -05:00
Greg Johnston
c4eeb4f39f Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-12 09:49:22 -05:00
Greg Johnston
37ab7b34f9 Fix imports when in SSR mode so we don't run wasm-bindgen code 2022-12-12 09:49:15 -05:00
Greg Johnston
fcae17eab7 Streamline streaming SSR 2022-12-12 09:48:45 -05:00
Greg Johnston
c4cc3e944b Merge in changes from main 2022-12-12 09:26:46 -05:00
Greg Johnston
c481e465b0 Update counter-isomorphic 2022-12-12 09:26:13 -05:00
Greg Johnston
073bd759b0 Add .gitignore 2022-12-12 09:25:57 -05:00
Greg Johnston
88435af844 Update counter-isomorphicexample 2022-12-12 09:25:47 -05:00
Jose Quesada
ff21f38626 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-12 07:29:51 -06:00
Jose Quesada
9bc8492245 removed impl of IntoView for &str and replaced with &'static str for Cow<'static, str> opt 2022-12-12 07:29:35 -06:00
Greg Johnston
2389cec5f7 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-11 20:21:35 -05:00
Greg Johnston
86d5f4c2e4 Update <For/> in example 2022-12-11 20:21:07 -05:00
Jose Quesada
811449d664 stopped DynChild from panicking when nested DynChild run within the same signal trigger but the parent runs before this child 2022-12-11 17:36:19 -06:00
Greg Johnston
109863c59a Update SSR benchmark code to new APIs 2022-12-11 17:42:15 -05:00
Greg Johnston
b9e8777bb1 Update <For/> API 2022-12-11 17:35:46 -05:00
Jose Quesada
d44c7f121b remove Transition DynChild workaround 2022-12-11 13:37:45 -06:00
Jose Quesada
9b1c4e42bd added missing generic and removed workaround for Suspense DynChild 2022-12-11 13:33:11 -06:00
Jose Quesada
ff026ea953 addressed clippy lints in release mode 2022-12-11 13:27:52 -06:00
Jose Quesada
9eb1f2fdf8 addressed clippy lints 2022-12-11 13:22:01 -06:00
Jose Quesada
06538ba021 fixed DynChild when changing from Text to anything else 2022-12-11 13:06:54 -06:00
Jose Quesada
14c6dcf902 now surrounding View::Text with quotes 2022-12-11 12:57:54 -06:00
Jose Quesada
a15dedb82d impl new fmt::Debug repr for View 2022-12-11 12:43:58 -06:00
Greg Johnston
43ffa1bcd7 Use Fn() -> Fragment for component children, and update router and Suspense/Transition 2022-12-10 22:12:08 -05:00
Greg Johnston
d30de9abce Add Transparent type to pass arbitrary data inside a View 2022-12-10 22:11:39 -05:00
Greg Johnston
f459253cdb Clear warning here and add explanation 2022-12-10 21:05:13 -05:00
Greg Johnston
a53f7ccdb7 Update Suspense and Transition with new impl IntoView pattern 2022-12-10 19:51:41 -05:00
Greg Johnston
b7bd4fc69c Update Counters example 2022-12-10 19:51:30 -05:00
Greg Johnston
9d43eb5503 Tweak handling of component children 2022-12-10 19:42:08 -05:00
Greg Johnston
335c040702 Implement IntoView for same set of primitive types 2022-12-10 19:33:56 -05:00
Greg Johnston
a4e5bf03d8 Start implementing IntoView for primitives 2022-12-10 19:28:28 -05:00
Greg Johnston
90de653a60 Concrete return types work 2022-12-10 19:28:11 -05:00
Greg Johnston
359feebccf Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-10 19:21:51 -05:00
Greg Johnston
6c29d85b8d Remove into_view in view and component macros 2022-12-10 19:21:46 -05:00
Jose Quesada
315407b866 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-10 15:32:51 -06:00
Jose Quesada
d22fa3c5e7 removed IntoChild 2022-12-10 15:32:36 -06:00
Greg Johnston
6d24af7748 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-10 16:22:55 -05:00
Greg Johnston
18bd2162cf Docs in component macro 2022-12-10 16:21:58 -05:00
Jose Quesada
74ea50a293 fixed Component::new to take anything that impl IntoView 2022-12-10 13:50:05 -06:00
Greg Johnston
4a9f906571 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-10 14:09:43 -05:00
Greg Johnston
4b970b3f1c Fix rerendering issue 2022-12-10 14:06:49 -05:00
Greg Johnston
6db086c0a6 Meant to delete this, not comment it out 2022-12-10 14:06:42 -05:00
Greg Johnston
a7da8232a7 I was wrong -- these will be automatically disposed by the parent scope in the correct order, and actually shouldn't be done manually 2022-12-10 14:06:18 -05:00
Jose Quesada
a5f868915a added trait for converting impl AsRef<web_sys::Element> to HtmlElement<AnyElement> 2022-12-10 12:16:08 -06:00
Jose Quesada
237bacc2da addressed all clippy lints 2022-12-10 10:50:01 -06:00
Jose Quesada
e09f5665ca Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-10 09:54:56 -06:00
Jose Quesada
11720302a2 addressed all clippy lints for web targets 2022-12-10 09:54:49 -06:00
Jose Quesada
e097ec84c2 started addressing clippy lints and fixed an undiscovered bug 2022-12-10 08:40:24 -06:00
Greg Johnston
bcb130deca Working on router 2022-12-10 09:15:05 -05:00
Greg Johnston
c4af033f2c Remove logging 2022-12-10 09:12:11 -05:00
Greg Johnston
5bdd150347 Get router working with new renderer 2022-12-10 08:32:58 -05:00
Greg Johnston
f9cc57acb9 Fix attributes in view macro 2022-12-10 08:32:30 -05:00
Greg Johnston
000d796149 Update router example 2022-12-10 08:32:19 -05:00
Greg Johnston
a7c16c9b09 Implement PartialEq and Eq for View 2022-12-10 07:37:07 -05:00
Greg Johnston
ad01d69540 <Suspense/> and <Transition/> 2022-12-09 22:52:30 -05:00
Greg Johnston
7959f5b324 Add signal helpers from main that are needed for <Transition/> 2022-12-09 22:52:14 -05:00
Greg Johnston
46e77d72ea Add transition 2022-12-09 22:23:42 -05:00
Greg Johnston
1b8db4d4f4 "Implement Clone on View and its affiliates. Necessary for <Transition/> and some routing features. No significant changes needed, every type involved could already derive Clone. 2022-12-09 22:17:26 -05:00
Greg Johnston
7264d902c6 RIP map_keyed and old <For/> component 2022-12-09 22:07:07 -05:00
Jose Quesada
8017b416fb Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-09 18:58:58 -06:00
Jose Quesada
dd16890021 Component now creating child scope, forgot about this 2022-12-09 18:58:38 -06:00
Jose Quesada
a17651fe02 removed the workspace override 2022-12-09 18:00:06 -06:00
Greg Johnston
ba34a9644e Work on Suspense 2022-12-09 18:27:32 -05:00
Jose Quesada
094492f076 impl IntoChild for tuples up to 26 in size 2022-12-09 17:13:37 -06:00
Jose Quesada
6d5994f72e removed TopoId 2022-12-09 17:08:11 -06:00
Jose Quesada
641a064200 removed IntoEach until we can come up with a better game plan 2022-12-09 17:04:32 -06:00
Jose Quesada
c83f29be1b fix DynChild saving it's old child, rather than the new one 2022-12-09 16:46:02 -06:00
Jose Quesada
bd3cc0b5ec fix DynChild trying to render old child rather than new one 2022-12-09 16:17:37 -06:00
Greg Johnston
0b448daf3a Fix SimpleCounter example in tests 2022-12-09 14:58:53 -05:00
Greg Johnston
f6eceaeaf9 Update counters exaple 2022-12-09 14:56:50 -05:00
Greg Johnston
9a114eb595 Avoid name conflicts between functions to create elements and local variables 2022-12-09 14:36:25 -05:00
Greg Johnston
c01dba5138 Merge pull request #160 from gbj/component-documentation
Allows documenting `Component` and `ComponentProps` in a single doc comment
2022-12-09 14:27:36 -05:00
Greg Johnston
1929f2d8b2 Merge pull request #161 from gbj/docs-improvements
Docs improvements
2022-12-09 13:56:18 -05:00
Greg Johnston
dc7f44933c Add cargo-leptos to Readme 2022-12-09 13:28:26 -05:00
Greg Johnston
b56dde9a6d Add working Tailwind example per issue #147 2022-12-09 13:05:20 -05:00
Greg Johnston
74ec8925dc Additional documentation for issue #156 2022-12-09 12:41:17 -05:00
Jose Quesada
1ee9c2432b impl IntoView for tuples of up to 26 fields as well as for (Scope, T) 2022-12-09 11:29:46 -06:00
Jose Quesada
b0c27c48f6 updated example to work with new Each 2022-12-09 10:49:15 -06:00
Jose Quesada
c74a8274f2 removed child scope creation within Each 2022-12-09 10:44:05 -06:00
Jose Quesada
2ceb7f8934 fixed Fragment which would try mounting it's children while hydrating 2022-12-09 10:03:08 -06:00
Jose Quesada
16af593277 added render_to_string entry point and fixed bug where id counter would not be reset on every server render call 2022-12-09 09:34:25 -06:00
Jose Quesada
51c8d85528 custom user id's on HtmlElement is now supported during hydration 2022-12-09 09:15:37 -06:00
Jose Quesada
4ffc53a1dc not panicking when elements/components cannot be found 2022-12-09 08:47:53 -06:00
Jose Quesada
b7eeba77a0 fixed DynChild on release to prevent it merging with surrounding text 2022-12-09 08:25:39 -06:00
Jose Quesada
a8fb2720b9 removed a sneaky c that was in the SSR id generation for components, as well as fixed an extra id call count that wasn't supposed to be there 2022-12-08 22:40:49 -06:00
Jose Quesada
a50f1c58f7 fixed Each optimizations 2022-12-08 20:38:59 -06:00
Jose Quesada
6528aadb90 removed debug statement 2022-12-08 19:18:23 -06:00
Jose Quesada
dcd9842fca Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-08 18:42:55 -06:00
Jose Quesada
5a976525c0 fixed Each moving items it did not need to move 2022-12-08 18:42:36 -06:00
Greg Johnston
b8559d4335 Transition counter example to use View 2022-12-08 19:33:01 -05:00
Greg Johnston
eb40f9f7c7 Remove leptos_dom/ssr dependencies 2022-12-08 19:32:46 -05:00
Greg Johnston
d0fa9b89bf Remove my logs 2022-12-08 19:25:50 -05:00
Greg Johnston
fbfd1a4f60 Fix other dependencies on leptos_dom/csr and leptos_dom/hydrate 2022-12-08 19:24:36 -05:00
Jose Quesada
21716ea59d fixed panic on DynChild for double taking an option 2022-12-08 18:15:57 -06:00
Jose Quesada
e641108ed3 fixed error compiling 2022-12-08 18:01:06 -06:00
Jose Quesada
c3b4945c7e removed all cfgs that depend on anything but web and wasm32 2022-12-08 17:56:52 -06:00
Jose Quesada
95c535290d Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-08 17:48:45 -06:00
Jose Quesada
6af0b1a8ed hydration is now determained at runtime startup 2022-12-08 17:48:30 -06:00
Greg Johnston
9928abf36d Fix closing-node creation in Each in release 2022-12-08 18:45:27 -05:00
Greg Johnston
f0257222f8 Fix cx injection in macro 2022-12-08 18:29:44 -05:00
Greg Johnston
135317b7b8 Method, not a field 2022-12-08 18:26:44 -05:00
Jose Quesada
6b24135070 fixed subtle bug when hydrating from a text node would cause DynChild to not unmount correctly 2022-12-08 17:00:55 -06:00
Jose Quesada
e08808c2ab fixed SSR compilation errors 2022-12-08 16:13:12 -06:00
Jose Quesada
a932f72a4f DynChild now creating child scopes 2022-12-08 16:07:44 -06:00
Jose Quesada
75befde788 now creating child scopes for Each 2022-12-08 15:17:12 -06:00
Greg Johnston
3d10bbb0c6 Merge pull request #159 from benwis/dashes
Replace _ with - for KDL files
2022-12-08 13:15:21 -05:00
Ben Wishovich
8d325fce5c Replace _ with - for KDL files 2022-12-08 10:08:04 -08:00
Jose Quesada
adac34790e now creating child scopes for Each items, still not cleaning them up, however 2022-12-08 11:50:22 -06:00
Jose Quesada
838d0d27c9 fixed DynChild not removing the text generated on SSR 2022-12-08 11:10:34 -06:00
Jose Quesada
d29d29c1d4 fixed .into_view(cx) calling order to match between SSR and CSR 2022-12-08 10:41:28 -06:00
Jose Quesada
61e206c227 corrected DynChild to now mount children on SSR 2022-12-08 09:19:00 -06:00
Jose Quesada
70ae60d4d5 fixed broken compilation 2022-12-08 08:56:53 -06:00
Greg Johnston
7e457ee202 Merge pull request #157 from akesson/integration-html-updates
Integration html updates
2022-12-08 08:05:25 -05:00
hakesson
bb282189c3 Add preload of js and wasm 2022-12-08 08:11:15 +01:00
hakesson
2694d2e93c Add missing init param 2022-12-08 08:10:56 +01:00
Greg Johnston
56457bc3ad Generate HydrationKey with Scope 2022-12-07 15:57:53 -05:00
Greg Johnston
45395fe580 Fix off-by-one issue 2022-12-07 15:46:09 -05:00
Jose Quesada
3a90ed6c21 added debug aids to find the off-by-one error 2022-12-07 12:33:39 -06:00
Jose Quesada
55b691e1b0 marker hydration fixed, but we have an off-by-one error on id generation 2022-12-07 12:05:12 -06:00
Jose Quesada
c9b57ffa85 changed the value to bind to another input while I fix text 2022-12-07 10:43:10 -06:00
Jose Quesada
c239716170 updated the example to work with hydartion 2022-12-07 10:04:54 -06:00
Jose Quesada
46ef1bcf5d initial impl of eager hydration 2022-12-07 09:36:36 -06:00
Jose Quesada
5ac06251d4 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-07 07:24:53 -06:00
Jose Quesada
395cfe6bf1 fixed SSR and CSR cfg features 2022-12-07 07:24:35 -06:00
Greg Johnston
963ff85a5f Get basic SSR example working 2022-12-06 23:04:48 -05:00
Greg Johnston
dcbdbc8925 <template/> doesn't work as a self-closing tag 2022-12-06 23:03:33 -05:00
Jose Quesada
c290d1e9d6 started impl hydration for HtmlElement 2022-12-06 20:14:35 -06:00
Jose Quesada
51e5c6ba62 fixed TopoId not having a sum field 2022-12-06 11:35:31 -06:00
Jose Quesada
ee470b37bb removed starting_offset when calculating children ids 2022-12-06 10:26:19 -06:00
Jose Quesada
74c716e0f6 fixed attrs that start with an empty value from not having a space 2022-12-06 10:12:14 -06:00
Jose Quesada
00c25b5605 fixed EachItem not getting <template /> markers, as well as Unit 2022-12-06 09:55:02 -06:00
Jose Quesada
280f7a7735 removed cx param from all helper methods 2022-12-06 07:32:49 -06:00
Jose Quesada
f02405b649 fixed IntoProperty from disconnection from overwritten Scope 2022-12-06 07:24:21 -06:00
Jose Quesada
d6ef65daf6 fixed IntoClass from not disconnecting from overwritten Scope 2022-12-06 07:21:14 -06:00
Jose Quesada
98414ac192 removed overzelus use of Scope in Attribute 2022-12-06 07:15:39 -06:00
Jose Quesada
8b7728096a fixed IntoChild to not disconnect from overwritten Scope 2022-12-05 20:51:00 -06:00
Greg Johnston
aec289e384 Get SSR benchmarks running again 2022-12-05 20:34:29 -05:00
Greg Johnston
432eda8d6d Add some often-used helper functions 2022-12-05 20:00:27 -05:00
Greg Johnston
aa10ab861a Fix new cx-passing API for Attribute 2022-12-05 16:09:22 -05:00
Greg Johnston
870d8f3542 Different form for passing in cx 2022-12-05 16:07:25 -05:00
Greg Johnston
166df8d4c4 Merge changes to remove cx everywhere 2022-12-05 16:05:12 -05:00
Greg Johnston
666269539d Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-05 16:04:54 -05:00
Greg Johnston
0230b1ffa5 Deal w/ merge issues and get SSR working 2022-12-05 16:04:02 -05:00
Jose Quesada
3dd789f0c2 fixed IntoAttribute from disconnecting from it's overwritten Scope 2022-12-05 14:30:21 -06:00
Greg Johnston
dbb0fca1cb Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-05 15:12:09 -05:00
Greg Johnston
b8bd3bef13 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-05 15:11:17 -05:00
Jose Quesada
59f753cebb impl all the macro helper traits for (Scope, T) 2022-12-05 14:09:39 -06:00
Greg Johnston
d5f91e67a5 First pass at SSR 2022-12-05 15:09:04 -05:00
Greg Johnston
1b854ed787 Warn if you re-set a NodeRef 2022-12-05 14:24:33 -05:00
Jose Quesada
13f8006162 added HtmlElement::node_ref 2022-12-05 13:19:34 -06:00
Jose Quesada
b3d813d0c1 renamed IntoNode to IntoView and Node to View, and fixed broken doc links 2022-12-05 12:39:42 -06:00
Jose Quesada
1fbe8bd790 fixed the example to show undelegated 2022-12-05 12:28:14 -06:00
Jose Quesada
eba8af3b38 fixed event names and removed superfluous debug stmt 2022-12-05 12:23:53 -06:00
Jose Quesada
589f2585cc added small list of events that don't bubble 2022-12-05 11:43:18 -06:00
Jose Quesada
b69119f11f added all events I could find 2022-12-05 11:12:31 -06:00
Greg Johnston
f893dca39b This should handle the disposal correctly, when the parent scope disposes, instead of on Drop (which is immediate.) 2022-12-05 09:24:52 -05:00
Greg Johnston
64722604b5 Move Component::new() invocation inside the #[component] macro to allow for things like the <Route/> component 2022-12-05 08:45:27 -05:00
Greg Johnston
b6a90f154f Remove useless attempt at better Intellisense 2022-12-04 23:04:06 -05:00
Greg Johnston
2ef8032110 The event names shouldn't actually start with "on" — that's the HTML attribute form 2022-12-04 23:03:54 -05:00
Greg Johnston
3992febbfc Restored these events so they can be used, just typed as Event 2022-12-04 23:03:19 -05:00
Greg Johnston
982cec9507 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-04 22:59:57 -05:00
Greg Johnston
4a5b3b3cc5 Use new event system 2022-12-04 22:59:45 -05:00
Jose Quesada
18ff334f70 disabled non-existant web_sys event types 2022-12-04 21:38:23 -06:00
Jose Quesada
d2b4ae30d1 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-04 21:30:55 -06:00
Jose Quesada
abed797027 added HtmlElement::prop to the builder 2022-12-04 21:30:48 -06:00
Greg Johnston
a4ca863d42 Should insert the actual event type instead of MouseEvent! 2022-12-04 22:26:28 -05:00
Jose Quesada
6737413103 made Undelegated inner field public so users can just wrap any event with it to force it undelegated 2022-12-04 21:23:16 -06:00
Greg Johnston
33c3851d5b Fix fake assignment 2022-12-04 22:13:07 -05:00
Greg Johnston
7c298272d3 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-04 22:11:00 -05:00
Greg Johnston
71a2a24d09 Working components in view! macro 2022-12-04 22:10:56 -05:00
Jose Quesada
cffdc56062 added Custom event helper type for events not included 2022-12-04 20:45:58 -06:00
Jose Quesada
c29c15e1b7 removed on_delegated, as this can be done through Delegated<E>` type 2022-12-04 20:36:18 -06:00
Jose Quesada
fe417d50af Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-04 17:22:24 -06:00
Jose Quesada
cc538f8427 added more events 2022-12-04 17:22:03 -06:00
Jose Quesada
07db7ae62b started the grooling process of adding typed events 2022-12-04 09:13:20 -06:00
Greg Johnston
09d2a56672 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-03 21:29:22 -05:00
Greg Johnston
8ea7e20dfb Builder-compatible view! macro 2022-12-03 21:28:39 -05:00
Greg Johnston
02c982c80f Missing cfg causing non-CSR builds to break; I may have removed this accidentally or somethign 2022-12-03 21:27:53 -05:00
Jose Quesada
a5b157f14f uncommented (presumably) accidental comment of components import 2022-12-03 17:58:51 -06:00
Jose Quesada
5d439ceee8 fixed merge conflict 2022-12-03 16:52:57 -06:00
Jose Quesada
282ece2fab fixed Each when moving to take into account backshift 2022-12-03 16:50:29 -06:00
Greg Johnston
205abd4cbc Add _prop macro helper 2022-12-03 14:47:39 -05:00
Greg Johnston
fbb372e618 Add is_server and is_dev helper macros 2022-12-03 14:47:31 -05:00
Greg Johnston
b4679ea688 Fix NodeRef on stable 2022-12-03 14:28:33 -05:00
Greg Johnston
d2f68d8a0a Add helpers for simple logging 2022-12-03 14:22:19 -05:00
Greg Johnston
ebe872ca57 Add NodeRef 2022-12-03 14:21:42 -05:00
Greg Johnston
bce1ed8d67 Add NodeRef 2022-12-03 14:21:36 -05:00
Greg Johnston
a87ffd6d5c Make it possible for macro to specify event types automatically by reducing to one generic 2022-12-03 14:20:18 -05:00
Greg Johnston
c0d2dde53a _class helper for macro 2022-12-03 14:05:59 -05:00
Greg Johnston
cd93ecb5ab Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-03 13:43:14 -05:00
Greg Johnston
4beee7f924 Fix bounds check -- was panicking on remove because added_delta was -1 2022-12-03 13:43:05 -05:00
Jose Quesada
234260a784 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-03 12:29:45 -06:00
Jose Quesada
e82d4d492b revert EachItem lazy text fill 2022-12-03 12:29:28 -06:00
Greg Johnston
aa0271a493 Add _attr helper for macro 2022-12-03 13:06:15 -05:00
Greg Johnston
29db252411 Remove fill_if_text since eager now 2022-12-03 13:06:06 -05:00
Jose Quesada
aa5caaf4f1 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-03 10:38:47 -06:00
Jose Quesada
718b5f8ae1 forgot to fill child text nodes of EachItem 2022-12-03 10:34:36 -06:00
Greg Johnston
48e4e01e24 Fast path for Text -> Text. I think this is probably broken at the moment (if you replace Text with something else) but can fix 2022-12-03 11:29:53 -05:00
Greg Johnston
7948a1914a Fill Text node eagerly 2022-12-03 10:54:38 -05:00
Greg Johnston
1fa023ee08 Merge branch 'leptos_dom_v2' of https://github.com/jquesada2016/leptos into leptos_dom_v2 2022-12-03 10:35:58 -05:00
Greg Johnston
cc1c264548 ._child() helper for macro (merges child and dyn_child) 2022-12-03 10:34:27 -05:00
Jose Quesada
11960efb1b impl Deref<Target = web_sys::HtmlElement> for HtmlElement<El>` 2022-12-03 08:45:29 -06:00
Jose Quesada
16706233c7 fixed Each move requiring backshifting all items 2022-12-02 21:17:27 -06:00
Jose Quesada
c49ed49580 refactored a bit and left the premise for fixing Move in Each 2022-12-01 23:14:30 -06:00
Greg Johnston
a0e0194475 FxHashSet uses a faster hashing algorithm than the default SipHash 2022-12-01 17:22:35 -05:00
Greg Johnston
d266943a8f id isn't necessary in the browser version, right? 2022-12-01 17:22:05 -05:00
Jose Quesada
86acd8a461 now reusing text nodes 2022-12-01 14:31:47 -06:00
Jose Quesada
786b7abcb7 fixed Each opts to handle more edge casses 2022-12-01 07:58:28 -06:00
Jose Quesada
05adbda4ca fixed panic when trying to get parent node 2022-11-30 20:31:09 -06:00
Jose Quesada
6db59d526f fixed panic when dropping components 2022-11-30 20:12:13 -06:00
Jose Quesada
0149ce1be1 removed need for 2 marker nodes 2022-11-30 13:29:51 -06:00
Jose Quesada
b058e68e4e addressed errors and clippy lints 2022-11-30 09:01:16 -06:00
Greg Johnston
0ef0417b5a Separate SSR + browser implementations, with eager creation in browser (+ string interning) 2022-11-29 22:15:27 -05:00
Jose Quesada
649378ffbe now using Range only for potentially large groups of nodes 2022-11-29 20:08:54 -06:00
Jose Quesada
060f8d7a6a usine clone_node on cached nodes 2022-11-29 17:00:42 -06:00
Jose Quesada
b8c125cd14 initial impl of eager builder 2022-11-29 16:50:52 -06:00
Jose Quesada
33afcf6b17 impl eager setting of attr 2022-11-29 15:49:32 -06:00
Jose Quesada
bbb188d2f6 added some more small opts, including using only a single Range obj 2022-11-29 13:44:03 -06:00
Jose Quesada
d8f8673ad3 tuned append opt to include the limit 2022-11-29 13:11:00 -06:00
Jose Quesada
a73c71842a added Each append opt 2022-11-29 12:49:46 -06:00
Jose Quesada
d59ce5aebf fixed replace all items opt 2022-11-29 12:01:19 -06:00
Jose Quesada
5b14aa98e9 Comment markers are now being created by cloning a reference 2022-11-29 11:15:29 -06:00
Jose Quesada
f78400a955 fixed merge conflict 2022-11-29 07:44:47 -06:00
Jose Quesada
488856fdcc removed broken debug assertions 2022-11-29 07:37:33 -06:00
Greg Johnston
42659e20cd Fast path for clearing 2022-11-28 21:51:23 -05:00
Greg Johnston
54b7b780c8 (Some of?) these debug assertions seem to break the release build 2022-11-28 21:37:47 -05:00
Jose Quesada
a638c3d39a removed superfluous console::log 2022-11-28 19:13:46 -06:00
Jose Quesada
ebb50cff6c fixed building on non-browser targets 2022-11-28 19:04:00 -06:00
Jose Quesada
9424c293d7 fixed ops conflicting with each other 2022-11-28 18:49:06 -06:00
Jose Quesada
0b72c5550b fixed Each clear opt 2022-11-28 17:26:48 -06:00
Jose Quesada
698527ddf6 fixed building in release 2022-11-28 15:02:09 -06:00
Jose Quesada
af067361a9 checking to make sure children is not empty before applying replace opt 2022-11-28 14:19:31 -06:00
Jose Quesada
04aa1585fa optimized the case of replacing all items in an Each 2022-11-28 14:04:14 -06:00
Jose Quesada
9e84a2c273 Each now optimized for clearing 2022-11-28 13:30:37 -06:00
Jose Quesada
9050572c68 EachItem is now removing it's direct children 2022-11-28 13:21:13 -06:00
Jose Quesada
40c6081256 DynChild is now responsible for removing it's direct children rather than relying on WebSysNode 2022-11-28 13:12:22 -06:00
Jose Quesada
484e6796c0 Component now creating a child scope` 2022-11-28 08:59:35 -06:00
Greg Johnston
fdef43c2fc Small performance optimizations: wasm-bindgen string interning and cached document() (+ a function to mount to any parent) 2022-11-28 08:45:09 -05:00
Greg Johnston
90854e38e6 Add event system 2022-11-27 19:42:37 -05:00
Jose Quesada
8fac1c5b3a greatly improved tracing visibility 2022-11-27 17:17:24 -06:00
Jose Quesada
6439964ef6 made children field of Component pub and added Component example to examples/test-bench 2022-11-27 11:57:35 -06:00
Jose Quesada
5b612d8084 fixed Component not mounting children on into_node calls 2022-11-27 11:46:36 -06:00
Jose Quesada
b2d9bc4aa8 removed duplicate value, as duplicate keys are invalid 2022-11-27 11:34:35 -06:00
Jose Quesada
f615dae87c applied new rustfmt config and added HtmlElement::dyn_attr 2022-11-27 11:32:18 -06:00
Jose Quesada
79058e1535 impl IntoNode for [Node; N] and `[HtmlElement<El>; N] 2022-11-27 09:49:57 -06:00
Jose Quesada
a51c12d152 added HtmlElement::attr_bool helper method 2022-11-27 09:45:38 -06:00
Jose Quesada
8999a24ec3 can now set id and attrs 2022-11-27 09:34:19 -06:00
Jose Quesada
da1916e35a renamed Each to EachKey 2022-11-27 07:29:17 -06:00
Jose Quesada
b1987648cf addressed most clippy lints 2022-11-27 07:20:33 -06:00
Jose Quesada
001323c058 fixed compilation on non-browser targets 2022-11-27 07:09:07 -06:00
Jose Quesada
55633560e7 Each is now fully working 2022-11-26 20:59:54 -06:00
Jose Quesada
be60713b13 updated prepare_for_move to use Range 2022-11-26 19:36:25 -06:00
Jose Quesada
000a4bf62d initial (broken move) impl of Each 2022-11-26 18:49:51 -06:00
Jose Quesada
68938054ca removed name and rename from DynChild 2022-11-26 09:37:56 -06:00
Jose Quesada
368b96424d refactored components to not render as many comments in prod 2022-11-26 09:33:57 -06:00
Jose Quesada
c5da652ac1 renamed Fragment representation from <Frgament> to <> 2022-11-26 07:17:12 -06:00
Jose Quesada
38f71a3cc9 impl Default for all HTML tags 2022-11-26 07:16:15 -06:00
Jose Quesada
2d21146665 basic CSR is working 2022-11-25 17:39:42 -06:00
Jose Quesada
24b1fc01ca added impl IntoNode for most types IntoChild supported 2022-11-25 14:37:47 -06:00
Jose Quesada
fbfdb9fd15 now collecting Scope only on `IntoNode::into_node 2022-11-25 13:10:51 -06:00
Jose Quesada
54074409ab only std::prelude clashing structs should have a trailing _ 2022-11-25 07:39:15 -06:00
Jose Quesada
e738c5c41f forgot to redirect docs to the builder fn 2022-11-24 21:37:46 -06:00
Jose Quesada
d3ec86ab18 added all HTML elements 2022-11-24 21:35:40 -06:00
Jose Quesada
ce5b8f95e7 forgot to insert opening and closing component marker nodes into the document fragment 2022-11-24 16:57:44 -06:00
Jose Quesada
6bb20aed15 initial API design 2022-11-24 14:45:31 -06:00
Jose Quesada
9b3c9eb90b clean slate 2022-11-24 09:16:57 -06:00
234 changed files with 14368 additions and 7595 deletions

1
.gitignore vendored
View File

@@ -6,4 +6,3 @@ blob.rs
Cargo.lock
**/*.rs.bk
.DS_Store
.leptos.kdl

View File

@@ -3,7 +3,6 @@ members = [
# core
"leptos",
"leptos_dom",
"leptos_core",
"leptos_config",
"leptos_macro",
"leptos_reactive",
@@ -17,28 +16,12 @@ members = [
"meta",
"router",
# examples
"examples/counter",
"examples/counter-isomorphic",
"examples/counters",
"examples/counters-stable",
"examples/fetch",
"examples/hackernews",
"examples/hackernews-axum",
"examples/parent-child",
"examples/router",
"examples/todomvc",
"examples/todo-app-sqlite",
"examples/todo-app-sqlite-axum",
"examples/todo-app-cbor",
"examples/view-tests",
# book
"docs/book/project/ch02_getting_started",
"docs/book/project/ch03_building_ui",
"docs/book/project/ch04_reactivity",
]
exclude = ["benchmarks"]
exclude = ["benchmarks", "examples"]
[profile.release]
codegen-units = 1
@@ -46,29 +29,4 @@ lto = true
opt-level = 'z'
[workspace.metadata.cargo-all-features]
skip_feature_sets = [
[
"csr",
"ssr",
],
[
"csr",
"hydrate",
],
[
"ssr",
"hydrate",
],
[
"serde",
"serde-lite",
],
[
"serde-lite",
"miniserde",
],
[
"serde",
"miniserde",
],
]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]

View File

@@ -1,4 +1,4 @@
**Please note:** This framework is in active development. I'm keeping it in a cycle of 0.0.x releases at the moment to indicate that its not even ready for its 0.1.0. Active work is being done on documentation and features, and APIs should not necessarily be considered stable. At the same time, it is more than a toy project or proof of concept, and I am actively using it for my own application development.
**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;">
@@ -12,7 +12,7 @@
use leptos::*;
#[component]
pub fn SimpleCounter(cx: Scope, initial_value: i32) -> Element {
pub fn SimpleCounter(cx: Scope, initial_value: i32) -> impl IntoView {
// create a reactive signal with the initial value
let (value, set_value) = create_signal(cx, initial_value);
@@ -22,7 +22,7 @@ pub fn SimpleCounter(cx: Scope, initial_value: i32) -> Element {
let decrement = move |_| set_value.update(|value| *value -= 1);
let increment = move |_| set_value.update(|value| *value += 1);
// this JSX is compiled to an HTML template string for performance
// create user interfaces with the declarative `view!` macro
view! {
cx,
<div>
@@ -51,7 +51,7 @@ Leptos is a full-stack, isomorphic Rust web framework leveraging fine-grained re
- **Isomorphic**: Leptos provides primitives to write isomorphic server functions, i.e., functions that can be called with the “same shape” on the client or server, but only run on the server. This means you can write your server-only logic (database requests, authentication etc.) alongside the client-side components that will consume it, and call server functions as if they were running in the browser.
- **Web**: Leptos is built on the Web platform and Web standards. The router is designed to use Web fundamentals (like links and forms) and build on top of them rather than trying to replace them.
- **Framework**: Leptos provides most of what you need to build a modern web app: a reactive system, templating library, and a router that works on both the server and client side.
- **Fine-grained reactivity**: The entire framework is build from reactive primitives. This allows for extremely performant code with minimal overhead: when a reactive signals value changes, it can update a single text node, toggle a single class, or remove an element from the DOM without any other code running. (_So, no virtual DOM!_)
- **Fine-grained reactivity**: The entire framework is built from reactive primitives. This allows for extremely performant code with minimal overhead: when a reactive signals value changes, it can update a single text node, toggle a single class, or remove an element from the DOM without any other code running. (_So, no virtual DOM!_)
- **Declarative**: Tell Leptos how you want the page to look, and let the framework tell the browser how to do it.
## Learn more
@@ -63,45 +63,37 @@ 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 youre using `nightly` Rust. If youre on stable, note the following:
Most of the examples assume youre using `nightly` Rust.
1. You need to enable the `"stable"` flag in `Cargo.toml`: `leptos = { version = "0.0", features = ["stable"] }`
To set up your Rust toolchain using `nightly` (and add the ability to compile Rust to WebAssembly, if you havent already)
```
rustup toolchain install nightly
rustup default nightly
rustup target add wasm32-unknown-unknown
```
If youre on `stable`, note the following:
1. You need to enable the `"stable"` flag in `Cargo.toml`: `leptos = { version = "0.1.0-alpha", features = ["stable"] }`
2. `nightly` enables the function call syntax for accessing and setting signals. If youre using `stable`,
youll just call `.get()`, `.set()`, or `.update()` manually. Check out the
[`counters-stable` example](https://github.com/gbj/leptos/blob/main/examples/counters-stable/src/main.rs)
for examples of the correct API.
## Benchmarks
## `cargo-leptos`
### Server-Side Rendering
[`cargo-leptos`](https://github.com/akesson/cargo-leptos) is a build tool that's designed to make it easy to build apps that run on both the client and the server, with seamless integration. The best way to get started with a real Leptos project right now is to use `cargo-leptos` and our [starter template](https://github.com/leptos-rs/start).
Ive created a benchmark comparing Leptoss HTML rendering on the server to [Tera](https://github.com/Keats/tera), [Yew](https://github.com/yewstack/yew), and [Sycamore](https://github.com/sycamore-rs/sycamore). You can find the benchmark [here](https://github.com/gbj/leptos/tree/main/benchmarks) and run it yourself using `cargo bench`. Leptos renders HTML roughly as fast as Tera, and scales well as templates become larger. It's significantly faster than the server-side HTML rendering done by similar frameworks.
<details>
<summary>Click to show results</summary>
<table>
<thead>
<tr><td><em>ns/iter</em></td><td>Tera</td><td>Leptos</td><td>Yew</td><td>Sycamore</td></tr>
</thead>
<tbody>
<tr><td>3 Counters</td><td align="right">3,454</td><td align="right">5,666</td><td align="right">34,984</td><td align="right">32,412</td></tr>
<tr><td>TodoMVC (no todos)</td><td align="right">2,396</td><td align="right">5,561</td><td align="right">38,725</td><td align="right">68,749</td></tr>
<tr><td>TodoMVC (1000 todos)</td><td align="right">3,829,447</td><td align="right">3,077,907</td><td align="right">5,125,639</td><td align="right">19,448,900</td></tr>
<tr><td><em>Average</em></td><td align="right">1.08</td><td align="right">1.65</td><td align="right">6.25</td><td align="right">9.36</td></tr>
</tbody>
</table>
</details>
### Client-Side Rendering
The gold standard for testing raw rendering performance for front-end web frameworks is the [js-framework-benchmark](https://github.com/krausest/js-framework-benchmark). The official results list Leptos as the fastest Rust/Wasm framework, slightly slower than SolidJS and significantly faster than popular JS frameworks like Svelte, Preact, and React.
<details>
<summary>Click to show results</summary>
<img width="913" alt="js-framework-benchmark results" src="https://user-images.githubusercontent.com/286622/198388168-d21e938b-5d59-4000-b373-91b48f1ec4d3.png">
</details>
```bash
cargo install cargo-leptos
cargo leptos new --git https://github.com/leptos-rs/start
cd [your project name]
cargo leptos watch
```
## FAQs
@@ -122,7 +114,7 @@ On the surface level, these libraries may seem similar. Yew is, of course, the m
- **VDOM vs. fine-grained:** Yew is built on the virtual DOM (VDOM) model: state changes cause components to re-render, generating a new virtual DOM tree. Yew diffs this against the previous VDOM, and applies those patches to the actual DOM. Component functions rerun whenever state changes. Leptos takes an entirely different approach. Components run once, creating (and returning) actual DOM nodes and setting up a reactive system to update those DOM nodes.
- **Performance:** This has huge performance implications: Leptos is simply _much_ faster at both creating and updating the UI than Yew is.
- **Mental model:** Adopting fine-grained reactivity also tends to simplify the mental model. There are no surprising components re-renders because there are no re-renders. Your app can be divided into components based on what makes sense for your app, because they have no performance implications.
- **Mental model:** Adopting fine-grained reactivity also tends to simplify the mental model. There are no surprising component re-renders because there are no re-renders. Your app can be divided into components based on what makes sense for your app, because they have no performance implications.
### How is this different from Sycamore?
@@ -136,17 +128,16 @@ There are some practical differences that make a significant difference:
- **Read-write segregation:** Leptos, like Solid, encourages read-write segregation between signal getters and setters, so you end up accessing signals with tuples like `let (count, set_count) = create_signal(cx, 0);` _(If you prefer or if it's more convenient for your API, you can use `create_rw_signal` to give a unified read/write signal.)_
- **Signals are functions:** In Leptos, you can call a signal to access it rather than calling a specific method (so, `count()` instead of `count.get()`) This creates a more consistent mental model: accessing a reactive value is always a matter of calling a function. For example:
```rust
let (count, set_count) = create_signal(cx, 0); // a signal
let double_count = move || count() * 2; // a derived signal
let memoized_count = create_memo(cx, move |_| count() * 3); // a memo
// all are accessed by calling them
assert_eq!(count(), 0);
assert_eq!(double_count(), 0);
assert_eq!(memoized_count(), 0);
```rust
let (count, set_count) = create_signal(cx, 0); // a signal
let double_count = move || count() * 2; // a derived signal
let memoized_count = create_memo(cx, move |_| count() * 3); // a memo
// all are accessed by calling them
assert_eq!(count(), 0);
assert_eq!(double_count(), 0);
assert_eq!(memoized_count(), 0);
// this function can accept any of those signals
fn do_work_on_signal(my_signal: impl Fn() -> i32) { ... }
```
// this function can accept any of those signals
fn do_work_on_signal(my_signal: impl Fn() -> i32) { ... }
```
- **Signals and scopes are `'static`:** Both Leptos and Sycamore ease the pain of moving signals in closures (in particular, event listeners) by making them `Copy`, to avoid the `{ let count = count.clone(); move |_| ... }` that's very familiar in Rust UI code. Sycamore does this by using bump allocation to tie the lifetimes of its signals to its scopes: since references are `Copy`, `&'a Signal<T>` can be moved into a closure. Leptos does this by using arena allocation and passing around indices: types like `ReadSignal<T>`, `WriteSignal<T>`, and `Memo<T>` are actually wrapper for indices into an arena. This means that both scopes and signals are both `Copy` and `'static` in Leptos, which means that they can be moved easily into closures without adding lifetime complexity.
- **Signals and scopes are `'static`:** Both Leptos and Sycamore ease the pain of moving signals in closures (in particular, event listeners) by making them `Copy`, to avoid the `{ let count = count.clone(); move |_| ... }` that's very familiar in Rust UI code. Sycamore does this by using bump allocation to tie the lifetimes of its signals to its scopes: since references are `Copy`, `&'a Signal<T>` can be moved into a closure. Leptos does this by using arena allocation and passing around indices: types like `ReadSignal<T>`, `WriteSignal<T>`, and `Memo<T>` are actually wrappers for indices into an arena. This means that both scopes and signals are both `Copy` and `'static` in Leptos, which means that they can be moved easily into closures without adding lifetime complexity.

View File

@@ -2,12 +2,12 @@ use test::Bencher;
#[bench]
fn leptos_ssr_bench(b: &mut Bencher) {
use leptos::*;
b.iter(|| {
use leptos::*;
_ = create_scope(create_runtime(), |cx| {
#[component]
fn Counter(cx: Scope, initial: i32) -> Element {
fn Counter(cx: Scope, initial: i32) -> impl IntoView {
let (value, set_value) = create_signal(cx, initial);
view! {
cx,
@@ -28,16 +28,16 @@ fn leptos_ssr_bench(b: &mut Bencher) {
<Counter initial=2/>
<Counter initial=3/>
</main>
};
}.into_view(cx).render_to_string(cx);
assert_eq!(
rendered,
"<main data-hk=\"0-0\"><h1>Welcome to our benchmark page.</h1><p>Here's some introductory text.</p><!--#--><div data-hk=\"0-2-0\"><button>-1</button><span>Value: <!--#-->1<!--/-->!</span><button>+1</button></div><!--/--><!--#--><div data-hk=\"0-3-0\"><button>-1</button><span>Value: <!--#-->2<!--/-->!</span><button>+1</button></div><!--/--><!--#--><div data-hk=\"0-4-0\"><button>-1</button><span>Value: <!--#-->3<!--/-->!</span><button>+1</button></div><!--/--></main>"
"<main><h1>Welcome to our benchmark page.</h1><p>Here's some introductory text.</p><div><button>-1</button><span>Value: <!>1<template id=\"_3\"></template>!</span><button>+1</button></div><template id=\"_1\"></template><div><button>-1</button><span>Value: <!>2<template id=\"_2\"></template>!</span><button>+1</button></div><template id=\"_0\"></template><div><button>-1</button><span>Value: <!>3<template id=\"_2\"></template>!</span><button>+1</button></div><template id=\"_0\"></template></main>"
);
});
});
}
/*
#[bench]
fn tera_ssr_bench(b: &mut Bencher) {
use tera::*;
@@ -194,3 +194,4 @@ fn yew_ssr_bench(b: &mut Bencher) {
});
});
}
*/

View File

@@ -1,4 +1,4 @@
use leptos::*;
pub use leptos::*;
use miniserde::*;
use web_sys::HtmlInputElement;
@@ -8,314 +8,320 @@ pub struct Todos(pub Vec<Todo>);
const STORAGE_KEY: &str = "todos-leptos";
impl Todos {
pub fn new(cx: Scope) -> Self {
Self(vec![])
}
pub fn new(cx: Scope) -> Self {
Self(vec![])
}
pub fn new_with_1000(cx: Scope) -> Self {
let todos = (0..1000)
.map(|id| Todo::new(cx, id, format!("Todo #{id}")))
.collect();
Self(todos)
}
pub fn new_with_1000(cx: Scope) -> Self {
let todos = (0..1000)
.map(|id| Todo::new(cx, id, format!("Todo #{id}")))
.collect();
Self(todos)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn add(&mut self, todo: Todo) {
self.0.push(todo);
}
pub fn add(&mut self, todo: Todo) {
self.0.push(todo);
}
pub fn remove(&mut self, id: usize) {
self.0.retain(|todo| todo.id != id);
}
pub fn remove(&mut self, id: usize) {
self.0.retain(|todo| todo.id != id);
}
pub fn remaining(&self) -> usize {
self.0.iter().filter(|todo| !(todo.completed)()).count()
}
pub fn remaining(&self) -> usize {
self.0.iter().filter(|todo| !(todo.completed)()).count()
}
pub fn completed(&self) -> usize {
self.0.iter().filter(|todo| (todo.completed)()).count()
}
pub fn completed(&self) -> usize {
self.0.iter().filter(|todo| (todo.completed)()).count()
}
pub fn toggle_all(&self) {
// if all are complete, mark them all active instead
if self.remaining() == 0 {
for todo in &self.0 {
if todo.completed.get() {
(todo.set_completed)(false);
}
}
}
// otherwise, mark them all complete
else {
for todo in &self.0 {
(todo.set_completed)(true);
}
pub fn toggle_all(&self) {
// if all are complete, mark them all active instead
if self.remaining() == 0 {
for todo in &self.0 {
if todo.completed.get() {
(todo.set_completed)(false);
}
}
}
// otherwise, mark them all complete
else {
for todo in &self.0 {
(todo.set_completed)(true);
}
}
}
fn clear_completed(&mut self) {
self.0.retain(|todo| !todo.completed.get());
}
fn clear_completed(&mut self) {
self.0.retain(|todo| !todo.completed.get());
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Todo {
pub id: usize,
pub title: ReadSignal<String>,
pub set_title: WriteSignal<String>,
pub completed: ReadSignal<bool>,
pub set_completed: WriteSignal<bool>,
pub id: usize,
pub title: ReadSignal<String>,
pub set_title: WriteSignal<String>,
pub completed: ReadSignal<bool>,
pub set_completed: WriteSignal<bool>,
}
impl Todo {
pub fn new(cx: Scope, id: usize, title: String) -> Self {
Self::new_with_completed(cx, id, title, false)
}
pub fn new(cx: Scope, id: usize, title: String) -> Self {
Self::new_with_completed(cx, id, title, false)
}
pub fn new_with_completed(cx: Scope, id: usize, title: String, completed: bool) -> Self {
let (title, set_title) = create_signal(cx, title);
let (completed, set_completed) = create_signal(cx, completed);
Self {
id,
title,
set_title,
completed,
set_completed,
}
pub fn new_with_completed(
cx: Scope,
id: usize,
title: String,
completed: bool,
) -> Self {
let (title, set_title) = create_signal(cx, title);
let (completed, set_completed) = create_signal(cx, completed);
Self {
id,
title,
set_title,
completed,
set_completed,
}
}
pub fn toggle(&self) {
self.set_completed
.update(|completed| *completed = !*completed);
}
pub fn toggle(&self) {
self
.set_completed
.update(|completed| *completed = !*completed);
}
}
const ESCAPE_KEY: u32 = 27;
const ENTER_KEY: u32 = 13;
#[component]
pub fn TodoMVC(cx: Scope, todos: Todos) -> Element {
let mut next_id = todos
pub fn TodoMVC(cx: Scope,todos: Todos) -> impl IntoView {
let mut next_id = todos
.0
.iter()
.map(|todo| todo.id)
.max()
.map(|last| last + 1)
.unwrap_or(0);
let (todos, set_todos) = create_signal(cx, todos);
provide_context(cx, set_todos);
let (mode, set_mode) = create_signal(cx, Mode::All);
window_event_listener("hashchange", move |_| {
let new_mode = location_hash().map(|hash| route(&hash)).unwrap_or_default();
set_mode(new_mode);
});
let add_todo = move |ev: web_sys::KeyboardEvent| {
let target = event_target::<HtmlInputElement>(&ev);
ev.stop_propagation();
let key_code = ev.unchecked_ref::<web_sys::KeyboardEvent>().key_code();
if key_code == ENTER_KEY {
let title = event_target_value(&ev);
let title = title.trim();
if !title.is_empty() {
let new = Todo::new(cx, next_id, title.to_string());
set_todos.update(|t| t.add(new));
next_id += 1;
target.set_value("");
}
}
};
let filtered_todos = create_memo::<Vec<Todo>>(cx, move |_| {
todos.with(|todos| match mode.get() {
Mode::All => todos.0.to_vec(),
Mode::Active => todos
.0
.iter()
.map(|todo| todo.id)
.max()
.map(|last| last + 1)
.unwrap_or(0);
.filter(|todo| !todo.completed.get())
.cloned()
.collect(),
Mode::Completed => todos
.0
.iter()
.filter(|todo| todo.completed.get())
.cloned()
.collect(),
})
});
let (todos, set_todos) = create_signal(cx, todos);
provide_context(cx, set_todos);
let (mode, set_mode) = create_signal(cx, Mode::All);
window_event_listener("hashchange", move |_| {
let new_mode = location_hash().map(|hash| route(&hash)).unwrap_or_default();
set_mode(new_mode);
});
let add_todo = move |ev: web_sys::KeyboardEvent| {
let target = event_target::<HtmlInputElement>(&ev);
ev.stop_propagation();
let key_code = ev.unchecked_ref::<web_sys::KeyboardEvent>().key_code();
if key_code == ENTER_KEY {
let title = event_target_value(&ev);
let title = title.trim();
if !title.is_empty() {
let new = Todo::new(cx, next_id, title.to_string());
set_todos.update(|t| t.add(new));
next_id += 1;
target.set_value("");
}
}
};
let filtered_todos = create_memo::<Vec<Todo>>(cx, move |_| {
todos.with(|todos| match mode.get() {
Mode::All => todos.0.to_vec(),
Mode::Active => todos
.0
.iter()
.filter(|todo| !todo.completed.get())
.cloned()
.collect(),
Mode::Completed => todos
.0
.iter()
.filter(|todo| todo.completed.get())
.cloned()
.collect(),
})
});
// effect to serialize to JSON
// this does reactive reads, so it will automatically serialize on any relevant change
create_effect(cx, move |_| {
if let Ok(Some(storage)) = window().local_storage() {
let objs = todos
.get()
.0
.iter()
.map(TodoSerialized::from)
.collect::<Vec<_>>();
let json = json::to_string(&objs);
if storage.set_item(STORAGE_KEY, &json).is_err() {
log::error!("error while trying to set item in localStorage");
}
}
});
view! { cx,
<main>
<section class="todoapp">
<header class="header">
<h1>"todos"</h1>
<input class="new-todo" placeholder="What needs to be done?" autofocus on:keydown=add_todo />
</header>
<section class="main" class:hidden={move || todos.with(|t| t.is_empty())}>
<input id="toggle-all" class="toggle-all" type="checkbox"
prop:checked={move || todos.with(|t| t.remaining() > 0)}
on:input=move |_| set_todos.update(|t| t.toggle_all())
/>
<label for="toggle-all">"Mark all as complete"</label>
<ul class="todo-list">
<For each=filtered_todos key=|todo| todo.id>
{move |cx, todo: &Todo| view! { cx, <Todo todo=todo.clone() /> }}
</For>
</ul>
</section>
<footer class="footer" class:hidden={move || todos.with(|t| t.is_empty())}>
<span class="todo-count">
<strong>{move || todos.with(|t| t.remaining().to_string())}</strong>
{move || if todos.with(|t| t.remaining()) == 1 {
" item"
} else {
" items"
}}
" left"
</span>
<ul class="filters">
<li><a href="#/" class="selected" class:selected={move || mode() == Mode::All}>"All"</a></li>
<li><a href="#/active" class:selected={move || mode() == Mode::Active}>"Active"</a></li>
<li><a href="#/completed" class:selected={move || mode() == Mode::Completed}>"Completed"</a></li>
</ul>
<button
class="clear-completed hidden"
class:hidden={move || todos.with(|t| t.completed() == 0)}
on:click=move |_| set_todos.update(|t| t.clear_completed())
>
"Clear completed"
</button>
</footer>
</section>
<footer class="info">
<p>"Double-click to edit a todo"</p>
<p>"Created by "<a href="http://todomvc.com">"Greg Johnston"</a></p>
<p>"Part of "<a href="http://todomvc.com">"TodoMVC"</a></p>
</footer>
</main>
// effect to serialize to JSON
// this does reactive reads, so it will automatically serialize on any relevant change
create_effect(cx, move |_| {
if let Ok(Some(storage)) = window().local_storage() {
let objs = todos
.get()
.0
.iter()
.map(TodoSerialized::from)
.collect::<Vec<_>>();
let json = json::to_string(&objs);
if storage.set_item(STORAGE_KEY, &json).is_err() {
log::error!("error while trying to set item in localStorage");
}
}
});
view! { cx,
<main>
<section class="todoapp">
<header class="header">
<h1>"todos"</h1>
<input class="new-todo" placeholder="What needs to be done?" autofocus="" on:keydown=add_todo />
</header>
<section class="main" class:hidden={move || todos.with(|t| t.is_empty())}>
<input id="toggle-all" class="toggle-all" type="checkbox"
prop:checked={move || todos.with(|t| t.remaining() > 0)}
on:input=move |_| set_todos.update(|t| t.toggle_all())
/>
<label for="toggle-all">"Mark all as complete"</label>
<ul class="todo-list">
<For
each=filtered_todos
key=|todo| todo.id
view=move |todo: Todo| view! { cx, <Todo todo=todo.clone() /> }
/>
</ul>
</section>
<footer class="footer" class:hidden={move || todos.with(|t| t.is_empty())}>
<span class="todo-count">
<strong>{move || todos.with(|t| t.remaining().to_string())}</strong>
{move || if todos.with(|t| t.remaining()) == 1 {
" item"
} else {
" items"
}}
" left"
</span>
<ul class="filters">
<li><a href="#/" class="selected" class:selected={move || mode() == Mode::All}>"All"</a></li>
<li><a href="#/active" class:selected={move || mode() == Mode::Active}>"Active"</a></li>
<li><a href="#/completed" class:selected={move || mode() == Mode::Completed}>"Completed"</a></li>
</ul>
<button
class="clear-completed hidden"
class:hidden={move || todos.with(|t| t.completed() == 0)}
on:click=move |_| set_todos.update(|t| t.clear_completed())
>
"Clear completed"
</button>
</footer>
</section>
<footer class="info">
<p>"Double-click to edit a todo"</p>
<p>"Created by "<a href="http://todomvc.com">"Greg Johnston"</a></p>
<p>"Part of "<a href="http://todomvc.com">"TodoMVC"</a></p>
</footer>
</main>
}.into_view(cx)
}
#[component]
pub fn Todo(cx: Scope, todo: Todo) -> Element {
let (editing, set_editing) = create_signal(cx, false);
let set_todos = use_context::<WriteSignal<Todos>>(cx).unwrap();
let input = NodeRef::new(cx);
pub fn Todo(cx: Scope, todo: Todo) -> impl IntoView {
let (editing, set_editing) = create_signal(cx, false);
let set_todos = use_context::<WriteSignal<Todos>>(cx).unwrap();
//let input = NodeRef::new(cx);
let save = move |value: &str| {
let value = value.trim();
if value.is_empty() {
set_todos.update(|t| t.remove(todo.id));
} else {
(todo.set_title)(value.to_string());
}
set_editing(false);
};
let save = move |value: &str| {
let value = value.trim();
if value.is_empty() {
set_todos.update(|t| t.remove(todo.id));
} else {
(todo.set_title)(value.to_string());
}
set_editing(false);
};
let tpl = view! { cx,
<li
class="todo"
class:editing={editing}
class:completed={move || (todo.completed)()}
_ref=input
>
<div class="view">
<input
class="toggle"
type="checkbox"
prop:checked={move || (todo.completed)()}
view! { cx,
<li
class="todo"
class:editing={editing}
class:completed={move || (todo.completed)()}
//_ref=input
>
<div class="view">
<input
class="toggle"
type="checkbox"
prop:checked={move || (todo.completed)()}
/>
<label on:dblclick=move |_| set_editing(true)>
{move || todo.title.get()}
</label>
<button class="destroy" on:click=move |_| set_todos.update(|t| t.remove(todo.id))/>
</div>
{move || editing().then(|| view! { cx,
<input
class="edit"
class:hidden={move || !(editing)()}
prop:value={move || todo.title.get()}
on:focusout=move |ev| save(&event_target_value(&ev))
on:keyup={move |ev| {
let key_code = ev.unchecked_ref::<web_sys::KeyboardEvent>().key_code();
if key_code == ENTER_KEY {
save(&event_target_value(&ev));
} else if key_code == ESCAPE_KEY {
set_editing(false);
}
}}
/>
})
}
</li>
};
tpl
/>
<label on:dblclick=move |_| set_editing(true)>
{move || todo.title.get()}
</label>
<button class="destroy" on:click=move |_| set_todos.update(|t| t.remove(todo.id))/>
</div>
{move || editing().then(|| view! { cx,
<input
class="edit"
class:hidden={move || !(editing)()}
prop:value={move || todo.title.get()}
on:focusout=move |ev| save(&event_target_value(&ev))
on:keyup={move |ev| {
let key_code = ev.unchecked_ref::<web_sys::KeyboardEvent>().key_code();
if key_code == ENTER_KEY {
save(&event_target_value(&ev));
} else if key_code == ESCAPE_KEY {
set_editing(false);
}
}}
/>
})
}
</li>
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
Active,
Completed,
All,
Active,
Completed,
All,
}
impl Default for Mode {
fn default() -> Self {
Mode::All
}
fn default() -> Self {
Mode::All
}
}
pub fn route(hash: &str) -> Mode {
match hash {
"/active" => Mode::Active,
"/completed" => Mode::Completed,
_ => Mode::All,
}
match hash {
"/active" => Mode::Active,
"/completed" => Mode::Completed,
_ => Mode::All,
}
}
#[derive(Serialize, Deserialize)]
pub struct TodoSerialized {
pub id: usize,
pub title: String,
pub completed: bool,
pub id: usize,
pub title: String,
pub completed: bool,
}
impl TodoSerialized {
pub fn into_todo(self, cx: Scope) -> Todo {
Todo::new_with_completed(cx, self.id, self.title, self.completed)
}
pub fn into_todo(self, cx: Scope) -> Todo {
Todo::new_with_completed(cx, self.id, self.title, self.completed)
}
}
impl From<&Todo> for TodoSerialized {
fn from(todo: &Todo) -> Self {
Self {
id: todo.id,
title: todo.title.get(),
completed: (todo.completed)(),
}
fn from(todo: &Todo) -> Self {
Self {
id: todo.id,
title: todo.title.get(),
completed: (todo.completed)(),
}
}
}

View File

@@ -7,15 +7,14 @@ mod yew;
#[bench]
fn leptos_todomvc_ssr(b: &mut Bencher) {
use self::leptos::*;
use ::leptos::*;
b.iter(|| {
use crate::todomvc::leptos::*;
_ = create_scope(create_runtime(), |cx| {
let rendered = view! {
cx,
<TodoMVC todos=Todos::new(cx)/>
};
}.into_view(cx).render_to_string(cx);
assert!(rendered.len() > 1);
});
@@ -59,15 +58,15 @@ fn yew_todomvc_ssr(b: &mut Bencher) {
#[bench]
fn leptos_todomvc_ssr_with_1000(b: &mut Bencher) {
use self::leptos::*;
use ::leptos::*;
b.iter(|| {
use self::leptos::*;
use ::leptos::*;
_ = create_scope(create_runtime(), |cx| {
let rendered = view! {
cx,
<TodoMVC todos=Todos::new_with_1000(cx)/>
};
}.into_view(cx).render_to_string(cx);
assert!(rendered.len() > 1);
});

View File

@@ -10,7 +10,7 @@ This document is intended as a running list of common issues, with example code
```rust
let (a, set_a) = create_signal(cx, 0);
let (b, set_a) = create_signal(cx, false);
let (b, set_b) = create_signal(cx, false);
create_effect(cx, move |_| {
if a() > 5 {

View File

@@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../../../leptos" }
leptos = "0.0.18"

View File

@@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../../../leptos" }
leptos = "0.0.18"

View File

@@ -4,7 +4,7 @@ fn main() {
mount_to_body(|cx| {
let name = "gbj";
let userid = 0;
let _input_element = NodeRef::new(cx);
let _input_element: Element;
view! {
cx,

View File

@@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../../../leptos" }
leptos = "0.0.18"

View File

@@ -1,44 +0,0 @@
[package]
name = "leptos-counter-isomorphic"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["openssl", "macros"] }
broadcaster = "1"
console_log = "0.2"
console_error_panic_hook = "0.1"
serde = { version = "1", features = ["derive"] }
futures = "0.3"
cfg-if = "1"
lazy_static = "1"
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
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"
gloo = { git = "https://github.com/rustwasm/gloo" }
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:actix-files",
"dep:actix-web",
"leptos/ssr",
"leptos_actix",
"leptos_meta/ssr",
"leptos_router/ssr",
]
[package.metadata.cargo-all-features]
denylist = ["actix-files", "actix-web", "leptos_actix"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]

View File

@@ -1,21 +0,0 @@
# Leptos Counter Isomorphic Example
This example demonstrates how to use a function isomorphically, to run a server side function from the browser and receive a result.
## Server Side Rendering With Hydration
To run it as a server side app with hydration, first you should run
```bash
wasm-pack build --target=web --no-default-features --features=hydrate
```
to generate the WebAssembly to provide hydration features for the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
```bash
cargo run --no-default-features --features=ssr
```
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above
> This should be temporary, and vastly improve once cargo-leptos becomes ready for prime time!

View File

@@ -3,5 +3,3 @@
This example creates a simple counter in a client side rendered app with Rust and WASM!
To run it, just issue the `trunk serve --open` command in the example root. This will build the app, run it, and open a new browser to serve it.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View File

@@ -2,14 +2,16 @@ use leptos::*;
/// A simple counter component.
///
/// You can document each of the properties passed to a component using the format below.
///
/// # Props
/// - **initial_value** [`i32`] - The value the counter should start at.
/// - **step** [`i32`] - The change that should be applied on each step.
/// You can use doc comments like this to document your component.
#[component]
pub fn SimpleCounter(cx: Scope, initial_value: i32, step: i32) -> web_sys::Element {
let (value, set_value) = create_signal(cx, 0);
pub fn SimpleCounter(
cx: Scope,
/// The starting value for the counter
initial_value: i32,
/// The change that should be applied each time the button is clicked.
step: i32
) -> impl IntoView {
let (value, set_value) = create_signal(cx, initial_value);
view! { cx,
<div>

View File

@@ -4,5 +4,10 @@ use leptos::*;
pub fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to_body(|cx| view! { cx, <SimpleCounter initial_value=3 step=1/> })
mount_to_body(|cx| view! { cx,
<SimpleCounter
initial_value=0
step=1
/>
})
}

View File

@@ -3,18 +3,24 @@ use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
use leptos::*;
use web_sys::HtmlElement;
use counter::*;
#[wasm_bindgen_test]
fn inc() {
mount_to_body(|cx| view! { cx, <SimpleCounter/> });
mount_to_body(|cx| view! { cx, <SimpleCounter initial_value=0 step=1/> });
let document = leptos::document();
let div = document.query_selector("div").unwrap().unwrap();
let dec = div
let clear = div
.first_child()
.unwrap()
.dyn_into::<HtmlElement>()
.unwrap();
let dec = clear
.next_sibling()
.unwrap()
.dyn_into::<HtmlElement>()
.unwrap();
let text = dec
.next_sibling()
.unwrap()
@@ -29,12 +35,16 @@ fn inc() {
inc.click();
inc.click();
assert_eq!(text.text_content(), Some("2".to_string()));
assert_eq!(text.text_content(), Some("Value: 2!".to_string()));
dec.click();
dec.click();
dec.click();
dec.click();
assert_eq!(text.text_content(), Some("-2".to_string()));
assert_eq!(text.text_content(), Some("Value: -2!".to_string()));
clear.click();
assert_eq!(text.text_content(), Some("Value: 0!".to_string()));
}

View File

@@ -0,0 +1 @@
.leptos.kdl

View File

@@ -0,0 +1,89 @@
[package]
name = "counter_isomorphic"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["openssl", "macros"] }
broadcaster = "1"
console_log = "0.2"
console_error_panic_hook = "0.1"
serde = { version = "1", features = ["derive"] }
futures = "0.3"
cfg-if = "1"
lazy_static = "1"
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
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"
gloo-net = { git = "https://github.com/rustwasm/gloo" }
[features]
default = []
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:actix-files",
"dep:actix-web",
"leptos/ssr",
"leptos_actix",
"leptos_meta/ssr",
"leptos_router/ssr",
]
stable = ["leptos/stable", "leptos_router/stable"]
[package.metadata.cargo-all-features]
denylist = ["actix-files", "actix-web", "leptos_actix"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
[package.metadata.leptos]
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
output-name = "counter_isomorphic"
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
site-root = "target/site"
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
# Defaults to pkg
site-pkg-dir = "pkg"
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
# style-file = "src/styles/tailwind.css"
# [Optional] Files in the asset-dir will be copied to the site-root directory
# assets-dir = "static/assets"
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
site-address = "127.0.0.1:3000"
# The port to use for automatic reload monitoring
reload-port = 3001
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
end2end-cmd = "npx playwright test"
# The browserlist query used for optimizing the CSS.
browserquery = "defaults"
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
watch = false
# The environment Leptos will run in, usually either "DEV" or "PROD"
env = "DEV"
# The features to use when compiling the bin target
#
# Optional. Can be over-ridden with the command line parameter --bin-features
bin-features = ["ssr"]
# If the --no-default-features flag should be used when compiling the bin target
#
# Optional. Defaults to false.
bin-default-features = false
# The features to use when compiling the lib target
#
# Optional. Can be over-ridden with the command line parameter --lib-features
lib-features = ["hydrate"]
# If the --no-default-features flag should be used when compiling the lib target
#
# Optional. Defaults to false.
lib-default-features = false

View File

@@ -0,0 +1,40 @@
# Leptos Counter Isomorphic Example
This example demonstrates how to use a function isomorphically, to run a server side function from the browser and receive a result.
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle. Make sure you have trunk installed with `cargo install trunk`.
## Server Side Rendering with cargo-leptos
cargo-leptos is now the easiest and most featureful way to build server side rendered apps with hydration. It provides automatic recompilation of client and server code, wasm optimisation, CSS minification, and more! Check out more about it [here](https://github.com/akesson/cargo-leptos)
1. Install cargo-leptos
```bash
cargo install --locked cargo-leptos
```
2. Build the site in watch mode, recompiling on file changes
```bash
cargo leptos watch
```
3. When ready to deploy, run
```bash
cargo leptos build --release
```
## Server Side Rendering without cargo-leptos
To run it as a server side app with hydration, you'll need to have wasm-pack installed.
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"pkg"`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time.
1. Install wasm-pack
```bash
cargo install wasm-pack
```
2. Build the Webassembly used to hydrate the HTML from the server
```bash
wasm-pack build --target=web --debug --no-default-features --features=hydrate
```
3. Run the server to serve the Webassembly, JS, and HTML
```bash
cargo run --no-default-features --features=ssr
```

View File

@@ -9,9 +9,9 @@ use broadcaster::BroadcastChannel;
#[cfg(feature = "ssr")]
pub fn register_server_functions() {
GetServerCount::register();
AdjustServerCount::register();
ClearServerCount::register();
_ = GetServerCount::register();
_ = AdjustServerCount::register();
_ = ClearServerCount::register();
}
#[cfg(feature = "ssr")]
@@ -43,41 +43,39 @@ pub async fn clear_server_count() -> Result<i32, ServerFnError> {
Ok(0)
}
#[component]
pub fn Counters(cx: Scope) -> Element {
pub fn Counters(cx: Scope) -> impl IntoView {
view! {
cx,
<div>
<Router>
<header>
<h1>"Server-Side Counters"</h1>
<p>"Each of these counters stores its data in the same variable on the server."</p>
<p>"The value is shared across connections. Try opening this is another browser tab to see what I mean."</p>
</header>
<nav>
<ul>
<li><A href="">"Simple"</A></li>
<li><A href="form">"Form-Based"</A></li>
<li><A href="multi">"Multi-User"</A></li>
</ul>
</nav>
<main>
<Routes>
<Route path="" element=|cx| view! {
cx,
<Counter/>
}/>
<Route path="form" element=|cx| view! {
cx,
<FormCounter/>
}/>
<Route path="multi" element=|cx| view! {
cx,
<MultiuserCounter/>
}/>
</Routes>
</main>
</Router>
</div>
<Router>
<header>
<h1>"Server-Side Counters"</h1>
<p>"Each of these counters stores its data in the same variable on the server."</p>
<p>"The value is shared across connections. Try opening this is another browser tab to see what I mean."</p>
</header>
<nav>
<ul>
<li><A href="">"Simple"</A></li>
<li><A href="form">"Form-Based"</A></li>
<li><A href="multi">"Multi-User"</A></li>
</ul>
</nav>
<main>
<Routes>
<Route path="" view=|cx| view! {
cx,
<Counter/>
}/>
<Route path="form" view=|cx| view! {
cx,
<FormCounter/>
}/>
<Route path="multi" view=|cx| view! {
cx,
<MultiuserCounter/>
}/>
</Routes>
</main>
</Router>
}
}
@@ -86,13 +84,13 @@ pub fn Counters(cx: Scope) -> Element {
// it's invalidated by one of the user's own actions
// This is the typical pattern for a CRUD app
#[component]
pub fn Counter(cx: Scope) -> Element {
pub fn Counter(cx: Scope) -> impl IntoView {
let dec = create_action(cx, |_| adjust_server_count(-1, "decing".into()));
let inc = create_action(cx, |_| adjust_server_count(1, "incing".into()));
let clear = create_action(cx, |_| clear_server_count());
let counter = create_resource(
cx,
move || (dec.version.get(), inc.version.get(), clear.version.get()),
move || (dec.version().get(), inc.version().get(), clear.version().get()),
|_| get_server_count(),
);
@@ -126,20 +124,15 @@ pub fn Counter(cx: Scope) -> Element {
// It uses the same invalidation pattern as the plain counter,
// but uses HTML forms to submit the actions
#[component]
pub fn FormCounter(cx: Scope) -> Element {
pub fn FormCounter(cx: Scope) -> impl IntoView {
let adjust = create_server_action::<AdjustServerCount>(cx);
let clear = create_server_action::<ClearServerCount>(cx);
let counter = create_resource(
cx,
{
let adjust = adjust.version;
let clear = clear.version;
move || (adjust.get(), clear.get())
},
move || (adjust.version().get(), clear.version().get()),
|_| {
log::debug!("FormCounter running fetcher");
get_server_count()
},
);
@@ -153,8 +146,6 @@ pub fn FormCounter(cx: Scope) -> Element {
.unwrap_or(0)
};
let adjust2 = adjust.clone();
view! {
cx,
<div>
@@ -174,7 +165,7 @@ pub fn FormCounter(cx: Scope) -> Element {
<input type="submit" value="-1"/>
</ActionForm>
<span>"Value: " {move || value().to_string()} "!"</span>
<ActionForm action=adjust2>
<ActionForm action=adjust>
<input type="hidden" name="delta" value="1"/>
<input type="hidden" name="msg" value="form value up"/>
<input type="submit" value="+1"/>
@@ -189,7 +180,7 @@ pub fn FormCounter(cx: Scope) -> Element {
// Whenever another user updates the value, it will update here
// This is the primitive pattern for live chat, collaborative editing, etc.
#[component]
pub fn MultiuserCounter(cx: Scope) -> Element {
pub fn MultiuserCounter(cx: Scope) -> impl IntoView {
let dec = create_action(cx, |_| adjust_server_count(-1, "dec dec goose".into()));
let inc = create_action(cx, |_| adjust_server_count(1, "inc inc moose".into()));
let clear = create_action(cx, |_| clear_server_count());
@@ -198,7 +189,7 @@ pub fn MultiuserCounter(cx: Scope) -> Element {
let multiplayer_value = {
use futures::StreamExt;
let mut source = gloo::net::eventsource::futures::EventSource::new("/api/events")
let mut source = gloo_net::eventsource::futures::EventSource::new("/api/events")
.expect_throw("couldn't connect to SSE stream");
let s = create_signal_from_stream(
cx,
@@ -228,7 +219,7 @@ pub fn MultiuserCounter(cx: Scope) -> Element {
<div>
<button on:click=move |_| clear.dispatch(())>"Clear"</button>
<button on:click=move |_| dec.dispatch(())>"-1"</button>
<span>"Multiplayer Value: " {move || multiplayer_value().unwrap_or_default().to_string()}</span>
<span>"Multiplayer Value: " {move || multiplayer_value.get().unwrap_or_default().to_string()}</span>
<button on:click=move |_| inc.dispatch(())>"+1"</button>
</div>
</div>

View File

@@ -14,7 +14,7 @@ cfg_if! {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
leptos::hydrate(body().unwrap(), |cx| {
mount_to_body(|cx| {
view! { cx, <Counters/> }
});
}

View File

@@ -9,7 +9,6 @@ cfg_if! {
use actix_files::{Files};
use actix_web::*;
use crate::counters::*;
use std::{net::SocketAddr, env};
#[get("/api/events")]
async fn counter_events() -> impl Responder {
@@ -30,17 +29,22 @@ cfg_if! {
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let addr = SocketAddr::from(([127,0,0,1],3000));
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();
HttpServer::new(move || {
let render_options: RenderOptions = RenderOptions::builder().pkg_path("/pkg/leptos_counter_isomorphic").reload_port(3001).socket_address(addr.clone()).environment(&env::var("RUST_ENV")).build();
render_options.write_to_file();
let leptos_options = &conf.leptos_options;
App::new()
.service(Files::new("/pkg", "./pkg"))
.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(render_options, |cx| view! { cx, <Counters/> }))
.route("/{tail:.*}", leptos_actix::render_app_to_stream(leptos_options.to_owned(), |cx| view! { cx, <Counters/> }))
//.wrap(middleware::Compress::default())
})
.bind(&addr)?

View File

@@ -1,10 +0,0 @@
# Leptos Counters Example on Rust Stable
This example showcases a basic Leptos app with many counters. It is a good example of how to setup a basic reactive app with signals and effects, and how to interact with browser events. Unlike the other counters example, it will compile on Rust stable, because it has the `stable` feature enabled.
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View File

@@ -1,10 +0,0 @@
# Leptos Counters Example
This example showcases a basic Leptos app with many counters. It is a good example of how to set up a basic reactive app with signals and effects, and how to interact with browser events.
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View File

@@ -11,7 +11,7 @@ struct CounterUpdater {
}
#[component]
pub fn Counters(cx: Scope) -> web_sys::Element {
pub fn Counters(cx: Scope) -> impl IntoView {
let (next_counter_id, set_next_counter_id) = create_signal(cx, 0);
let (counters, set_counters) = create_signal::<CounterHolder>(cx, vec![]);
provide_context(cx, CounterUpdater { set_counters });
@@ -39,7 +39,7 @@ pub fn Counters(cx: Scope) -> web_sys::Element {
};
view! { cx,
<div>
<>
<button on:click=add_counter>
"Add Counter"
</button>
@@ -63,16 +63,18 @@ pub fn Counters(cx: Scope) -> web_sys::Element {
" counters."
</p>
<ul>
<For each=counters key=|counter| counter.0>{
|cx, (id, (value, set_value)): &(usize, (ReadSignal<i32>, WriteSignal<i32>))| {
<For
each=counters
key=|counter| counter.0
view=move |(id, (value, set_value)): (usize, (ReadSignal<i32>, WriteSignal<i32>))| {
view! {
cx,
<Counter id=*id value=*value set_value=*set_value/>
<Counter id value set_value/>
}
}
}</For>
/>
</ul>
</div>
</>
}
}
@@ -82,7 +84,7 @@ fn Counter(
id: usize,
value: ReadSignal<i32>,
set_value: WriteSignal<i32>,
) -> web_sys::Element {
) -> impl IntoView {
let CounterUpdater { set_counters } = use_context(cx).unwrap_throw();
let input = move |ev| set_value(event_target_value(&ev).parse::<i32>().unwrap_or_default());

View File

@@ -1,7 +1,7 @@
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
use leptos::{wasm_bindgen::JsValue, *};
use leptos::*;
use web_sys::HtmlElement;
use counters::{Counters, CountersProps};
@@ -75,37 +75,39 @@ fn inc() {
dec_button.click();
}
// we can use RSX in test comparisons!
// note that if RSX template creation is bugged, this probably won't catch it
// (because the same bug will be reproduced in both sides of the assertion)
// so I use HTML tests for most internal testing like this
// but in user-land testing, RSX comparanda are cool
assert_eq!(
div.outer_html(),
view! { cx,
<div>
<button>"Add Counter"</button>
<button>"Add 1000 Counters"</button>
<button>"Clear Counters"</button>
<p>"Total: "<span>"3"</span>" from "<span>"2"</span>" counters."</p>
<ul>
<li>
<button>"-1"</button>
<input type="text"/>
<span>"1"</span>
<button>"+1"</button>
<button>"x"</button>
</li>
<li>
<button>"-1"</button>
<input type="text"/>
<span>"2"</span>
<button>"+1"</button>
<button>"x"</button>
</li>
</ul>
</div>
}
.outer_html()
);
run_scope(create_runtime(), move |cx| {
// we can use RSX in test comparisons!
// note that if RSX template creation is bugged, this probably won't catch it
// (because the same bug will be reproduced in both sides of the assertion)
// so I use HTML tests for most internal testing like this
// but in user-land testing, RSX comparanda are cool
assert_eq!(
div.outer_html(),
view! { cx,
<div>
<button>"Add Counter"</button>
<button>"Add 1000 Counters"</button>
<button>"Clear Counters"</button>
<p>"Total: "<span>"3"</span>" from "<span>"2"</span>" counters."</p>
<ul>
<li>
<button>"-1"</button>
<input type="text"/>
<span>"1"</span>
<button>"+1"</button>
<button>"x"</button>
</li>
<li>
<button>"-1"</button>
<input type="text"/>
<span>"2"</span>
<button>"+1"</button>
<button>"x"</button>
</li>
</ul>
</div>
}
.outer_html()
);
});
}

View File

@@ -17,7 +17,7 @@ struct CounterUpdater {
}
#[component]
pub fn Counters(cx: Scope) -> web_sys::Element {
pub fn Counters(cx: Scope) -> impl IntoView {
let (next_counter_id, set_next_counter_id) = create_signal(cx, 0);
let (counters, set_counters) = create_signal::<CounterHolder>(cx, vec![]);
provide_context(cx, CounterUpdater { set_counters });
@@ -69,14 +69,16 @@ pub fn Counters(cx: Scope) -> web_sys::Element {
" counters."
</p>
<ul>
<For each={move || counters.get()} key={|counter| counter.0}>{
|cx, (id, (value, set_value)): &(usize, (ReadSignal<i32>, WriteSignal<i32>))| {
<For
each={move || counters.get()}
key={|counter| counter.0}
view=move |(id, (value, set_value))| {
view! {
cx,
<Counter id=*id value=*value set_value=*set_value/>
<Counter id value set_value/>
}
}
}</For>
/>
</ul>
</div>
}
@@ -88,14 +90,14 @@ fn Counter(
id: usize,
value: ReadSignal<i32>,
set_value: WriteSignal<i32>,
) -> web_sys::Element {
) -> impl IntoView {
let CounterUpdater { set_counters } = use_context(cx).unwrap_throw();
let input = move |ev| set_value.set(event_target_value(&ev).parse::<i32>().unwrap_or_default());
view! { cx,
<li>
<button on:click={move |_| set_value.update(move |value| *value -= 1)}>"-1"</button>
<button on:click=move |_| set_value.update(move |value| *value -= 1)>"-1"</button>
<input type="text"
prop:value={move || value.get().to_string()}
on:input=input

View File

@@ -11,7 +11,7 @@ serde = { version = "1", features = ["derive"] }
log = "0.4"
console_log = "0.2"
console_error_panic_hook = "0.1.7"
gloo-timers = { version = "0.2", features = ["futures"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.0"

View File

@@ -1,10 +0,0 @@
# Client Side Fetch
This example shows how to fetch data from the client in WebAssembly.
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View File

@@ -1,6 +1,3 @@
use std::time::Duration;
use gloo_timers::future::TimeoutFuture;
use leptos::*;
use serde::{Deserialize, Serialize};
@@ -10,10 +7,6 @@ pub struct Cat {
}
async fn fetch_cats(count: u32) -> Result<Vec<String>, ()> {
// artificial delay
// the cat API is too fast to show the transition
TimeoutFuture::new(500).await;
if count > 0 {
let res = reqwasm::http::Request::get(&format!(
"https://api.thecatapi.com/v1/images/search?limit={}",
@@ -34,50 +27,41 @@ async fn fetch_cats(count: u32) -> Result<Vec<String>, ()> {
}
}
pub fn fetch_example(cx: Scope) -> web_sys::Element {
pub fn fetch_example(cx: Scope) -> impl IntoView {
let (cat_count, set_cat_count) = create_signal::<u32>(cx, 1);
let cats = create_resource(cx, cat_count, |count| fetch_cats(count));
let (pending, set_pending) = create_signal(cx, false);
view! { cx,
view! { cx,
<div>
<label>
"How many cats would you like?"
<input type="number"
value={move || cat_count.get().to_string()}
prop:value={move || cat_count.get().to_string()}
on:input=move |ev| {
let val = event_target_value(&ev).parse::<u32>().unwrap_or(0);
set_cat_count(val);
}
/>
</label>
{move || pending().then(|| view! { cx, <p>"Loading more cats..."</p> })}
<div>
// <Transition/> holds the previous value while new async data is being loaded
// Switch the <Transition/> to <Suspense/> to fall back to "Loading..." every time
<Transition
fallback={"Loading (Suspense Fallback)...".to_string()}
set_pending
>
{move || {
cats.read().map(|data| match data {
Err(_) => view! { cx, <pre>"Error"</pre> },
Ok(cats) => view! { cx,
<div>{
cats.iter()
.map(|src| {
view! { cx,
<img src={src}/>
}
})
.collect::<Vec<_>>()
}</div>
},
})
}
<Transition fallback=move || view! { cx, <div>"Loading (Suspense Fallback)..."</div>}>
{move || {
cats.read().map(|data| match data {
Err(_) => view! { cx, <pre>"Error"</pre> }.into_view(cx),
Ok(cats) => view! { cx,
<div>{
cats.iter()
.map(|src| {
view! { cx,
<img src={src}/>
}
})
.collect::<Vec<_>>()
}</div>
}.into_view(cx),
})
}
</Transition>
</div>
}
</Transition>
</div>
}
}

View File

@@ -1,8 +0,0 @@
# Leptos in a GTK App
This example creates a basic GTK app that uses Leptoss reactive primitives.
## Build and Run
Unlike the other examples, this has a variety of build prerequisites that are out of scope of this crate. More detail on that can be found [here](https://gtk-rs.org/gtk4-rs/stable/latest/book/installation.html). The example comes from [here](https://gtk-rs.org/gtk4-rs/stable/latest/book/hello_world.html) and should be
runnable with `cargo run` if you have the GTK prerequisites installed.

View File

@@ -6,7 +6,7 @@ const APP_ID: &str = "dev.leptos.Counter";
// Basic GTK app setup from https://gtk-rs.org/gtk4-rs/stable/latest/book/hello_world.html
fn main() {
_ = create_scope(create_runtime(), |cx| {
_ = create_scope(|cx| {
// Create a new application
let app = Application::builder().application_id(APP_ID).build();

View File

@@ -1,51 +0,0 @@
[package]
name = "leptos-hackernews-axum"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
anyhow = "1.0.66"
console_log = "0.2.0"
console_error_panic_hook = "0.1.7"
futures = "0.3.25"
cfg-if = "1.0.0"
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_meta = { path = "../../meta", default-features = false }
leptos_router = { path = "../../router", default-features = false }
log = "0.4.17"
simple_logger = "4.0.0"
serde = { version = "1.0.148", features = ["derive"] }
serde_json = "1.0.89"
gloo-net = { version = "0.2.5", features = ["http"] }
reqwest = { version = "0.11.13", features = ["json"] }
axum = { version = "0.6.1", optional = true }
tower = { version = "0.4.13", optional = true }
tower-http = { version = "0.3.4", features = ["fs"], optional = true }
tokio = { version = "1.22.0", features = ["full"], optional = true }
http = { version = "0.2.8", optional = true }
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"dep:http",
"leptos/ssr",
"leptos_axum",
"leptos_meta/ssr",
"leptos_router/ssr",
]
[package.metadata.cargo-all-features]
denylist = ["axum", "tower", "tower-http", "tokio", "http", "leptos_axum"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]

View File

@@ -1,29 +0,0 @@
# Leptos Hacker News Example with Axum
This example creates a basic clone of the Hacker News site. It showcases Leptos' ability to create both a client-side rendered app, and a server side rendered app with hydration, in a single repository. This repo differs from the main Hacker News example by using Axum as it's server.
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)
## Server Side Rendering With Hydration
To run it as a server side app with hydration, first you should run
```bash
wasm-pack build --target=web --no-default-features --features=hydrate
```
to generate the WebAssembly to hydrate the HTML that is generated on the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
```bash
cargo run --no-default-features --features=ssr
```
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above
> This should be temporary, and vastly improve once cargo-leptos becomes ready for prime time!

View File

@@ -1,103 +0,0 @@
use crate::api;
use leptos::*;
use leptos_router::*;
#[component]
pub fn Story(cx: Scope) -> Element {
let params = use_params_map(cx);
let story = create_resource(
cx,
move || params().get("id").cloned().unwrap_or_default(),
move |id| async move { api::fetch_api::<api::Story>(&api::story(&format!("item/{id}"))).await },
);
view! { cx,
<div>
{move || story.read().map(|story| match story {
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
Some(story) => view! { cx,
<div class="item-view">
<div class="item-view-header">
<a href=story.url target="_blank">
<h1>{story.title}</h1>
</a>
<span class="host">
"("{story.domain}")"
</span>
{story.user.map(|user| view! { cx, <p class="meta">
{story.points}
" points | by "
<A href=format!("/users/{}", user)>{user.clone()}</A>
{format!(" {}", story.time_ago)}
</p>})}
</div>
<div class="item-view-comments">
<p class="item-view-comments-header">
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"No comments yet.".into()
}}
</p>
<ul class="comment-children">
<For each=move || story.comments.clone().unwrap_or_default() key=|comment| comment.id>
{move |cx, comment: &api::Comment| view! { cx, <Comment comment=comment.clone() /> }}
</For>
</ul>
</div>
</div>
}})}
</div>
}
}
#[component]
pub fn Comment(cx: Scope, comment: api::Comment) -> Element {
let (open, set_open) = create_signal(cx, true);
view! { cx,
<li class="comment">
<div class="by">
<A href=format!("/users/{}", comment.user.clone().unwrap_or_default())>{comment.user.clone()}</A>
{format!(" {}", comment.time_ago)}
</div>
<div class="text" inner_html=comment.content></div>
{(!comment.comments.is_empty()).then(|| {
view! { cx,
<div>
<div class="toggle" class:open=open>
<a on:click=move |_| set_open.update(|n| *n = !*n)>
{
let comments_len = comment.comments.len();
move || if open() {
"[-]".into()
} else {
format!("[+] {}{} collapsed", comments_len, pluralize(comments_len))
}
}
</a>
</div>
{move || open().then({
let comments = comment.comments.clone();
move || view! { cx,
<ul class="comment-children">
<For each=move || comments.clone() key=|comment| comment.id>
{|cx, comment: &api::Comment| view! { cx, <Comment comment=comment.clone() /> }}
</For>
</ul>
}
})}
</div>
}
})}
</li>
}
}
fn pluralize(n: usize) -> &'static str {
if n == 1 {
" reply"
} else {
" replies"
}
}

View File

@@ -1,39 +0,0 @@
use crate::api::{self, User};
use leptos::*;
use leptos_router::*;
#[component]
pub fn User(cx: Scope) -> Element {
let params = use_params_map(cx);
let user = create_resource(
cx,
move || params().get("id").cloned().unwrap_or_default(),
move |id| async move { api::fetch_api::<User>(&api::user(&id)).await },
);
view! { cx,
<div class="user-view">
{move || user.read().map(|user| match user {
None => view! { cx, <h1>"User not found."</h1> },
Some(user) => view! { cx,
<div>
<h1>"User: " {&user.id}</h1>
<ul class="meta">
<li>
<span class="label">"Created: "</span> {user.created}
</li>
<li>
<span class="label">"Karma: "</span> {user.karma}
</li>
{user.about.as_ref().map(|about| view! { cx, <li inner_html=about class="about"></li> })}
</ul>
<p class="links">
<a href=format!("https://news.ycombinator.com/submitted?id={}", user.id)>"submissions"</a>
" | "
<a href=format!("https://news.ycombinator.com/threads?id={}", user.id)>"comments"</a>
</p>
</div>
}
})}
</div>
}
}

View File

@@ -1,5 +1,5 @@
[package]
name = "leptos-hackernews"
name = "hackernews"
version = "0.1.0"
edition = "2021"
@@ -14,10 +14,10 @@ console_log = "0.2"
console_error_panic_hook = "0.1"
futures = "0.3"
cfg-if = "1"
leptos = { version = "0.0.20", default-features = false, features = ["serde"] }
leptos_meta = { version = "0.0", default-features = false }
leptos_actix = { version = "0.0.2", default-features = false, optional = true }
leptos_router = { version = "0.0", default-features = false }
leptos = { path = "../../leptos", default-features = false, features = ["serde"] }
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"
serde = { version = "1", features = ["derive"] }
@@ -25,10 +25,11 @@ serde_json = "1"
gloo-net = { version = "0.2", features = ["http"] }
reqwest = { version = "0.11", features = ["json"] }
# openssl = { version = "0.10", features = ["v110"] }
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
tracing = "0.1"
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
@@ -43,3 +44,47 @@ ssr = [
[package.metadata.cargo-all-features]
denylist = ["actix-files", "actix-web", "leptos_actix"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
[package.metadata.leptos]
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
output-name = "hackernews"
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
site-root = "target/site"
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
# Defaults to pkg
site-pkg-dir = "pkg"
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
style-file = "./style.css"
# [Optional] Files in the asset-dir will be copied to the site-root directory
# assets-dir = "static/assets"
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
site-address = "127.0.0.1:3000"
# The port to use for automatic reload monitoring
reload-port = 3001
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
end2end-cmd = "npx playwright test"
# The browserlist query used for optimizing the CSS.
browserquery = "defaults"
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
watch = false
# The environment Leptos will run in, usually either "DEV" or "PROD"
env = "DEV"
# The features to use when compiling the bin target
#
# Optional. Can be over-ridden with the command line parameter --bin-features
bin-features = ["ssr"]
# If the --no-default-features flag should be used when compiling the bin target
#
# Optional. Defaults to false.
bin-default-features = false
# The features to use when compiling the lib target
#
# Optional. Can be over-ridden with the command line parameter --lib-features
lib-features = ["hydrate"]
# If the --no-default-features flag should be used when compiling the lib target
#
# Optional. Defaults to false.
lib-default-features = false

View File

@@ -1,29 +1,40 @@
# Leptos Hacker News Example
This example creates a basic clone of the Hacker News site. It showcases Leptoss ability to create both a client-side rendered app, and a server side rendered app with hydration, in a single repository. It uses Actix as its backend.
This example creates a basic clone of the Hacker News site. It showcases Leptos' ability to create both a client-side rendered app, and a server side rendered app with hydration, in a single repository
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle. Make sure you have trunk installed with `cargo install trunk`.
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle.
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)
## Server Side Rendering With Hydration
To run it as a server side app with hydration, first you should run
## Server Side Rendering with cargo-leptos
cargo-leptos is now the easiest and most featureful way to build server side rendered apps with hydration. It provides automatic recompilation of client and server code, wasm optimisation, CSS minification, and more! Check out more about it [here](https://github.com/akesson/cargo-leptos)
1. Install cargo-leptos
```bash
wasm-pack build --target=web --no-default-features --features=hydrate
cargo install --locked cargo-leptos
```
2. Build the site in watch mode, recompiling on file changes
```bash
cargo leptos watch
```
3. When ready to deploy, run
```bash
cargo leptos build --release
```
to generate the WebAssembly to hydrate the HTML that is generated on the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
## Server Side Rendering without cargo-leptos
To run it as a server side app with hydration, you'll need to have wasm-pack installed.
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"pkg"`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time.
1. Install wasm-pack
```bash
cargo install wasm-pack
```
2. Build the Webassembly used to hydrate the HTML from the server
```bash
wasm-pack build --target=web --debug --no-default-features --features=hydrate
```
3. Run the server to serve the Webassembly, JS, and HTML
```bash
cargo run --no-default-features --features=ssr
```
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above
> This should be temporary, and vastly improve once cargo-leptos becomes ready for prime time!

View File

@@ -1,5 +1,5 @@
use cfg_if::cfg_if;
use leptos::*;
use leptos::{component, view, IntoView, Scope};
use leptos_meta::*;
use leptos_router::*;
mod api;
@@ -10,25 +10,24 @@ use routes::story::*;
use routes::users::*;
#[component]
pub fn App(cx: Scope) -> Element {
provide_context(cx, MetaContext::default());
pub fn App(cx: Scope) -> impl IntoView {
provide_meta_context(cx);
view! {
cx,
<div>
<Stylesheet href="/style.css"/>
<>
<Stylesheet id="leptos" href="./target/site/pkg/hackernews.css"/>
<Meta name="description" content="Leptos implementation of a HackerNews demo."/>
<Router>
<Nav />
<main>
<Routes>
<Route path="users/:id" element=|cx| view! { cx, <User/> }/>
<Route path="stories/:id" element=|cx| view! { cx, <Story/> }/>
<Route path="*stories" element=|cx| view! { cx, <Stories/> }/>
<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/> }/>
</Routes>
</main>
</Router>
</div>
</>
}
}
@@ -41,7 +40,7 @@ cfg_if! {
pub fn hydrate() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
leptos::hydrate(body().unwrap(), move |cx| {
leptos::mount_to_body(move |cx| {
view! { cx, <App/> }
});
}

View File

@@ -7,8 +7,7 @@ cfg_if! {
if #[cfg(feature = "ssr")] {
use actix_files::{Files};
use actix_web::*;
use leptos_hackernews::*;
use std::{net::SocketAddr, env};
use hackernews::{App,AppProps};
#[get("/style.css")]
async fn css() -> impl Responder {
@@ -17,16 +16,20 @@ cfg_if! {
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let addr = SocketAddr::from(([127,0,0,1],3000));
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
let addr = conf.leptos_options.site_address.clone();
HttpServer::new(move || {
let render_options: RenderOptions = RenderOptions::builder().pkg_path("/pkg/leptos_hackernews").reload_port(3001).socket_address(addr.clone()).environment(&env::var("RUST_ENV")).build();
render_options.write_to_file();
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"))
.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(render_options, |cx| view! { cx, <App/> }))
.route("/{tail:.*}", leptos_actix::render_app_to_stream(leptos_options.to_owned(), |cx| view! { cx, <App/> }))
//.wrap(middleware::Compress::default())
})
.bind(&addr)?

View File

@@ -1,12 +1,12 @@
use leptos::*;
use leptos::{component, Scope, IntoView, view};
use leptos_router::*;
#[component]
pub fn Nav(cx: Scope) -> Element {
pub fn Nav(cx: Scope) -> impl IntoView {
view! { cx,
<header class="header">
<nav class="inner">
<A href="/" class="home".to_string()>
<A href="/">
<strong>"HN"</strong>
</A>
<A href="/new">

View File

@@ -14,7 +14,7 @@ fn category(from: &str) -> &'static str {
}
#[component]
pub fn Stories(cx: Scope) -> Element {
pub fn Stories(cx: Scope) -> impl IntoView {
let query = use_query_map(cx);
let params = use_params_map(cx);
let page = move || {
@@ -54,14 +54,14 @@ pub fn Stories(cx: Scope) -> Element {
>
"< prev"
</a>
}
}.into_any()
} else {
view! {
cx,
<span class="page-link disabled" aria-hidden="true">
"< prev"
</span>
}
}.into_any()
}}
</span>
<span>"page " {page}</span>
@@ -79,24 +79,26 @@ pub fn Stories(cx: Scope) -> Element {
<main class="news-list">
<div>
<Transition
fallback=view! { cx, <p>"Loading..."</p> }
set_pending
fallback=move || view! { cx, <p>"Loading..."</p> }
set_pending=set_pending.into()
>
{move || match stories.read() {
None => None,
Some(None) => Some(view! { cx, <p>"Error loading stories."</p> }),
Some(None) => Some(view! { cx, <p>"Error loading stories."</p> }.into_any()),
Some(Some(stories)) => {
Some(view! { cx,
<ul>
<For each=move || stories.clone() key=|story| story.id>{
move |cx: Scope, story: &api::Story| {
<For
each=move || stories.clone()
key=|story| story.id
view=move |story: api::Story| {
view! { cx,
<Story story=story.clone() />
<Story story/>
}
}
}</For>
/>
</ul>
})
}.into_any())
}
}}
</Transition>
@@ -107,7 +109,7 @@ pub fn Stories(cx: Scope) -> Element {
}
#[component]
fn Story(cx: Scope, story: api::Story) -> Element {
fn Story(cx: Scope, story: api::Story) -> impl IntoView {
view! { cx,
<li class="news-item">
<span class="score">{story.points}</span>
@@ -120,10 +122,10 @@ fn Story(cx: Scope, story: api::Story) -> Element {
</a>
<span class="host">"("{story.domain}")"</span>
</span>
}
}.into_view(cx)
} else {
let title = story.title.clone();
view! { cx, <A href=format!("/stories/{}", story.id)>{title.clone()}</A> }
view! { cx, <A href=format!("/stories/{}", story.id)>{title.clone()}</A> }.into_view(cx)
}}
</span>
<br />
@@ -142,17 +144,15 @@ fn Story(cx: Scope, story: api::Story) -> Element {
}}
</A>
</span>
}
}.into_view(cx)
} else {
let title = story.title.clone();
view! { cx, <A href=format!("/item/{}", story.id)>{title.clone()}</A> }
view! { cx, <A href=format!("/item/{}", story.id)>{title.clone()}</A> }.into_view(cx)
}}
</span>
{(story.story_type != "link").then(|| view! { cx,
<span>
//{" "}
<span class="label">{story.story_type}</span>
</span>
" "
<span class="label">{story.story_type}</span>
})}
</li>
}

View File

@@ -4,7 +4,7 @@ use leptos_meta::*;
use leptos_router::*;
#[component]
pub fn Story(cx: Scope) -> Element {
pub fn Story(cx: Scope) -> impl IntoView {
let params = use_params_map(cx);
let story = create_resource(
cx,
@@ -20,48 +20,53 @@ pub fn Story(cx: Scope) -> Element {
let meta_description = move || story.read().and_then(|story| story.map(|story| story.title.clone())).unwrap_or_else(|| "Loading story...".to_string());
view! { cx,
<div>
<>
<Meta name="description" content=meta_description/>
{move || story.read().map(|story| match story {
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
Some(story) => view! { cx,
<div class="item-view">
<div class="item-view-header">
<a href=story.url target="_blank">
<h1>{story.title}</h1>
</a>
<span class="host">
"("{story.domain}")"
</span>
{story.user.map(|user| view! { cx, <p class="meta">
{story.points}
" points | by "
<A href=format!("/users/{}", user)>{user.clone()}</A>
{format!(" {}", story.time_ago)}
</p>})}
<Suspense fallback=|| view! { cx, "Loading..." }>
{move || story.read().map(|story| match story {
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
Some(story) => view! { cx,
<div class="item-view">
<div class="item-view-header">
<a href=story.url target="_blank">
<h1>{story.title}</h1>
</a>
<span class="host">
"("{story.domain}")"
</span>
{story.user.map(|user| view! { cx, <p class="meta">
{story.points}
" points | by "
<A href=format!("/users/{}", user)>{user.clone()}</A>
{format!(" {}", story.time_ago)}
</p>})}
</div>
<div class="item-view-comments">
<p class="item-view-comments-header">
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"No comments yet.".into()
}}
</p>
<ul class="comment-children">
<For
each=move || story.comments.clone().unwrap_or_default()
key=|comment| comment.id
view=move |comment| view! { cx, <Comment comment /> }
/>
</ul>
</div>
</div>
<div class="item-view-comments">
<p class="item-view-comments-header">
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"No comments yet.".into()
}}
</p>
<ul class="comment-children">
<For each=move || story.comments.clone().unwrap_or_default() key=|comment| comment.id>
{move |cx, comment: &api::Comment| view! { cx, <Comment comment=comment.clone() /> }}
</For>
</ul>
</div>
</div>
}})}
</div>
}})
}
</Suspense>
</>
}
}
#[component]
pub fn Comment(cx: Scope, comment: api::Comment) -> Element {
pub fn Comment(cx: Scope, comment: api::Comment) -> impl IntoView {
let (open, set_open) = create_signal(cx, true);
view! { cx,
@@ -90,9 +95,11 @@ pub fn Comment(cx: Scope, comment: api::Comment) -> Element {
let comments = comment.comments.clone();
move || view! { cx,
<ul class="comment-children">
<For each=move || comments.clone() key=|comment| comment.id>
{|cx, comment: &api::Comment| view! { cx, <Comment comment=comment.clone() /> }}
</For>
<For
each=move || comments.clone()
key=|comment| comment.id
view=move |comment: api::Comment| view! { cx, <Comment comment /> }
/>
</ul>
}
})}

View File

@@ -3,7 +3,7 @@ use leptos::*;
use leptos_router::*;
#[component]
pub fn User(cx: Scope) -> Element {
pub fn User(cx: Scope) -> impl IntoView {
let params = use_params_map(cx);
let user = create_resource(
cx,
@@ -18,28 +18,30 @@ pub fn User(cx: Scope) -> Element {
);
view! { cx,
<div class="user-view">
{move || user.read().map(|user| match user {
None => view! { cx, <h1>"User not found."</h1> },
Some(user) => view! { cx,
<div>
<h1>"User: " {&user.id}</h1>
<ul class="meta">
<li>
<span class="label">"Created: "</span> {user.created}
</li>
<li>
<span class="label">"Karma: "</span> {user.karma}
</li>
{user.about.as_ref().map(|about| view! { cx, <li inner_html=about class="about"></li> })}
</ul>
<p class="links">
<a href=format!("https://news.ycombinator.com/submitted?id={}", user.id)>"submissions"</a>
" | "
<a href=format!("https://news.ycombinator.com/threads?id={}", user.id)>"comments"</a>
</p>
</div>
}
})}
<Suspense fallback=|| view! { cx, "Loading..." }>
{move || user.read().map(|user| match user {
None => view! { cx, <h1>"User not found."</h1> }.into_any(),
Some(user) => view! { cx,
<div>
<h1>"User: " {&user.id}</h1>
<ul class="meta">
<li>
<span class="label">"Created: "</span> {user.created}
</li>
<li>
<span class="label">"Karma: "</span> {user.karma}
</li>
{user.about.as_ref().map(|about| view! { cx, <li inner_html=about class="about"></li> })}
</ul>
<p class="links">
<a href=format!("https://news.ycombinator.com/submitted?id={}", user.id)>"submissions"</a>
" | "
<a href=format!("https://news.ycombinator.com/threads?id={}", user.id)>"comments"</a>
</p>
</div>
}.into_any()
})}
</Suspense>
</div>
}
}

View File

@@ -0,0 +1,98 @@
[package]
name = "hackernews_axum"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
anyhow = "1.0.66"
console_log = "0.2.0"
console_error_panic_hook = "0.1.7"
futures = "0.3.25"
cfg-if = "1.0.0"
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_meta = { path = "../../meta", default-features = false }
leptos_router = { path = "../../router", default-features = false }
log = "0.4.17"
simple_logger = "4.0.0"
serde = { version = "1.0.148", features = ["derive"] }
serde_json = "1.0.89"
gloo-net = { version = "0.2.5", features = ["http"] }
reqwest = { version = "0.11.13", features = ["json"] }
axum = { version = "0.6.1", optional = true }
tower = { version = "0.4.13", optional = true }
tower-http = { version = "0.3.4", features = ["fs"], optional = true }
tokio = { version = "1.22.0", features = ["full"], optional = true }
http = { version = "0.2.8", optional = true }
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
wasm-bindgen = "0.2"
tracing = "0.1"
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"dep:http",
"leptos/ssr",
"leptos_axum",
"leptos_meta/ssr",
"leptos_router/ssr",
]
[package.metadata.cargo-all-features]
denylist = ["axum", "tower", "tower-http", "tokio", "http", "leptos_axum"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
[package.metadata.leptos]
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
output-name = "hackernews_axum"
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
site-root = "target/site"
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
# Defaults to pkg
site-pkg-dir = "pkg"
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
style-file = "./style.css"
# [Optional] Files in the asset-dir will be copied to the site-root directory
# assets-dir = "static/assets"
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
site-address = "127.0.0.1:3000"
# The port to use for automatic reload monitoring
reload-port = 3001
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
end2end-cmd = "npx playwright test"
# The browserlist query used for optimizing the CSS.
browserquery = "defaults"
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
watch = false
# The environment Leptos will run in, usually either "DEV" or "PROD"
env = "DEV"
# The features to use when compiling the bin target
#
# Optional. Can be over-ridden with the command line parameter --bin-features
bin-features = ["ssr"]
# If the --no-default-features flag should be used when compiling the bin target
#
# Optional. Defaults to false.
bin-default-features = false
# The features to use when compiling the lib target
#
# Optional. Can be over-ridden with the command line parameter --lib-features
lib-features = ["hydrate"]
# If the --no-default-features flag should be used when compiling the lib target
#
# Optional. Defaults to false.
lib-default-features = false

View File

@@ -0,0 +1,40 @@
# Leptos Hacker News Example with Axum
This example creates a basic clone of the Hacker News site. It showcases Leptos' ability to create both a client-side rendered app, and a server side rendered app with hydration, in a single repository. This repo differs from the main Hacker News example by using Axum as it's server.
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle. Make sure you have trunk installed with `cargo install trunk`.
## Server Side Rendering with cargo-leptos
cargo-leptos is now the easiest and most featureful way to build server side rendered apps with hydration. It provides automatic recompilation of client and server code, wasm optimisation, CSS minification, and more! Check out more about it [here](https://github.com/akesson/cargo-leptos)
1. Install cargo-leptos
```bash
cargo install --locked cargo-leptos
```
2. Build the site in watch mode, recompiling on file changes
```bash
cargo leptos watch
```
3. When ready to deploy, run
```bash
cargo leptos build --release
```
## Server Side Rendering without cargo-leptos
To run it as a server side app with hydration, you'll need to have wasm-pack installed.
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"pkg"`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time.
1. Install wasm-pack
```bash
cargo install wasm-pack
```
2. Build the Webassembly used to hydrate the HTML from the server
```bash
wasm-pack build --target=web --debug --no-default-features --features=hydrate
```
3. Run the server to serve the Webassembly, JS, and HTML
```bash
cargo run --no-default-features --features=ssr
```

View File

@@ -1,4 +1,4 @@
use leptos::Serializable;
use leptos::{on_cleanup, Scope, Serializable};
use serde::{Deserialize, Serialize};
pub fn story(path: &str) -> String {
@@ -10,11 +10,15 @@ pub fn user(path: &str) -> String {
}
#[cfg(not(feature = "ssr"))]
pub async fn fetch_api<T>(path: &str) -> Option<T>
pub async fn fetch_api<T>(cx: Scope, path: &str) -> Option<T>
where
T: Serializable,
{
let abort_controller = web_sys::AbortController::new().ok();
let abort_signal = abort_controller.as_ref().map(|a| a.signal());
let json = gloo_net::http::Request::get(path)
.abort_signal(abort_signal.as_ref())
.send()
.await
.map_err(|e| log::error!("{e}"))
@@ -22,11 +26,19 @@ where
.text()
.await
.ok()?;
// abort in-flight requests if the Scope is disposed
// i.e., if we've navigated away from this page
on_cleanup(cx, move || {
if let Some(abort_controller) = abort_controller {
abort_controller.abort()
}
});
T::from_json(&json).ok()
}
#[cfg(feature = "ssr")]
pub async fn fetch_api<T>(path: &str) -> Option<T>
pub async fn fetch_api<T>(cx: Scope, path: &str) -> Option<T>
where
T: Serializable,
{

View File

@@ -0,0 +1,66 @@
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "ssr")] {
use axum::{
body::{boxed, Body, BoxBody},
http::{Request, Response, StatusCode, Uri},
};
use tower::ServiceExt;
use tower_http::services::ServeDir;
pub async fn file_handler(uri: Uri) -> Result<Response<BoxBody>, (StatusCode, String)> {
let res = get_static_file(uri.clone(), "/pkg").await?;
println!("FIRST URI{:?}", uri);
if res.status() == StatusCode::NOT_FOUND {
// try with `.html`
// TODO: handle if the Uri has query parameters
match format!("{}.html", uri).parse() {
Ok(uri_html) => get_static_file(uri_html, "/pkg").await,
Err(_) => Err((StatusCode::INTERNAL_SERVER_ERROR, "Invalid URI".to_string())),
}
} else {
Ok(res)
}
}
pub async fn get_static_file_handler(uri: Uri) -> Result<Response<BoxBody>, (StatusCode, String)> {
let res = get_static_file(uri.clone(), "/static").await?;
println!("FIRST URI{:?}", uri);
if res.status() == StatusCode::NOT_FOUND {
Err((StatusCode::INTERNAL_SERVER_ERROR, "Invalid URI".to_string()))
} else {
Ok(res)
}
}
async fn get_static_file(uri: Uri, base: &str) -> Result<Response<BoxBody>, (StatusCode, String)> {
let req = Request::builder().uri(&uri).body(Body::empty()).unwrap();
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
// When run normally, the root should be the crate root
println!("Base: {:#?}", base);
if base == "/static" {
match ServeDir::new("./static").oneshot(req).await {
Ok(res) => Ok(res.map(boxed)),
Err(err) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", err),
))
}
} else if base == "/pkg" {
match ServeDir::new("./pkg").oneshot(req).await {
Ok(res) => Ok(res.map(boxed)),
Err(err) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", err),
)),
}
} else{
Err((StatusCode::NOT_FOUND, "Not Found".to_string()))
}
}
}
}

View File

@@ -1,8 +1,9 @@
use cfg_if::cfg_if;
use leptos::*;
use leptos::{component, view, IntoView, Scope};
use leptos_meta::*;
use leptos_router::*;
mod api;
pub mod handlers;
mod routes;
use routes::nav::*;
use routes::stories::*;
@@ -10,24 +11,24 @@ use routes::story::*;
use routes::users::*;
#[component]
pub fn App(cx: Scope) -> Element {
provide_context(cx, MetaContext::default());
pub fn App(cx: Scope) -> impl IntoView {
provide_meta_context(cx);
view! {
cx,
<div>
<Stylesheet href="/static/style.css"/>
<>
<Stylesheet id="leptos" href="./target/site/pkg/hackernews_axum.css"/>
<Meta name="description" content="Leptos implementation of a HackerNews demo."/>
<Router>
<Nav />
<main>
<Routes>
<Route path="users/:id" element=|cx| view! { cx, <User/> }/>
<Route path="stories/:id" element=|cx| view! { cx, <Story/> }/>
<Route path="*stories" element=|cx| view! { cx, <Stories/> }/>
<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/> }/>
</Routes>
</main>
</Router>
</div>
</>
}
}
@@ -40,7 +41,7 @@ cfg_if! {
pub fn hydrate() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
leptos::hydrate(body().unwrap(), move |cx| {
leptos::mount_to_body(move |cx| {
view! { cx, <App/> }
});
}

View File

@@ -5,20 +5,26 @@ use leptos::*;
cfg_if! {
if #[cfg(feature = "ssr")] {
use axum::{
routing::{get},
Router,
error_handling::HandleError,
};
use http::StatusCode;
use std::net::SocketAddr;
use tower_http::services::ServeDir;
use std::env;
#[tokio::main]
async fn main() {
use leptos_hackernews_axum::*;
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
use hackernews_axum::*;
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}");
simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
@@ -28,6 +34,7 @@ if #[cfg(feature = "ssr")] {
// 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) {
@@ -37,14 +44,13 @@ if #[cfg(feature = "ssr")] {
)
}
let render_options: RenderOptions = RenderOptions::builder().pkg_path("/pkg/leptos_hackernews_axum").socket_address(addr).reload_port(3001).environment(&env::var("RUST_ENV")).build();
render_options.write_to_file();
// build our application with a route
let app = Router::new()
// `GET /` goes to `root`
.nest_service("/pkg", pkg_service)
.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(render_options, |cx| view! { cx, <App/> }));
.fallback(leptos_axum::render_app_to_stream(leptos_options, |cx| view! { cx, <App/> }));
// run our app with hyper
// `axum::Server` is a re-export of `hyper::Server`
@@ -58,7 +64,7 @@ if #[cfg(feature = "ssr")] {
// client-only stuff for Trunk
else {
use leptos_hackernews_axum::*;
use hackernews_axum::*;
pub fn main() {
console_error_panic_hook::set_once();

View File

@@ -1,8 +1,8 @@
use leptos::*;
use leptos::{component, Scope, IntoView, view};
use leptos_router::*;
#[component]
pub fn Nav(cx: Scope) -> Element {
pub fn Nav(cx: Scope) -> impl IntoView {
view! { cx,
<header class="header">
<nav class="inner">

View File

@@ -14,7 +14,7 @@ fn category(from: &str) -> &'static str {
}
#[component]
pub fn Stories(cx: Scope) -> Element {
pub fn Stories(cx: Scope) -> impl IntoView {
let query = use_query_map(cx);
let params = use_params_map(cx);
let page = move || {
@@ -32,11 +32,13 @@ pub fn Stories(cx: Scope) -> Element {
move || (page(), story_type()),
move |(page, story_type)| async move {
let path = format!("{}?page={}", category(&story_type), page);
api::fetch_api::<Vec<api::Story>>(&api::story(&path)).await
api::fetch_api::<Vec<api::Story>>(cx, &api::story(&path)).await
},
);
let (pending, set_pending) = create_signal(cx, false);
let hide_more_link = move || stories.read().unwrap_or(None).unwrap_or_default().len() < 28;
let hide_more_link =
move || pending() || stories.read().unwrap_or(None).unwrap_or_default().len() < 28;
view! {
cx,
@@ -52,14 +54,14 @@ pub fn Stories(cx: Scope) -> Element {
>
"< prev"
</a>
}
}.into_any()
} else {
view! {
cx,
<span class="page-link disabled" aria-hidden="true">
"< prev"
</span>
}
}.into_any()
}}
</span>
<span>"page " {page}</span>
@@ -76,25 +78,30 @@ pub fn Stories(cx: Scope) -> Element {
</div>
<main class="news-list">
<div>
<Suspense fallback=view! { cx, <p>"Loading..."</p> }>
<Transition
fallback=move || view! { cx, <p>"Loading..."</p> }
set_pending=set_pending.into()
>
{move || match stories.read() {
None => None,
Some(None) => Some(view! { cx, <p>"Error loading stories."</p> }),
Some(None) => Some(view! { cx, <p>"Error loading stories."</p> }.into_any()),
Some(Some(stories)) => {
Some(view! { cx,
<ul>
<For each=move || stories.clone() key=|story| story.id>{
move |cx: Scope, story: &api::Story| {
<For
each=move || stories.clone()
key=|story| story.id
view=move |story: api::Story| {
view! { cx,
<Story story=story.clone() />
<Story story/>
}
}
}</For>
/>
</ul>
})
}.into_any())
}
}}
</Suspense>
</Transition>
</div>
</main>
</div>
@@ -102,7 +109,7 @@ pub fn Stories(cx: Scope) -> Element {
}
#[component]
fn Story(cx: Scope, story: api::Story) -> Element {
fn Story(cx: Scope, story: api::Story) -> impl IntoView {
view! { cx,
<li class="news-item">
<span class="score">{story.points}</span>
@@ -115,10 +122,10 @@ fn Story(cx: Scope, story: api::Story) -> Element {
</a>
<span class="host">"("{story.domain}")"</span>
</span>
}
}.into_view(cx)
} else {
let title = story.title.clone();
view! { cx, <A href=format!("/stories/{}", story.id)>{title.clone()}</A> }
view! { cx, <A href=format!("/stories/{}", story.id)>{title.clone()}</A> }.into_view(cx)
}}
</span>
<br />
@@ -137,17 +144,15 @@ fn Story(cx: Scope, story: api::Story) -> Element {
}}
</A>
</span>
}
}.into_view(cx)
} else {
let title = story.title.clone();
view! { cx, <A href=format!("/item/{}", story.id)>{title.clone()}</A> }
view! { cx, <A href=format!("/item/{}", story.id)>{title.clone()}</A> }.into_view(cx)
}}
</span>
{(story.story_type != "link").then(|| view! { cx,
<span>
//{" "}
<span class="label">{story.story_type}</span>
</span>
" "
<span class="label">{story.story_type}</span>
})}
</li>
}

View File

@@ -0,0 +1,119 @@
use crate::api;
use leptos::*;
use leptos_meta::*;
use leptos_router::*;
#[component]
pub fn Story(cx: Scope) -> impl IntoView {
let params = use_params_map(cx);
let story = create_resource(
cx,
move || params().get("id").cloned().unwrap_or_default(),
move |id| async move {
if id.is_empty() {
None
} else {
api::fetch_api::<api::Story>(cx, &api::story(&format!("item/{id}"))).await
}
},
);
let meta_description = move || story.read().and_then(|story| story.map(|story| story.title.clone())).unwrap_or_else(|| "Loading story...".to_string());
view! { cx,
<>
<Meta name="description" content=meta_description/>
<Suspense fallback=|| view! { cx, "Loading..." }>
{move || story.read().map(|story| match story {
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
Some(story) => view! { cx,
<div class="item-view">
<div class="item-view-header">
<a href=story.url target="_blank">
<h1>{story.title}</h1>
</a>
<span class="host">
"("{story.domain}")"
</span>
{story.user.map(|user| view! { cx, <p class="meta">
{story.points}
" points | by "
<A href=format!("/users/{}", user)>{user.clone()}</A>
{format!(" {}", story.time_ago)}
</p>})}
</div>
<div class="item-view-comments">
<p class="item-view-comments-header">
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"No comments yet.".into()
}}
</p>
<ul class="comment-children">
<For
each=move || story.comments.clone().unwrap_or_default()
key=|comment| comment.id
view=move |comment| view! { cx, <Comment comment /> }
/>
</ul>
</div>
</div>
}})
}
</Suspense>
</>
}
}
#[component]
pub fn Comment(cx: Scope, comment: api::Comment) -> impl IntoView {
let (open, set_open) = create_signal(cx, true);
view! { cx,
<li class="comment">
<div class="by">
<A href=format!("/users/{}", comment.user.clone().unwrap_or_default())>{comment.user.clone()}</A>
{format!(" {}", comment.time_ago)}
</div>
<div class="text" inner_html=comment.content></div>
{(!comment.comments.is_empty()).then(|| {
view! { cx,
<div>
<div class="toggle" class:open=open>
<a on:click=move |_| set_open.update(|n| *n = !*n)>
{
let comments_len = comment.comments.len();
move || if open() {
"[-]".into()
} else {
format!("[+] {}{} collapsed", comments_len, pluralize(comments_len))
}
}
</a>
</div>
{move || open().then({
let comments = comment.comments.clone();
move || view! { cx,
<ul class="comment-children">
<For
each=move || comments.clone()
key=|comment| comment.id
view=move |comment: api::Comment| view! { cx, <Comment comment /> }
/>
</ul>
}
})}
</div>
}
})}
</li>
}
}
fn pluralize(n: usize) -> &'static str {
if n == 1 {
" reply"
} else {
" replies"
}
}

View File

@@ -0,0 +1,47 @@
use crate::api::{self, User};
use leptos::*;
use leptos_router::*;
#[component]
pub fn User(cx: Scope) -> impl IntoView {
let params = use_params_map(cx);
let user = create_resource(
cx,
move || params().get("id").cloned().unwrap_or_default(),
move |id| async move {
if id.is_empty() {
None
} else {
api::fetch_api::<User>(cx, &api::user(&id)).await
}
},
);
view! { cx,
<div class="user-view">
<Suspense fallback=|| view! { cx, "Loading..." }>
{move || user.read().map(|user| match user {
None => view! { cx, <h1>"User not found."</h1> }.into_any(),
Some(user) => view! { cx,
<div>
<h1>"User: " {&user.id}</h1>
<ul class="meta">
<li>
<span class="label">"Created: "</span> {user.created}
</li>
<li>
<span class="label">"Karma: "</span> {user.karma}
</li>
{user.about.as_ref().map(|about| view! { cx, <li inner_html=about class="about"></li> })}
</ul>
<p class="links">
<a href=format!("https://news.ycombinator.com/submitted?id={}", user.id)>"submissions"</a>
" | "
<a href=format!("https://news.ycombinator.com/threads?id={}", user.id)>"comments"</a>
</p>
</div>
}.into_any()
})}
</Suspense>
</div>
}
}

View File

@@ -0,0 +1,326 @@
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
font-size: 15px;
background-color: #f2f3f5;
margin: 0;
padding-top: 55px;
color: #34495e;
overflow-y: scroll
}
a {
color: #34495e;
text-decoration: none
}
.header {
background-color: #335d92;
position: fixed;
z-index: 999;
height: 55px;
top: 0;
left: 0;
right: 0
}
.header .inner {
max-width: 800px;
box-sizing: border-box;
margin: 0 auto;
padding: 15px 5px
}
.header a {
color: rgba(255, 255, 255, .8);
line-height: 24px;
transition: color .15s ease;
display: inline-block;
vertical-align: middle;
font-weight: 300;
letter-spacing: .075em;
margin-right: 1.8em
}
.header a:hover {
color: #fff
}
.header a.active {
color: #fff;
font-weight: 400
}
.header a:nth-child(6) {
margin-right: 0
}
.header .github {
color: #fff;
font-size: .9em;
margin: 0;
float: right
}
.logo {
width: 24px;
margin-right: 10px;
display: inline-block;
vertical-align: middle
}
.view {
max-width: 800px;
margin: 0 auto;
position: relative
}
.fade-enter-active,
.fade-exit-active {
transition: all .2s ease
}
.fade-enter,
.fade-exit-active {
opacity: 0
}
@media (max-width:860px) {
.header .inner {
padding: 15px 30px
}
}
@media (max-width:600px) {
.header .inner {
padding: 15px
}
.header a {
margin-right: 1em
}
.header .github {
display: none
}
}
.news-view {
padding-top: 45px
}
.news-list,
.news-list-nav {
background-color: #fff;
border-radius: 2px
}
.news-list-nav {
padding: 15px 30px;
position: fixed;
text-align: center;
top: 55px;
left: 0;
right: 0;
z-index: 998;
box-shadow: 0 1px 2px rgba(0, 0, 0, .1)
}
.news-list-nav .page-link {
margin: 0 1em
}
.news-list-nav .disabled {
color: #aaa
}
.news-list {
position: absolute;
margin: 30px 0;
width: 100%;
transition: all .5s cubic-bezier(.55, 0, .1, 1)
}
.news-list ul {
list-style-type: none;
padding: 0;
margin: 0
}
@media (max-width:600px) {
.news-list {
margin: 10px 0
}
}
.news-item {
background-color: #fff;
padding: 20px 30px 20px 80px;
border-bottom: 1px solid #eee;
position: relative;
line-height: 20px
}
.news-item .score {
color: #335d92;
font-size: 1.1em;
font-weight: 700;
position: absolute;
top: 50%;
left: 0;
width: 80px;
text-align: center;
margin-top: -10px
}
.news-item .host,
.news-item .meta {
font-size: .85em;
color: #626262
}
.news-item .host a,
.news-item .meta a {
color: #626262;
text-decoration: underline
}
.news-item .host a:hover,
.news-item .meta a:hover {
color: #335d92
}
.item-view-header {
background-color: #fff;
padding: 1.8em 2em 1em;
box-shadow: 0 1px 2px rgba(0, 0, 0, .1)
}
.item-view-header h1 {
display: inline;
font-size: 1.5em;
margin: 0;
margin-right: .5em
}
.item-view-header .host,
.item-view-header .meta,
.item-view-header .meta a {
color: #626262
}
.item-view-header .meta a {
text-decoration: underline
}
.item-view-comments {
background-color: #fff;
margin-top: 10px;
padding: 0 2em .5em
}
.item-view-comments-header {
margin: 0;
font-size: 1.1em;
padding: 1em 0;
position: relative
}
.item-view-comments-header .spinner {
display: inline-block;
margin: -15px 0
}
.comment-children {
list-style-type: none;
padding: 0;
margin: 0
}
@media (max-width:600px) {
.item-view-header h1 {
font-size: 1.25em
}
}
.comment-children .comment-children {
margin-left: 1.5em
}
.comment {
border-top: 1px solid #eee;
position: relative
}
.comment .by,
.comment .text,
.comment .toggle {
font-size: .9em;
margin: 1em 0
}
.comment .by {
color: #626262
}
.comment .by a {
color: #626262;
text-decoration: underline
}
.comment .text {
overflow-wrap: break-word
}
.comment .text a:hover {
color: #335d92
}
.comment .text pre {
white-space: pre-wrap
}
.comment .toggle {
background-color: #fffbf2;
padding: .3em .5em;
border-radius: 4px
}
.comment .toggle a {
color: #626262;
cursor: pointer
}
.comment .toggle.open {
padding: 0;
background-color: transparent;
margin-bottom: -.5em
}
.user-view {
background-color: #fff;
box-sizing: border-box;
padding: 2em 3em
}
.user-view h1 {
margin: 0;
font-size: 1.5em
}
.user-view .meta {
list-style-type: none;
padding: 0
}
.user-view .label {
display: inline-block;
min-width: 4em
}
.user-view .about {
margin: 1em 0
}
.user-view .links a {
text-decoration: underline
}

View File

@@ -1,17 +0,0 @@
# Parent Child Example
This example highlights four different ways that child components can communicate with their parent:
1. <ButtonA/>: passing a WriteSignal as one of the child component props,
for the child component to write into and the parent to read
2. <ButtonB/>: passing a closure as one of the child component props, for
the child component to call
3. <ButtonC/>: adding a simple event listener on the child component itself
4. <ButtonD/>: providing a context that is used in the component (rather than prop drilling)
## Client Side Rendering
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
app into one CSR bundle
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View File

@@ -7,23 +7,21 @@ use web_sys::MouseEvent;
// for the child component to write into and the parent to read
// 2) <ButtonB/>: passing a closure as one of the child component props, for
// the child component to call
// 3) <ButtonC/>: adding a simple event listener on the child component itself
// 4) <ButtonD/>: providing a context that is used in the component (rather than prop drilling)
// 4) <ButtonC/>: providing a context that is used in the component (rather than prop drilling)
#[derive(Copy, Clone)]
struct SmallcapsContext(WriteSignal<bool>);
#[component]
pub fn App(cx: Scope) -> Element {
pub fn App(cx: Scope) -> impl IntoView {
// just some signals to toggle three classes on our <p>
let (red, set_red) = create_signal(cx, false);
let (right, set_right) = create_signal(cx, false);
let (italics, set_italics) = create_signal(cx, false);
let (smallcaps, set_smallcaps) = create_signal(cx, false);
// the newtype pattern isn't *necessary* here but is a good practice
// it avoids confusion with other possible future `WriteSignal<bool>` contexts
// and makes it easier to refer to it in ButtonD
// and makes it easier to refer to it in ButtonC
provide_context(cx, SmallcapsContext(set_smallcaps));
view! {
@@ -33,7 +31,6 @@ pub fn App(cx: Scope) -> Element {
// class: attributes take F: Fn() => bool, and these signals all implement Fn()
class:red=red
class:right=right
class:italics=italics
class:smallcaps=smallcaps
>
"Lorem ipsum sit dolor amet."
@@ -45,18 +42,19 @@ pub fn App(cx: Scope) -> Element {
// Button B: pass a closure
<ButtonB on_click=move |_| set_right.update(|value| *value = !*value)/>
// Button C: components that return an Element, like elements, can take on: event handler attributes
<ButtonC on:click=move |_| set_italics.update(|value| *value = !*value)/>
// Button D gets its setter from context rather than props
<ButtonD/>
<ButtonC/>
</main>
}
}
// Button A receives a signal setter and updates the signal itself
/// Button A receives a signal setter and updates the signal itself
#[component]
pub fn ButtonA(cx: Scope, setter: WriteSignal<bool>) -> Element {
pub fn ButtonA(
cx: Scope,
/// Signal that will be toggled when the button is clicked.
setter: WriteSignal<bool>
) -> impl IntoView {
view! {
cx,
<button
@@ -67,9 +65,13 @@ pub fn ButtonA(cx: Scope, setter: WriteSignal<bool>) -> Element {
}
}
// Button B receives a closure
/// Button B receives a closure
#[component]
pub fn ButtonB<F>(cx: Scope, on_click: F) -> Element
pub fn ButtonB<F>(
cx: Scope,
/// Callback that will be invoked when the button is clicked.
on_click: F
) -> impl IntoView
where
F: Fn(MouseEvent) + 'static,
{
@@ -95,22 +97,10 @@ where
// if Rust ever had named function arguments we could drop this requirement
}
// Button C will have its event listener added by the parent
// This is just a way of encapsulating whatever markup you need for the button
/// Button D is very similar to Button A, but instead of passing the setter as a prop
/// we get it from the context
#[component]
pub fn ButtonC(cx: Scope) -> Element {
view! {
cx,
<button>
"Toggle Italics"
</button>
}
}
// Button D is very similar to Button A, but instead of passing the setter as a prop
// we get it from the context
#[component]
pub fn ButtonD(cx: Scope) -> Element {
pub fn ButtonC(cx: Scope) -> impl IntoView {
let setter = use_context::<SmallcapsContext>(cx).unwrap().0;
view! {

View File

@@ -7,7 +7,7 @@ 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"

View File

@@ -1,11 +1,8 @@
# Leptos Router Example
This example demonstrates how Leptoss router for client side routing.
## Build and Run it
This example demonstrates how Leptos' router works
## Run it
```bash
trunk serve --open
```
> If you don't have `trunk` installed, [click here for install instructions.](https://trunkrs.dev/)

View File

@@ -27,9 +27,9 @@ pub struct Contact {
pub phone: String,
}
pub async fn get_contacts(search: String) -> Vec<ContactSummary> {
pub async fn get_contacts(_search: String) -> Vec<ContactSummary> {
// fake an API call with an artificial delay
delay(Duration::from_millis(300)).await;
_ = delay(Duration::from_millis(300)).await;
vec![
ContactSummary {
id: 0,
@@ -51,7 +51,7 @@ pub async fn get_contacts(search: String) -> Vec<ContactSummary> {
pub async fn get_contact(id: Option<usize>) -> Option<Contact> {
// fake an API call with an artificial delay
delay(Duration::from_millis(500)).await;
_ = delay(Duration::from_millis(500)).await;
match id {
Some(0) => Some(Contact {
id: 0,
@@ -97,7 +97,7 @@ fn delay(duration: Duration) -> impl Future<Output = Result<(), Canceled>> {
let (tx, rx) = oneshot::channel();
set_timeout(
move || {
tx.send(());
_ = tx.send(());
},
duration,
);

View File

@@ -1,55 +1,54 @@
mod api;
use api::{Contact, ContactSummary};
use leptos::*;
use leptos_meta::*;
use leptos_router::*;
use crate::api::{get_contact, get_contacts};
pub fn router_example(cx: Scope) -> Element {
provide_context(cx, MetaContext::default());
#[component]
pub fn RouterExample(cx: Scope) -> impl IntoView {
log::debug!("rendering <RouterExample/>");
view! { cx,
<div id="root">
<Router>
<nav>
<A exact=true href="/">"Contacts"</A>
<A href="about">"About"</A>
<A href="settings">"Settings"</A>
</nav>
<main>
<Routes>
<Router>
<nav>
<A exact=true href="/">"Contacts"</A>
<A href="about">"About"</A>
<A href="settings">"Settings"</A>
</nav>
<main>
<Routes>
<Route
path=""
view=move |cx| view! { cx, <ContactList/> }
>
<Route
path=""
element=move |cx| view! { cx, <ContactList/> }
>
<Route
path=":id"
element=move |cx| view! { cx, <Contact/> }
/>
<Route
path="/"
element=move |_| view! { cx, <p>"Select a contact."</p> }
/>
</Route>
<Route
path="about"
element=move |cx| view! { cx, <About/> }
path=":id"
view=move |cx| view! { cx, <Contact/> }
/>
<Route
path="settings"
element=move |cx| view! { cx, <Settings/> }
path="/"
view=move |_| view! { cx, <p>"Select a contact."</p> }
/>
</Routes>
</main>
</Router>
</div>
</Route>
<Route
path="about"
view=move |cx| view! { cx, <About/> }
/>
<Route
path="settings"
view=move |cx| view! { cx, <Settings/> }
/>
</Routes>
</main>
</Router>
}
}
#[component]
pub fn ContactList(cx: Scope) -> Element {
log!("rendering ContactList");
pub fn ContactList(cx: Scope) -> impl IntoView {
log::debug!("rendering <ContactList/>");
let location = use_location(cx);
let contacts = create_resource(cx, move || location.search.get(), get_contacts);
let contacts = move || {
@@ -78,8 +77,9 @@ pub fn ContactList(cx: Scope) -> Element {
}
#[component]
pub fn Contact(cx: Scope) -> Element {
log!("rendering <Contact/> page");
pub fn Contact(cx: Scope) -> impl IntoView {
log::debug!("rendering <Contact/>");
let params = use_params_map(cx);
let contact = create_resource(
cx,
@@ -103,41 +103,42 @@ pub fn Contact(cx: Scope) -> Element {
// I'm only doing this explicitly for the example
None => None,
// Some(None) => has loaded and found no contact
Some(None) => Some(view! { cx, <p>"No contact with this ID was found."</p> }),
Some(None) => Some(view! { cx, <p>"No contact with this ID was found."</p> }.into_any()),
// Some(Some) => has loaded and found a contact
Some(Some(contact)) => Some(view! { cx,
<section class="card">
<h1>{contact.first_name} " " {contact.last_name}</h1>
<p>{contact.address_1}<br/>{contact.address_2}</p>
</section>
}),
}.into_any()),
};
view! { cx,
<div class="contact">
<Suspense fallback=move || view! { cx, <p>"Loading..."</p> }>
<Transition fallback=move || view! { cx, <p>"Loading..."</p> }>
{contact_display}
</Suspense>
</Transition>
</div>
}
}
#[component]
pub fn About(_cx: Scope) -> Element {
log!("rendering About page");
pub fn About(cx: Scope) -> impl IntoView {
log::debug!("rendering <About/>");
view! { cx,
<div>
<>
<h1>"About"</h1>
<p>"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."</p>
</div>
</>
}
}
#[component]
pub fn Settings(_cx: Scope) -> Element {
log!("rendering Settings page");
pub fn Settings(cx: Scope) -> impl IntoView {
log::debug!("rendering <Settings/>");
view! { cx,
<div>
<>
<h1>"Settings"</h1>
<form>
<fieldset>
@@ -147,6 +148,6 @@ pub fn Settings(_cx: Scope) -> Element {
</fieldset>
<pre>"This page is just a placeholder."</pre>
</form>
</div>
</>
}
}

View File

@@ -1,9 +1,9 @@
use leptos::*;
use router::router_example;
use router::*;
pub fn main() {
console_error_panic_hook::set_once();
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to_body(router_example)
mount_to_body(|cx| view! { cx, <RouterExample/> })
}

10
examples/tailwind/.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk

View File

@@ -0,0 +1,120 @@
[package]
name = "tailwind"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_meta = { path = "../../meta", default-features = false }
leptos_router = { path = "../../router", default-features = false }
gloo-net = { version = "0.2", features = ["http"] }
log = "0.4"
cfg-if = "1.0"
# dependecies for client (enable when csr or hydrate set)
wasm-bindgen = { version = "0.2", optional = true }
console_log = { version = "0.2", optional = true }
console_error_panic_hook = { version = "0.1", optional = true }
# dependecies for server (enable when ssr set)
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", features = ["macros"], optional = true }
futures = { version = "0.3", optional = true }
simple_logger = { version = "4.0", optional = true }
serde_json = { version = "1.0", optional = true }
reqwest = { version = "0.11", features = ["json"], optional = true }
[features]
default = ["csr"]
hydrate = [
"leptos/hydrate",
"leptos_meta/hydrate",
"leptos_router/hydrate",
"dep:wasm-bindgen",
"dep:console_log",
"dep:console_error_panic_hook",
]
csr = [
"leptos/csr",
"leptos_meta/csr",
"leptos_router/csr",
"dep:wasm-bindgen",
"dep:console_log",
"dep:console_error_panic_hook",
]
ssr = [
"leptos/ssr",
"leptos_meta/ssr",
"leptos_router/ssr",
"dep:reqwest",
"dep:leptos_actix",
"dep:actix-web",
"dep:actix-files",
"dep:futures",
"dep:simple_logger",
"dep:serde_json",
]
[package.metadata.cargo-all-features]
denylist = ["actix-files", "actix-web", "leptos_actix"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
[profile.release]
codegen-units = 1
lto = true
opt-level = 'z'
[package.metadata.leptos]
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
output-name = "tailwind"
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
site-root = "target/site"
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
# Defaults to pkg
site-pkg-dir = "pkg"
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
# style-file = "src/styles/tailwind.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.
site-address = "127.0.0.1:3000"
# The port to use for automatic reload monitoring
reload-port = 3001
# [Optional] Command to use when running end2end tests. It will run in the end2end dir.
end2end-cmd = "npx playwright test"
# The browserlist query used for optimizing the CSS.
browserquery = "defaults"
# Set by cargo-leptos watch when building with tha tool. Controls whether autoreload JS will be included in the head
watch = false
# The environment Leptos will run in, usually either "DEV" or "PROD"
env = "DEV"
# The features to use when compiling the bin target
#
# Optional. Can be over-ridden with the command line parameter --bin-features
bin-features = ["ssr"]
# If the --no-default-features flag should be used when compiling the bin target
#
# Optional. Defaults to false.
bin-default-features = false
# The features to use when compiling the lib target
#
# Optional. Can be over-ridden with the command line parameter --lib-features
lib-features = ["hydrate"]
# If the --no-default-features flag should be used when compiling the lib target
#
# Optional. Defaults to false.
lib-default-features = false

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 Greg Johnston
Copyright (c) 2022 henrik
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

105
examples/tailwind/README.md Normal file
View File

@@ -0,0 +1,105 @@
# Leptos Starter Template
This is a template demonstrating how to integrate [TailwindCSS](https://tailwindcss.com/) with the [Leptos](https://github.com/gbj/leptos) web framework and the [cargo-leptos](https://github.com/akesson/cargo-leptos) tool.
If you don't have `cargo-leptos` installed you can install it with
`cargo install --locked cargo-leptos`
Then run
`npx tailwindcss -i ./input.css -o ./style/output.css --watch`
and
`cargo leptos watch`
in this directory.
You can begin editing your app at `src/app.rs`.
## Installing Tailwind
You can install Tailwind using `npm`:
```bash
npm install -D tailwindcss
```
If you'd rather not use `npm`, you can install the Tailwind binary [here](https://github.com/tailwindlabs/tailwindcss/releases).
## Setting up with VS Code and Additional Tools
If you're using VS Code, add the following to your `settings.json`
```json
"emmet.includeLanguages": {
"rust": "html",
"*.rs": "html"
},
"tailwindCSS.includeLanguages": {
"rust": "html",
"*.rs": "html"
},
"files.associations": {
"*.rs": "rust"
},
"editor.quickSuggestions": {
"other": "on",
"comments": "on",
"strings": true
},
"css.validate": false,
```
Install [Tailwind CSS Intellisense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss).
Install "VS Browser" extension, a browser at the right window.
Allow vscode Ports forward: 3000, 3001.
## Notes about Tooling
By default, `cargo-leptos` uses `nightly` Rust, `cargo-generate`, and `sass`. If you run into any trouble, you may need to install one or more of these tools.
1. `rustup toolchain install nightly --allow-downgrade` - make sure you have Rust nightly
2. `rustup default nightly` - setup nightly as default, or you can use rust-toolchain file later on
3. `rustup target add wasm32-unknown-unknown` - add the ability to compile Rust to WebAssembly
4. `cargo install cargo-generate` - install `cargo-generate` binary (should be installed automatically in future)
5. `npm install -g sass` - install `dart-sass` (should be optional in future
## Alternatives to cargo-leptos
This crate can be run without `cargo-leptos`, using `wasm-pack` and `cargo`. To do so, you'll need to install some other tools.
1. `cargo install wasm-pack`
### Server Side Rendering With Hydration
To run it as a server side app with hydration, first you should run
```bash
wasm-pack build --target=web --no-default-features --features=hydrate
```
to generate the WebAssembly to hydrate the HTML delivered from the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
```bash
cargo run --no-default-features --features=ssr
```
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above before running
> `cargo run`
### Client Side Rendering
You'll need to install trunk to client side render this bundle.
1. `cargo install trunk`
Then the site can be served with `trunk serve --open`
## Attribution
Many thanks to GreatGreg for putting together this guide. You can find the original, with added details, [here](https://github.com/gbj/leptos/discussions/125).

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View File

@@ -0,0 +1,74 @@
{
"name": "end2end",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "end2end",
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.28.0"
}
},
"node_modules/@playwright/test": {
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.0.tgz",
"integrity": "sha512-vrHs5DFTPwYox5SGKq/7TDn/S4q6RA1zArd7uhO6EyP9hj3XgZBBM12ktMbnDQNxh/fL1IUKsTNLxihmsU38lQ==",
"dev": true,
"dependencies": {
"@types/node": "*",
"playwright-core": "1.28.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=14"
}
},
"node_modules/@types/node": {
"version": "18.11.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
"dev": true
},
"node_modules/playwright-core": {
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.0.tgz",
"integrity": "sha512-nJLknd28kPBiCNTbqpu6Wmkrh63OEqJSFw9xOfL9qxfNwody7h6/L3O2dZoWQ6Oxcm0VOHjWmGiCUGkc0X3VZA==",
"dev": true,
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=14"
}
}
},
"dependencies": {
"@playwright/test": {
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.0.tgz",
"integrity": "sha512-vrHs5DFTPwYox5SGKq/7TDn/S4q6RA1zArd7uhO6EyP9hj3XgZBBM12ktMbnDQNxh/fL1IUKsTNLxihmsU38lQ==",
"dev": true,
"requires": {
"@types/node": "*",
"playwright-core": "1.28.0"
}
},
"@types/node": {
"version": "18.11.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
"dev": true
},
"playwright-core": {
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.0.tgz",
"integrity": "sha512-nJLknd28kPBiCNTbqpu6Wmkrh63OEqJSFw9xOfL9qxfNwody7h6/L3O2dZoWQ6Oxcm0VOHjWmGiCUGkc0X3VZA==",
"dev": true
}
}
}

View File

@@ -0,0 +1,13 @@
{
"name": "end2end",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.28.0"
}
}

View File

@@ -0,0 +1,107 @@
import type { PlaywrightTestConfig } from "@playwright/test";
import { devices } from "@playwright/test";
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
const config: PlaywrightTestConfig = {
testDir: "./tests",
/* Maximum time one test can run for. */
timeout: 30 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 5000,
},
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
},
},
{
name: "firefox",
use: {
...devices["Desktop Firefox"],
},
},
{
name: "webkit",
use: {
...devices["Desktop Safari"],
},
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: {
// ...devices['Pixel 5'],
// },
// },
// {
// name: 'Mobile Safari',
// use: {
// ...devices['iPhone 12'],
// },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: {
// channel: 'msedge',
// },
// },
// {
// name: 'Google Chrome',
// use: {
// channel: 'chrome',
// },
// },
],
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
// outputDir: 'test-results/',
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// port: 3000,
// },
};
export default config;

View File

@@ -0,0 +1,9 @@
import { test, expect } from "@playwright/test";
test("homepage has title and links to intro page", async ({ page }) => {
await page.goto("http://localhost:3000/");
await expect(page).toHaveTitle("Cargo Leptos");
await expect(page.locator("h1")).toHaveText("Hi from your Leptos WASM!");
});

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Cargo Leptos</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- INJECT HEAD -->
</head>
<body>
<!-- INJECT BODY -->
</body>
</html>

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1,28 @@
use leptos::*;
use leptos_meta::*;
#[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>
}
}

View File

@@ -0,0 +1,39 @@
mod app;
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "hydrate")] {
use wasm_bindgen::prelude::wasm_bindgen;
use crate::app::*;
use leptos::*;
#[wasm_bindgen]
pub fn hydrate() {
console_error_panic_hook::set_once();
_ = console_log::init_with_level(log::Level::Debug);
log!("hydrate mode - hydrating");
leptos::mount_to_body(|cx| {
view! { cx, <App/> }
});
}
}
else if #[cfg(feature = "csr")] {
use wasm_bindgen::prelude::wasm_bindgen;
#[wasm_bindgen(start)]
pub fn main() {
use app::*;
use leptos::*;
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
log!("csr mode - mounting to body");
mount_to_body(|cx| {
view! { cx, <App /> }
});
}
}
}

View File

@@ -0,0 +1,43 @@
mod app;
#[cfg(feature = "ssr")]
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "ssr")] {
use actix_files::Files;
use actix_web::*;
use leptos::*;
use crate::app::*;
#[get("/style.css")]
async fn css() -> impl Responder {
actix_files::NamedFile::open_async("./style/output.css").await
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
let addr = conf.leptos_options.site_address.clone();
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}");
HttpServer::new(move || {
let leptos_options = &conf.leptos_options;
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/> }))
.wrap(middleware::Compress::default())
})
.bind(&addr)?
.run()
.await
}
}
else {
pub fn main() {}
}
}

View File

@@ -0,0 +1,583 @@
/*
! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com
*/
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box;
/* 1 */
border-width: 0;
/* 2 */
border-style: solid;
/* 2 */
border-color: #e5e7eb;
/* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
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 {
line-height: 1.5;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
-moz-tab-size: 4;
/* 3 */
-o-tab-size: 4;
tab-size: 4;
/* 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 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0;
/* 1 */
line-height: inherit;
/* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0;
/* 1 */
color: inherit;
/* 2 */
border-top-width: 1px;
/* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font family by default.
2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* 1 */
font-size: 1em;
/* 2 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0;
/* 1 */
border-color: inherit;
/* 2 */
border-collapse: collapse;
/* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
/* 1 */
font-size: 100%;
/* 1 */
font-weight: inherit;
/* 1 */
line-height: inherit;
/* 1 */
color: inherit;
/* 1 */
margin: 0;
/* 2 */
padding: 0;
/* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button;
/* 1 */
background-color: transparent;
/* 2 */
background-image: none;
/* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::-moz-placeholder, textarea::-moz-placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
input::placeholder,
textarea::placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block;
/* 1 */
vertical-align: middle;
/* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
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: ;
}
::backdrop {
--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: ;
}
.my-0 {
margin-top: 0px;
margin-bottom: 0px;
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}
.max-w-3xl {
max-width: 48rem;
}
.rounded-lg {
border-radius: 0.5rem;
}
.bg-sky-600 {
--tw-bg-opacity: 1;
background-color: rgb(2 132 199 / var(--tw-bg-opacity));
}
.p-6 {
padding: 1.5rem;
}
.px-10 {
padding-left: 2.5rem;
padding-right: 2.5rem;
}
.px-5 {
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.py-3 {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
}
.pb-10 {
padding-bottom: 2.5rem;
}
.text-left {
text-align: left;
}
.text-center {
text-align: center;
}
.text-4xl {
font-size: 2.25rem;
line-height: 2.5rem;
}
.text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.hover\:bg-sky-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(3 105 161 / var(--tw-bg-opacity));
}

View File

@@ -0,0 +1,10 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: {
files: ["*.html", "./src/**/*.rs"],
},
theme: {
extend: {},
},
plugins: [],
}

View File

@@ -1,48 +0,0 @@
[package]
name = "todo-app-cbor"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["openssl", "macros"] }
anyhow = "1"
broadcaster = "1"
console_log = "0.2"
console_error_panic_hook = "0.1"
serde = { version = "1", features = ["derive"] }
futures = "0.3"
cfg-if = "1"
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
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"
gloo = { git = "https://github.com/rustwasm/gloo" }
sqlx = { version = "0.6", features = [
"runtime-tokio-rustls",
"sqlite",
], optional = true }
[features]
default = ["ssr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:actix-files",
"dep:actix-web",
"dep:sqlx",
"leptos/ssr",
"leptos_actix",
"leptos_meta/ssr",
"leptos_router/ssr",
]
[package.metadata.cargo-all-features]
denylist = ["actix-files", "actix-web", "leptos_actix", "sqlx"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]

View File

@@ -1,22 +0,0 @@
# Leptos Todo App Sqlite with CBOR
This example creates a basic todo app with an Actix backend that uses Leptos' server functions to call sqlx from the client and seamlessly run it on the server. It is identical to the todo-app-sqlite example, but utilizes CBOR encoding for one of the server functions
## Server Side Rendering With Hydration
To run it as a server side app with hydration, first you should run
```bash
wasm-pack build --target=web --no-default-features --features=hydrate
```
to generate the WebAssembly to hydrate the HTML that is generated on the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
```bash
cargo run --no-default-features --features=ssr
```
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above
> This should be temporary, and vastly improve once cargo-leptos becomes ready for prime time!

Binary file not shown.

View File

@@ -1,49 +0,0 @@
use cfg_if::cfg_if;
use leptos::*;
mod todo;
// boilerplate to run in different modes
cfg_if! {
// server-only stuff
if #[cfg(feature = "ssr")] {
use actix_files::{Files};
use actix_web::*;
use crate::todo::*;
use std::{ net::SocketAddr,env };
#[get("/style.css")]
async fn css() -> impl Responder {
actix_files::NamedFile::open_async("./style.css").await
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let mut conn = db().await.expect("couldn't connect to DB");
sqlx::migrate!()
.run(&mut conn)
.await
.expect("could not run SQLx migrations");
crate::todo::register_server_functions();
let addr = SocketAddr::from(([127,0,0,1],3000));
HttpServer::new(move || {
let render_options: RenderOptions = RenderOptions::builder().pkg_path("/pkg/todo_app_sqlite").reload_port(3001).socket_address(addr.clone()).environment(&env::var("RUST_ENV")).build();
render_options.write_to_file();
App::new()
.service(Files::new("/pkg", "./pkg"))
.service(css)
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
.route("/{tail:.*}", leptos_actix::render_app_to_stream(render_options, |cx| view! { cx, <TodoApp/> }))
//.wrap(middleware::Compress::default())
})
.bind(&addr)?
.run()
.await
}
} else {
fn main() {
// no client-side main function
}
}
}

View File

@@ -1,212 +0,0 @@
use cfg_if::cfg_if;
use leptos::*;
use leptos_meta::*;
use leptos_router::*;
use serde::{Deserialize, Serialize};
cfg_if! {
if #[cfg(feature = "ssr")] {
use sqlx::{Connection, SqliteConnection};
pub async fn db() -> Result<SqliteConnection, ServerFnError> {
Ok(SqliteConnection::connect("sqlite:Todos.db").await.map_err(|e| ServerFnError::ServerError(e.to_string()))?)
}
pub fn register_server_functions() {
_ = GetTodos::register();
_ = AddTodo::register();
_ = DeleteTodo::register();
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
pub struct Todo {
id: u16,
title: String,
completed: bool,
}
} else {
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Todo {
id: u16,
title: String,
completed: bool,
}
}
}
#[server(GetTodos, "/api", "Url")]
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 futures::TryStreamExt;
let mut conn = db().await?;
let mut todos = Vec::new();
let mut rows = sqlx::query_as::<_, Todo>("SELECT * FROM todos").fetch(&mut conn);
while let Some(row) = rows
.try_next()
.await
.map_err(|e| ServerFnError::ServerError(e.to_string()))?
{
todos.push(row);
}
Ok(todos)
}
#[server(AddTodo, "/api", "Cbor")]
pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
let mut conn = db().await?;
// fake API delay
std::thread::sleep(std::time::Duration::from_millis(1250));
sqlx::query("INSERT INTO todos (title, completed) VALUES ($1, false)")
.bind(title)
.execute(&mut conn)
.await
.map(|_| ())
.map_err(|e| ServerFnError::ServerError(e.to_string()))
}
#[server(DeleteTodo, "/api")]
pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
let mut conn = db().await?;
sqlx::query("DELETE FROM todos WHERE id = $1")
.bind(id)
.execute(&mut conn)
.await
.map(|_| ())
.map_err(|e| ServerFnError::ServerError(e.to_string()))
}
#[component]
pub fn TodoApp(cx: Scope) -> Element {
view! {
cx,
<div>
<Stylesheet href="/style.css"/>
<Router>
<header>
<h1>"My Tasks"</h1>
</header>
<main>
<Routes>
<Route path="" element=|cx| view! {
cx,
<Todos/>
}/>
</Routes>
</main>
</Router>
</div>
}
}
#[component]
pub fn Todos(cx: Scope) -> Element {
let add_todo = create_server_multi_action::<AddTodo>(cx);
let delete_todo = create_server_action::<DeleteTodo>(cx);
let submissions = add_todo.submissions();
// track mutations that should lead us to refresh the list
let add_changed = add_todo.version;
let todo_deleted = delete_todo.version;
// list of todos is loaded from the server in reaction to changes
let todos = create_resource(
cx,
move || (add_changed(), todo_deleted()),
move |_| get_todos(cx),
);
view! {
cx,
<div>
<MultiActionForm action=add_todo>
<label>
"Add a Todo"
<input type="text" name="title"/>
</label>
<input type="submit" value="Add"/>
</MultiActionForm>
<div>
<Suspense fallback=view! {cx, <p>"Loading..."</p> }>
{
let delete_todo = delete_todo.clone();
move || {
let existing_todos = {
let delete_todo = delete_todo.clone();
move || {
todos
.read()
.map({
let delete_todo = delete_todo.clone();
move |todos| match todos {
Err(e) => {
vec![view! { cx, <pre class="error">"Server Error: " {e.to_string()}</pre>}]
}
Ok(todos) => {
if todos.is_empty() {
vec![view! { cx, <p>"No tasks were found."</p> }]
} else {
todos
.into_iter()
.map({
let delete_todo = delete_todo.clone();
move |todo| {
let delete_todo = delete_todo.clone();
view! {
cx,
<li>
{todo.title}
<ActionForm action=delete_todo.clone()>
<input type="hidden" name="id" value=todo.id/>
<input type="submit" value="X"/>
</ActionForm>
</li>
}
}
})
.collect::<Vec<_>>()
}
}
}
})
.unwrap_or_default()
}
};
let pending_todos = move || {
submissions
.get()
.into_iter()
.filter(|submission| submission.pending().get())
.map(|submission| {
view! {
cx,
<li class="pending">{move || submission.input.get().map(|data| data.title) }</li>
}
})
.collect::<Vec<_>>()
};
view! {
cx,
<ul>
<div>{existing_todos}</div>
<div>{pending_todos}</div>
</ul>
}
}
}
</Suspense>
</div>
</div>
}
}

View File

@@ -1,64 +0,0 @@
[package]
name = "todo-app-sqlite-axum"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
anyhow = "1.0.66"
console_log = "0.2.0"
console_error_panic_hook = "0.1.7"
futures = "0.3.25"
cfg-if = "1.0.0"
leptos = { path = "../../../leptos/leptos", default-features = false, features = [
"serde",
] }
leptos_axum = { path = "../../../leptos/integrations/axum", default-features = false, optional = true }
leptos_meta = { path = "../../../leptos/meta", default-features = false }
leptos_router = { path = "../../../leptos/router", default-features = false }
log = "0.4.17"
simple_logger = "4.0.0"
serde = { version = "1.0.148", features = ["derive"] }
serde_json = "1.0.89"
gloo-net = { version = "0.2.5", features = ["http"] }
reqwest = { version = "0.11.13", features = ["json"] }
axum = { version = "0.6.1", optional = true }
tower = { version = "0.4.13", optional = true }
tower-http = { version = "0.3.4", features = ["fs"], optional = true }
tokio = { version = "1.22.0", features = ["full"], optional = true }
http = { version = "0.2.8", optional = true }
sqlx = { version = "0.6.2", features = [
"runtime-tokio-rustls",
"sqlite",
], optional = true }
[features]
default = ["csr"]
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"dep:http",
"dep:sqlx",
"leptos/ssr",
"leptos_meta/ssr",
"leptos_router/ssr",
"leptos_axum",
]
[package.metadata.cargo-all-features]
denylist = [
"axum",
"tower",
"tower-http",
"tokio",
"http",
"sqlx",
"leptos_axum",
]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]

View File

@@ -1,22 +0,0 @@
# Leptos Todo App Sqlite with Axum
This example creates a basic todo app with an Axum backend that uses Leptos' server functions to call sqlx from the client and seamlessly run it on the server.
## Server Side Rendering With Hydration
To run it as a server side app with hydration, first you should run
```bash
wasm-pack build --target=web --no-default-features --features=hydrate
```
to generate the WebAssembly to hydrate the HTML that is generated on the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
```bash
cargo run --no-default-features --features=ssr
```
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above
> This should be temporary, and vastly improve once cargo-leptos becomes ready for prime time!

View File

@@ -1,213 +0,0 @@
use cfg_if::cfg_if;
use leptos::*;
use leptos_router::*;
use serde::{Deserialize, Serialize};
cfg_if! {
if #[cfg(feature = "ssr")] {
use sqlx::{Connection, SqliteConnection};
pub async fn db() -> Result<SqliteConnection, ServerFnError> {
Ok(SqliteConnection::connect("sqlite:Todos.db").await.map_err(|e| ServerFnError::ServerError(e.to_string()))?)
}
pub fn register_server_functions() {
_ = GetTodos::register();
_ = AddTodo::register();
_ = DeleteTodo::register();
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
pub struct Todo {
id: u16,
title: String,
completed: bool,
}
} else {
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Todo {
id: u16,
title: String,
completed: bool,
}
}
}
#[server(GetTodos, "/api")]
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 = use_context::<http::Request<axum::body::BoxBody>>(cx)
// .expect("couldn't get HttpRequest from context");
// println!("req.path = {:?}", req.uri());
use futures::TryStreamExt;
let mut conn = db().await?;
let mut todos = Vec::new();
let mut rows = sqlx::query_as::<_, Todo>("SELECT * FROM todos").fetch(&mut conn);
while let Some(row) = rows
.try_next()
.await
.map_err(|e| ServerFnError::ServerError(e.to_string()))?
{
todos.push(row);
}
Ok(todos)
}
#[server(AddTodo, "/api")]
pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
let mut conn = db().await?;
// fake API delay
std::thread::sleep(std::time::Duration::from_millis(1250));
match sqlx::query("INSERT INTO todos (title, completed) VALUES ($1, false)")
.bind(title)
.execute(&mut conn)
.await
{
Ok(_row) => Ok(()),
Err(e) => Err(ServerFnError::ServerError(e.to_string())),
}
}
#[server(DeleteTodo, "/api")]
pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
let mut conn = db().await?;
sqlx::query("DELETE FROM todos WHERE id = $1")
.bind(id)
.execute(&mut conn)
.await
.map(|_| ())
.map_err(|e| ServerFnError::ServerError(e.to_string()))
}
#[component]
pub fn TodoApp(cx: Scope) -> Element {
view! {
cx,
<div>
<Router>
<header>
<h1>"My Tasks"</h1>
</header>
<main>
<Routes>
<Route path="" element=|cx| view! {
cx,
<Todos/>
}/>
</Routes>
</main>
</Router>
</div>
}
}
#[component]
pub fn Todos(cx: Scope) -> Element {
let add_todo = create_server_multi_action::<AddTodo>(cx);
let delete_todo = create_server_action::<DeleteTodo>(cx);
let submissions = add_todo.submissions();
// track mutations that should lead us to refresh the list
let add_changed = add_todo.version;
let todo_deleted = delete_todo.version;
// list of todos is loaded from the server in reaction to changes
let todos = create_resource(
cx,
move || (add_changed(), todo_deleted()),
move |_| get_todos(cx),
);
view! {
cx,
<div>
<MultiActionForm action=add_todo>
<label>
"Add a Todo"
<input type="text" name="title"/>
</label>
<input type="submit" value="Add"/>
</MultiActionForm>
<div>
<Suspense fallback=view! {cx, <p>"Loading..."</p> }>
{
let delete_todo = delete_todo.clone();
move || {
let existing_todos = {
let delete_todo = delete_todo.clone();
move || {
todos
.read()
.map({
let delete_todo = delete_todo.clone();
move |todos| match todos {
Err(e) => {
vec![view! { cx, <pre class="error">"Server Error: " {e.to_string()}</pre>}]
}
Ok(todos) => {
if todos.is_empty() {
vec![view! { cx, <p>"No tasks were found."</p> }]
} else {
todos
.into_iter()
.map({
let delete_todo = delete_todo.clone();
move |todo| {
let delete_todo = delete_todo.clone();
view! {
cx,
<li>
{todo.title}
<ActionForm action=delete_todo.clone()>
<input type="hidden" name="id" value=todo.id/>
<input type="submit" value="X"/>
</ActionForm>
</li>
}
}
})
.collect::<Vec<_>>()
}
}
}
})
.unwrap_or_default()
}
};
let pending_todos = move || {
submissions
.get()
.into_iter()
.filter(|submission| submission.pending().get())
.map(|submission| {
view! {
cx,
<li class="pending">{move || submission.input.get().map(|data| data.title) }</li>
}
})
.collect::<Vec<_>>()
};
view! {
cx,
<ul>
<div>{existing_todos}</div>
<div>{pending_todos}</div>
</ul>
}
}
}
</Suspense>
</div>
</div>
}
}

View File

@@ -1,48 +0,0 @@
[package]
name = "todo-app-sqlite"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
actix-files = { version = "0.6.2", optional = true }
actix-web = { version = "4.2.1", optional = true, features = ["openssl", "macros"] }
anyhow = "1.0.66"
broadcaster = "1.0.0"
console_log = "0.2.0"
console_error_panic_hook = "0.1.7"
serde = { version = "1.0.148", features = ["derive"] }
futures = "0.3.25"
cfg-if = "1.0.0"
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_meta = { path = "../../meta", default-features = false }
leptos_router = { path = "../../router", default-features = false }
log = "0.4.17"
simple_logger = "4.0.0"
gloo = { git = "https://github.com/rustwasm/gloo" }
sqlx = { version = "0.6.2", features = [
"runtime-tokio-rustls",
"sqlite",
], optional = true }
[features]
default = ["ssr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:actix-files",
"dep:actix-web",
"dep:sqlx",
"leptos/ssr",
"leptos_actix",
"leptos_meta/ssr",
"leptos_router/ssr",
]
[package.metadata.cargo-all-features]
denylist = ["actix-files", "actix-web", "leptos_actix", "sqlx"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]

View File

@@ -1,22 +0,0 @@
# Leptos Todo App Sqlite
This example creates a basic todo app with an Actix backend that uses Leptos' server functions to call sqlx from the client and seamlessly run it on the server
## Server Side Rendering With Hydration
To run it as a server side app with hydration, first you should run
```bash
wasm-pack build --target=web --no-default-features --features=hydrate
```
to generate the WebAssembly to hydrate the HTML that is generated on the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
```bash
cargo run --no-default-features --features=ssr
```
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above
> This should be temporary, and vastly improve once cargo-leptos becomes ready for prime time!

Some files were not shown because too many files have changed in this diff Show More