Compare commits

..

445 Commits

Author SHA1 Message Date
Greg Johnston
f8c036ab51 chore: unused import 2024-06-28 15:28:17 -04:00
Greg Johnston
d31c48b115 restore fallback for compressed version 2024-06-28 15:28:03 -04:00
Greg Johnston
46376bbd0c docs: working on memo docs 2024-06-28 15:21:24 -04:00
Greg Johnston
0d83042621 0.7.0-alpha 2024-06-28 15:21:24 -04:00
Greg Johnston
a951bad070 feat: support reactive and asynchronous ProtectedRoute conditions 2024-06-28 15:21:24 -04:00
Greg Johnston
0bf9063a76 fix: correct For behavior when mounting with siblings, and when clearing 2024-06-28 15:21:24 -04:00
Ben Wishovich
78581d87cb Make get_configuration sync (#2647)
* Made get_configuraiton sync

* Update examples
2024-06-28 15:21:24 -04:00
Greg Johnston
af1525d4ea examples: update directives tests 2024-06-28 15:21:24 -04:00
Greg Johnston
61a9d45060 chore: cargo fmt 2024-06-28 15:21:24 -04:00
Greg Johnston
1bf2982d03 make RemoveEventHandler a concrete type 2024-06-28 15:21:24 -04:00
Greg Johnston
5d7dfd360e feat: add ElementExt to give access to the same view APIs at runtime that we do at compile time 2024-06-28 15:21:24 -04:00
Greg Johnston
704b66ec31 feat: add ElementExt to give access to the same view APIs at runtime that we do at compile time 2024-06-28 15:21:24 -04:00
Greg Johnston
3ff20c3708 chore: cargo fmt 2024-06-28 15:21:24 -04:00
Greg Johnston
7c2df473c2 chore: cargo fmt 2024-06-28 15:21:24 -04:00
Greg Johnston
a75edf3526 docs: warn on unused RenderEffect 2024-06-28 15:21:24 -04:00
Greg Johnston
f0e259f42f chore: missing Debug implementations 2024-06-28 15:21:24 -04:00
Greg Johnston
e3e9ad54c6 chore: suppress unnecessary .into() warning 2024-06-28 15:21:24 -04:00
Greg Johnston
d344f96138 cargo fmt 2024-06-28 15:21:24 -04:00
Greg Johnston
876ce79c2a updated directives example 2024-06-28 15:21:24 -04:00
brofrain
336e702b86 fix: update HtmlViewState & BodyViewState as well 2024-06-28 15:21:24 -04:00
Kajetan Welc
09bdeb7286 fix: do not accidentally mount things before meta tags in the <head> when updating the DOM v2 2024-06-28 15:21:24 -04:00
Greg Johnston
6aac824207 remove log 2024-06-28 15:21:24 -04:00
Greg Johnston
baf11b30b3 preliminary work on directives (not useful yet until we have an ElementExt that allows you to do things declaratively from an Element 2024-06-28 15:21:24 -04:00
Greg Johnston
6e6d16ffe5 omit () entirely if it is the only child of an HTML element 2024-06-28 15:21:24 -04:00
Greg Johnston
cadd3cdd09 fix: don't override a text node's 'next sibling after text' position if it's in Either, now that they don't have separate marker nodes 2024-06-28 15:21:24 -04:00
Greg Johnston
548e5acb39 fix: do not accidentally mount things before meta tags in the <head> when updating the DOM 2024-06-28 15:21:24 -04:00
Greg Johnston
d135dbf337 fix docs for hydrate_islands 2024-06-28 15:21:24 -04:00
Greg Johnston
c1458fd061 hackernews islands example 2024-06-28 15:21:24 -04:00
Greg Johnston
57170beca6 update static file serving in Axum examples 2024-06-28 15:21:00 -04:00
Greg Johnston
72492f3102 fix islands ci setup 2024-06-28 15:20:05 -04:00
Greg Johnston
ea22cd3757 fix: remove extra comment at end of Suspense now that Either no longer requires it 2024-06-28 15:20:05 -04:00
Greg Johnston
0dded850ab feat: provide static file handling/fallback directly in integration 2024-06-28 15:20:05 -04:00
Greg Johnston
c189fbef6c islands example 2024-06-28 15:20:05 -04:00
Greg Johnston
ee8ac29496 remove unused tests and dependencies 2024-06-28 15:20:05 -04:00
Greg Johnston
645c4abee3 fix: () in templates 2024-06-28 15:20:05 -04:00
Greg Johnston
532ee6daea chore: clippy 2024-06-28 15:20:05 -04:00
Greg Johnston
4f65c4440a fix js-framework-benchmark for stable 2024-06-28 15:20:05 -04:00
Greg Johnston
c7fd25d164 update hackernews_axum to 0.7 2024-06-28 15:20:05 -04:00
Greg Johnston
5f944f997f fix ErrorBoundary starting in error state in CSR 2024-06-28 15:20:05 -04:00
Greg Johnston
3a633be215 fix example tests 2024-06-28 15:20:05 -04:00
Greg Johnston
7581348bcd remove unnecessary logs 2024-06-28 15:20:05 -04:00
Greg Johnston
c6e8058b82 update islands example 2024-06-28 15:20:05 -04:00
Greg Johnston
dd6e05559f chore: unused hooks 2024-06-28 15:20:05 -04:00
Greg Johnston
d9d80344fb regression test for 7094dee150 2024-06-28 15:20:05 -04:00
Greg Johnston
c82661b5b1 fix: signals mark subscribers dirty, but don't say they're always dirty if they haven't changed 2024-06-28 15:20:04 -04:00
Greg Johnston
262df175dd make Routes fallback run lazily 2024-06-28 15:20:04 -04:00
Greg Johnston
8d13b3e679 make ErrorBoundary fallback run lazily 2024-06-28 15:20:04 -04:00
Greg Johnston
bd92397ac7 examples: errors_axum 2024-06-28 15:20:04 -04:00
Greg Johnston
b464d5c842 chore(ci): fix examples 2024-06-28 15:20:04 -04:00
Greg Johnston
beecba5532 enable reactive-graph hydration when hydration is enabled 2024-06-28 15:20:04 -04:00
Greg Johnston
c880250729 allow conversion directly from Arc signal types to MaybeSignal 2024-06-28 15:20:04 -04:00
Greg Johnston
463f5f4440 expose Owner::shared_context() 2024-06-28 15:20:04 -04:00
Greg Johnston
bc2cd0c7c1 0.7 Provider component 2024-06-28 15:20:04 -04:00
Greg Johnston
db467fb153 fix tests 2024-06-28 15:20:04 -04:00
Greg Johnston
da37908d81 fix reactive styles 2024-06-28 15:20:04 -04:00
Greg Johnston
df31ffb00a fix: correctly rebuild reactive attributes to avoid stale signals 2024-06-28 15:20:04 -04:00
Greg Johnston
53778936fa type-erase RenderEffeect functions for binary size improvements 2024-06-28 15:20:04 -04:00
luoxiaozero
897751de40 feat: Attr exposes PhantomData field (#2641) 2024-06-28 15:20:04 -04:00
Greg Johnston
1c024b7464 chore(ci): remove warnings in tests 2024-06-28 15:20:04 -04:00
Greg Johnston
9f2963c4a9 feat: 0.7 query signals 2024-06-28 15:20:04 -04:00
Greg Johnston
e4a2344110 chore(ci): add Makefiles for smaller packages 2024-06-28 15:20:04 -04:00
Greg Johnston
68c4038fe5 chore(ci): only run semver checks if not labeled 'breaking' 2024-06-28 15:20:04 -04:00
Greg Johnston
057b449ba2 chore: clippy 2024-06-28 15:20:04 -04:00
Greg Johnston
4b323829f0 rename from new_serde to new 2024-06-28 15:20:04 -04:00
Greg Johnston
e58d4f8fa8 remove most remaining marker/placeholder elements 2024-06-28 15:20:04 -04:00
Greg Johnston
85cf4bfde8 add trait impls and encodings for SharedValue 2024-06-28 15:20:04 -04:00
Greg Johnston
fdf8250770 default to SerdeJson encoding for resources, use new_str() for Str encoding 2024-06-28 15:20:04 -04:00
Greg Johnston
947079ef99 feat: synchronous serialized values with SharedValue 2024-06-28 15:20:04 -04:00
Greg Johnston
618f649a4c whenever we create a new root Owner, set it as the current owner, which will make it the default owner (e.g., during SSR) instead of None 2024-06-28 15:20:04 -04:00
Greg Johnston
da31d321ec fix: can't memoize JS properties, because they can be set between signal updates by user input 2024-06-28 15:20:04 -04:00
Greg Johnston
a62b154ec8 fix: correctly escape style and class attributes 2024-06-28 15:20:04 -04:00
Greg Johnston
30544ca05b chore: clippy 2024-06-28 15:20:04 -04:00
Greg Johnston
eaa039efcc don't require spawn_local for actios 2024-06-28 15:20:04 -04:00
Greg Johnston
f1f5e19136 catch resource reads inside Signal during Suspense 2024-06-28 15:20:04 -04:00
Greg Johnston
f14702c39f fix CSS file names 2024-06-28 15:20:04 -04:00
Greg Johnston
602b36ee60 update todo app csr 2024-06-28 15:20:04 -04:00
Greg Johnston
8605e3f0cb fix: correctly escape text nodes, except in script/style tags 2024-06-28 15:20:04 -04:00
Greg Johnston
f45dbf25ab chore: clean up examples for CI 2024-06-28 15:20:04 -04:00
Greg Johnston
9be2bad400 fix: provide matched route via context when rebuilding (so <A> works) 2024-06-28 15:20:04 -04:00
Greg Johnston
0600a646be unused 2024-06-28 15:20:04 -04:00
Greg Johnston
722bedee63 refactor insert_before_this to find parent lazily, and use it for rebuilding reactive components by replacing their whole contents 2024-06-28 15:20:04 -04:00
Greg Johnston
597eb4fc04 make sure SendWrapper supports Futures 2024-06-28 15:20:04 -04:00
Greg Johnston
5ec26aeada move several complex examples into projects 2024-06-28 15:20:04 -04:00
Greg Johnston
c84e189a61 update js-framework-benchmark example 2024-06-28 15:20:04 -04:00
Greg Johnston
88831c5543 fix test text 2024-06-28 15:20:04 -04:00
Greg Johnston
f455c55b85 fix: custom Stream implementation for streaming resource data that supports nested data/multiple polled values, rather than taking it all at once at the beginning 2024-06-28 15:20:04 -04:00
Greg Johnston
dec1b02579 suspense_tests: actually wait for other resource in nested case 2024-06-28 15:20:04 -04:00
Greg Johnston
c4ccc306ca can save a lookup here 2024-06-28 15:20:04 -04:00
Greg Johnston
330c2928fc remove unused workspace member 2024-06-28 15:20:04 -04:00
Greg Johnston
464c42f314 update workflows 2024-06-28 15:20:04 -04:00
Greg Johnston
6d71383637 remove unused leptos_reactive integration 2024-06-28 15:20:04 -04:00
Greg Johnston
2a99996e53 remove old router files 2024-06-28 15:20:04 -04:00
Greg Johnston
21292c059f fix Cargo.toml after merge 2024-06-28 15:20:04 -04:00
Greg Johnston
36b9354869 re-enable all routes 2024-06-28 15:20:04 -04:00
Greg Johnston
c2cbfd50a3 chore: clean up warnings and logging 2024-06-28 15:20:04 -04:00
Greg Johnston
499b7ea4fc reverted Fn()/FnMut() change 2024-06-28 15:20:04 -04:00
Greg Johnston
54595e0ef8 Revert "fix: constrain reactive rendering to Fn(), because using dry_resolve() for Suspense requires idempotent render functions so that they can be called once (to register resources) and called a second time to resolve"
This reverts commit 7ec5c77ba3e8f45bae04a7661a56741f95125adb.
2024-06-28 15:20:04 -04:00
Greg Johnston
3eb6b14da7 progress on updating suspense tests 2024-06-28 15:20:04 -04:00
Greg Johnston
1d0865bee7 add server redirects 2024-06-28 15:20:04 -04:00
Greg Johnston
7145efc813 fix: relative path resolution 2024-06-28 15:20:04 -04:00
Greg Johnston
db99714f66 simplifying todo examples 2024-06-28 15:20:04 -04:00
Greg Johnston
e184438b2c initial work updating suspense tests 2024-06-28 15:20:04 -04:00
Greg Johnston
0a0d1803b8 clarify hydrate/csr warning 2024-06-28 15:20:04 -04:00
Greg Johnston
812ad7c59f export actions in prelude 2024-06-28 15:20:04 -04:00
Greg Johnston
8105a27b13 remove Into<_> by default for setting signals, because it interferes with type inference 2024-06-28 15:20:04 -04:00
Greg Johnston
eb0fa2cc9e add support for unsync actions 2024-06-28 15:20:04 -04:00
Greg Johnston
fec59ff13f simplifying and updating server fns example 2024-06-28 15:20:04 -04:00
Greg Johnston
134a9d1593 change name to shell 2024-06-28 15:20:04 -04:00
Greg Johnston
506e5a7470 update control flow components to new Fn() constraint 2024-06-28 15:20:04 -04:00
Greg Johnston
08bfcdb0ff fix: constrain reactive rendering to Fn(), because using dry_resolve() for Suspense requires idempotent render functions so that they can be called once (to register resources) and called a second time to resolve 2024-06-28 15:20:04 -04:00
Greg Johnston
97c1a32200 add Debug impl 2024-06-28 15:20:04 -04:00
Greg Johnston
3bf17f9ff7 fix: actually concatenate nested routes during route generation 2024-06-28 15:20:04 -04:00
Greg Johnston
aff6939f79 reorganize Outlet export 2024-06-28 15:20:04 -04:00
Greg Johnston
4c7223136c examples: porting to 0.7 and cleaning up 2024-06-28 15:20:04 -04:00
Greg Johnston
0379a3c2ff fix merge 2024-06-28 15:20:04 -04:00
Rakshith Ravi
8dacdb5917 Update import statements in examples (#2625) 2024-06-28 15:20:04 -04:00
Greg Johnston
6fa4b0cd4c examples: use application 404 page 2024-06-28 15:20:04 -04:00
Greg Johnston
17442f3e94 chore: cargo fmt 2024-06-28 15:20:04 -04:00
Greg Johnston
f9100a6752 chore: cargo fmt 2024-06-28 15:20:03 -04:00
Greg Johnston
bffe2607d8 add warnings if correct features not set for browser 2024-06-28 15:20:03 -04:00
Greg Johnston
e8f2c5a931 use csr feature so that reactivity runs 2024-06-28 15:20:03 -04:00
Greg Johnston
382ece1686 chore: clippy 2024-06-28 15:20:03 -04:00
Greg Johnston
4be21eb298 examples: fix input type so tests work, and update text to make the purpose clearer 2024-06-28 15:20:03 -04:00
Greg Johnston
6c9f3a9c2d remove leptos_reactive (moved into reactive_graph and leptos_server) 2024-06-28 15:20:03 -04:00
Greg Johnston
04d1ed3371 fix: Clone for ArcResource and default to SerdeJson for Resource 2024-06-28 15:20:03 -04:00
Greg Johnston
a9552411fe chore: clippy 2024-06-28 15:20:03 -04:00
Greg Johnston
ffbaa482b9 fix: don't dispose of parent owners before Suspense children have been rendered 2024-06-28 15:20:03 -04:00
Greg Johnston
fc670fb473 warn if trying to use meta on server side without context 2024-06-28 15:20:03 -04:00
Greg Johnston
d911638221 only run RenderEffects when effects are enabled 2024-06-28 15:20:03 -04:00
Greg Johnston
90513e8156 add set_pending to <Transition/> 2024-06-28 15:20:03 -04:00
Greg Johnston
abc53662f8 simplify FlatRoutes logic by using existing OwnedView infrastructure 2024-06-28 15:20:03 -04:00
Greg Johnston
914db2da00 reexport tick() for testing 2024-06-28 15:20:03 -04:00
Greg Johnston
68273127c2 fix: ensure that leptos_meta and leptos_router are in SSR mode if using one of the server integrations 2024-06-28 15:20:03 -04:00
Greg Johnston
b277e8322e examples: update hackernews for SSR support 2024-06-28 15:20:03 -04:00
Greg Johnston
a4f68d8061 fix: correctly notify multiple subscribers to same AsyncDerived 2024-06-28 15:20:03 -04:00
Greg Johnston
d9ed2caf5a fix: Routes SSR 2024-06-28 15:20:03 -04:00
Greg Johnston
df4189f7bf fix: correct owner for HTML rendering in FlatRoutes 2024-06-28 15:20:03 -04:00
Greg Johnston
944329fdba testing: provide tick() that can be called anywhere in tests 2024-06-28 15:20:03 -04:00
Greg Johnston
136f96b090 testing: provide tick() that can be called anywhere in tests 2024-06-28 15:20:03 -04:00
Greg Johnston
869fc2fb01 fix portal tests 2024-06-28 15:20:03 -04:00
Greg Johnston
a28042e863 fix cleanups in render effects 2024-06-28 15:20:03 -04:00
Greg Johnston
1dda2fb1dd chore: clippy 2024-06-28 15:20:03 -04:00
Greg Johnston
3f094e76ae update counters_isomorphic 2024-06-28 15:20:03 -04:00
Greg Johnston
5e8ff1f222 reexport spawn and spawn_local 2024-06-28 15:20:03 -04:00
Greg Johnston
8a9466acda ReadSignal from stream 2024-06-28 15:20:03 -04:00
Greg Johnston
c4c491935b refactor to allow rendering Resource directly in view 2024-06-28 15:20:03 -04:00
Greg Johnston
ddbbb68255 removed AnimatedShow example (duplicates the component docs) 2024-06-28 15:20:03 -04:00
Greg Johnston
b9be78bfc9 properly serialize errors 2024-06-28 15:20:03 -04:00
Greg Johnston
324a3e5e37 chore: clear warning 2024-06-28 15:20:03 -04:00
Greg Johnston
32666b97fe pick up on server action error in both server and client 2024-06-28 15:20:03 -04:00
Greg Johnston
a03870315e fmt and chores in examples 2024-06-28 15:20:03 -04:00
Greg Johnston
ddc68b9698 fix: serialize an empty string into HTML so it still works as a text node 2024-06-28 15:20:03 -04:00
Greg Johnston
4437e9cc75 fix: make router fallback lazy 2024-06-28 15:20:03 -04:00
Greg Johnston
e800108c46 add expect_context 2024-06-28 15:20:03 -04:00
Greg Johnston
136be456b2 fix attr:class when spreading onto a component 2024-06-28 15:20:03 -04:00
Greg Johnston
a8f75f9678 add ServerAction error handling for any error type (closes #2325) 2024-06-28 15:20:03 -04:00
Greg Johnston
0e4398bc86 remove unused import 2024-06-28 15:20:03 -04:00
Greg Johnston
fb38f51f82 clean up example 2024-06-28 15:20:03 -04:00
Greg Johnston
7a7bc394df add CollectView 2024-06-28 15:20:03 -04:00
Greg Johnston
c78608d1d0 Actix todo_app_sqlite 2024-06-28 15:20:03 -04:00
Greg Johnston
b7d3a628a1 fix: don't drop Owner in FlatRoutes until route has been rendered (thanks @benwis) 2024-06-28 15:20:03 -04:00
Greg Johnston
6556a0b6a5 fix: make sure all resource reads are registered 2024-06-28 15:20:03 -04:00
Greg Johnston
5a7ddeb9af chore: clippy and unused dependencies in integrations 2024-06-28 15:20:03 -04:00
Greg Johnston
2a9ac08d6e add some tracing and debug info to HTML elements 2024-06-28 15:20:03 -04:00
Greg Johnston
b896e4d5bf refactor integrations and add Actix integration 2024-06-28 15:20:03 -04:00
Greg Johnston
757435b218 feat: 0.7 nonce support 2024-06-28 15:20:03 -04:00
Greg Johnston
65b2dc4136 ResponseOptions support 2024-06-28 15:20:03 -04:00
Greg Johnston
c83435812d fix counters tests 2024-06-28 15:20:03 -04:00
Greg Johnston
e10d872c02 allow .children() on HTML elements 2024-06-28 15:20:03 -04:00
Greg Johnston
c335203205 remove async demo 2024-06-28 15:20:03 -04:00
Greg Johnston
98ccf27909 fix: writing to lock that has a read 2024-06-28 15:20:03 -04:00
Greg Johnston
521a591ee4 allow untracking on write guards to support maybe_update 2024-06-28 15:20:03 -04:00
Greg Johnston
7dbfb0a86e feat: allow .write() on all writeable signals 2024-06-28 15:20:03 -04:00
Greg Johnston
0894dbff58 feat: add .by_ref() to create a Future from an AsyncDerived (etc.) that takes a reference, rather than cloning 2024-06-28 15:20:03 -04:00
Greg Johnston
cce4b0aef0 use impl trait in props 2024-06-28 15:20:03 -04:00
Greg Johnston
dea4a2a91a update wasm-bindgen testing approaches 2024-06-28 15:20:03 -04:00
Greg Johnston
828ea30c86 chore: clearing warnings in examples 2024-06-28 15:20:03 -04:00
Greg Johnston
fb583262f4 additional warnings 2024-06-28 15:20:03 -04:00
Greg Johnston
e92711166d cargo fmt 2024-06-28 15:20:03 -04:00
Greg Johnston
db134bce48 chore: clear up... a few warnings 2024-06-28 15:20:03 -04:00
Greg Johnston
7d54ecf4a1 update sledgehammer integration 2024-06-28 15:20:03 -04:00
Greg Johnston
37112d9cc2 remove signal function setter Send-only implementation (dead code) 2024-06-28 15:20:03 -04:00
Greg Johnston
dd21e7e8b7 remove signal function call Read implementations (dead code) 2024-06-28 15:20:03 -04:00
Greg Johnston
63e120f147 remove leptos_reactive dependency 2024-06-28 15:20:03 -04:00
Greg Johnston
70b3a7b259 oco merge issues 2024-06-28 15:20:03 -04:00
Greg Johnston
2b5321dcdd feat: return an async guard from .await rather than cloning the value every time 2024-06-28 15:20:03 -04:00
Greg Johnston
6a6770ccee fix return type in async tests 2024-06-28 15:20:03 -04:00
Greg Johnston
5d9eab2e03 fix tests that run effects 2024-06-28 15:20:03 -04:00
Greg Johnston
6e24177139 feat: return Option from AsyncDerived.get() instead of AsyncState 2024-06-28 15:20:03 -04:00
Greg Johnston
51720368ce example: restore ErrorBoundary 2024-06-28 15:20:03 -04:00
Greg Johnston
906bb15c13 docs for Owner and context 2024-06-28 15:20:03 -04:00
Greg Johnston
39154d917a fix tests 2024-06-28 15:20:03 -04:00
Greg Johnston
e7d3d4ecf2 poll AsyncDerived synchronously so that it has the correct value during hydration if it reads from a resource 2024-06-28 15:20:02 -04:00
Greg Johnston
a88375204d correct dirty-checking on AsyncDerived 2024-06-28 15:20:02 -04:00
Greg Johnston
7442147737 fix regular suspense if nothing was read synchronously 2024-06-28 15:20:02 -04:00
Greg Johnston
d20b8b52a0 missing dry_resolve on Static 2024-06-28 15:20:02 -04:00
Greg Johnston
bd26c611e5 feat: support *either* .await or reactive reads inside Suspense 2024-06-28 15:20:02 -04:00
Greg Johnston
679799f9c8 feat: 0.7 slots 2024-06-28 15:20:02 -04:00
Greg Johnston
a3efe5a228 feat: enhanced spreading syntax 2024-06-28 15:20:02 -04:00
Greg Johnston
5c7a5654eb fix external navigations 2024-06-28 15:20:02 -04:00
Greg Johnston
d481c1b3c8 make WindowListenerHandle Send + Sync so it can be remove via on_cleanup 2024-06-28 15:20:02 -04:00
Greg Johnston
e9d86a549f restore ssr/hydration for Routes 2024-06-28 15:20:02 -04:00
Greg Johnston
9290a1a66e get nested Routes working again 2024-06-28 15:20:02 -04:00
Greg Johnston
89b15e784b default to Params::get() giving an owned value (which you want in a derived signal), but use reference in the macro 2024-06-28 15:20:02 -04:00
Greg Johnston
f1c1401da6 add proper dirty checking on AsyncDerived so it can read from memos properly 2024-06-28 15:20:02 -04:00
Greg Johnston
3eca539423 make NavigateOptions pub 2024-06-28 15:20:02 -04:00
Greg Johnston
6c09456f5a feat: owning memo 2024-06-28 15:20:02 -04:00
Greg Johnston
3190ebc971 remove warnings in tests and only run if effects are enabled 2024-06-28 15:20:02 -04:00
Greg Johnston
d82215c919 fix: prevent memos that have changed from re-triggering the running effect, by setting the Observer during .update_if_necessary() 2024-06-28 15:20:02 -04:00
Greg Johnston
a35b9f283c feat: add Popover API 2024-06-28 15:20:02 -04:00
Greg Johnston
0eca06e5a2 fix Script children 2024-06-28 15:20:02 -04:00
Greg Johnston
aa32552f1a fix hydration of Suspend by including the missing placeholder it expects during hydration 2024-06-28 15:20:02 -04:00
Greg Johnston
472674a841 add missing marker comments for Result 2024-06-28 15:20:02 -04:00
Greg Johnston
86aa1a019e include marker comments in html len 2024-06-28 15:20:02 -04:00
Greg Johnston
0734af4cb1 unused owner 2024-06-28 15:20:02 -04:00
Greg Johnston
ced6510e7d impl From/Into for Signal/ArcSignal 2024-06-28 15:20:02 -04:00
Greg Johnston
df0e59bf8a add ArcSignal::derive() 2024-06-28 15:20:02 -04:00
Greg Johnston
717c99a1c5 routing progress indicator 2024-06-28 15:20:02 -04:00
Greg Johnston
4d28b134d4 missing min attribute 2024-06-28 15:20:02 -04:00
Greg Johnston
a74fe75e10 clean up 2024-06-28 15:20:02 -04:00
Greg Johnston
a38b410587 remove log 2024-06-28 15:20:02 -04:00
Greg Johnston
23256f4d9d relax trait bounds on reactive types where possible 2024-06-28 15:20:02 -04:00
Greg Johnston
16d6110e75 add SignalSetter 2024-06-28 15:20:02 -04:00
Greg Johnston
c79852cea5 use transition between navigations 2024-06-28 15:20:02 -04:00
Greg Johnston
97c5a7ca9d add async transitions that wait for any AsyncDerived created/triggered under them before resolving 2024-06-28 15:20:02 -04:00
Greg Johnston
dac4549117 resolve() on OwnedView 2024-06-28 15:20:02 -04:00
Greg Johnston
ff4e3338e0 restore hydration feature for some of its feature-gating benefits for Resource deserialization 2024-06-28 15:20:02 -04:00
Greg Johnston
1f4de404ee cargo fmt 2024-06-28 15:20:02 -04:00
Greg Johnston
adf5fe83b1 create separate URL/params signals for each route, to prevent updating them and running side effects while navigating away 2024-06-28 15:20:02 -04:00
Greg Johnston
88feadecc9 fmt 2024-06-28 15:20:02 -04:00
Greg Johnston
9ddebd2413 distinguish between dirty and check in effects, so that memos and signals both work correctly 2024-06-28 15:20:02 -04:00
Greg Johnston
6e97cb30a1 remove unused feature 2024-06-28 15:20:02 -04:00
Greg Johnston
1dfb34413c updated future impls 2024-06-28 15:20:02 -04:00
Greg Johnston
92b2d72bfb check whether ArcAsyncDerived actually needs to run when marked check 2024-06-28 15:20:02 -04:00
Greg Johnston
ebbf1e9118 add ancestry debugging for owners 2024-06-28 15:20:02 -04:00
Greg Johnston
9e84e1f57c lazy Future construction for AsyncDerived 2024-06-28 15:20:02 -04:00
Greg Johnston
8be0f499b9 support Resource in CSR for backward-compat 2024-06-28 15:20:02 -04:00
Greg Johnston
481c0c8e19 allow let: syntax to work 2024-06-28 15:20:02 -04:00
Greg Johnston
9515840c9a scope Suspense/Transition correctly within ownership tree 2024-06-28 15:20:02 -04:00
Greg Johnston
83cd043ac7 upgrading hackernews example 2024-06-28 15:20:02 -04:00
Greg Johnston
e103b10a02 immediately commit URL signal updates 2024-06-28 15:20:02 -04:00
Greg Johnston
1ca0039f91 reexport A from router::components 2024-06-28 15:20:02 -04:00
Greg Johnston
2826b5d252 add IntoAny to tachys prelude 2024-06-28 15:20:02 -04:00
Greg Johnston
cb3fb964d9 finish support for innerHTML 2024-06-28 15:20:02 -04:00
Greg Johnston
06ea212d40 feat: iterating over items in children with ChildrenFragment, ChildrenFragmentFn, ChildrenFragmentMut 2024-06-28 15:20:02 -04:00
Greg Johnston
4cf0a6782c only warn about non-reactive accesses if effects are enabled 2024-06-28 15:20:02 -04:00
Greg Johnston
1d11836c23 only run effects on client 2024-06-28 15:20:02 -04:00
Greg Johnston
6c6a009281 resolve() implementation for AnyView 2024-06-28 15:20:02 -04:00
Greg Johnston
bc66faea1c fix deadlock on nested Signals 2024-06-28 15:20:02 -04:00
Greg Johnston
ada87a4b4d fix FlatRouter SSR/hydration after lazy routes 2024-06-28 15:20:02 -04:00
Greg Johnston
0fc64a432f feat: nested islands with context for 0.7 2024-06-28 15:20:02 -04:00
Greg Johnston
13b22c7623 fix: correct Send + Sync bounds for children 2024-06-28 15:20:02 -04:00
Greg Johnston
d481dee95f feat: minimal island support in 0.7 2024-06-28 15:20:02 -04:00
Greg Johnston
65c6527527 docs: full docs and doctests for Action/MultiAction 2024-06-28 15:20:02 -04:00
Greg Johnston
f96b8b24ec remove support for rendering guards directly, as they are !Send and holding onto them in State is also a bad idea 2024-06-28 15:20:02 -04:00
Greg Johnston
079cb4d675 support nightly static values for style:key="value" 2024-06-28 15:20:02 -04:00
Greg Johnston
2f39ac5c33 revert to using .get() for function calls 2024-06-28 15:20:02 -04:00
Greg Johnston
dca7c857ae fix: only rerun effects if they have dirty ancestors (or it's the first run) 2024-06-28 15:20:02 -04:00
Greg Johnston
ef3b95fca0 start working on porting over docs and tests and 0.7... 2024-06-28 15:20:02 -04:00
Greg Johnston
f5d95b2f73 fix meta issue with attributes 2024-06-28 15:20:02 -04:00
Greg Johnston
b906625a83 fix tracing issue 2024-06-28 15:20:02 -04:00
Greg Johnston
cfd369e5ff chore: get tests in a working state 2024-06-28 15:20:02 -04:00
Greg Johnston
36ab0e8ead fix: make Selector Send/Sync 2024-06-28 15:20:02 -04:00
Greg Johnston
440e397bb2 docs: runtime warning if you use .track() outside a tracking context 2024-06-28 15:20:02 -04:00
Greg Johnston
c5520dca48 preliminary tracing for tachys 2024-06-28 15:20:02 -04:00
Greg Johnston
2aacf81380 chore: warnings 2024-06-28 15:20:02 -04:00
Greg Johnston
43f70983ac examples: router in 0.7 2024-06-28 15:20:02 -04:00
Greg Johnston
386665d6b6 fix: passing context through router 2024-06-28 15:20:02 -04:00
Greg Johnston
38b84b3c61 chore: fix warnings about variable case 2024-06-28 15:20:02 -04:00
Greg Johnston
b1ee80ef9e examples: timer in 0.7 2024-06-28 15:20:02 -04:00
Greg Johnston
3586f772a4 feat: Portals in 0.7 2024-06-28 15:20:02 -04:00
Greg Johnston
b2920ea32a allow either eager or lazy routes 2024-06-28 15:20:02 -04:00
Greg Johnston
0f5cb43d26 update StoredValue API in callbacks 2024-06-28 15:20:02 -04:00
Greg Johnston
4731bb0716 chore: clippy warnings 2024-06-28 15:20:02 -04:00
Greg Johnston
7d8e51f81a smooth out StoredValue APIs 2024-06-28 15:20:02 -04:00
Greg Johnston
38d7acc7d0 MaybeSignal and MaybeProp 2024-06-28 15:20:02 -04:00
Marc-Stefan Cassola
393cd6577b added a few old deprecated functions to help users port (#2580) 2024-06-28 15:20:02 -04:00
Greg Johnston
db1de18549 implement With(Untracked) for Signal 2024-06-28 15:20:02 -04:00
Greg Johnston
8225697a50 don't over-rerender nested router 2024-06-28 15:20:02 -04:00
Greg Johnston
6e77f3a25f initial async routing work (to support bundle splitting) 2024-06-28 15:20:02 -04:00
Greg Johnston
fcd5f682e2 noop attribute 'spreading' for routers 2024-06-28 15:20:02 -04:00
Greg Johnston
7427c3417f support arbitrary attributes on components in view 2024-06-28 15:20:01 -04:00
Greg Johnston
436bd3f743 full attribute spreading 2024-06-28 15:20:01 -04:00
Greg Johnston
c11055cb0d update counter_without_macros imports 2024-06-28 15:20:01 -04:00
Greg Johnston
816deeba86 stashing 2024-06-28 15:20:01 -04:00
Greg Johnston
8fa9c2a210 give a route to upgrade any attribute into a cloneable one 2024-06-28 15:20:01 -04:00
Greg Johnston
cfc5bb6c82 work on attribute spreading 2024-06-28 15:20:01 -04:00
Greg Johnston
9fb0b021c1 disable AddAnyAttr again now that I remember why it was broken 2024-06-28 15:20:01 -04:00
Greg Johnston
a9e9832b10 reenable AnyAttr 2024-06-28 15:20:01 -04:00
Greg Johnston
e2a47e979b reorganizing exports and updating examples 2024-06-28 15:20:01 -04:00
Greg Johnston
5fc03b8f54 prep for preview release 2024-06-28 15:20:01 -04:00
Greg Johnston
4b805e1c72 fix reorganized exports 2024-06-28 15:20:01 -04:00
Greg Johnston
4478444734 prep for preview release 2024-06-28 15:20:01 -04:00
Greg Johnston
243e88cd35 prep for preview release 2024-06-28 15:20:01 -04:00
Greg Johnston
e775a64bb9 prep for preview release 2024-06-28 15:20:01 -04:00
Greg Johnston
9c2c9c4eca module restructuring for 0.7 2024-06-28 15:20:01 -04:00
Greg Johnston
a347f25959 let ErrorBoundary own the fallback 2024-06-28 15:20:01 -04:00
Greg Johnston
2cb146f6af make Suspend a transparent wrapper 2024-06-28 15:20:01 -04:00
Greg Johnston
0a611c9448 provide params properly in FlatRouter 2024-06-28 15:20:01 -04:00
Greg Johnston
d76b7a3bf3 clear some warnings 2024-06-28 15:20:01 -04:00
Greg Johnston
1775060fb0 rename TupleBuilder to NextTuple and prep for release 2024-06-28 15:20:01 -04:00
Greg Johnston
f576babd6f prep for preview release 2024-06-28 15:20:01 -04:00
Greg Johnston
07eaa15902 prep for preview release 2024-06-28 15:20:01 -04:00
Greg Johnston
ce83ca2504 prep for preview release 2024-06-28 15:20:01 -04:00
Greg Johnston
5584f3fc37 prep for preview release 2024-06-28 15:20:01 -04:00
Greg Johnston
d470de5a4c rename any_error 2024-06-28 15:20:01 -04:00
Greg Johnston
74faa51022 prep for preview release 2024-06-28 15:20:01 -04:00
Greg Johnston
65a400e084 remove twiggy file 2024-06-28 15:20:01 -04:00
Greg Johnston
35dc9b5e25 move router crates 2024-06-28 15:20:01 -04:00
Greg Johnston
3c63069aaf comparison demo 2024-06-28 15:20:01 -04:00
Greg Johnston
1199d5afff SSR optimizations for binary size, and flat router 2024-06-28 15:20:01 -04:00
Greg Johnston
56f9df9188 work on Axum integration and on error boundaries 2024-06-28 15:20:01 -04:00
Greg Johnston
732cfca287 stash 2024-06-28 15:20:01 -04:00
Greg Johnston
23546f55dd feat: add <A> 2024-06-28 15:20:01 -04:00
Greg Johnston
18bd20b7fb add use_navigate and Redirect 2024-06-28 15:20:01 -04:00
Greg Johnston
df3b714b6a preliminary use_navigate work 2024-06-28 15:20:01 -04:00
Greg Johnston
406bc24f13 fix fallback => match update 2024-06-28 15:20:01 -04:00
Greg Johnston
136b08a1fb add more hooks and primitives to router 2024-06-28 15:20:01 -04:00
Greg Johnston
5e6274d338 nested route CSR working 2024-06-28 15:20:01 -04:00
Greg Johnston
db98405215 continuing on nested routes 2024-06-28 15:20:01 -04:00
Greg Johnston
262d0ccc3e continuing on nested routes 2024-06-28 15:20:01 -04:00
Greg Johnston
86f8e5e903 working on reconfiguring nested routing 2024-06-28 15:20:01 -04:00
Greg Johnston
8f47cb7a20 make placeholder-finding code consistent across container types 2024-06-28 15:20:01 -04:00
Greg Johnston
32b4defa36 remove logs 2024-06-28 15:20:01 -04:00
Greg Johnston
51117f52c3 fix Transition hydration 2024-06-28 15:20:01 -04:00
Greg Johnston
27445ce1f7 remove TryCatch/fallible rendering in favor of better ErrorBoundary model 2024-06-28 15:20:01 -04:00
Greg Johnston
a355ef0201 finish todo_app_sqlite_axum 2024-06-28 15:20:01 -04:00
Greg Johnston
caf51420f9 fix Vec hydration 2024-06-28 15:20:01 -04:00
Greg Johnston
077ea24f05 add MultiActionForm 2024-06-28 15:20:01 -04:00
Greg Johnston
1166d69d3a add MultiAction/ServerMultiAction 2024-06-28 15:20:01 -04:00
Greg Johnston
0541e8ce90 stash 2024-06-28 15:20:01 -04:00
Greg Johnston
98f891d45c ErrorBoundary SSR and serialization of errors to support hydration 2024-06-28 15:20:01 -04:00
Greg Johnston
075235f8dd get types working with nested ErrorBoundary/Suspense 2024-06-28 15:20:01 -04:00
Greg Johnston
69db634da1 probably as far as I can go with the current SuspenseBoundary approach 2024-06-28 15:20:01 -04:00
Greg Johnston
147ba5b7e6 fix static types 2024-06-28 15:20:01 -04:00
Greg Johnston
d3192c9839 fix cancellation logic for server fn requests 2024-06-28 15:20:01 -04:00
Greg Johnston
86de41c068 only subscribe to memo manually if already loaded 2024-06-28 15:20:01 -04:00
Greg Johnston
9d31d885c5 Suspense SSR 2024-06-28 15:20:01 -04:00
Greg Johnston
e07227f1c4 updates toward todo_app_sqlite 2024-06-28 15:20:00 -04:00
Greg Johnston
97321c58f0 fix stable examples 2024-06-28 15:20:00 -04:00
Greg Johnston
8e247f421e implement rendering traits for signals directly on stable 2024-06-28 15:20:00 -04:00
Greg Johnston
f45cb64cd9 suspend!() macro 2024-06-28 15:20:00 -04:00
Greg Johnston
5df6bdbabd add Transition 2024-06-28 15:20:00 -04:00
Greg Johnston
30fa0ac7d3 loosen requirements for Show 2024-06-28 15:20:00 -04:00
Greg Johnston
e09267bde6 working on examples 2024-06-28 15:20:00 -04:00
Greg Johnston
cb1f75551f finish TodoMVC example 2024-06-28 15:20:00 -04:00
Greg Johnston
f42ab13332 simplify Suspense: this should still work with hydration 2024-06-28 15:20:00 -04:00
Greg Johnston
b479fd4913 working model for Suspense with new version 2024-06-28 15:20:00 -04:00
Greg Johnston
3aacbd7bba probably as far as I can go with the current SuspenseBoundary approach 2024-06-28 15:20:00 -04:00
Greg Johnston
ffe82dff78 fix ErrorBoundary/Suspense 2024-06-28 15:20:00 -04:00
Greg Johnston
0edf5686fb feat: ErrorBoundary and Suspense 2024-06-28 15:20:00 -04:00
Greg Johnston
f7c5f62d20 feat: ErrorBoundary 2024-06-28 15:20:00 -04:00
Greg Johnston
1857c6b614 fix Cargo.toml merge issues 2024-06-28 15:20:00 -04:00
Greg Johnston
df856a7b52 working on examples 2024-06-28 15:20:00 -04:00
Greg Johnston
4f9510f145 styling with CSS 2024-06-28 15:20:00 -04:00
Greg Johnston
3b8ebd88a7 example with isomorphic GTK/web design system 2024-06-28 15:20:00 -04:00
Greg Johnston
89ff803672 gtk example 2024-06-28 15:20:00 -04:00
Greg Johnston
b1241408ac ErrorBoundary component 2024-06-28 15:20:00 -04:00
Greg Johnston
521d9aa27f Suspense/Transition components 2024-06-28 15:20:00 -04:00
Greg Johnston
98474eff66 GTK example for 0.7 2024-06-28 15:20:00 -04:00
Greg Johnston
e7e58c063b add serde-wasm-bindgen encoding for resources 2024-06-28 15:20:00 -04:00
Greg Johnston
720934741d add typed children 2024-06-28 15:20:00 -04:00
Greg Johnston
16e934bf88 scope Arena to each request 2024-06-28 15:20:00 -04:00
Greg Johnston
2de66a34cf correctly omit HTML-generating code from AnyView 2024-06-28 15:20:00 -04:00
Greg Johnston
ec44cdc123 experimental sledgehammer Renderer backend 2024-06-28 15:20:00 -04:00
Greg Johnston
40849d65d1 fix async context issues, add flat routing 2024-06-28 15:20:00 -04:00
Greg Johnston
6b34d0287f test more dynamic string length work 2024-06-28 15:20:00 -04:00
Greg Johnston
1c9b955e9b attribute value escaping 2024-06-28 15:20:00 -04:00
Greg Johnston
2595ddcfe6 work related to 0.7 blog port 2024-06-28 15:20:00 -04:00
Greg Johnston
5df260c419 work related to 0.7 blog port 2024-06-28 15:20:00 -04:00
Greg Johnston
a7997ea1d5 completing work on meta 2024-06-28 15:20:00 -04:00
Greg Johnston
c6b097c664 completing work on meta 2024-06-28 15:20:00 -04:00
Greg Johnston
dcdcaaf01c initial work on meta 2024-06-28 15:20:00 -04:00
Greg Johnston
d971a86ee0 initial work on meta 2024-06-28 15:20:00 -04:00
Greg Johnston
099ba87216 stash 2024-06-28 15:20:00 -04:00
Greg Johnston
746e39b295 fix nested route rebuilding 2024-06-28 15:20:00 -04:00
Greg Johnston
1e0a3006cf navigation between nested routes 2024-06-28 15:20:00 -04:00
Greg Johnston
0cae2ca4d1 initial stage for working nested route rendering 2024-06-28 15:20:00 -04:00
Greg Johnston
d2da5c6a60 stash 2024-06-28 15:20:00 -04:00
Greg Johnston
263be1d726 stash 2024-06-28 15:20:00 -04:00
Greg Johnston
f942d334c2 get basic routing working 2024-06-28 15:20:00 -04:00
Greg Johnston
cf56b6c623 stash 2024-06-28 15:20:00 -04:00
Greg Johnston
2fdf87d416 reorganize 2024-06-28 15:20:00 -04:00
Greg Johnston
c0cb79a396 working on nesting routing 2024-06-28 15:20:00 -04:00
Greg Johnston
096a1e48f2 reorganize 2024-06-28 15:20:00 -04:00
Greg Johnston
e1a2650277 stash 2024-06-28 15:20:00 -04:00
Greg Johnston
d4c1b07fec abstract interface to walk nested routes and to access views 2024-06-28 15:20:00 -04:00
Greg Johnston
8abbff3060 reorganize and clean up 2024-06-28 15:20:00 -04:00
Greg Johnston
fcd5162ee2 nested route matching 2024-06-28 15:20:00 -04:00
Greg Johnston
85def69017 stash 2024-06-28 15:20:00 -04:00
Greg Johnston
4152a5f852 stash 2024-06-28 15:20:00 -04:00
Greg Johnston
c0c3ec8348 work on routing utils 2024-06-28 15:20:00 -04:00
Greg Johnston
b710c629db nested route matching working 2024-06-28 15:20:00 -04:00
Greg Johnston
a73ac0236e stash 2024-06-28 15:20:00 -04:00
Greg Johnston
16eb8e6162 use either_of crate 2024-06-28 15:20:00 -04:00
Greg Johnston
cd9d899c62 nested routes take 1 2024-06-28 15:20:00 -04:00
Greg Johnston
991b94291a split EitherOfX into its own crate 2024-06-28 15:20:00 -04:00
Greg Johnston
8910b3d36c work on routing 2024-06-28 15:20:00 -04:00
Greg Johnston
bb37f3b64b work on routing 2024-06-28 15:20:00 -04:00
Greg Johnston
a710cc4468 set up routing 2024-06-28 15:20:00 -04:00
Greg Johnston
d8e7ade42a clear warning 2024-06-28 15:20:00 -04:00
Greg Johnston
c19817ea0d chore: clear warnings 2024-06-28 15:20:00 -04:00
Greg Johnston
b7c2e45a39 pass on: to components (and lay basis for passing all other attributes) 2024-06-28 15:20:00 -04:00
Greg Johnston
cf38ac3e6f working on AddAttr 2024-06-28 15:20:00 -04:00
Greg Johnston
29bbd926e8 remove boilerplate: require that Node, Element, etc. types always be Clone + 'static 2024-06-28 15:20:00 -04:00
Greg Johnston
f19424bcc0 parent_child example 2024-06-28 15:20:00 -04:00
Greg Johnston
4e73a1ec92 use AnyError for all try_ rendering errors, so that they can compose 2024-06-28 15:20:00 -04:00
Greg Johnston
da96fdaa57 support for guards with class: syntax 2024-06-28 15:20:00 -04:00
Greg Johnston
c317048196 add Borrow implementation to make it easier to abstract over T and Guard<T> 2024-06-28 15:20:00 -04:00
Greg Johnston
01a7a71059 finish error boundary (fix last state transition issue) 2024-06-28 15:20:00 -04:00
Greg Johnston
111d6bc389 progress on error boundary that works with nested reactivity 2024-06-28 15:20:00 -04:00
Greg Johnston
bafb1efcff progress on error boundary that works with nested reactivity 2024-06-28 15:20:00 -04:00
Greg Johnston
e81f73ae8b error example 2024-06-28 15:20:00 -04:00
Greg Johnston
028f0a5152 enable event delegation 2024-06-28 15:20:00 -04:00
Greg Johnston
db597d047d fix release build 2024-06-28 15:20:00 -04:00
Greg Johnston
d1074d44c6 update TODO.md 2024-06-28 15:20:00 -04:00
Greg Johnston
464ec943ee store effects in reactive system 2024-06-28 15:20:00 -04:00
Greg Johnston
0d7186ca0c todomvc example 2024-06-28 15:20:00 -04:00
Greg Johnston
4ef7b64f66 work on async demo 2024-06-28 15:20:00 -04:00
Greg Johnston
87bdc5b47a clone values for Futures 2024-06-28 15:20:00 -04:00
Greg Johnston
a341f99355 make guard types more nestable/flexible so that we can implement render traits on any of them 2024-06-28 15:20:00 -04:00
Greg Johnston
f2eec684a8 work on async demo 2024-06-28 15:20:00 -04:00
Greg Johnston
cbee62973a work on async demo 2024-06-28 15:20:00 -04:00
Greg Johnston
14682c1caf feat: create generic any_spawner crate to share between reactive system and renderer 2024-06-28 15:20:00 -04:00
Greg Johnston
1cbea396ea add other methods on Stored 2024-06-28 15:20:00 -04:00
Greg Johnston
9c57f6a218 work on async demo 2024-06-28 15:20:00 -04:00
Greg Johnston
e052230343 stash: working on jsfb 2024-06-28 15:20:00 -04:00
Greg Johnston
85e26e19ac chore: remove unnecessary log 2024-06-28 15:20:00 -04:00
Greg Johnston
31cb6aaa05 fix: correct owner for rows of For, correct cleanup of arenas 2024-06-28 15:20:00 -04:00
Greg Johnston
7965302b89 fix: close memory leak in tasks waiting on channels 2024-06-28 15:20:00 -04:00
Greg Johnston
62ac27dbeb feat: typed event targets 2024-06-28 15:20:00 -04:00
Greg Johnston
9ee801b57b working on examples 2024-06-28 15:20:00 -04:00
Greg Johnston
6e3b6d5d61 begin migrating to leptos and leptos_dom packages 2024-06-28 15:20:00 -04:00
Greg Johnston
01f2977b9d stash 2024-06-28 15:20:00 -04:00
Greg Johnston
c557b1236a stash 2024-06-28 15:19:59 -04:00
Greg Johnston
0ae77d0b5a chore: remove leptos_reactive and add reactive_graph 2024-06-28 15:19:59 -04:00
Greg Johnston
18f73b1482 feat: improved ergonomics of read guards 2024-06-28 15:19:59 -04:00
Greg Johnston
6f96a67d08 feat: add Readable implementation for all types 2024-06-28 15:19:59 -04:00
Greg Johnston
222b7a17c9 feat: add no_std support in appropriate crates 2024-06-28 15:19:59 -04:00
Greg Johnston
45e7847452 feat: tachys 2024-06-28 15:19:59 -04:00
Greg Johnston
087a1c332b feat: add Fn traits 2024-06-28 15:19:59 -04:00
Greg Johnston
37dae6ae90 docs: note re: execution order (see #2261 and #2262) 2024-06-28 15:19:59 -04:00
Greg Johnston
5a8821c336 feat: modular SharedContext for hydration 2024-06-28 15:19:59 -04:00
Greg Johnston
132dcced68 chore: split OrPoisoned trait into its own crate for reuse 2024-06-28 15:19:59 -04:00
Greg Johnston
79cb763d82 feat: modular, trait-based, Send/Sync reactive system 2024-06-28 15:19:59 -04:00
375 changed files with 16975 additions and 19102 deletions

View File

@@ -1,13 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "cargo"
directories:
- "/"
- "/examples/*"
- "/benchmarks"
schedule:
interval: "daily"

View File

@@ -14,8 +14,8 @@ jobs:
test:
needs: [get-leptos-changed]
if: github.event.pull_request.labels[0].name == 'semver' # needs.get-leptos-changed.outputs.leptos_changed == 'true' && github.event.pull_request.labels[0].name != 'breaking'
name: Run semver check (nightly-2024-08-01)
if: needs.get-leptos-changed.outputs.leptos_changed == 'true' && github.event.pull_request.labels[0].name != 'breaking'
name: Run semver check (nightly-2024-04-14)
runs-on: ubuntu-latest
steps:
@@ -25,4 +25,4 @@ jobs:
- name: Semver Checks
uses: obi1kenobi/cargo-semver-checks-action@v2
with:
rust-toolchain: nightly-2024-08-01
rust-toolchain: nightly-2024-04-14

View File

@@ -49,4 +49,4 @@ jobs:
with:
directory: ${{ matrix.directory }}
cargo_make_task: "ci"
toolchain: nightly-2024-08-01
toolchain: nightly-2024-04-14

View File

@@ -26,7 +26,7 @@ jobs:
- name: Get example project directories that changed
id: changed-dirs
uses: tj-actions/changed-files@v44
uses: tj-actions/changed-files@v41
with:
dir_names: true
dir_names_max_depth: "2"

View File

@@ -21,7 +21,7 @@ jobs:
- name: Get example files that changed
id: changed-files
uses: tj-actions/changed-files@v44
uses: tj-actions/changed-files@v43
with:
files: |
examples/**

View File

@@ -21,7 +21,7 @@ jobs:
- name: Get source files that changed
id: changed-source
uses: tj-actions/changed-files@v44
uses: tj-actions/changed-files@v43
with:
files: |
any_error/**

View File

@@ -64,7 +64,7 @@ jobs:
with:
node-version: 20
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v3
name: Install pnpm
id: pnpm-install
with:

View File

@@ -19,8 +19,6 @@ members = [
"leptos_macro",
"leptos_server",
"reactive_graph",
"reactive_stores",
"reactive_stores_macro",
"server_fn",
"server_fn_macro",
"server_fn/server_fn_macro_default",
@@ -37,39 +35,35 @@ members = [
"router_macro",
"any_error",
]
exclude = ["benchmarks", "examples", "projects"]
exclude = ["benchmarks", "examples"]
[workspace.package]
version = "0.7.0-beta2"
edition = "2021"
rust-version = "1.76"
version = "0.7.0-alpha"
rust-version = "1.75"
[workspace.dependencies]
throw_error = { path = "./any_error/", version = "0.2.0-beta2" }
any_spawner = { path = "./any_spawner/", version = "0.1.0" }
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1.0" }
either_of = { path = "./either_of/", version = "0.1.0" }
hydration_context = { path = "./hydration_context", version = "0.2.0-beta2" }
leptos = { path = "./leptos", version = "0.7.0-beta2" }
leptos_config = { path = "./leptos_config", version = "0.7.0-beta2" }
leptos_dom = { path = "./leptos_dom", version = "0.7.0-beta2" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-beta2" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-beta2" }
leptos_macro = { path = "./leptos_macro", version = "0.7.0-beta2" }
leptos_router = { path = "./router", version = "0.7.0-beta2" }
leptos_router_macro = { path = "./router_macro", version = "0.7.0-beta2" }
leptos_server = { path = "./leptos_server", version = "0.7.0-beta2" }
leptos_meta = { path = "./meta", version = "0.7.0-beta2" }
next_tuple = { path = "./next_tuple", version = "0.1.0-beta2" }
oco_ref = { path = "./oco", version = "0.2.0" }
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
reactive_graph = { path = "./reactive_graph", version = "0.1.0-beta2" }
reactive_stores = { path = "./reactive_stores", version = "0.1.0-beta2" }
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0-beta2" }
server_fn = { path = "./server_fn", version = "0.7.0-beta2" }
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-beta2" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-beta2" }
tachys = { path = "./tachys", version = "0.1.0-beta2" }
throw_error = { path = "./any_error/", version = "0.1" }
any_spawner = { path = "./any_spawner/", version = "0.1" }
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
either_of = { path = "./either_of/", version = "0.1" }
hydration_context = { path = "./hydration_context", version = "0.2.0-alpha" }
leptos = { path = "./leptos", version = "0.7.0-alpha" }
leptos_config = { path = "./leptos_config", version = "0.7.0-alpha" }
leptos_dom = { path = "./leptos_dom", version = "0.7.0-alpha" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.0-alpha" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.0-alpha" }
leptos_macro = { path = "./leptos_macro", version = "0.7.0-alpha" }
leptos_router = { path = "./router", version = "0.7.0-alpha" }
leptos_server = { path = "./leptos_server", version = "0.7.0-alpha" }
leptos_meta = { path = "./meta", version = "0.7.0-alpha" }
next_tuple = { path = "./next_tuple", version = "0.1.0-alpha" }
oco_ref = { path = "./oco", version = "0.2" }
or_poisoned = { path = "./or_poisoned", version = "0.1" }
reactive_graph = { path = "./reactive_graph", version = "0.1.0-alpha" }
server_fn = { path = "./server_fn", version = "0.7.0-alpha" }
server_fn_macro = { path = "./server_fn_macro", version = "0.7.0-alpha" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.0-alpha" }
tachys = { path = "./tachys", version = "0.1.0-alpha" }
[profile.release]
codegen-units = 1

View File

@@ -12,8 +12,6 @@
You can find a list of useful libraries and example projects at [`awesome-leptos`](https://github.com/leptos-rs/awesome-leptos).
# The `main` branch is currently undergoing major changes in preparation for the [0.7](https://github.com/leptos-rs/leptos/milestone/4) release. For a stable version, please use the [v0.6.13 tag](https://github.com/leptos-rs/leptos/tree/v0.6.13)
# Leptos
```rust

View File

@@ -1,13 +1,13 @@
[package]
name = "throw_error"
version = "0.2.0-beta2"
edition = "2021"
version = "0.1.0"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"
repository = "https://github.com/leptos-rs/leptos"
description = "Utilities for wrapping, throwing, and catching errors."
rust-version.workspace = true
edition.workspace = true
[dependencies]
pin-project-lite = "0.2.14"
pin-project-lite = "0.2"

View File

@@ -9,7 +9,7 @@ use std::{
error,
fmt::{self, Display},
future::Future,
mem, ops,
ops,
pin::Pin,
sync::Arc,
task::{Context, Poll},
@@ -92,25 +92,9 @@ thread_local! {
static ERROR_HOOK: RefCell<Option<Arc<dyn ErrorHook>>> = RefCell::new(None);
}
/// Resets the error hook to its previous state when dropped.
pub struct ResetErrorHookOnDrop(Option<Arc<dyn ErrorHook>>);
impl Drop for ResetErrorHookOnDrop {
fn drop(&mut self) {
ERROR_HOOK.with_borrow_mut(|this| *this = self.0.take())
}
}
/// Returns the current error hook.
pub fn get_error_hook() -> Option<Arc<dyn ErrorHook>> {
ERROR_HOOK.with_borrow(Clone::clone)
}
/// Sets the current thread-local error hook, which will be invoked when [`throw`] is called.
pub fn set_error_hook(hook: Arc<dyn ErrorHook>) -> ResetErrorHookOnDrop {
ResetErrorHookOnDrop(
ERROR_HOOK.with_borrow_mut(|this| mem::replace(this, Some(hook))),
)
pub fn set_error_hook(hook: Arc<dyn ErrorHook>) {
ERROR_HOOK.with_borrow_mut(|this| *this = Some(hook))
}
/// Invokes the error hook set by [`set_error_hook`] with the given error.
@@ -156,10 +140,9 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
let _hook = this
.hook
.as_ref()
.map(|hook| set_error_hook(Arc::clone(hook)));
if let Some(hook) = &this.hook {
set_error_hook(Arc::clone(hook))
}
this.inner.poll(cx)
}
}

View File

@@ -1,22 +1,22 @@
[package]
name = "any_spawner"
version = "0.1.1"
edition = "2021"
version = "0.1.0"
authors = ["Greg Johnston"]
license = "MIT"
readme = "../README.md"
repository = "https://github.com/leptos-rs/leptos"
description = "Spawn asynchronous tasks in an executor-independent way."
edition.workspace = true
[dependencies]
futures = "0.3.30"
glib = { version = "0.20.0", optional = true }
thiserror = "1.0"
tokio = { version = "1.39", optional = true, default-features = false, features = [
"rt",
futures = "0.3"
glib = { version = "0.19", optional = true }
thiserror = "1"
tokio = { version = "1", optional = true, default-features = false, features = [
"rt",
] }
tracing = { version = "0.1.40", optional = true }
wasm-bindgen-futures = { version = "0.4.42", optional = true }
tracing = { version = "0.1", optional = true }
wasm-bindgen-futures = { version = "0.4", optional = true }
[features]
tracing = ["dep:tracing"]

View File

@@ -27,7 +27,6 @@
#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
use std::{future::Future, pin::Pin, sync::OnceLock};
use thiserror::Error;

View File

@@ -6,31 +6,31 @@ rust-version.workspace = true
[dependencies]
l0410 = { package = "leptos", version = "0.4.10", features = [
"nightly",
"ssr",
"nightly",
"ssr",
] }
leptos = { path = "../leptos", features = ["ssr", "nightly"] }
leptos_reactive = { path = "../leptos_reactive", features = ["ssr", "nightly"] }
tachydom = { git = "https://github.com/gbj/tachys", features = [
"nightly",
"leptos",
"nightly",
"leptos",
] }
tachy_maccy = { git = "https://github.com/gbj/tachys", features = ["nightly"] }
sycamore = { version = "0.8.0", features = ["ssr"] }
yew = { version = "0.20.0", features = ["ssr"] }
tokio-test = "0.4.0"
miniserde = "0.1.0"
gloo = "0.8.0"
uuid = { version = "1.0", features = ["serde", "v4", "wasm-bindgen"] }
wasm-bindgen = "0.2.0"
lazy_static = "1.0"
log = "0.4.0"
strum = "0.24.0"
strum_macros = "0.24.0"
serde = { version = "1.0", features = ["derive", "rc"] }
serde_json = "1.0"
tera = "1.0"
sycamore = { version = "0.8", features = ["ssr"] }
yew = { version = "0.20", features = ["ssr"] }
tokio-test = "0.4"
miniserde = "0.1"
gloo = "0.8"
uuid = { version = "1", features = ["serde", "v4", "wasm-bindgen"] }
wasm-bindgen = "0.2"
lazy_static = "1"
log = "0.4"
strum = "0.24"
strum_macros = "0.24"
serde = { version = "1", features = ["derive", "rc"] }
serde_json = "1"
tera = "1"
[dependencies.web-sys]
version = "0.3.0"
version = "0.3"
features = ["Window", "Document", "HtmlElement", "HtmlInputElement"]

View File

@@ -1,5 +1,6 @@
[package]
name = "const_str_slice_concat"
edition = "2021"
version = "0.1.0"
authors = ["Greg Johnston"]
license = "MIT"
@@ -7,6 +8,5 @@ readme = "../README.md"
repository = "https://github.com/leptos-rs/leptos"
description = "Utilities for const concatenation of string slices."
rust-version.workspace = true
edition.workspace = true
[dependencies]

View File

@@ -1,5 +1,6 @@
[package]
name = "either_of"
edition = "2021"
version = "0.1.0"
authors = ["Greg Johnston"]
license = "MIT"
@@ -7,7 +8,6 @@ readme = "../README.md"
repository = "https://github.com/leptos-rs/leptos"
description = "Utilities for working with enumerated types that contain one of 2..n other types."
rust-version.workspace = true
edition.workspace = true
[dependencies]
pin-project-lite = "0.2.14"
pin-project-lite = "0.2"

View File

@@ -7,18 +7,19 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
actix-files = { version = "0.6.6", optional = true }
actix-web = { version = "4.8", optional = true, features = ["macros"] }
console_error_panic_hook = "0.1.7"
cfg-if = "1.0"
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["macros"] }
console_error_panic_hook = "0.1"
cfg-if = "1"
leptos = { path = "../../leptos" }
leptos_meta = { path = "../../meta" }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_router = { path = "../../router" }
wasm-bindgen = "0.2.93"
serde = { version = "1.0", features = ["derive"] }
wasm-bindgen = "0.2"
serde = { version = "1", features = ["derive"] }
[features]
csr = ["leptos/csr"]
hydrate = ["leptos/hydrate"]
ssr = [
"dep:actix-files",

View File

@@ -1,9 +1,68 @@
# Action Form Error Handling Example
<picture>
<source srcset="https://raw.githubusercontent.com/leptos-rs/leptos/main/docs/logos/Leptos_logo_Solid_White.svg" media="(prefers-color-scheme: dark)">
<img src="https://raw.githubusercontent.com/leptos-rs/leptos/main/docs/logos/Leptos_logo_RGB.svg" alt="Leptos Logo">
</picture>
## Getting Started
# Leptos Starter Template
See the [Examples README](../README.md) for setup and run instructions.
This is a template for use with the [Leptos](https://github.com/leptos-rs/leptos) web framework and the [cargo-leptos](https://github.com/akesson/cargo-leptos) tool.
## Quick Start
## Creating your template repo
Execute `cargo leptos watch` to run this example.
If you don't have `cargo-leptos` installed you can install it with
`cargo install cargo-leptos`
Then run
`cargo leptos new --git leptos-rs/start`
to generate a new project template (you will be prompted to enter a project name).
`cd {projectname}`
to go to your newly created project.
Of course, you should explore around the project structure, but the best place to start with your application code is in `src/app.rs`.
## Running your project
`cargo leptos watch`
By default, you can access your local project at `http://localhost:3000`
## Installing Additional Tools
By default, `cargo-leptos` uses `nightly` Rust, `cargo-generate`, and `sass`. If you run into any trouble, you may need to install one or more of these tools.
1. `rustup toolchain install nightly --allow-downgrade` - make sure you have Rust nightly
2. `rustup target add wasm32-unknown-unknown` - add the ability to compile Rust to WebAssembly
3. `cargo install cargo-generate` - install `cargo-generate` binary (should be installed automatically in future)
4. `npm install -g sass` - install `dart-sass` (should be optional in future)
## Executing a Server on a Remote Machine Without the Toolchain
After running a `cargo leptos build --release` the minimum files needed are:
1. The server binary located in `target/server/release`
2. The `site` directory and all files within located in `target/site`
Copy these files to your remote server. The directory structure should be:
```text
leptos_start
site/
```
Set the following environment variables (updating for your project as needed):
```sh
export LEPTOS_OUTPUT_NAME="leptos_start"
export LEPTOS_SITE_ROOT="site"
export LEPTOS_SITE_PKG_DIR="pkg"
export LEPTOS_SITE_ADDR="127.0.0.1:3000"
export LEPTOS_RELOAD_PORT="3001"
```
Finally, run the server binary.
## Notes about CSR and Trunk:
Although it is not recommended, you can also run your project without server integration using the feature `csr` and `trunk serve`:
`trunk serve --open --features csr`
This may be useful for integrating external tools which require a static site, e.g. `tauri`.

View File

@@ -52,10 +52,23 @@ async fn main() -> std::io::Result<()> {
.await
}
#[cfg(not(feature = "ssr"))]
#[cfg(not(any(feature = "ssr", feature = "csr")))]
pub fn main() {
// no client-side main function
// unless we want this to work with e.g., Trunk for pure client-side testing
// see lib.rs for hydration function instead
// see optional feature `csr` instead
}
#[cfg(all(not(feature = "ssr"), feature = "csr"))]
pub fn main() {
// a client-side main function is required for using `trunk serve`
// prefer using `cargo leptos serve` instead
// to run: `trunk serve --open --features csr`
use action_form_error_handling::app::*;
use leptos::prelude::*;
console_error_panic_hook::set_once();
mount_to_body(App);
}

View File

@@ -10,12 +10,11 @@ lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
console_log = "1.0"
log = "0.4.22"
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"
gloo-timers = { version = "0.3.0", features = ["futures"] }
[dev-dependencies]
wasm-bindgen = "0.2.93"
wasm-bindgen-test = "0.3.42"
web-sys = "0.3.70"
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.0"
web-sys = "0.3"

View File

@@ -11,34 +11,34 @@ codegen-units = 1
lto = true
[dependencies]
actix-files = { version = "0.6.6", optional = true }
actix-web = { version = "4.8", optional = true, features = ["macros"] }
broadcaster = "1.0"
console_log = "1.0"
console_error_panic_hook = "0.1.7"
futures = "0.3.30"
lazy_static = "1.5"
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["macros"] }
broadcaster = "1"
console_log = "1"
console_error_panic_hook = "0.1"
futures = "0.3"
lazy_static = "1"
leptos = { path = "../../leptos" }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_router = { path = "../../router" }
log = "0.4.22"
once_cell = "1.19"
gloo-net = { version = "0.6.0" }
wasm-bindgen = "0.2.93"
serde = { version = "1.0", features = ["derive"] }
simple_logger = "5.0"
tracing = { version = "0.1.40", optional = true }
log = "0.4"
once_cell = "1.18"
gloo-net = { git = "https://github.com/rustwasm/gloo" }
wasm-bindgen = "0.2"
serde = { version = "1", features = ["derive"] }
simple_logger = "4.3"
tracing = { version = "0.1", optional = true }
send_wrapper = "0.6.0"
[features]
hydrate = ["leptos/hydrate"]
ssr = [
"dep:actix-files",
"dep:actix-web",
"dep:tracing",
"leptos/ssr",
"leptos_actix",
"leptos_router/ssr",
"dep:actix-files",
"dep:actix-web",
"dep:tracing",
"leptos/ssr",
"leptos_actix",
"leptos_router/ssr",
]
[package.metadata.cargo-all-features]

View File

@@ -76,9 +76,18 @@ pub fn Counters() -> impl IntoView {
</nav>
<main>
<FlatRoutes fallback=|| "Not found.">
<Route path=StaticSegment("") view=Counter/>
<Route path=StaticSegment("form") view=FormCounter/>
<Route path=StaticSegment("multi") view=MultiuserCounter/>
<Route
path=StaticSegment("")
view=Counter
/>
<Route
path=StaticSegment("form")
view=FormCounter
/>
<Route
path=StaticSegment("multi")
view=MultiuserCounter
/>
</FlatRoutes>
</main>
</Router>
@@ -113,10 +122,13 @@ pub fn Counter() -> impl IntoView {
</p>
<ErrorBoundary fallback=|errors| move || format!("Error: {:#?}", errors.get())>
<div>
<button on:click=move |_| { clear.dispatch(()); }>"Clear"</button>
<button on:click=move |_| { dec.dispatch(()); }>"-1"</button>
<span>"Value: " <Suspense>{counter} "!"</Suspense></span>
<button on:click=move |_| { inc.dispatch(()); }>"+1"</button>
<button on:click=move |_| clear.dispatch(())>"Clear"</button>
<button on:click=move |_| dec.dispatch(())>"-1"</button>
<span>
"Value: "
<Suspense>{counter} "!" </Suspense>
</span>
<button on:click=move |_| inc.dispatch(())>"+1"</button>
</div>
</ErrorBoundary>
</div>
@@ -224,12 +236,12 @@ pub fn MultiuserCounter() -> impl IntoView {
"This one uses server-sent events (SSE) to live-update when other users make changes."
</p>
<div>
<button on:click=move |_| { clear.dispatch(()); }>"Clear"</button>
<button on:click=move |_| { dec.dispatch(()); }>"-1"</button>
<button on:click=move |_| clear.dispatch(())>"Clear"</button>
<button on:click=move |_| dec.dispatch(())>"-1"</button>
<span>
"Multiplayer Value: " {move || multiplayer_value.get().unwrap_or_default()}
</span>
<button on:click=move |_| { inc.dispatch(()); }>"+1"</button>
<button on:click=move |_| inc.dispatch(())>"+1"</button>
</div>
</div>
}

View File

@@ -13,6 +13,6 @@ leptos_router = { path = "../../router", features = [] }
console_error_panic_hook = "0.1.7"
[dev-dependencies]
wasm-bindgen = "0.2.93"
wasm-bindgen-test = "0.3.42"
web-sys = "0.3.70"
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.0"
web-sys = "0.3"

View File

@@ -14,10 +14,10 @@ console_error_panic_hook = "0.1.7"
[dev-dependencies]
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.42"
pretty_assertions = "1.4"
rstest = "0.22.0"
wasm-bindgen-test = "0.3.34"
pretty_assertions = "1.3.0"
rstest = "0.17.0"
[dev-dependencies.web-sys]
features = ["HtmlElement", "XPathResult"]
version = "0.3.70"
version = "0.3.61"

View File

@@ -4,10 +4,10 @@ version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
leptos = { path = "../../leptos", features = ["csr"] }
console_error_panic_hook = "0.1.7"
[dev-dependencies]
wasm-bindgen-test = "0.3.42"
wasm-bindgen = "0.2.93"
web-sys = "0.3.70"
wasm-bindgen-test = "0.3.0"
wasm-bindgen = "0.2"
web-sys = "0.3"

View File

@@ -23,7 +23,7 @@ async fn inc() {
div.inner_html(),
"<button>Add Counter</button><button>Add 1000 \
Counters</button><button>Clear Counters</button><p>Total: \
<span>0</span> from <span>0</span> counters.</p><ul><!----></ul>"
<span>0</span> from <span>0</span> counters.</p><ul></ul>"
);
// add 3 counters
@@ -45,7 +45,7 @@ async fn inc() {
type=\"text\"><span>0</span><button>+1</button><button>x</button></\
li><li><button>-1</button><input \
type=\"text\"><span>0</span><button>+1</button><button>x</button></\
li><!----></ul>"
li></ul>"
);
let counters = div
@@ -87,7 +87,7 @@ async fn inc() {
type=\"text\"><span>2</span><button>+1</button><button>x</button></\
li><li><button>-1</button><input \
type=\"text\"><span>3</span><button>+1</button><button>x</button></\
li><!----></ul>"
li></ul>"
);
// remove the first counter
@@ -110,6 +110,6 @@ async fn inc() {
type=\"text\"><span>2</span><button>+1</button><button>x</button></\
li><li><button>-1</button><input \
type=\"text\"><span>3</span><button>+1</button><button>x</button></\
li><!----></ul>"
li></ul>"
);
}

View File

@@ -5,12 +5,12 @@ edition = "2021"
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
log = "0.4.22"
console_log = "1.0"
log = "0.4"
console_log = "1"
console_error_panic_hook = "0.1.7"
web-sys = { version = "0.3.70", features = ["Clipboard", "Navigator"] }
web-sys = { version = "0.3", features = ["Clipboard", "Navigator"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.42"
wasm-bindgen = "0.2.93"
web-sys = { version = "0.3.70", features = ["NodeList"] }
wasm-bindgen-test = "0.3.0"
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["NodeList"] }

View File

@@ -24,7 +24,11 @@ pub fn copy_to_clipboard(el: Element, content: &str) {
evt.prevent_default();
evt.stop_propagation();
let _ = window().navigator().clipboard().write_text(&content);
let _ = window()
.navigator()
.clipboard()
.expect("navigator.clipboard to be available")
.write_text(&content);
el.set_inner_html(&format!("Copied \"{}\"", &content));
});
@@ -49,6 +53,7 @@ impl From<()> for Amount {
}
}
// .into() will automatically be called on the parameter
pub fn add_dot(el: Element, amount: Amount) {
use leptos::wasm_bindgen::JsCast;
let el = el.unchecked_into::<web_sys::HtmlElement>();
@@ -77,17 +82,12 @@ pub fn App() -> impl IntoView {
let data = "Hello World!";
view! {
<a href="#" use:copy_to_clipboard=data>
"Copy \""
{data}
"\" to clipboard"
</a>
<a href="#" use:copy_to_clipboard=data>"Copy \"" {data} "\" to clipboard"</a>
// automatically applies the directive to every root element in `SomeComponent`
<SomeComponent use:highlight/>
<SomeComponent use:highlight />
// no value will default to `().into()`
<button use:add_dot>"Add a dot"</button>
// can manually call `.into()` to convert to the correct type
// (automatically calling `.into()` prevents using generics in directive functions)
<button use:add_dot=5.into()>"Add 5 dots"</button>
// `5.into()` automatically called
<button use:add_dot=5>"Add 5 dots"</button>
}
}

View File

@@ -9,6 +9,6 @@ lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
console_log = "1.0"
log = "0.4.22"
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"

View File

@@ -7,19 +7,19 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = "0.1.7"
console_error_panic_hook = "0.1"
leptos = { path = "../../leptos" }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_meta = { path = "../../meta" }
leptos_router = { path = "../../router" }
serde = { version = "1.0", features = ["derive"] }
axum = { version = "0.7.5", optional = true }
tower = { version = "0.4.13", optional = true }
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
tokio = { version = "1.39", features = ["full"], optional = true }
http = { version = "1.1" }
serde = { version = "1", features = ["derive"] }
axum = { version = "0.7", optional = true }
tower = { version = "0.4", optional = true }
tower-http = { version = "0.5", features = ["fs"], optional = true }
tokio = { version = "1", features = ["full"], optional = true }
http = { version = "1.0" }
thiserror = "1.0"
wasm-bindgen = "0.2.93"
wasm-bindgen = "0.2"
[features]
hydrate = ["leptos/hydrate"]

View File

@@ -9,16 +9,16 @@ lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr", "tracing"] }
reqwasm = "0.5.0"
gloo-timers = { version = "0.3.0", features = ["futures"] }
serde = { version = "1.0", features = ["derive"] }
log = "0.4.22"
console_log = "1.0"
console_error_panic_hook = "0.1.7"
thiserror = "1.0"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
tracing-subscriber-wasm = "0.1.0"
reqwasm = "0.5"
gloo-timers = { version = "0.3", features = ["futures"] }
serde = { version = "1", features = ["derive"] }
log = "0.4"
console_log = "1"
console_error_panic_hook = "0.1"
thiserror = "1"
tracing = "0.1"
tracing-subscriber = "0.3"
tracing-subscriber-wasm = "0.1"
[dev-dependencies]
wasm-bindgen-test = "0.3.42"
wasm-bindgen-test = "0.3"

View File

@@ -85,7 +85,7 @@ pub fn fetch_example() -> impl IntoView {
<Transition fallback=|| view! { <div>"Loading..."</div> } {..spreadable}>
<ErrorBoundary fallback>
<ul>
{move || Suspend::new(async move {
{move || Suspend(async move {
cats.await
.map(|cats| {
cats.iter()

View File

@@ -4,15 +4,15 @@ version = "0.1.0"
edition = "2021"
[dependencies]
leptos = { path = "../../leptos" }
leptos = { path = "../../leptos", features = ["csr"] }
throw_error = { path = "../../any_error/" }
# these are used to build the integration
gtk = { version = "0.9.0", package = "gtk4" }
any_spawner = { path = "../../any_spawner/" }
next_tuple = { path = "../../next_tuple/" }
paste = "1.0"
gtk = { version = "0.8.0", package = "gtk4", optional = true }
paste = "1.0.14"
# we want to support using glib for the reactive runtime event loop
any_spawner = { path = "../../any_spawner/", features = ["glib"] }
# yes, we want effects to run: this is a "frontend," not a backend
reactive_graph = { path = "../../reactive_graph", features = ["effects"] }
console_error_panic_hook = { version = "0.1", optional = true }
[features]
gtk = ["dep:gtk", "any_spawner/glib"]
wasm = ["any_spawner/wasm-bindgen", "dep:console_error_panic_hook"]

View File

@@ -56,12 +56,11 @@ impl Mountable<LeptosGtk> for Element {
.insert_before(&parent.0, marker.as_ref().map(|m| &m.0));
}
fn insert_before_this(&self, child: &mut dyn Mountable<LeptosGtk>) -> bool {
if let Some(parent) = self.0.parent() {
child.mount(&Element(parent), Some(self));
return true;
}
false
fn insert_before_this(&self,
child: &mut dyn Mountable<LeptosGtk>,
) -> bool {
child.mount(parent, Some(self.as_ref()));
true
}
}
@@ -80,8 +79,11 @@ impl Mountable<LeptosGtk> for Text {
.insert_before(&parent.0, marker.as_ref().map(|m| &m.0));
}
fn insert_before_this(&self, child: &mut dyn Mountable<LeptosGtk>) -> bool {
self.0.insert_before_this(child)
fn insert_before_this(&self,
child: &mut dyn Mountable<LeptosGtk>,
) -> bool {
child.mount(parent, Some(self.as_ref()));
true
}
}
@@ -330,12 +332,16 @@ where
parent: &<LeptosGtk as Renderer>::Element,
marker: Option<&<LeptosGtk as Renderer>::Node>,
) {
println!("mounting {}", std::any::type_name::<Widg>());
self.children.mount(&self.widget, None);
LeptosGtk::insert_node(parent, &self.widget, marker);
}
fn insert_before_this(&self, child: &mut dyn Mountable<LeptosGtk>) -> bool {
self.widget.insert_before_this(child)
fn insert_before_this(&self,
child: &mut dyn Mountable<LeptosGtk>,
) -> bool {
child.mount(parent, Some(self.widget.as_ref()));
true
}
}

View File

@@ -1,8 +1,20 @@
use any_spawner::Executor;
use gtk::{prelude::*, Application, ApplicationWindow, Orientation};
use leptos::prelude::*;
use leptos_gtk::LeptosGtk;
#[cfg(feature = "gtk")]
use gtk::{
glib::Value, prelude::*, Application, ApplicationWindow, Orientation,
Widget,
};
#[cfg(feature = "wasm")]
use leptos::tachys::{dom::body, html::element, html::event as ev};
use leptos::{
logging,
prelude::*,
reactive_graph::{effect::Effect, owner::Owner, signal::RwSignal},
Executor, For, ForProps,
};
#[cfg(feature = "gtk")]
use leptos_gtk::{Element, LGtkWidget, LeptosGtk};
use std::{mem, thread, time::Duration};
#[cfg(feature = "gtk")]
mod leptos_gtk;
const APP_ID: &str = "dev.leptos.Counter";
@@ -10,39 +22,59 @@ const APP_ID: &str = "dev.leptos.Counter";
// Basic GTK app setup from https://gtk-rs.org/gtk4-rs/stable/latest/book/hello_world.html
fn main() {
// use the glib event loop to power the reactive system
_ = Executor::init_glib();
let app = Application::builder().application_id(APP_ID).build();
#[cfg(feature = "gtk")]
{
_ = Executor::init_glib();
let app = Application::builder().application_id(APP_ID).build();
app.connect_startup(|_| load_css());
app.connect_startup(|_| load_css());
app.connect_activate(|app| {
// Connect to "activate" signal of `app`
app.connect_activate(|app| {
// Connect to "activate" signal of `app`
let owner = Owner::new();
let view = owner.with(ui);
let (root, state) = leptos_gtk::root(view);
let window = ApplicationWindow::builder()
.application(app)
.title("TachyGTK")
.child(&root)
.build();
// Present window
window.present();
mem::forget((owner, state));
});
app.run();
}
#[cfg(all(feature = "wasm", not(feature = "gtk")))]
{
console_error_panic_hook::set_once();
_ = Executor::init_wasm_bindgen();
let owner = Owner::new();
let view = owner.with(ui);
let (root, state) = leptos_gtk::root(view);
let window = ApplicationWindow::builder()
.application(app)
.title("TachyGTK")
.child(&root)
.build();
// Present window
window.present();
let mut state = view.build();
state.mount(&body().into(), None);
mem::forget((owner, state));
});
app.run();
}
}
fn ui() -> impl Render<LeptosGtk> {
#[cfg(feature = "gtk")]
type Rndr = LeptosGtk;
#[cfg(all(feature = "wasm", not(feature = "gtk")))]
type Rndr = Dom;
fn ui() -> impl Render<Rndr> {
let value = RwSignal::new(0);
let rows = RwSignal::new(vec![1, 2, 3, 4, 5]);
Effect::new(move |_| {
println!("value = {}", value.get());
logging::log!("value = {}", value.get());
});
// just an example of multithreaded reactivity
#[cfg(feature = "gtk")]
thread::spawn(move || loop {
thread::sleep(Duration::from_millis(250));
value.update(|n| *n += 1);
@@ -50,10 +82,7 @@ fn ui() -> impl Render<LeptosGtk> {
vstack((
hstack((
button("-1", move || {
println!("clicked -1");
value.update(|n| *n -= 1);
}),
button("-1", move || value.update(|n| *n -= 1)),
move || value.get().to_string(),
button("+1", move || value.update(|n| *n += 1)),
)),
@@ -62,36 +91,75 @@ fn ui() -> impl Render<LeptosGtk> {
items.swap(1, 3);
})
}),
hstack(rows),
hstack(For(ForProps::builder()
.each(move || rows.get())
.key(|k| *k)
.children(|v| v)
.build())),
))
}
fn button(
label: impl Render<LeptosGtk>,
label: impl Render<Rndr>,
callback: impl Fn() + Send + Sync + 'static,
) -> impl Render<LeptosGtk> {
leptos_gtk::button()
.child(label)
.connect("clicked", move |_| {
callback();
None
})
) -> impl Render<Rndr> {
#[cfg(feature = "gtk")]
{
leptos_gtk::button()
.child(label)
.connect("clicked", move |_| {
callback();
None
})
}
#[cfg(all(feature = "wasm", not(feature = "gtk")))]
{
element::button()
.on(ev::click, move |_| callback())
.child(label)
}
}
fn vstack(children: impl Render<LeptosGtk>) -> impl Render<LeptosGtk> {
leptos_gtk::r#box()
.orientation(Orientation::Vertical)
.spacing(12)
.child(children)
fn vstack(children: impl Render<Rndr>) -> impl Render<Rndr> {
#[cfg(feature = "gtk")]
{
leptos_gtk::r#box()
.orientation(Orientation::Vertical)
.spacing(12)
.child(children)
}
#[cfg(all(feature = "wasm", not(feature = "gtk")))]
{
element::div()
.style(("display", "flex"))
.style(("flex-direction", "column"))
.style(("align-items", "center"))
.style(("justify-content", "center"))
.style(("margin", "1rem"))
.child(children)
}
}
fn hstack(children: impl Render<LeptosGtk>) -> impl Render<LeptosGtk> {
leptos_gtk::r#box()
.orientation(Orientation::Horizontal)
.spacing(12)
.child(children)
fn hstack(children: impl Render<Rndr>) -> impl Render<Rndr> {
#[cfg(feature = "gtk")]
{
leptos_gtk::r#box()
.orientation(Orientation::Horizontal)
.spacing(12)
.child(children)
}
#[cfg(all(feature = "wasm", not(feature = "gtk")))]
{
element::div()
.style(("display", "flex"))
.style(("align-items", "center"))
.style(("justify-content", "center"))
.style(("margin", "1rem"))
.child(children)
}
}
#[cfg(feature = "gtk")]
fn load_css() {
use gtk::{gdk::Display, CssProvider};

View File

@@ -13,27 +13,31 @@ panic = "abort"
lto = true
[dependencies]
actix-files = { version = "0.6.6", optional = true }
actix-web = { version = "4.8", optional = true, features = ["macros"] }
console_log = "1.0"
console_error_panic_hook = "0.1.7"
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["macros"] }
console_log = "1"
console_error_panic_hook = "0.1"
leptos = { path = "../../leptos" }
leptos_meta = { path = "../../meta" }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_router = { path = "../../router" }
log = "0.4.22"
serde = { version = "1.0", features = ["derive"] }
gloo-net = { version = "0.6.0", features = ["http"] }
reqwest = { version = "0.12.5", features = ["json"] }
wasm-bindgen = "0.2.93"
web-sys = { version = "0.3.70", features = ["AbortController", "AbortSignal"] }
log = "0.4"
serde = { version = "1", features = ["derive"] }
gloo-net = { version = "0.2", features = ["http"] }
reqwest = { version = "0.11", features = ["json"] }
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
send_wrapper = "0.6.0"
[features]
default = ["csr"]
csr = ["leptos/csr"]
hydrate = ["leptos/hydrate"]
ssr = ["dep:actix-files", "dep:actix-web", "dep:leptos_actix", "leptos/ssr"]
ssr = [
"dep:actix-files",
"dep:actix-web",
"dep:leptos_actix",
"leptos/ssr",
]
[profile.wasm-release]
inherits = "release"

View File

@@ -20,7 +20,7 @@ pub fn Story() -> impl IntoView {
},
);
Suspense(SuspenseProps::builder().fallback(|| "Loading...").children(ToChildren::to_children(move || Suspend::new(async move {
Suspense(SuspenseProps::builder().fallback(|| "Loading...").children(ToChildren::to_children(move || Suspend(async move {
match story.await.clone() {
None => Either::Left("Story not found."),
Some(story) => {

View File

@@ -19,7 +19,7 @@ pub fn User() -> impl IntoView {
view! {
<div class="user-view">
<Suspense fallback=|| view! { "Loading..." }>
{move || Suspend::new(async move { match user.await.clone() {
{move || Suspend(async move { match user.await.clone() {
None => Either::Left(view! { <h1>"User not found."</h1> }),
Some(user) => Either::Right(view! {
<div>

View File

@@ -11,22 +11,22 @@ codegen-units = 1
lto = true
[dependencies]
console_error_panic_hook = "0.1.7"
console_error_panic_hook = "0.1"
leptos = { path = "../../leptos" }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_meta = { path = "../../meta" }
leptos_router = { path = "../../router" }
serde = { version = "1.0", features = ["derive"] }
tracing = "0.1.40"
gloo-net = { version = "0.6.0", features = ["http"] }
reqwest = { version = "0.12.5", features = ["json"] }
axum = { version = "0.7.5", optional = true }
tower = { version = "0.4.13", optional = true }
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
tokio = { version = "1.39", features = ["full"], optional = true }
http = { version = "1.1", optional = true }
web-sys = { version = "0.3.70", features = ["AbortController", "AbortSignal"] }
wasm-bindgen = "0.2.93"
tracing = "0.1"
gloo-net = { version = "0.4", features = ["http"] }
reqwest = { version = "0.11", features = ["json"] }
axum = { version = "0.7", optional = true }
tower = { version = "0.4", optional = true }
tower-http = { version = "0.5", features = ["fs"], optional = true }
tokio = { version = "1", features = ["full"], optional = true }
http = { version = "1.0", optional = true }
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
wasm-bindgen = "0.2"
send_wrapper = { version = "0.6.0", features = ["futures"] }
[features]

View File

@@ -20,7 +20,7 @@ pub fn Story() -> impl IntoView {
},
);
Suspense(SuspenseProps::builder().fallback(|| "Loading...").children(ToChildren::to_children(move || Suspend::new(async move {
Suspense(SuspenseProps::builder().fallback(|| "Loading...").children(ToChildren::to_children(move || Suspend(async move {
match story.await.clone() {
None => Either::Left("Story not found."),
Some(story) => {

View File

@@ -19,7 +19,7 @@ pub fn User() -> impl IntoView {
view! {
<div class="user-view">
<Suspense fallback=|| view! { "Loading..." }>
{move || Suspend::new(async move { match user.await.clone() {
{move || Suspend(async move { match user.await.clone() {
None => Either::Left(view! { <h1>"User not found."</h1> }),
Some(user) => Either::Right(view! {
<div>

View File

@@ -11,35 +11,34 @@ codegen-units = 1
lto = true
[dependencies]
console_error_panic_hook = "0.1.7"
leptos = { path = "../../leptos", features = ["experimental-islands"] }
console_error_panic_hook = "0.1"
leptos = { path = "../../leptos", features = [
"experimental-islands",
] }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_meta = { path = "../../meta" }
leptos_router = { path = "../../router" }
leptos_router = { path = "../../router"}
serde = { version = "1.0", features = ["derive"] }
tracing = "0.1.40"
gloo-net = { version = "0.6.0", features = ["http"] }
reqwest = { version = "0.12.5", features = ["json"] }
axum = { version = "0.7.5", optional = true, features = ["http2"] }
tower = { version = "0.4.13", optional = true }
tower-http = { version = "0.5.2", features = [
tracing = "0.1"
gloo-net = { version = "0.4", features = ["http"] }
reqwest = { version = "0.11", features = ["json"] }
axum = { version = "0.7", optional = true, features = ["http2"] }
tower = { version = "0.4", optional = true }
tower-http = { version = "0.5", features = [
"fs",
"compression-gzip",
"compression-br",
], optional = true }
tokio = { version = "1.39", features = ["full"], optional = true }
http = { version = "1.1", optional = true }
web-sys = { version = "0.3.70", features = ["AbortController", "AbortSignal"] }
wasm-bindgen = "0.2.93"
lazy_static = "1.5"
rust-embed = { version = "8.5", features = [
"axum",
"mime_guess",
"tokio",
], optional = true }
mime_guess = { version = "2.0", optional = true }
tokio = { version = "1", features = ["full"], optional = true }
http = { version = "1.0", optional = true }
web-sys = { version = "0.3", features = ["AbortController", "AbortSignal"] }
wasm-bindgen = "0.2"
lazy_static = "1.4.0"
rust-embed = { version = "8", features = ["axum", "mime_guess", "tokio"], optional = true }
mime_guess = { version = "2.0.4", optional = true }
[features]
default = []
csr = ["leptos/csr"]
hydrate = ["leptos/hydrate"]
ssr = [

View File

@@ -26,7 +26,7 @@ pub fn Story() -> impl IntoView {
},
);
Suspense(SuspenseProps::builder().fallback(|| "Loading...").children(ToChildren::to_children(move || Suspend::new(async move {
Suspense(SuspenseProps::builder().fallback(|| "Loading...").children(ToChildren::to_children(move || Suspend(async move {
match story.await.ok().flatten() {
None => Either::Left("Story not found."),
Some(story) => {

View File

@@ -26,7 +26,7 @@ pub fn User() -> impl IntoView {
view! {
<div class="user-view">
<Suspense fallback=|| view! { "Loading..." }>
{move || Suspend::new(async move { match user.await.ok().flatten() {
{move || Suspend(async move { match user.await.ok().flatten() {
None => Either::Left(view! { <h1>"User not found."</h1> }),
Some(user) => Either::Right(view! {
<div>

View File

@@ -1,5 +1,5 @@
[package]
name = "hackernews_js_fetch"
name = "hackernews-js-fetch"
version = "0.1.0"
edition = "2021"
@@ -11,64 +11,63 @@ codegen-units = 1
lto = true
[dependencies]
console_log = "1.0.0"
console_error_panic_hook = "0.1.7"
console_log = "1.0"
log = "0.4.22"
cfg-if = "1.0.0"
leptos = { path = "../../leptos" }
leptos_axum = { path = "../../integrations/axum", default-features = false, optional = true }
leptos_meta = { path = "../../meta" }
leptos_router = { path = "../../router" }
leptos_server = { path = "../../leptos_server", optional = true }
serde = { version = "1.0", features = ["derive"] }
tracing = "0.1.40"
gloo-net = { version = "0.6.0", features = ["http"] }
reqwest = { version = "0.12.5", features = ["json"] }
axum = { version = "0.7.5", default-features = false, optional = true }
log = "0.4.17"
simple_logger = "4.0.0"
serde = { version = "1.0.148", features = ["derive"] }
tracing = "0.1"
gloo-net = { version = "0.4.0", features = ["http"] }
reqwest = { version = "0.11.13", features = ["json"] }
axum = { version = "0.7", default-features = false, optional = true }
tower = { version = "0.4.13", optional = true }
http = { version = "1.1", optional = true }
web-sys = { version = "0.3.70", features = [
http = { version = "0.2.11", optional = true }
web-sys = { version = "0.3", features = [
"AbortController",
"AbortSignal",
"Request",
"Response",
] }
getrandom = { version = "0.2.15", features = ["js"] }
wasm-bindgen = "0.2.93"
wasm-bindgen-futures = { version = "0.4.42", features = [
wasm-bindgen = "0.2"
wasm-bindgen-futures = { version = "0.4.37", features = [
"futures-core-03-stream",
], optional = true }
axum-js-fetch = { git = "https://github.com/seanaye/axum-js-fetch", optional = true }
send_wrapper = { version = "0.6.0", features = ["futures"] }
lazy_static = "1.4.0"
[features]
hydrate = ["leptos/hydrate"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:axum",
"dep:tower",
"dep:http",
"dep:axum",
"dep:wasm-bindgen-futures",
"dep:axum-js-fetch",
"leptos/ssr",
"leptos_axum/wasm",
"leptos_meta/ssr",
"leptos_router/ssr",
"leptos_server/serde-wasm-bindgen",
]
[package.metadata.cargo-all-features]
denylist = ["axum", "tower", "http", "leptos_axum"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
skip_feature_sets = [["ssr", "hydrate"]]
[package.metadata.leptos]
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
output-name = "hackernews_js_fetch"
output-name = "hackernews_axum"
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
site-root = "target/site"
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
# Defaults to pkg
site-pkg-dir = "pkg"
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
style-file = "style.css"
style-file = "./style.css"
# [Optional] Files in the asset-dir will be copied to the site-root directory
assets-dir = "public"
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.

View File

@@ -1,6 +1,6 @@
extend = [
{ path = "../cargo-make/main.toml" },
{ path = "../cargo-make/deno-build.toml" },
{ path = "../cargo-make/main.toml" },
{ path = "../cargo-make/deno-build.toml" },
]
[env]

View File

@@ -2,6 +2,8 @@
This example uses the basic Hacker News example as its basis, but shows how to run the server side as WASM running in a JS environment. In this example, Deno is used as the runtime.
**NOTE**: This example is slightly out of date pending an update to [`axum-js-fetch`](https://github.com/seanaye/axum-js-fetch/), which was waiting on a version of `gloo-net` that uses `http` 1.0. It still works with Leptos 0.5 and Axum 0.6, but not with the versions of Leptos (0.6 and later) that support Axum 1.0.
## Server Side Rendering with Deno
To run the Deno version, run

View File

@@ -1,8 +1,5 @@
{
"version": "3",
"redirects": {
"https://deno.land/std/http/file_server.ts": "https://deno.land/std@0.224.0/http/file_server.ts"
},
"version": "2",
"remote": {
"https://deno.land/std@0.177.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462",
"https://deno.land/std@0.177.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3",
@@ -56,49 +53,6 @@
"https://deno.land/std@0.177.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d",
"https://deno.land/std@0.177.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1",
"https://deno.land/std@0.177.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba",
"https://deno.land/std@0.177.0/version.ts": "259c8866ec257c3511b437baa95205a86761abaef852a9b2199072accb2ef046",
"https://deno.land/std@0.224.0/assert/assert.ts": "09d30564c09de846855b7b071e62b5974b001bb72a4b797958fe0660e7849834",
"https://deno.land/std@0.224.0/assert/assertion_error.ts": "ba8752bd27ebc51f723702fac2f54d3e94447598f54264a6653d6413738a8917",
"https://deno.land/std@0.224.0/cli/parse_args.ts": "5250832fb7c544d9111e8a41ad272c016f5a53f975ef84d5a9fe5fcb70566ece",
"https://deno.land/std@0.224.0/encoding/_util.ts": "beacef316c1255da9bc8e95afb1fa56ed69baef919c88dc06ae6cb7a6103d376",
"https://deno.land/std@0.224.0/encoding/base64.ts": "dd59695391584c8ffc5a296ba82bcdba6dd8a84d41a6a539fbee8e5075286eaf",
"https://deno.land/std@0.224.0/fmt/bytes.ts": "7b294a4b9cf0297efa55acb55d50610f3e116a0ac772d1df0ae00f0b833ccd4a",
"https://deno.land/std@0.224.0/fmt/colors.ts": "508563c0659dd7198ba4bbf87e97f654af3c34eb56ba790260f252ad8012e1c5",
"https://deno.land/std@0.224.0/http/etag.ts": "9ca56531be682f202e4239971931060b688ee5c362688e239eeaca39db9e72cb",
"https://deno.land/std@0.224.0/http/file_server.ts": "2a5392195b8e7713288f274d071711b705bb5b3220294d76cce495d456c61a93",
"https://deno.land/std@0.224.0/http/status.ts": "ed61b4882af2514a81aefd3245e8df4c47b9a8e54929a903577643d2d1ebf514",
"https://deno.land/std@0.224.0/media_types/_db.ts": "19563a2491cd81b53b9c1c6ffd1a9145c355042d4a854c52f6e1424f73ff3923",
"https://deno.land/std@0.224.0/media_types/_util.ts": "e0b8da0c7d8ad2015cf27ac16ddf0809ac984b2f3ec79f7fa4206659d4f10deb",
"https://deno.land/std@0.224.0/media_types/content_type.ts": "ed3f2e1f243b418ad3f441edc95fd92efbadb0f9bde36219c7564c67f9639513",
"https://deno.land/std@0.224.0/media_types/format_media_type.ts": "ffef4718afa2489530cb94021bb865a466eb02037609f7e82899c017959d288a",
"https://deno.land/std@0.224.0/media_types/get_charset.ts": "277ebfceb205bd34e616fe6764ef03fb277b77f040706272bea8680806ae3f11",
"https://deno.land/std@0.224.0/media_types/parse_media_type.ts": "487f000a38c230ccbac25420a50f600862e06796d0eee19d19631b9e84ee9654",
"https://deno.land/std@0.224.0/media_types/type_by_extension.ts": "bf4e3f5d6b58b624d5daa01cbb8b1e86d9939940a77e7c26e796a075b60ec73b",
"https://deno.land/std@0.224.0/media_types/vendor/mime-db.v1.52.0.ts": "0218d2c7d900e8cd6fa4a866e0c387712af4af9a1bae55d6b2546c73d273a1e6",
"https://deno.land/std@0.224.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8",
"https://deno.land/std@0.224.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c",
"https://deno.land/std@0.224.0/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8",
"https://deno.land/std@0.224.0/path/_common/normalize_string.ts": "33edef773c2a8e242761f731adeb2bd6d683e9c69e4e3d0092985bede74f4ac3",
"https://deno.land/std@0.224.0/path/_common/relative.ts": "faa2753d9b32320ed4ada0733261e3357c186e5705678d9dd08b97527deae607",
"https://deno.land/std@0.224.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15",
"https://deno.land/std@0.224.0/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36",
"https://deno.land/std@0.224.0/path/extname.ts": "593303db8ae8c865cbd9ceec6e55d4b9ac5410c1e276bfd3131916591b954441",
"https://deno.land/std@0.224.0/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a",
"https://deno.land/std@0.224.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d",
"https://deno.land/std@0.224.0/path/posix/extname.ts": "e398c1d9d1908d3756a7ed94199fcd169e79466dd88feffd2f47ce0abf9d61d2",
"https://deno.land/std@0.224.0/path/posix/join.ts": "7fc2cb3716aa1b863e990baf30b101d768db479e70b7313b4866a088db016f63",
"https://deno.land/std@0.224.0/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91",
"https://deno.land/std@0.224.0/path/posix/relative.ts": "3907d6eda41f0ff723d336125a1ad4349112cd4d48f693859980314d5b9da31c",
"https://deno.land/std@0.224.0/path/posix/resolve.ts": "08b699cfeee10cb6857ccab38fa4b2ec703b0ea33e8e69964f29d02a2d5257cf",
"https://deno.land/std@0.224.0/path/relative.ts": "ab739d727180ed8727e34ed71d976912461d98e2b76de3d3de834c1066667add",
"https://deno.land/std@0.224.0/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d",
"https://deno.land/std@0.224.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808",
"https://deno.land/std@0.224.0/path/windows/extname.ts": "165a61b00d781257fda1e9606a48c78b06815385e7d703232548dbfc95346bef",
"https://deno.land/std@0.224.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf",
"https://deno.land/std@0.224.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780",
"https://deno.land/std@0.224.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7",
"https://deno.land/std@0.224.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972",
"https://deno.land/std@0.224.0/streams/byte_slice_stream.ts": "5bbdcadb118390affa9b3d0a0f73ef8e83754f59bb89df349add669dd9369713",
"https://deno.land/std@0.224.0/version.ts": "f6a28c9704d82d1c095988777e30e6172eb674a6570974a0d27a653be769bbbe"
"https://deno.land/std@0.177.0/version.ts": "259c8866ec257c3511b437baa95205a86761abaef852a9b2199072accb2ef046"
}
}

View File

@@ -1,5 +1,5 @@
use leptos::logging;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use leptos::Serializable;
use serde::{Deserialize, Serialize};
pub fn story(path: &str) -> String {
format!("https://node-hnapi.herokuapp.com/{path}")
@@ -10,51 +10,46 @@ pub fn user(path: &str) -> String {
}
#[cfg(not(feature = "ssr"))]
pub fn fetch_api<T>(
path: &str,
) -> impl std::future::Future<Output = Option<T>> + Send + '_
pub async fn fetch_api<T>(path: &str) -> Option<T>
where
T: Serialize + DeserializeOwned,
T: Serializable,
{
use leptos::prelude::on_cleanup;
use send_wrapper::SendWrapper;
let abort_controller = web_sys::AbortController::new().ok();
let abort_signal = abort_controller.as_ref().map(|a| a.signal());
SendWrapper::new(async move {
let abort_controller =
SendWrapper::new(web_sys::AbortController::new().ok());
let abort_signal = abort_controller.as_ref().map(|a| a.signal());
// abort in-flight requests if, e.g., we've navigated away from this page
leptos::on_cleanup(move || {
if let Some(abort_controller) = abort_controller {
abort_controller.abort()
}
});
// abort in-flight requests if, e.g., we've navigated away from this page
on_cleanup(move || {
if let Some(abort_controller) = abort_controller.take() {
abort_controller.abort()
}
});
let json = gloo_net::http::Request::get(path)
.abort_signal(abort_signal.as_ref())
.send()
.await
.map_err(|e| log::error!("{e}"))
.ok()?
.text()
.await
.ok()?;
gloo_net::http::Request::get(path)
.abort_signal(abort_signal.as_ref())
.send()
.await
.map_err(|e| logging::error!("{e}"))
.ok()?
.json()
.await
.ok()
})
T::de(&json).ok()
}
#[cfg(feature = "ssr")]
pub async fn fetch_api<T>(path: &str) -> Option<T>
where
T: Serialize + DeserializeOwned,
T: Serializable,
{
reqwest::get(path)
let json = reqwest::get(path)
.await
.map_err(|e| logging::error!("{e}"))
.map_err(|e| log::error!("{e}"))
.ok()?
.json()
.text()
.await
.ok()
.ok()?;
T::de(&json).map_err(|e| log::error!("{e}")).ok()
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]

View File

@@ -0,0 +1,28 @@
use leptos::{view, Errors, For, IntoView, RwSignal, SignalGet, View};
// A basic function to display errors served by the error boundaries. Feel free to do more complicated things
// here than just displaying them
pub fn error_template(errors: Option<RwSignal<Errors>>) -> View {
let Some(errors) = errors else {
panic!("No Errors found and we expected errors!");
};
view! {
<h1>"Errors"</h1>
<For
// a function that returns the items we're iterating over; a signal is fine
each=move || errors.get()
// a unique key for each item as a reference
key=|(key, _)| key.clone()
// renders each item to a view
children= move |(_, error)| {
let error_string = error.to_string();
view! {
<p>"Error: " {error_string}</p>
}
}
/>
}
.into_view()
}

View File

@@ -0,0 +1,43 @@
use crate::error_template::error_template;
use axum::{
body::Body,
extract::State,
http::{Request, Response, StatusCode, Uri},
response::{IntoResponse, Response as AxumResponse},
};
//use tower::ServiceExt;
use leptos::LeptosOptions;
pub async fn file_and_error_handler(
uri: Uri,
State(options): State<LeptosOptions>,
req: Request<Body>,
) -> AxumResponse {
let root = options.site_root.clone();
let res = get_static_file(uri.clone(), &root).await.unwrap();
if res.status() == StatusCode::OK {
res.into_response()
} else {
let handler =
leptos_axum::render_app_to_stream(options.to_owned(), || {
error_template(None)
});
handler(req).await.into_response()
}
}
async fn get_static_file(
uri: Uri,
root: &str,
) -> Result<Response<Body>, (StatusCode, String)> {
let req = Request::builder()
.uri(uri.clone())
.body(Body::empty())
.unwrap();
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
// This path is relative to the cargo root
_ = req;
_ = root;
todo!()
}

View File

@@ -1,71 +1,46 @@
use leptos::prelude::*;
use leptos::{component, view, IntoView};
use leptos_meta::*;
use leptos_router::*;
mod api;
pub mod error_template;
#[cfg(feature = "ssr")]
pub mod fallback;
mod routes;
use leptos_meta::{provide_meta_context, Link, Meta, MetaTags, Stylesheet};
use leptos_router::{
components::{FlatRoutes, Route, Router, RoutingProgress},
ParamSegment, StaticSegment,
};
use routes::{nav::*, stories::*, story::*, users::*};
use std::time::Duration;
pub fn shell(options: LeptosOptions) -> impl IntoView {
view! {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<AutoReload options=options.clone()/>
<HydrationScripts options/>
<MetaTags/>
</head>
<body>
<App/>
</body>
</html>
}
}
use wasm_bindgen::prelude::wasm_bindgen;
#[component]
pub fn App() -> impl IntoView {
provide_meta_context();
let (is_routing, set_is_routing) = signal(false);
view! {
<Stylesheet id="leptos" href="/public/style.css"/>
<Link rel="shortcut icon" type_="image/ico" href="/public/favicon.ico"/>
<Stylesheet id="leptos" href="/public/style.css"/>
<Meta name="description" content="Leptos implementation of a HackerNews demo."/>
<Router set_is_routing>
// shows a progress bar while async data are loading
<div class="routing-progress">
<RoutingProgress is_routing max_time=Duration::from_millis(250)/>
</div>
<Nav/>
<Router>
<Nav />
<main>
<FlatRoutes fallback=|| "Not found.">
<Route path=(StaticSegment("users"), ParamSegment("id")) view=User/>
<Route path=(StaticSegment("stories"), ParamSegment("id")) view=Story/>
<Route path=ParamSegment("stories") view=Stories/>
// TODO allow optional params without duplication
<Route path=StaticSegment("") view=Stories/>
</FlatRoutes>
<Routes>
<Route path="users/:id" view=User/>
<Route path="stories/:id" view=Story/>
<Route path=":stories?" view=Stories/>
</Routes>
</main>
</Router>
}
}
#[cfg(feature = "hydrate")]
#[wasm_bindgen::prelude::wasm_bindgen]
#[wasm_bindgen]
pub fn hydrate() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
leptos::mount::hydrate_body(App);
leptos::mount_to_body(App);
}
#[cfg(feature = "ssr")]
mod ssr_imports {
use crate::{shell, App};
use axum::Router;
use crate::App;
use axum::{routing::post, Router};
use leptos::prelude::*;
use leptos_axum::{generate_route_list, LeptosRoutes};
use log::{info, Level};
@@ -84,15 +59,12 @@ mod ssr_imports {
.output_name("client")
.site_pkg_dir("pkg")
.build();
let routes = generate_route_list(App);
// build our application with a route
let app = Router::new()
.leptos_routes(&leptos_options, routes, {
let leptos_options = leptos_options.clone();
move || shell(leptos_options.clone())
})
let app: axum::Router<()> = Router::new()
.leptos_routes(&leptos_options, routes, App)
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
.with_state(leptos_options);
info!("creating handler instance");

View File

@@ -1,12 +1,12 @@
use leptos::prelude::*;
use leptos_router::components::A;
use leptos::{component, view, IntoView};
use leptos_router::*;
#[component]
pub fn Nav() -> impl IntoView {
view! {
<header class="header">
<nav class="inner">
<A href="/home">
<A href="/">
<strong>"HN"</strong>
</A>
<A href="/new">
@@ -21,12 +21,7 @@ pub fn Nav() -> impl IntoView {
<A href="/job">
<strong>"Jobs"</strong>
</A>
<a
class="github"
href="http://github.com/leptos-rs/leptos"
target="_blank"
rel="noreferrer"
>
<a class="github" href="http://github.com/leptos-rs/leptos" target="_blank" rel="noreferrer">
"Built with Leptos"
</a>
</nav>

View File

@@ -1,10 +1,6 @@
use crate::api;
use leptos::{either::Either, prelude::*};
use leptos_router::{
components::A,
hooks::{use_params_map, use_query_map},
};
use send_wrapper::SendWrapper;
use leptos::prelude::*;
use leptos_router::*;
fn category(from: &str) -> &'static str {
match from {
@@ -22,67 +18,57 @@ pub fn Stories() -> impl IntoView {
let params = use_params_map();
let page = move || {
query
.read()
.get("page")
.and_then(|page| page.parse::<usize>().ok())
.with(|q| q.get("page").and_then(|page| page.parse::<usize>().ok()))
.unwrap_or(1)
};
let story_type = move || {
params
.read()
.get("stories")
.with(|p| p.get("stories").cloned())
.unwrap_or_else(|| "top".to_string())
};
let stories = Resource::new(
let stories = create_resource(
move || (page(), story_type()),
move |(page, story_type)| {
SendWrapper::new(async move {
let path = format!("{}?page={}", category(&story_type), page);
api::fetch_api::<Vec<api::Story>>(&api::story(&path)).await
})
move |(page, story_type)| async move {
let path = format!("{}?page={}", category(&story_type), page);
api::fetch_api::<Vec<api::Story>>(&api::story(&path)).await
},
);
let (pending, set_pending) = signal(false);
let (pending, set_pending) = create_signal(false);
let hide_more_link = move || match &*stories.read() {
Some(Some(stories)) => stories.len() < 28,
_ => true
} || pending.get();
let hide_more_link = move || {
stories.get().unwrap_or(None).unwrap_or_default().len() < 28
|| pending.get()
};
view! {
<div class="news-view">
<div class="news-list-nav">
<span>
{move || {
if page() > 1 {
Either::Left(
view! {
<a
class="page-link"
href=move || {
format!("/{}?page={}", story_type(), page() - 1)
}
aria-label="Previous Page"
>
"< prev"
</a>
},
)
} else {
Either::Right(
view! {
<span class="page-link disabled" aria-hidden="true">
"< prev"
</span>
},
)
}
{move || if page() > 1 {
view! {
<a class="page-link"
href=move || format!("/{}?page={}", story_type(), page() - 1)
attr:aria_label="Previous Page"
>
"< prev"
</a>
}.into_any()
} else {
view! {
<span class="page-link disabled" aria-hidden="true">
"< prev"
</span>
}.into_any()
}}
</span>
<span>"page " {page}</span>
<span class="page-link" class:disabled=hide_more_link aria-hidden=hide_more_link>
<a
href=move || format!("/{}?page={}", story_type(), page() + 1)
<span class="page-link"
class:disabled=hide_more_link
aria-hidden=hide_more_link
>
<a href=move || format!("/{}?page={}", story_type(), page() + 1)
aria-label="Next Page"
>
"more >"
@@ -91,19 +77,27 @@ pub fn Stories() -> impl IntoView {
</div>
<main class="news-list">
<div>
<Transition fallback=move || view! { <p>"Loading..."</p> } set_pending>
<Show when=move || {
stories.read().as_ref().map(Option::is_none).unwrap_or(false)
}>> <p>"Error loading stories."</p></Show>
<ul>
<For
each=move || stories.get().unwrap_or_default().unwrap_or_default()
key=|story| story.id
let:story
>
<Story story/>
</For>
</ul>
<Transition
fallback=move || view! { <p>"Loading..."</p> }
set_pending
>
{move || match stories.get() {
None => None,
Some(None) => Some(view! { <p>"Error loading stories."</p> }.into_any()),
Some(Some(stories)) => {
Some(view! {
<ul>
<For
each=move || stories.clone()
key=|story| story.id
let:story
>
<Story story/>
</For>
</ul>
}.into_any())
}
}}
</Transition>
</div>
</main>
@@ -114,67 +108,49 @@ pub fn Stories() -> impl IntoView {
#[component]
fn Story(story: api::Story) -> impl IntoView {
view! {
<li class="news-item">
<li class="news-item">
<span class="score">{story.points}</span>
<span class="title">
{if !story.url.starts_with("item?id=") {
Either::Left(
view! {
<span>
<a href=story.url target="_blank" rel="noreferrer">
{story.title.clone()}
</a>
<span class="host">"("{story.domain}")"</span>
</span>
},
)
view! {
<span>
<a href=story.url target="_blank" rel="noreferrer">
{story.title.clone()}
</a>
<span class="host">"("{story.domain}")"</span>
</span>
}.into_view()
} else {
let title = story.title.clone();
Either::Right(view! { <A href=format!("/stories/{}", story.id)>{title}</A> })
view! { <A href=format!("/stories/{}", story.id)>{title.clone()}</A> }.into_view()
}}
</span>
<br/>
<br />
<span class="meta">
{if story.story_type != "job" {
Either::Left(
view! {
<span>
{"by "}
{story
.user
.map(|user| {
view! {
<A href=format!("/users/{user}")>{user.clone()}</A>
}
})} {format!(" {} | ", story.time_ago)}
<A href=format!(
"/stories/{}",
story.id,
)>
{if story.comments_count.unwrap_or_default() > 0 {
format!(
"{} comments",
story.comments_count.unwrap_or_default(),
)
} else {
"discuss".into()
}}
</A>
</span>
},
)
view! {
<span>
{"by "}
{story.user.map(|user| view ! { <A href=format!("/users/{user}")>{user.clone()}</A>})}
{format!(" {} | ", story.time_ago)}
<A href=format!("/stories/{}", story.id)>
{if story.comments_count.unwrap_or_default() > 0 {
format!("{} comments", story.comments_count.unwrap_or_default())
} else {
"discuss".into()
}}
</A>
</span>
}.into_view()
} else {
let title = story.title.clone();
Either::Right(view! { <A href=format!("/item/{}", story.id)>{title}</A> })
view! { <A href=format!("/item/{}", story.id)>{title.clone()}</A> }.into_view()
}}
</span>
{(story.story_type != "link")
.then(|| {
view! {
" "
<span class="label">{story.story_type}</span>
}
})}
{(story.story_type != "link").then(|| view! {
" "
<span class="label">{story.story_type}</span>
})}
</li>
}
}

View File

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

View File

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

View File

@@ -7,32 +7,32 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = "0.1.7"
futures = "0.3.30"
http = "1.1"
console_error_panic_hook = "0.1"
futures = "0.3"
http = "1.0"
leptos = { path = "../../leptos", features = [
"tracing",
"experimental-islands",
"tracing",
"experimental-islands",
] }
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
leptos_axum = { path = "../../integrations/axum", optional = true }
log = "0.4.22"
serde = { version = "1.0", features = ["derive"] }
axum = { version = "0.7.5", optional = true }
tower = { version = "0.4.13", optional = true }
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
tokio = { version = "1.39", features = ["full"], optional = true }
wasm-bindgen = "0.2.93"
log = "0.4"
serde = { version = "1", features = ["derive"] }
axum = { version = "0.7", optional = true }
tower = { version = "0.4", optional = true }
tower-http = { version = "0.5", features = ["fs"], optional = true }
tokio = { version = "1", features = ["full"], optional = true }
wasm-bindgen = "0.2"
[features]
hydrate = ["leptos/hydrate"]
ssr = [
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"leptos/ssr",
"dep:leptos_axum",
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"leptos/ssr",
"dep:leptos_axum",
]
[profile.wasm-release]

View File

@@ -1,94 +0,0 @@
[package]
name = "islands"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = "0.1.7"
futures = "0.3.30"
http = "1.1"
leptos = { path = "../../leptos", features = [
"tracing",
"experimental-islands",
] }
leptos_router = { path = "../../router" }
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
leptos_axum = { path = "../../integrations/axum", features = [
"islands-router",
], optional = true }
log = "0.4.22"
serde = { version = "1.0", features = ["derive"] }
axum = { version = "0.7.5", optional = true }
tower = { version = "0.4.13", optional = true }
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
tokio = { version = "1.39", features = ["full"], optional = true }
wasm-bindgen = "0.2.93"
[features]
hydrate = ["leptos/hydrate"]
ssr = [
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"leptos/ssr",
"dep:leptos_axum",
]
[profile.wasm-release]
inherits = "release"
opt-level = 'z'
lto = true
codegen-units = 1
panic = "abort"
[package.metadata.cargo-all-features]
denylist = ["axum", "tower", "tower-http", "tokio", "sqlx", "leptos_axum"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
[package.metadata.leptos]
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
output-name = "islands"
# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
site-root = "target/site"
# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
# Defaults to pkg
site-pkg-dir = "pkg"
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
style-file = "./style.css"
# [Optional] Files in the asset-dir will be copied to the site-root directory
assets-dir = "public"
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
site-addr = "127.0.0.1:3000"
# The port to use for automatic reload monitoring
reload-port = 3001
# The browserlist query used for optimizing the CSS.
browserquery = "defaults"
# Set by cargo-leptos watch when building with that tool. Controls whether autoreload JS will be included in the head
watch = false
# The environment Leptos will run in, usually either "DEV" or "PROD"
env = "DEV"
# The features to use when compiling the bin target
#
# Optional. Can be over-ridden with the command line parameter --bin-features
bin-features = ["ssr"]
# If the --no-default-features flag should be used when compiling the bin target
#
# Optional. Defaults to false.
bin-default-features = false
# The features to use when compiling the lib target
#
# Optional. Can be over-ridden with the command line parameter --lib-features
lib-features = ["hydrate"]
# If the --no-default-features flag should be used when compiling the lib target
#
# Optional. Defaults to false.
lib-default-features = false
lib-profile-release = "wasm-release"

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 Greg Johnston
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,4 +0,0 @@
extend = [
{ path = "../cargo-make/main.toml" },
{ path = "../cargo-make/cargo-leptos.toml" },
]

View File

@@ -1,19 +0,0 @@
# Leptos Todo App Sqlite with Axum
This example creates a basic todo app with an Axum backend that uses Leptos' server functions to call sqlx from the client and seamlessly run it on the server.
## Getting Started
See the [Examples README](../README.md) for setup and run instructions.
## E2E Testing
See the [E2E README](./e2e/README.md) for more information about the testing strategy.
## Rendering
See the [SSR Notes](../SSR_NOTES.md) for more information about Server Side Rendering.
## Quick Start
Run `cargo leptos watch` to run this example.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,135 +0,0 @@
window.addEventListener("click", async (ev) => {
// confirm that this is an <a> that meets our requirements
if (
ev.defaultPrevented ||
ev.button !== 0 ||
ev.metaKey ||
ev.altKey ||
ev.ctrlKey ||
ev.shiftKey
)
return;
/** @type HTMLAnchorElement | undefined;*/
const a = ev
.composedPath()
.find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
if (!a) return;
const svg = a.namespaceURI === "http://www.w3.org/2000/svg";
const href = svg ? a.href.baseVal : a.href;
const target = svg ? a.target.baseVal : a.target;
if (target || (!href && !a.hasAttribute("state"))) return;
const rel = (a.getAttribute("rel") || "").split(/\s+/);
if (a.hasAttribute("download") || (rel && rel.includes("external"))) return;
const url = svg ? new URL(href, document.baseURI) : new URL(href);
if (
url.origin !== window.location.origin // ||
// TODO base
//(basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase()))
)
return;
ev.preventDefault();
// fetch the new page
const resp = await fetch(url);
const htmlString = await resp.text();
// Use DOMParser to parse the HTML string
const parser = new DOMParser();
// TODO parse from the request stream instead?
const doc = parser.parseFromString(htmlString, 'text/html');
// The 'doc' variable now contains the parsed DOM
const transition = document.startViewTransition(async () => {
const oldDocWalker = document.createTreeWalker(document);
const newDocWalker = doc.createTreeWalker(doc);
let oldNode = oldDocWalker.currentNode;
let newNode = newDocWalker.currentNode;
while(oldDocWalker.nextNode() && newDocWalker.nextNode()) {
oldNode = oldDocWalker.currentNode;
newNode = newDocWalker.currentNode;
// if the nodes are different, we need to replace the old with the new
// because of the typed view tree, this should never actually happen
if (oldNode.nodeType !== newNode.nodeType) {
oldNode.replaceWith(newNode);
}
// if it's a text node, just update the text with the new text
else if (oldNode.nodeType === Node.TEXT_NODE) {
oldNode.textContent = newNode.textContent;
}
// if it's an element, replace if it's a different tag, or update attributes
else if (oldNode.nodeType === Node.ELEMENT_NODE) {
/** @type Element */
const oldEl = oldNode;
/** @type Element */
const newEl = newNode;
if (oldEl.tagName !== newEl.tagName) {
oldEl.replaceWith(newEl);
}
else {
for(const attr of newEl.attributes) {
oldEl.setAttribute(attr.name, attr.value);
}
}
}
// we use comment "branch marker" nodes to distinguish between different branches in the statically-typed view tree
// if one of these marker is hit, then there are two options
// 1) it's the same branch, and we just keep walking until the end
// 2) it's a different branch, in which case the old can be replaced with the new wholesale
else if (oldNode.nodeType === Node.COMMENT_NODE) {
const oldText = oldNode.textContent;
const newText = newNode.textContent;
if(oldText.startsWith("bo") && newText !== oldText) {
oldDocWalker.nextNode();
newDocWalker.nextNode();
const oldRange = new Range();
const newRange = new Range();
let oldBranches = 1;
let newBranches = 1;
while(oldBranches > 0 && newBranches > 0) {
if(oldDocWalker.nextNode() && newDocWalker.nextNode()) {
console.log(oldDocWalker.currentNode, newDocWalker.currentNode);
if(oldDocWalker.currentNode.nodeType === Node.COMMENT_NODE) {
if(oldDocWalker.currentNode.textContent.startsWith("bo")) {
oldBranches += 1;
} else if(oldDocWalker.currentNode.textContent.startsWith("bc")) {
oldBranches -= 1;
}
}
if(newDocWalker.currentNode.nodeType === Node.COMMENT_NODE) {
if(newDocWalker.currentNode.textContent.startsWith("bo")) {
newBranches += 1;
} else if(newDocWalker.currentNode.textContent.startsWith("bc")) {
newBranches -= 1;
}
}
}
}
try {
oldRange.setStartAfter(oldNode);
oldRange.setEndBefore(oldDocWalker.currentNode);
newRange.setStartAfter(newNode);
newRange.setEndBefore(newDocWalker.currentNode);
const newContents = newRange.extractContents();
oldRange.deleteContents();
oldRange.insertNode(newContents);
oldNode.replaceWith(newNode);
oldDocWalker.currentNode.replaceWith(newDocWalker.currentNode);
} catch (e) {
console.error(e);
}
} }
}
});
await transition;
window.history.pushState(undefined, null, url);
});

View File

@@ -1,2 +0,0 @@
[toolchain]
channel = "stable" # test change

View File

@@ -1,59 +0,0 @@
use leptos::prelude::*;
use leptos_router::{
components::{FlatRoutes, Route, Router},
StaticSegment,
};
pub fn shell(options: LeptosOptions) -> impl IntoView {
view! {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<AutoReload options=options.clone()/>
<HydrationScripts options=options islands=true/>
<link rel="stylesheet" id="leptos" href="/pkg/islands.css"/>
<link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
</head>
<body>
<App/>
</body>
</html>
}
}
#[component]
pub fn App() -> impl IntoView {
view! {
<script src="/routing.js"></script>
<Router>
<header>
<h1>"My Application"</h1>
</header>
<nav>
<a href="/">"Page A"</a>
<a href="/b">"Page B"</a>
</nav>
<main>
<p>
<label>"Home Checkbox" <input type="checkbox"/></label>
</p>
<FlatRoutes fallback=|| "Not found.">
<Route path=StaticSegment("") view=PageA/>
<Route path=StaticSegment("b") view=PageB/>
</FlatRoutes>
</main>
</Router>
}
}
#[component]
pub fn PageA() -> impl IntoView {
view! { <label>"Page A" <input type="checkbox"/></label> }
}
#[component]
pub fn PageB() -> impl IntoView {
view! { <label>"Page B" <input type="checkbox"/></label> }
}

View File

@@ -1,8 +0,0 @@
pub mod app;
#[cfg(feature = "hydrate")]
#[wasm_bindgen::prelude::wasm_bindgen]
pub fn hydrate() {
console_error_panic_hook::set_once();
leptos::mount::hydrate_islands();
}

View File

@@ -1,30 +0,0 @@
use axum::Router;
use islands::app::{shell, App};
use leptos::prelude::*;
use leptos_axum::{generate_route_list, LeptosRoutes};
#[tokio::main]
async fn main() {
// Setting this to None means we'll be using cargo-leptos and its env vars
let conf = get_configuration(None).unwrap();
let leptos_options = conf.leptos_options;
let addr = leptos_options.site_addr;
let routes = generate_route_list(App);
// build our application with a route
let app = Router::new()
.leptos_routes(&leptos_options, routes, {
let leptos_options = leptos_options.clone();
move || shell(leptos_options.clone())
})
.fallback(leptos_axum::file_and_error_handler(shell))
.with_state(leptos_options);
// run our app with hyper
// `axum::Server` is a re-export of `hyper::Server`
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
println!("listening on http://{}", &addr);
axum::serve(listener, app.into_make_service())
.await
.unwrap();
}

View File

@@ -1,3 +0,0 @@
.pending {
color: purple;
}

View File

@@ -8,13 +8,13 @@ codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] } # for actual benchmarking, add `nightly` and `delegation` features
leptos = { path = "../../leptos", features = ["csr"] } # for actual benchmarking, add `nightly` and `event-delegation` features
# used in rand, but we need to enable js feature
getrandom = { version = "0.2.15", features = ["js"] }
getrandom = { version = "0.2.7", features = ["js"] }
rand = { version = "0.8.5", features = ["small_rng"] }
console_error_panic_hook = "0.1.7"
[dev-dependencies]
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.42"
wasm-bindgen-test = "0.3.0"
web-sys = "0.3"

View File

@@ -9,7 +9,7 @@ lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
console_log = "1.0"
log = "0.4.22"
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"
web-sys = "0.3.70"
web-sys = "0.3"

View File

@@ -5,12 +5,12 @@ edition = "2021"
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
log = "0.4.22"
console_log = "1.0"
log = "0.4"
console_log = "1"
console_error_panic_hook = "0.1.7"
wasm-bindgen = "0.2.93"
wasm-bindgen = "0.2"
[dev-dependencies]
wasm-bindgen-test = "0.3.42"
wasm-bindgen = "0.2.93"
web-sys = "0.3.70"
wasm-bindgen-test = "0.3.0"
wasm-bindgen = "0.2"
web-sys = "0.3"

View File

@@ -10,16 +10,15 @@ codegen-units = 1
panic = "abort"
[dependencies]
console_log = "1.0"
console_log = "1"
leptos = { path = "../../leptos", features = ["csr", "tracing"] }
leptos_router = { path = "../../router" } #, features = ["tracing"] }
leptos_router_macro = { path = "../../router_macro" }
serde = { version = "1.0", features = ["derive"] }
futures = "0.3.30"
leptos_router = { path = "../../router" } #, features = ["tracing"] }
serde = { version = "1", features = ["derive"] }
futures = "0.3"
console_error_panic_hook = "0.1.7"
tracing-subscriber = "0.3.18"
tracing-subscriber-wasm = "0.1.0"
tracing = "0.1.40"
[dev-dependencies]
wasm-bindgen-test = "0.3.42"
wasm-bindgen-test = "0.3.0"

View File

@@ -4,14 +4,12 @@ use leptos::either::Either;
use leptos::prelude::*;
use leptos_router::{
components::{
Form, Outlet, ParentRoute, ProtectedRoute, Redirect, Route, Router,
Routes, A,
Outlet, ParentRoute, ProtectedRoute, Redirect, Route, Router, Routes, A,
},
hooks::{use_navigate, use_params, use_query_map},
params::Params,
MatchNestedRoutes,
MatchNestedRoutes, ParamSegment, StaticSegment,
};
use leptos_router_macro::path;
use tracing::info;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -39,22 +37,27 @@ pub fn RouterExample() -> impl IntoView {
<A href="/about">"About"</A>
<A href="/settings">"Settings"</A>
<A href="/redirect-home">"Redirect to Home"</A>
<button on:click=move |_| {
set_logged_in.update(|n| *n = !*n)
}>{move || if logged_in.get() { "Log Out" } else { "Log In" }}</button>
<button on:click=move |_| set_logged_in.update(|n| *n = !*n)>
{move || if logged_in.get() {
"Log Out"
} else {
"Log In"
}}
</button>
</nav>
<main>
<Routes fallback=|| "This page could not be found.">
// paths can be created using the path!() macro, or provided as types like
// StaticSegment("about")
<Route path=path!("about") view=About/>
<Route path=StaticSegment("about") view=About/>
<ProtectedRoute
path=path!("settings")
path=StaticSegment("settings")
condition=move || Some(logged_in.get())
redirect_path=|| "/"
view=Settings
/>
<Route path=path!("redirect-home") view=|| view! { <Redirect path="/"/> }/>
<Route
path=StaticSegment("redirect-home")
view=|| view! { <Redirect path="/"/> }
/>
<ContactRoutes/>
</Routes>
</main>
@@ -67,12 +70,11 @@ pub fn RouterExample() -> impl IntoView {
#[component]
pub fn ContactRoutes() -> impl MatchNestedRoutes<Dom> + Clone {
view! {
<ParentRoute path=path!("") view=ContactList>
<Route path=path!("/") view=|| "Select a contact."/>
<Route path=path!("/:id") view=Contact/>
<ParentRoute path=StaticSegment("") view=ContactList>
<Route path=StaticSegment("") view=|| "Select a contact."/>
<Route path=ParamSegment("id") view=Contact/>
</ParentRoute>
}
.into_inner()
}
#[component]
@@ -93,7 +95,7 @@ pub fn ContactList() -> impl IntoView {
get_contacts(search.get())
});
let contacts = move || {
Suspend::new(async move {
Suspend(async move {
// this data doesn't change frequently so we can use .map().collect() instead of a keyed <For/>
contacts.await
.into_iter()
@@ -152,7 +154,7 @@ pub fn Contact() -> impl IntoView {
});
let contact_display = move || {
Suspend::new(async move {
Suspend(async move {
match contact.await {
None => Either::Left(
view! { <p>"No contact with this ID was found."</p> },
@@ -215,19 +217,13 @@ pub fn Settings() -> impl IntoView {
view! {
<h1>"Settings"</h1>
<Form action="">
<form>
<fieldset>
<legend>"Name"</legend>
<input type="text" name="first_name" placeholder="First"/>
<input type="text" name="last_name" placeholder="Last"/>
</fieldset>
<input type="submit"/>
<p>
"This uses the " <code>"<Form/>"</code>
" component, which enhances forms by using client-side navigation for "
<code>"GET"</code> " requests, and client-side requests for " <code>"POST"</code>
" requests, without requiring a full page reload."
</p>
</Form>
<pre>"This page is just a placeholder."</pre>
</form>
}
}

View File

@@ -7,52 +7,45 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = "0.1.7"
futures = "0.3.30"
http = "1.1"
console_error_panic_hook = "0.1"
futures = "0.3"
http = "1.0"
leptos = { path = "../../leptos" }
leptos_axum = { path = "../../integrations/axum", optional = true }
server_fn = { path = "../../server_fn", features = [
"serde-lite",
"rkyv",
"multipart",
] }
log = "0.4.22"
simple_logger = "5.0"
serde = { version = "1.0", features = ["derive"] }
axum = { version = "0.7.5", optional = true }
tower = { version = "0.4.13", optional = true }
tower-http = { version = "0.5.2", features = [
"fs",
"tracing",
"trace",
], optional = true }
tokio = { version = "1.39", features = ["full"], optional = true }
server_fn = { path = "../../server_fn", features = ["serde-lite", "rkyv", "multipart"] }
log = "0.4"
simple_logger = "4.0"
serde = { version = "1", features = ["derive"] }
axum = { version = "0.7", optional = true }
tower = { version = "0.4", optional = true }
tower-http = { version = "0.5", features = ["fs", "tracing", "trace"], optional = true }
tokio = { version = "1", features = ["full"], optional = true }
thiserror = "1.0"
wasm-bindgen = "0.2.93"
wasm-bindgen = "0.2"
serde_toml = "0.0.1"
toml = "0.8.19"
web-sys = { version = "0.3.70", features = ["FileList", "File"] }
strum = { version = "0.26.3", features = ["strum_macros", "derive"] }
notify = { version = "6.1", optional = true }
pin-project-lite = "0.2.14"
dashmap = { version = "6.0", optional = true }
once_cell = { version = "1.19", optional = true }
async-broadcast = { version = "0.7.1", optional = true }
toml = "0.8.8"
web-sys = { version = "0.3.67", features = ["FileList", "File"] }
strum = { version = "0.25.0", features = ["strum_macros", "derive"] }
notify = { version = "6.1.1", optional = true }
pin-project-lite = "0.2.13"
dashmap = { version = "5.5.3", optional = true }
once_cell = { version = "1.19.0", optional = true }
async-broadcast = { version = "0.6.0", optional = true }
send_wrapper = "0.6.0"
[features]
hydrate = ["leptos/hydrate"]
ssr = [
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"leptos/ssr",
"dep:leptos_axum",
"dep:notify",
"dep:dashmap",
"dep:once_cell",
"dep:async-broadcast",
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"leptos/ssr",
"dep:leptos_axum",
"dep:notify",
"dep:dashmap",
"dep:once_cell",
"dep:async-broadcast",
]
[package.metadata.cargo-all-features]

View File

@@ -28,7 +28,7 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<AutoReload options=options.clone()/>
<AutoReload options=options.clone() />
<HydrationScripts options/>
<meta name="color-scheme" content="dark light"/>
<link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
@@ -101,19 +101,19 @@ pub fn SpawnLocal() -> impl IntoView {
view! {
<h3>Using <code>spawn_local</code></h3>
<p>
"You can call a server function by using " <code>"spawn_local"</code>
" in an event listener. "
"You can call a server function by using "<code>"spawn_local"</code> " in an event listener. "
"Clicking this button should alert with the uppercase version of the input."
</p>
<input node_ref=input_ref placeholder="Type something here."/>
<button on:click=move |_| {
let value = input_ref.get().unwrap().value();
spawn_local(async move {
let uppercase_text = shouting_text(value).await.unwrap_or_else(|e| e.to_string());
set_shout_result.set(uppercase_text);
});
}>
<button
on:click=move |_| {
let value = input_ref.get().unwrap().value();
spawn_local(async move {
let uppercase_text = shouting_text(value).await.unwrap_or_else(|e| e.to_string());
set_shout_result.set(uppercase_text);
});
}
>
{shout_result}
</button>
}
@@ -185,11 +185,17 @@ pub fn WithAnAction() -> impl IntoView {
"These often work well as actions."
</p>
<input node_ref=input_ref placeholder="Type something here."/>
<button on:click=move |_| {
let text = input_ref.get().unwrap().value();
action.dispatch(text.into());
}>
<button
on:click=move |_| {
let text = input_ref.get().unwrap().value();
action.dispatch(text.into());
// note: technically, this `action` takes `AddRow` (the server fn type) as its
// argument
//
// however, for any one-argument server functions, `From<_>` is implemented between
// the server function type and the type of this single argument
}
>
Submit
</button>
<p>You submitted: {move || format!("{:?}", action.input().get())}</p>
@@ -215,9 +221,7 @@ pub fn WithActionForm() -> impl IntoView {
view! {
<h3>Using <code>"<ActionForm/>"</code></h3>
<p>
<code>"<ActionForm/>"</code>
"lets you use an HTML "
<code>"<form>"</code>
<code>"<ActionForm/>"</code> "lets you use an HTML " <code>"<form>"</code>
"to call a server function in a way that gracefully degrades."
</p>
<ActionForm action>
@@ -230,8 +234,7 @@ pub fn WithActionForm() -> impl IntoView {
</ActionForm>
<p>You submitted: {move || format!("{:?}", action.input().get())}</p>
<p>The result was: {move || format!("{:?}", action.value().get())}</p>
<Transition>
archive underaligned: need alignment 4 but have alignment 1
<Transition>archive underaligned: need alignment 4 but have alignment 1
<p>Total rows: {row_count}</p>
</Transition>
}
@@ -270,21 +273,24 @@ pub fn ServerFnArgumentExample() -> impl IntoView {
view! {
<h3>Custom arguments to the <code>#[server]</code> " macro"</h3>
<p>This example shows how to specify additional behavior, including:</p>
<ul>
<li>Specific server function <strong>paths</strong></li>
<li>Mixing and matching input and output <strong>encodings</strong></li>
<li>Adding custom <strong>middleware</strong> on a per-server-fn basis</li>
</ul>
<p>
This example shows how to specify additional behavior including
<ul>
<li>Specific server function <strong>paths</strong></li>
<li>Mixing and matching input and output <strong>encodings</strong></li>
<li>Adding custom <strong>middleware</strong> on a per-server-fn basis</li>
</ul>
</p>
<input node_ref=input_ref placeholder="Type something here."/>
<button on:click=move |_| {
let value = input_ref.get().unwrap().value();
spawn_local(async move {
let length = length_of_input(value).await.unwrap_or(0);
set_result.set(length);
});
}>
<button
on:click=move |_| {
let value = input_ref.get().unwrap().value();
spawn_local(async move {
let length = length_of_input(value).await.unwrap_or(0);
set_result.set(length);
});
}
>
Click to see length
</button>
<p>Length is {result}</p>
@@ -316,15 +322,18 @@ pub fn RkyvExample() -> impl IntoView {
view! {
<h3>Using <code>rkyv</code> encoding</h3>
<input node_ref=input_ref placeholder="Type something here."/>
<button on:click=move |_| {
let value = input_ref.get().unwrap().value();
set_input.set(value);
}>
<button
on:click=move |_| {
let value = input_ref.get().unwrap().value();
set_input.set(value);
}
>
Click to capitalize
</button>
<p>{input}</p>
<Transition>{rkyv_result}</Transition>
<Transition>
{rkyv_result}
</Transition>
}
}
@@ -361,9 +370,10 @@ pub fn FileUpload() -> impl IntoView {
Ok(count)
}
let upload_action = Action::new_local(|data: &FormData| {
let upload_action = Action::new_unsync(|data: &FormData| {
let data = data.to_owned();
// `MultipartData` implements `From<FormData>`
file_length(data.clone().into())
file_length(data.into())
});
view! {
@@ -373,25 +383,21 @@ pub fn FileUpload() -> impl IntoView {
ev.prevent_default();
let target = ev.target().unwrap().unchecked_into::<HtmlFormElement>();
let form_data = FormData::new_with_form(&target).unwrap();
upload_action.dispatch_local(form_data);
upload_action.dispatch_unsync(form_data);
}>
<input type="file" name="file_to_upload"/>
<input type="submit"/>
</form>
<p>
{move || {
if upload_action.input_local().read().is_none() && upload_action.value().read().is_none()
{
"Upload a file.".to_string()
} else if upload_action.pending().get() {
"Uploading...".to_string()
} else if let Some(Ok(value)) = upload_action.value().get() {
value.to_string()
} else {
format!("{:?}", upload_action.value().get())
}
{move || if upload_action.input().get().is_none() && upload_action.value().get().is_none() {
"Upload a file.".to_string()
} else if upload_action.pending().get() {
"Uploading...".to_string()
} else if let Some(Ok(value)) = upload_action.value().get() {
value.to_string()
} else {
format!("{:?}", upload_action.value().get())
}}
</p>
}
}
@@ -553,17 +559,9 @@ pub fn FileUploadWithProgress() -> impl IntoView {
<input type="submit"/>
</form>
{move || filename.get().map(|filename| view! { <p>Uploading {filename}</p> })}
{move || {
max.get()
.map(|max| {
view! {
<progress
max=max
value=move || current.get().unwrap_or_default()
></progress>
}
})
}}
{move || max.get().map(|max| view! {
<progress max=max value=move || current.get().unwrap_or_default()/>
})}
}
}
#[component]
@@ -618,27 +616,9 @@ pub fn FileWatcher() -> impl IntoView {
<h3>Watching files and returning a streaming response</h3>
<p>Files changed since you loaded the page:</p>
<ul>
{move || {
files
.get()
.into_iter()
.map(|file| {
view! {
<li>
<code>{file}</code>
</li>
}
})
.collect::<Vec<_>>()
}}
{move || files.get().into_iter().map(|file| view! { <li><code>{file}</code></li> }).collect::<Vec<_>>()}
</ul>
<p>
<em>
Add or remove some text files in the <code>watched_files</code>
directory and see the list of changes here.
</em>
</p>
<p><em>Add or remove some text files in the <code>watched_files</code> directory and see the list of changes here.</em></p>
}
}
@@ -688,17 +668,20 @@ pub fn CustomErrorTypes() -> impl IntoView {
the rules!"
</p>
<input node_ref=input_ref placeholder="Type something here."/>
<button on:click=move |_| {
let value = input_ref.get().unwrap().value();
spawn_local(async move {
let data = ascii_uppercase(value).await;
set_result.set(Some(data));
});
}>
<button
on:click=move |_| {
let value = input_ref.get().unwrap().value();
spawn_local(async move {
let data = ascii_uppercase(value).await;
set_result.set(Some(data));
});
}
>
"Submit"
</button>
<p>{move || format!("{:?}", result.get())}</p>
<p>
{move || format!("{:?}", result.get())}
</p>
}
}
@@ -809,14 +792,15 @@ pub fn CustomEncoding() -> impl IntoView {
"This example creates a custom encoding that sends server fn data using TOML. Why? Well... why not?"
</p>
<input node_ref=input_ref placeholder="Type something here."/>
<button on:click=move |_| {
let value = input_ref.get().unwrap().value();
spawn_local(async move {
let new_value = why_not(value, ", but in TOML!!!".to_string()).await.unwrap();
set_result.set(new_value.0.modified);
});
}>
<button
on:click=move |_| {
let value = input_ref.get().unwrap().value();
spawn_local(async move {
let new_value = why_not(value, ", but in TOML!!!".to_string()).await.unwrap();
set_result.set(new_value.0.modified);
});
}
>
Submit
</button>
<p>{result}</p>
@@ -869,14 +853,8 @@ pub fn CustomClientExample() -> impl IntoView {
view! {
<h3>Custom clients</h3>
<p>
You can define a custom server function client to do something like adding a header to every request.
</p>
<p>
Check the network request in your browser devtools to see how this client adds a custom header.
</p>
<button on:click=|_| spawn_local(async {
fn_with_custom_client().await.unwrap()
})>Click me</button>
<p>You can define a custom server function client to do something like adding a header to every request.</p>
<p>Check the network request in your browser devtools to see how this client adds a custom header.</p>
<button on:click=|_| spawn_local(async { fn_with_custom_client().await.unwrap() })>Click me</button>
}
}

View File

@@ -37,21 +37,21 @@ pub fn ErrorTemplate(
}
view! {
<h1>"Errors"</h1>
<For
// a function that returns the items we're iterating over; a signal is fine
each=move || { errors.clone().into_iter().enumerate() }
// a unique key for each item as a reference
key=|(index, _error)| *index
// renders each item to a view
children=move |error| {
let error_string = error.1.to_string();
let error_code = error.1.status_code();
view! {
<h2>{error_code.to_string()}</h2>
<p>"Error: " {error_string}</p>
}
}
/>
<h1>"Errors"</h1>
<For
// a function that returns the items we're iterating over; a signal is fine
each= move || {errors.clone().into_iter().enumerate()}
// a unique key for each item as a reference
key=|(index, _error)| *index
// renders each item to a view
children=move |error| {
let error_string = error.1.to_string();
let error_code= error.1.status_code();
view! {
<h2>{error_code.to_string()}</h2>
<p>"Error: " {error_string}</p>
}
}
/>
}
}

View File

@@ -9,6 +9,6 @@ lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
console_log = "1.0"
log = "0.4.22"
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"

View File

@@ -7,20 +7,20 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
actix-files = { version = "0.6.6", optional = true }
actix-web = { version = "4.8", optional = true, features = ["macros"] }
console_error_panic_hook = "0.1.7"
console_log = "1.0"
lazy_static = "1.5"
leptos = { path = "../../leptos" }
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["macros"] }
console_error_panic_hook = "0.1"
console_log = "1"
lazy_static = "1"
leptos = { path = "../../leptos"}
leptos_meta = { path = "../../meta" }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_router = { path = "../../router" }
log = "0.4.22"
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0"
tokio = { version = "1.39", features = ["time"] }
wasm-bindgen = "0.2.93"
log = "0.4"
serde = { version = "1", features = ["derive"] }
thiserror = "1"
tokio = { version = "1", features = ["time"] }
wasm-bindgen = "0.2"
[features]
hydrate = ["leptos/hydrate"]

View File

@@ -64,7 +64,7 @@ fn HomePage() -> impl IntoView {
view! {
<h1>"My Great Blog"</h1>
<Suspense fallback=move || view! { <p>"Loading posts..."</p> }>
<p>"number of posts: " {Suspend::new(async move { posts2.await })}</p>
<p>"number of posts: " {Suspend(async move { posts2.await })}</p>
</Suspense>
<Suspense fallback=move || view! { <p>"Loading posts..."</p> }>
<ul>
@@ -105,7 +105,7 @@ fn Post() -> impl IntoView {
}
});
let post_view = Suspend::new(async move {
let post_view = Suspend(async move {
match post_resource.await.to_owned() {
Ok(Ok(post)) => Ok(view! {
<h1>{post.title.clone()}</h1>

View File

@@ -7,39 +7,37 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
console_error_panic_hook = "0.1.7"
console_log = "1.0"
lazy_static = "1.5"
leptos = { path = "../../leptos", features = [
"hydration",
] } #"nightly", "hydration"] }
console_error_panic_hook = "0.1"
console_log = "1"
lazy_static = "1"
leptos = { path = "../../leptos", features = ["hydration" ] } #"nightly", "hydration"] }
leptos_meta = { path = "../../meta" }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_router = { path = "../../router" }
log = "0.4.22"
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0"
axum = { version = "0.7.5", optional = true }
tower = { version = "0.4.13", optional = true }
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
tokio = { version = "1.39", features = [
"rt-multi-thread",
"macros",
"time",
log = "0.4"
serde = { version = "1", features = ["derive"] }
thiserror = "1"
axum = { version = "0.7", optional = true }
tower = { version = "0.4", optional = true }
tower-http = { version = "0.5", features = ["fs"], optional = true }
tokio = { version = "1", features = [
"rt-multi-thread",
"macros",
"time",
], optional = true }
wasm-bindgen = "0.2.93"
wasm-bindgen = "0.2"
[features]
hydrate = ["leptos/hydrate"]
ssr = [
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"leptos/ssr",
"leptos_meta/ssr",
"dep:leptos_axum",
"leptos_router/ssr",
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"leptos/ssr",
"leptos_meta/ssr",
"dep:leptos_axum",
"leptos_router/ssr",
]
[profile.release]

View File

@@ -20,7 +20,7 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<AutoReload options=options.clone()/>
<AutoReload options=options.clone() />
<HydrationScripts options/>
<MetaTags/>
</head>
@@ -64,24 +64,15 @@ pub fn App() -> impl IntoView {
<a href="/admin">"Admin"</a>
<Transition>
<ActionForm action=toggle_admin>
<input
type="hidden"
name="is_admin"
value=move || {
(!is_admin.get().and_then(|n| n.ok()).unwrap_or_default())
.to_string()
}
<input type="hidden" name="is_admin"
value=move || (!is_admin.get().and_then(|n| n.ok()).unwrap_or_default()).to_string()
/>
<button>
{move || {
if is_admin.get().and_then(Result::ok).unwrap_or_default() {
"Log Out"
} else {
"Log In"
}
{move || if is_admin.get().and_then(Result::ok).unwrap_or_default() {
"Log Out"
} else {
"Log In"
}}
</button>
</ActionForm>
</Transition>
@@ -103,10 +94,6 @@ pub fn App() -> impl IntoView {
view=Post
ssr=SsrMode::InOrder
/>
<Route
path=(StaticSegment("post_partially_blocked"), ParamSegment("id"))
view=Post
/>
<ProtectedRoute
path=StaticSegment("admin")
view=Admin
@@ -140,7 +127,7 @@ fn HomePage() -> impl IntoView {
view! {
<h1>"My Great Blog"</h1>
<Suspense fallback=move || view! { <p>"Loading posts..."</p> }>
<p>"number of posts: " {Suspend::new(async move { posts2.await })}</p>
<p>"number of posts: " {Suspend(async move { posts2.await })}</p>
</Suspense>
<Suspense fallback=move || view! { <p>"Loading posts..."</p> }>
<ul>
@@ -148,15 +135,7 @@ fn HomePage() -> impl IntoView {
<li>
<a href=format!("/post/{}", post.id)>{post.title.clone()}</a>
"|"
<a href=format!(
"/post_in_order/{}",
post.id,
)>{post.title.clone()} "(in order)"</a>
"|"
<a href=format!(
"/post_partially_blocked/{}",
post.id,
)>{post.title} "(partially blocked)"</a>
<a href=format!("/post_in_order/{}", post.id)>{post.title} "(in order)"</a>
</li>
</For>
</ul>
@@ -179,7 +158,7 @@ fn Post() -> impl IntoView {
.map_err(|_| PostError::InvalidId)
})
};
let post_resource = Resource::new_blocking(id, |id| async move {
let post_resource = Resource::new(id, |id| async move {
match id {
Err(e) => Err(e),
Ok(id) => get_post(id)
@@ -188,43 +167,18 @@ fn Post() -> impl IntoView {
.map_err(|_| PostError::ServerError),
}
});
let comments_resource = Resource::new(id, |id| async move {
match id {
Err(e) => Err(e),
Ok(id) => {
get_comments(id).await.map_err(|_| PostError::ServerError)
}
}
});
let post_view = Suspend::new(async move {
match post_resource.await {
Ok(Ok(post)) => {
Ok(view! {
<h1>{post.title.clone()}</h1>
<p>{post.content.clone()}</p>
let post_view = Suspend(async move {
match post_resource.await.to_owned() {
Ok(Ok(post)) => Ok(view! {
<h1>{post.title.clone()}</h1>
<p>{post.content.clone()}</p>
// since we're using async rendering for this page,
// this metadata should be included in the actual HTML <head>
// when it's first served
<Title text=post.title/>
<Meta name="description" content=post.content/>
})
}
_ => Err(PostError::ServerError),
}
});
let comments_view = Suspend::new(async move {
match comments_resource.await {
Ok(comments) => Ok(view! {
<h1>"Comments"</h1>
<ul>
{comments
.into_iter()
.map(|comment| view! { <li>{comment}</li> })
.collect_view()}
</ul>
// since we're using async rendering for this page,
// this metadata should be included in the actual HTML <head>
// when it's first served
<Title text=post.title/>
<Meta name="description" content=post.content/>
}),
_ => Err(PostError::ServerError),
}
@@ -251,13 +205,14 @@ fn Post() -> impl IntoView {
}
}>{post_view}</ErrorBoundary>
</Suspense>
<Suspense fallback=move || view! { <p>"Loading comments..."</p> }>{comments_view}</Suspense>
}
}
#[component]
pub fn Admin() -> impl IntoView {
view! { <p>"You can only see this page if you're logged in."</p> }
view! {
<p>"You can only see this page if you're logged in."</p>
}
}
// Dummy API
@@ -321,10 +276,3 @@ pub async fn get_post(id: usize) -> Result<Option<Post>, ServerFnError> {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
Ok(POSTS.iter().find(|post| post.id == id).cloned())
}
#[server]
pub async fn get_comments(id: usize) -> Result<Vec<String>, ServerFnError> {
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
_ = id;
Ok(vec!["Some comment".into(), "Some other comment".into()])
}

View File

@@ -1,20 +0,0 @@
[package]
name = "stores"
version = "0.1.0"
edition = "2021"
[profile.release]
opt-level = 'z'
codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
reactive_stores = { path = "../../reactive_stores" }
reactive_stores_macro = { path = "../../reactive_stores_macro" }
console_error_panic_hook = "0.1.7"
[dev-dependencies]
wasm-bindgen = "0.2.93"
wasm-bindgen-test = "0.3.42"
web-sys = "0.3.70"

View File

@@ -1,3 +0,0 @@
extend = [
{ path = "../cargo-make/main.toml" },
]

View File

@@ -1,11 +0,0 @@
# Leptos Counter Example
This example creates a simple counter in a client side rendered app with Rust and WASM!
## Getting Started
See the [Examples README](../README.md) for setup and run instructions.
## Quick Start
Run `trunk serve --open` to run this example.

View File

@@ -1,8 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<link data-trunk rel="rust" data-wasm-opt="z"/>
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
</head>
<body></body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,2 +0,0 @@
[toolchain]
channel = "stable" # test change

View File

@@ -1,130 +0,0 @@
use leptos::prelude::*;
use reactive_stores::{Field, Store, StoreFieldIterator};
use reactive_stores_macro::Store;
#[derive(Debug, Store)]
struct Todos {
user: String,
todos: Vec<Todo>,
}
#[derive(Debug, Store)]
struct Todo {
label: String,
completed: bool,
}
impl Todo {
pub fn new(label: impl ToString) -> Self {
Self {
label: label.to_string(),
completed: false,
}
}
}
fn data() -> Todos {
Todos {
user: "Bob".to_string(),
todos: vec![
Todo {
label: "Create reactive store".to_string(),
completed: true,
},
Todo {
label: "???".to_string(),
completed: false,
},
Todo {
label: "Profit".to_string(),
completed: false,
},
],
}
}
#[component]
pub fn App() -> impl IntoView {
let store = Store::new(data());
let input_ref = NodeRef::new();
let rows = move || {
store
.todos()
.iter()
.enumerate()
.map(|(idx, todo)| view! { <TodoRow store idx todo/> })
.collect_view()
};
view! {
<p>"Hello, " {move || store.user().get()}</p>
<form on:submit=move |ev| {
ev.prevent_default();
store.todos().write().push(Todo::new(input_ref.get().unwrap().value()));
}>
<label>"Add a Todo" <input type="text" node_ref=input_ref/></label>
<input type="submit"/>
</form>
<ol>{rows}</ol>
<div style="display: flex"></div>
}
}
#[component]
fn TodoRow(
store: Store<Todos>,
idx: usize,
#[prop(into)] todo: Field<Todo>,
) -> impl IntoView {
let completed = todo.completed();
let title = todo.label();
let editing = RwSignal::new(false);
view! {
<li
style:text-decoration=move || {
completed.get().then_some("line-through").unwrap_or_default()
}
class:foo=move || completed.get()
>
<p
class:hidden=move || editing.get()
on:click=move |_| {
editing.update(|n| *n = !*n);
}
>
{move || title.get()}
</p>
<input
class:hidden=move || !(editing.get())
type="text"
prop:value=move || title.get()
on:change=move |ev| {
title.set(event_target_value(&ev));
editing.set(false);
}
on:blur=move |_| editing.set(false)
autofocus
/>
<input
type="checkbox"
prop:checked=move || completed.get()
on:click=move |_| { completed.update(|n| *n = !*n) }
/>
<button on:click=move |_| {
store
.todos()
.update(|todos| {
todos.remove(idx);
});
}>"X"</button>
</li>
}
}

View File

@@ -1,7 +0,0 @@
use leptos::prelude::*;
use stores::App;
pub fn main() {
console_error_panic_hook::set_once();
mount_to_body(App)
}

View File

@@ -7,26 +7,26 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
actix-files = { version = "0.6.6", optional = true }
actix-web = { version = "4.8", optional = true, features = ["macros"] }
console_error_panic_hook = "0.1.7"
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["macros"] }
console_error_panic_hook = "0.1"
leptos = { path = "../../leptos" }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_router = { path = "../../router" }
log = "0.4.22"
wasm-bindgen = "0.2.93"
serde = "1.0"
tokio = { version = "1.39", features = ["time", "rt"], optional = true }
log = "0.4"
wasm-bindgen = "0.2"
serde = "1.0.159"
tokio = { version = "1.29", features = ["time", "rt"], optional = true }
[features]
hydrate = ["leptos/hydrate"]
ssr = [
"dep:actix-files",
"dep:actix-web",
"dep:leptos_actix",
"leptos/ssr",
"leptos_router/ssr",
"dep:tokio",
"dep:actix-files",
"dep:actix-web",
"dep:leptos_actix",
"leptos/ssr",
"leptos_router/ssr",
"dep:tokio",
]
[package.metadata.leptos]

View File

@@ -4,14 +4,14 @@ version = "0.1.0"
edition = "2021"
[dev-dependencies]
anyhow = "1.0"
async-trait = "0.1.81"
cucumber = "0.21.1"
fantoccini = "0.21.1"
pretty_assertions = "1.4"
serde_json = "1.0"
tokio = { version = "1.39", features = ["macros", "rt-multi-thread", "time"] }
url = "2.5"
anyhow = "1.0.72"
async-trait = "0.1.72"
cucumber = "0.19.1"
fantoccini = "0.19.3"
pretty_assertions = "1.4.0"
serde_json = "1.0.104"
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread", "time"] }
url = "2.4.0"
[[test]]
name = "app_suite"

View File

@@ -8,8 +8,8 @@ This example demonstrates e2e testing with Rust using executable requirements.
|---|---|---|
| [Cucumber](https://github.com/cucumber-rs/cucumber/tree/main) | Test Runner | Run [Gherkin](https://cucumber.io/docs/gherkin/reference/) specifications as Rust tests |
| [Fantoccini](https://github.com/jonhoo/fantoccini/tree/main) | Browser Client | Interact with web pages through WebDriver |
| [Cargo Leptos](https://github.com/leptos-rs/cargo-leptos) | Build Tool | Compile example and start the server and end-2-end tests |
| [chromedriver](https://chromedriver.chromium.org/downloads) | WebDriver | Provide WebDriver for Chrome |
| [Cargo Leptos ](https://github.com/leptos-rs/cargo-leptos) | Build Tool | Compile example and start the server and end-2-end tests |
| [chromedriver](https://chromedriver.chromium.org/downloads) | WebDriver | Provide WebDriver for Chrome
## Testing Organization

View File

@@ -51,63 +51,51 @@ pub fn App() -> impl IntoView {
// out-of-order
<ParentRoute
path=StaticSegment("out-of-order")
view=|| {
view! {
<SecondaryNav/>
<h1>"Out-of-Order"</h1>
<Outlet/>
}
view=|| view! {
<SecondaryNav/>
<h1>"Out-of-Order"</h1>
<Outlet/>
}
>
<Route path=StaticSegment("") view=Nested/>
<Route path=StaticSegment("inside") view=NestedResourceInside/>
<Route path=StaticSegment("single") view=Single/>
<Route path=StaticSegment("parallel") view=Parallel/>
<Route path=StaticSegment("inside-component") view=InsideComponent/>
<Route path=StaticSegment("local") view=LocalResource/>
<Route path=StaticSegment("none") view=None/>
</ParentRoute>
// in-order
<ParentRoute
path=StaticSegment("in-order")
ssr=SsrMode::InOrder
view=|| {
view! {
<SecondaryNav/>
<h1>"In-Order"</h1>
<Outlet/>
}
view=|| view! {
<SecondaryNav/>
<h1>"In-Order"</h1>
<Outlet/>
}
>
<Route path=StaticSegment("") view=Nested/>
<Route path=StaticSegment("inside") view=NestedResourceInside/>
<Route path=StaticSegment("single") view=Single/>
<Route path=StaticSegment("parallel") view=Parallel/>
<Route path=StaticSegment("inside-component") view=InsideComponent/>
<Route path=StaticSegment("local") view=LocalResource/>
<Route path=StaticSegment("none") view=None/>
</ParentRoute>
// async
<ParentRoute
path=StaticSegment("async")
ssr=SsrMode::Async
view=|| {
view! {
<SecondaryNav/>
<h1>"Async"</h1>
<Outlet/>
}
view=|| view! {
<SecondaryNav/>
<h1>"Async"</h1>
<Outlet/>
}
>
<Route path=StaticSegment("") view=Nested/>
<Route path=StaticSegment("inside") view=NestedResourceInside/>
<Route path=StaticSegment("single") view=Single/>
<Route path=StaticSegment("parallel") view=Parallel/>
<Route path=StaticSegment("inside-component") view=InsideComponent/>
<Route path=StaticSegment("local") view=LocalResource/>
<Route path=StaticSegment("none") view=None/>
</ParentRoute>
</Routes>
@@ -120,16 +108,11 @@ pub fn App() -> impl IntoView {
fn SecondaryNav() -> impl IntoView {
view! {
<nav>
<A href="" exact=true>
"Nested"
</A>
<A href="inside" exact=true>
"Nested (resource created inside)"
</A>
<A href="" exact=true>"Nested"</A>
<A href="inside" exact=true>"Nested (resource created inside)"</A>
<A href="single">"Single"</A>
<A href="parallel">"Parallel"</A>
<A href="inside-component">"Inside Component"</A>
<A href="local">"Local Resource"</A>
<A href="none">"No Resources"</A>
</nav>
}
@@ -143,28 +126,21 @@ fn Nested() -> impl IntoView {
view! {
<div>
<Suspense fallback=|| {
"Loading 1..."
}>
<Suspense fallback=|| "Loading 1...">
{move || {
one_second.get().map(|_| view! { <p id="loaded-1">"One Second: Loaded 1!"</p> })
one_second.get().map(|_| view! {
<p id="loaded-1">"One Second: Loaded 1!"</p>
})
}}
<Suspense fallback=|| {
"Loading 2..."
}>
<Suspense fallback=|| "Loading 2...">
{move || {
two_second
.get()
.map(|_| {
view! {
<p id="loaded-2">"Two Second: Loaded 2!"</p>
<button on:click=move |_| {
set_count.update(|n| *n += 1)
}>{count}</button>
}
})
two_second.get().map(|_| view! {
<p id="loaded-2">"Two Second: Loaded 2!"</p>
<button on:click=move |_| set_count.update(|n| *n += 1)>
{count}
</button>
})
}}
</Suspense>
</Suspense>
</div>
@@ -178,27 +154,25 @@ fn NestedResourceInside() -> impl IntoView {
view! {
<div>
<Suspense fallback=|| {
"Loading 1..."
}>
{Suspend::new(async move {
<Suspense fallback=|| "Loading 1...">
{Suspend(async move {
_ = one_second.await;
let two_second = Resource::new(
|| (),
move |_| async move { second_wait_fn(WAIT_TWO_SECONDS).await },
);
let two_second = Resource::new(|| (), move |_| async move {
second_wait_fn(WAIT_TWO_SECONDS).await
});
view! {
<p id="loaded-1">"One Second: Loaded 1!"</p>
<Suspense fallback=|| "Loading 2...">
<span id="loaded-2">
"Loaded 2 (created inside first suspense)!: "
{Suspend::new(async move { format!("{:?}", two_second.await) })}
{Suspend(async move { format!("{:?}", two_second.await)})}
</span>
<button on:click=move |_| set_count.update(|n| *n += 1)>{count}</button>
<button on:click=move |_| set_count.update(|n| *n += 1)>
{count}
</button>
</Suspense>
}
})}
</Suspense>
</div>
}
@@ -212,42 +186,25 @@ fn Parallel() -> impl IntoView {
view! {
<div>
<Suspense fallback=|| {
"Loading 1..."
}>
<Suspense fallback=|| "Loading 1...">
{move || {
one_second
.get()
.map(move |_| {
view! {
<p id="loaded-1">"One Second: Loaded 1!"</p>
<button on:click=move |_| {
set_count.update(|n| *n += 1)
}>{count}</button>
}
})
one_second.get().map(move |_| view! {
<p id="loaded-1">"One Second: Loaded 1!"</p>
<button on:click=move |_| set_count.update(|n| *n += 1)>
{count}
</button>
})
}}
</Suspense>
<Suspense fallback=|| {
"Loading 2..."
}>
<Suspense fallback=|| "Loading 2...">
{move || {
two_second
.get()
.map(move |_| {
view! {
<p id="loaded-2">"Two Second: Loaded 2!"</p>
<button
id="second-count"
on:click=move |_| set_count.update(|n| *n += 1)
>
{count}
</button>
}
})
two_second.get().map(move |_| view! {
<p id="loaded-2">"Two Second: Loaded 2!"</p>
<button id="second-count" on:click=move |_| set_count.update(|n| *n += 1)>
{count}
</button>
})
}}
</Suspense>
</div>
}
@@ -260,17 +217,18 @@ fn Single() -> impl IntoView {
view! {
<div>
<Suspense fallback=|| {
"Loading 1..."
}>
{move || {
one_second.get().map(|_| view! { <p id="loaded-1">"One Second: Loaded 1!"</p> })
}}
<Suspense fallback=|| "Loading 1...">
{move || {
one_second.get().map(|_| view! {
<p id="loaded-1">"One Second: Loaded 1!"</p>
})
}}
</Suspense>
<p id="following-message">"Children following Suspense should hydrate properly."</p>
<div>
<button on:click=move |_| set_count.update(|n| *n += 1)>{count}</button>
<button on:click=move |_| set_count.update(|n| *n += 1)>
{count}
</button>
</div>
</div>
}
@@ -286,7 +244,9 @@ fn InsideComponent() -> impl IntoView {
<InsideComponentChild/>
<p id="following-message">"Children following Suspense should hydrate properly."</p>
<div>
<button on:click=move |_| set_count.update(|n| *n += 1)>{count}</button>
<button on:click=move |_| set_count.update(|n| *n += 1)>
{count}
</button>
</div>
</div>
}
@@ -296,47 +256,16 @@ fn InsideComponent() -> impl IntoView {
fn InsideComponentChild() -> impl IntoView {
let one_second = Resource::new(|| WAIT_ONE_SECOND, first_wait_fn);
view! {
<Suspense fallback=|| {
"Loading 1..."
}>
{move || {
one_second.get().map(|_| view! { <p id="loaded-1">"One Second: Loaded 1!"</p> })
}}
<Suspense fallback=|| "Loading 1...">
{move || {
one_second.get().map(|_| view! {
<p id="loaded-1">"One Second: Loaded 1!"</p>
})
}}
</Suspense>
}
}
#[component]
fn LocalResource() -> impl IntoView {
let one_second = Resource::new(|| WAIT_ONE_SECOND, first_wait_fn);
let local = LocalResource::new(|| first_wait_fn(WAIT_ONE_SECOND));
let (count, set_count) = signal(0);
view! {
<div>
<Suspense fallback=|| {
"Loading 1..."
}>
{move || {
one_second.get().map(|_| view! { <p id="loaded-1">"One Second: Loaded 1!"</p> })
}}
{move || {
Suspend::new(async move {
let value = local.await;
view! { <p id="loaded-2">"One Second: Local Loaded " {value} "!"</p> }
})
}}
</Suspense>
<p id="following-message">"Children following Suspense should hydrate properly."</p>
<div>
<button on:click=move |_| set_count.update(|n| *n += 1)>{count}</button>
</div>
</div>
}
}
#[component]
fn None() -> impl IntoView {
let (count, set_count) = signal(0);
@@ -345,7 +274,9 @@ fn None() -> impl IntoView {
<div>
<Suspense fallback=|| "Loading 1...">
<p id="inside-message">"Children inside Suspense should hydrate properly."</p>
<button on:click=move |_| set_count.update(|n| *n += 1)>{count}</button>
<button on:click=move |_| set_count.update(|n| *n += 1)>
{count}
</button>
</Suspense>
<p id="following-message">"Children following Suspense should hydrate properly."</p>
<div>

View File

@@ -13,15 +13,19 @@ leptos_meta = { path = "../../meta" }
leptos_router = { path = "../../router" }
# dependencies for browser (enable when hydrate set)
console_error_panic_hook = { version = "0.1.7", optional = true }
wasm-bindgen = { version = "0.2.93", optional = true }
console_error_panic_hook = { version = "0.1", optional = true }
wasm-bindgen = { version = "0.2", optional = true }
# dependencies for server (enable when ssr set)
actix-files = { version = "0.6.6", optional = true }
actix-web = { version = "4.8", features = ["macros"], optional = true }
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", features = ["macros"], optional = true }
[features]
hydrate = ["leptos/hydrate", "dep:wasm-bindgen", "dep:console_error_panic_hook"]
hydrate = [
"leptos/hydrate",
"dep:wasm-bindgen",
"dep:console_error_panic_hook",
]
ssr = [
"leptos/ssr",
"leptos_meta/ssr",

View File

@@ -7,35 +7,32 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
axum = { version = "0.7.5", optional = true }
console_error_panic_hook = "0.1.7"
axum = { version = "0.7", optional = true }
console_error_panic_hook = "0.1"
leptos = { path = "../../leptos" }
leptos_meta = { path = "../../meta" }
leptos_axum = { path = "../../integrations/axum", optional = true }
leptos_router = { path = "../../router" }
tokio = { version = "1.39", features = [
"rt-multi-thread",
"macros",
], optional = true }
tower = { version = "0.4.13", optional = true }
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
wasm-bindgen = "0.2.93"
tokio = { version = "1", features = ["rt-multi-thread", "macros"], optional = true }
tower = { version = "0.4", optional = true }
tower-http = { version = "0.5", features = ["fs"], optional = true }
wasm-bindgen = "0.2"
thiserror = "1.0"
tracing = { version = "0.1.40", optional = true }
http = "1.1"
tracing = { version = "0.1", optional = true }
http = "1.0"
[features]
hydrate = ["leptos/hydrate"]
ssr = [
"dep:axum",
"dep:tokio",
"dep:tower",
"dep:tower-http",
"dep:leptos_axum",
"leptos/ssr",
"leptos_meta/ssr",
"leptos_router/ssr",
"dep:tracing",
"dep:axum",
"dep:tokio",
"dep:tower",
"dep:tower-http",
"dep:leptos_axum",
"leptos/ssr",
"leptos_meta/ssr",
"leptos_router/ssr",
"dep:tracing",
]
[package.metadata.cargo-all-features]

View File

@@ -7,5 +7,5 @@ edition = "2021"
leptos = { path = "../../leptos", features = ["csr"] }
leptos_meta = { path = "../../meta" }
leptos_router = { path = "../../router" }
gloo-net = { version = "0.6.0", features = ["http"] }
console_error_panic_hook = { version = "0.1.7" }
gloo-net = { version = "0.5", features = ["http"] }
console_error_panic_hook = { version = "0.1" }

View File

@@ -8,15 +8,15 @@ codegen-units = 1
lto = true
[dependencies]
leptos = { path = "../../leptos", features = ["csr"] }
console_log = "1.0"
log = "0.4.22"
leptos = { path = "../../leptos" }
console_log = "1"
log = "0.4"
console_error_panic_hook = "0.1.7"
wasm-bindgen = "0.2.93"
wasm-bindgen = "0.2"
[dependencies.web-sys]
version = "0.3.70"
version = "0.3"
features = ["Window"]
[dev-dependencies]
wasm-bindgen-test = "0.3.42"
wasm-bindgen-test = "0.3.0"

View File

@@ -7,36 +7,36 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
actix-files = { version = "0.6.6", optional = true }
actix-web = { version = "4.8", optional = true, features = ["macros"] }
anyhow = "1.0"
broadcaster = "1.0"
console_log = "1.0"
actix-files = { version = "0.6.2", optional = true }
actix-web = { version = "4.2.1", optional = true, features = ["macros"] }
anyhow = "1.0.68"
broadcaster = "1.0.0"
console_log = "1.0.0"
console_error_panic_hook = "0.1.7"
serde = { version = "1.0", features = ["derive"] }
futures = "0.3.30"
leptos = { path = "../../leptos" }
serde = { version = "1.0.152", features = ["derive"] }
futures = "0.3.25"
leptos = { path = "../../leptos"}
leptos_actix = { path = "../../integrations/actix", optional = true }
log = "0.4.22"
simple_logger = "5.0"
log = "0.4.17"
simple_logger = "4.0.0"
gloo = { git = "https://github.com/rustwasm/gloo" }
sqlx = { version = "0.8.0", features = [
"runtime-tokio-rustls",
"sqlite",
sqlx = { version = "0.6.2", features = [
"runtime-tokio-rustls",
"sqlite",
], optional = true }
wasm-bindgen = "0.2.93"
tokio = { version = "1.39", features = ["rt", "time"], optional = true }
wasm-bindgen = "0.2"
tokio = { version = "1", features = ["rt", "time"], optional = true }
server_fn = { path = "../../server_fn", features = ["cbor"] }
[features]
hydrate = ["leptos/hydrate"]
ssr = [
"dep:actix-files",
"dep:actix-web",
"dep:sqlx",
"leptos/ssr",
"leptos_actix",
"dep:tokio",
"dep:actix-files",
"dep:actix-web",
"dep:sqlx",
"leptos/ssr",
"leptos_actix",
"dep:tokio",
]
[package.metadata.cargo-all-features]

View File

@@ -4,14 +4,14 @@ version = "0.1.0"
edition = "2021"
[dev-dependencies]
anyhow = "1.0"
async-trait = "0.1.81"
cucumber = "0.21.1"
fantoccini = "0.21.1"
pretty_assertions = "1.4"
serde_json = "1.0"
tokio = { version = "1.39", features = ["macros", "rt-multi-thread", "time"] }
url = "2.5"
anyhow = "1.0.72"
async-trait = "0.1.72"
cucumber = "0.19.1"
fantoccini = "0.19.3"
pretty_assertions = "1.4.0"
serde_json = "1.0.104"
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread", "time"] }
url = "2.4.0"
[[test]]
name = "app_suite"

View File

@@ -113,7 +113,7 @@ pub fn Todos() -> impl IntoView {
);
let existing_todos = move || {
Suspend::new(async move {
Suspend(async move {
todos
.await
.map(|todos| {

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