mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 10:11:56 -05:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55ea00afdd | ||
|
|
f7d5567a35 | ||
|
|
61ca6465df | ||
|
|
10a833d763 | ||
|
|
17982e8ac5 | ||
|
|
6cfb6227f5 | ||
|
|
159852b8d7 | ||
|
|
6f95713b59 | ||
|
|
e17afd4559 | ||
|
|
e0aa1e245b | ||
|
|
7951a6e9cf | ||
|
|
1f39299303 | ||
|
|
2be4610233 | ||
|
|
af254e9b61 | ||
|
|
4aae8a5088 | ||
|
|
7ff044cef6 | ||
|
|
11122b575e | ||
|
|
a62ee4031b | ||
|
|
19b43607a1 | ||
|
|
884297706a | ||
|
|
fb4c208609 | ||
|
|
5701e74efb | ||
|
|
2afe8e202a |
11
.github/workflows/test.yml
vendored
11
.github/workflows/test.yml
vendored
@@ -39,16 +39,7 @@ jobs:
|
||||
- name: Run Rustfmt
|
||||
run: cargo fmt -- --check
|
||||
|
||||
- name: Cargo cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ matrix.rust }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Run tests with all features
|
||||
run: cargo make ci
|
||||
|
||||
51
CODE_OF_CONDUCT.md
Normal file
51
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
_This Code of Conduct is based on the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct)
|
||||
and the [Bevy Code of Conduct](https://raw.githubusercontent.com/bevyengine/bevy/main/CODE_OF_CONDUCT.md),
|
||||
which are adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling)
|
||||
and the [Contributor Covenant](https://www.contributor-covenant.org)._
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
We are a community of people learning and exploring how to build better web applications
|
||||
with Rust. When interacting with one another, please remember that there are no experts and there are
|
||||
no stupid questions. Assume the best in other people's communication, and take a step back if
|
||||
you find yourself getting defensive.
|
||||
|
||||
Please note the following guidelines as well:
|
||||
|
||||
* Please avoid using overtly sexual aliases or other nicknames that might detract from a friendly, safe and welcoming environment for all.
|
||||
* Please be kind and courteous. There’s no need to be mean or rude.
|
||||
* Respect that people have differences of opinion and that every design or implementation choice carries a trade-off and numerous costs. There is seldom a right answer.
|
||||
* Please keep unstructured critique to a minimum. If you have solid ideas you want to experiment with, make a fork and see how it works.
|
||||
* We will exclude you from interaction if you insult, demean or harass anyone. That is not welcome behavior. We interpret the term “harassment” as including the definition in the [Citizen Code of Conduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md); if you have any lack of clarity about what might be included in that concept, please read their definition. In particular, we don’t tolerate behavior that excludes people in socially marginalized groups.
|
||||
* Private harassment is also unacceptable. No matter who you are, if you feel you have been or are being harassed or made uncomfortable by a community member, please contact the maintainers immediately. Whether you’re a regular contributor or a newcomer, we care about making this community a safe place for you and we’ve got your back.
|
||||
* Do not make casual mention of slavery or indentured servitude and/or false comparisons of one's occupation or situation to slavery. Please consider using or asking about alternate terminology when referring to such metaphors in technology.
|
||||
* Likewise any spamming, trolling, flaming, baiting or other attention-stealing behavior is not welcome.
|
||||
|
||||
## Moderation
|
||||
|
||||
These are the policies for upholding [our community’s standards of conduct](#our-standards). If you feel that a thread needs moderation, please contact the maintainers.
|
||||
|
||||
1. Remarks that violate the community standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed. (Cursing is allowed, but never targeting another user, and never in a hateful manner).
|
||||
2. Remarks that maintainers find inappropriate, whether listed in the code of conduct or not, are also not allowed.
|
||||
3. Maintainers will first respond to such remarks with a warning.
|
||||
4. If the warning is unheeded, the user will be “kicked,” i.e., kicked out of the communication channel to cool off.
|
||||
5. If the user comes back and continues to make trouble, they will be banned, i.e., indefinitely excluded.
|
||||
6. Maintainers may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology.
|
||||
7. If a maintainer bans someone and you think it was unjustified, please take it up with that maintainer, or with a different maintainer, in private. Complaints about bans in-channel are not allowed.
|
||||
8. Maintainers are held to a higher standard than other community members. If a maintainer creates an inappropriate situation, they should expect less leeway than others.
|
||||
|
||||
The enforcement policies in the code of conduct apply to all official venues, including Discord channels, GitHub repositories, and all other forums.
|
||||
15
README.md
15
README.md
@@ -1,5 +1,3 @@
|
||||
**NOTE: We're in the middle of merging changes and making fixes to support our upcoming `0.1.0` release. Some of the examples may be in a broken state. You can continue using the `0.0` releases with no issues.**
|
||||
|
||||
<picture>
|
||||
<source srcset="https://raw.githubusercontent.com/leptos-rs/leptos/main/docs/logos/Leptos_logo_pref_dark_RGB.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">
|
||||
@@ -61,9 +59,9 @@ Leptos is a full-stack, isomorphic Rust web framework leveraging fine-grained re
|
||||
|
||||
Here are some resources for learning more about Leptos:
|
||||
|
||||
- [Examples](https://github.com/gbj/leptos/tree/main/examples)
|
||||
- [Examples](https://github.com/leptos-rs/leptos/tree/main/examples)
|
||||
- [API Documentation](https://docs.rs/leptos/latest/leptos/)
|
||||
- [Common Bugs](https://github.com/gbj/leptos/tree/main/docs/COMMON_BUGS.md) (and how to fix them!)
|
||||
- [Common Bugs](https://github.com/leptos-rs/leptos/tree/main/docs/COMMON_BUGS.md) (and how to fix them!)
|
||||
- Leptos Guide (in progress)
|
||||
|
||||
## `nightly` Note
|
||||
@@ -82,13 +80,13 @@ If you’re on `stable`, note the following:
|
||||
|
||||
1. You need to enable the `"stable"` flag in `Cargo.toml`: `leptos = { version = "0.1.0-alpha", features = ["stable"] }`
|
||||
2. `nightly` enables the function call syntax for accessing and setting signals. If you’re using `stable`,
|
||||
you’ll just call `.get()`, `.set()`, or `.update()` manually. Check out the
|
||||
[`counters-stable` example](https://github.com/gbj/leptos/blob/main/examples/counters-stable/src/main.rs)
|
||||
you’ll just call `.get()`, `.set()`, or `.update()` manually. Check out the
|
||||
[`counters_stable` example](https://github.com/leptos-rs/leptos/blob/main/examples/counters_stable/src/main.rs)
|
||||
for examples of the correct API.
|
||||
|
||||
## `cargo-leptos`
|
||||
|
||||
[`cargo-leptos`](https://github.com/akesson/cargo-leptos) is a build tool that's designed to make it easy to build apps that run on both the client and the server, with seamless integration. The best way to get started with a real Leptos project right now is to use `cargo-leptos` and our [starter template](https://github.com/leptos-rs/start).
|
||||
[`cargo-leptos`](https://github.com/leptos-rs/cargo-leptos) is a build tool that's designed to make it easy to build apps that run on both the client and the server, with seamless integration. The best way to get started with a real Leptos project right now is to use `cargo-leptos` and our [starter template](https://github.com/leptos-rs/start).
|
||||
|
||||
```bash
|
||||
cargo install cargo-leptos
|
||||
@@ -108,7 +106,7 @@ Sure! Obviously the `view` macro is for generating DOM nodes but you can use the
|
||||
- Use event listeners to update signals
|
||||
- Create effects to update the UI
|
||||
|
||||
I've put together a [very simple GTK example](https://github.com/gbj/leptos/blob/main/examples/gtk/src/main.rs) so you can see what I mean.
|
||||
I've put together a [very simple GTK example](https://github.com/leptos-rs/leptos/blob/main/examples/gtk/src/main.rs) so you can see what I mean.
|
||||
|
||||
### How is this different from Yew/Dioxus?
|
||||
|
||||
@@ -126,7 +124,6 @@ There are some practical differences that make a significant difference:
|
||||
|
||||
- **Maturity:** Sycamore is obviously a much more mature and stable library with a larger ecosystem.
|
||||
- **Templating:** Leptos uses a JSX-like template format (built on [syn-rsx](https://github.com/stoically/syn-rsx)) for its `view` macro. Sycamore offers the choice of its own templating DSL or a builder syntax.
|
||||
- **Template node cloning:** Leptos's `view` macro compiles to a static HTML string and a set of instructions of how to assign its reactive values. This means that at runtime, Leptos can clone a `<template>` node rather than calling `document.createElement()` to create DOM nodes. This is a _significantly_ faster way of rendering components.
|
||||
- **Read-write segregation:** Leptos, like Solid, encourages read-write segregation between signal getters and setters, so you end up accessing signals with tuples like `let (count, set_count) = create_signal(cx, 0);` _(If you prefer or if it's more convenient for your API, you can use `create_rw_signal` to give a unified read/write signal.)_
|
||||
- **Signals are functions:** In Leptos, you can call a signal to access it rather than calling a specific method (so, `count()` instead of `count.get()`) This creates a more consistent mental model: accessing a reactive value is always a matter of calling a function. For example:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Introduction
|
||||
|
||||
This book is intended as an introduction to the [Leptos](https://github.com/gbj/leptos) Web framework. Together, we’ll build a simple todo app—first as a client-side app, then as a full-stack app.
|
||||
This book is intended as an introduction to the [Leptos](https://github.com/leptos-rs/leptos) Web framework. Together, we’ll build a simple todo app—first as a client-side app, then as a full-stack app.
|
||||
|
||||
The guide doesn’t assume you know anything about fine-grained reactivity or the details of modern Web frameworks. It does assume you are familiar with the Rust programming language, HTML, CSS, and the DOM and other Web APIs.
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Getting Started
|
||||
|
||||
> The code for this chapter can be found [here](https://github.com/gbj/leptos/tree/main/docs/book/project/ch02_getting_started).
|
||||
> The code for this chapter can be found [here](https://github.com/leptos-rs/leptos/tree/main/docs/book/project/ch02_getting_started).
|
||||
|
||||
The easiest way to get started using Leptos is to use [Trunk](https://trunkrs.dev/), as many of our [examples](https://github.com/gbj/leptos/tree/main/examples) do. (Trunk is a simple build tool that includes a dev server.)
|
||||
The easiest way to get started using Leptos is to use [Trunk](https://trunkrs.dev/), as many of our [examples](https://github.com/leptos-rs/leptos/tree/main/examples) do. (Trunk is a simple build tool that includes a dev server.)
|
||||
|
||||
If you don’t already have it installed, you can install Trunk by running
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Templating: Building User Interfaces
|
||||
|
||||
> The code for this chapter can be found [here](https://github.com/gbj/leptos/tree/main/docs/book/project/ch03_building_ui).
|
||||
> The code for this chapter can be found [here](https://github.com/leptos-rs/leptos/tree/main/docs/book/project/ch03_building_ui).
|
||||
|
||||
## RSX and the `view!` macro
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ cargo leptos build --release
|
||||
## Server Side Rendering without cargo-leptos
|
||||
To run it as a server side app with hydration, you'll need to have wasm-pack installed.
|
||||
|
||||
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"pkg"`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time.
|
||||
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"."`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time. You'll also want to edit the call to `get_configuration()` to pass in `Some(Cargo.toml)`, so that Leptos will read the settings instead of cargo-leptos. If you do so, your file/folder names cannot include dashes.
|
||||
1. Install wasm-pack
|
||||
```bash
|
||||
cargo install wasm-pack
|
||||
|
||||
@@ -33,7 +33,9 @@ cfg_if! {
|
||||
|
||||
crate::counters::register_server_functions();
|
||||
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars.
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
|
||||
let addr = conf.leptos_options.site_address.clone();
|
||||
let routes = generate_route_list(|cx| view! { cx, <Counters/> });
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ cargo leptos build --release
|
||||
## Server Side Rendering without cargo-leptos
|
||||
To run it as a server side app with hydration, you'll need to have wasm-pack installed.
|
||||
|
||||
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"pkg"`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time.
|
||||
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"."`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time. You'll also want to edit the call to `get_configuration()` to pass in `Some(Cargo.toml)`, so that Leptos will read the settings instead of cargo-leptos. If you do so, your file/folder names cannot include dashes..
|
||||
1. Install wasm-pack
|
||||
```bash
|
||||
cargo install wasm-pack
|
||||
|
||||
@@ -21,7 +21,9 @@ cfg_if! {
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars.
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
|
||||
let addr = conf.leptos_options.site_address.clone();
|
||||
// Generate the list of routes in your Leptos App
|
||||
let routes = generate_route_list(|cx| view! { cx, <App/> });
|
||||
|
||||
@@ -25,7 +25,7 @@ cargo leptos build --release
|
||||
## Server Side Rendering without cargo-leptos
|
||||
To run it as a server side app with hydration, you'll need to have wasm-pack installed.
|
||||
|
||||
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"pkg"`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time.
|
||||
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"."`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time. You'll also want to edit the call to `get_configuration()` to pass in `Some(Cargo.toml)`, so that Leptos will read the settings instead of cargo-leptos. If you do so, your file/folder names cannot include dashes..
|
||||
1. Install wasm-pack
|
||||
```bash
|
||||
cargo install wasm-pack
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Leptos Starter Template
|
||||
|
||||
This is a template demonstrating how to integrate [TailwindCSS](https://tailwindcss.com/) with the [Leptos](https://github.com/gbj/leptos) web framework and the [cargo-leptos](https://github.com/akesson/cargo-leptos) tool.
|
||||
This is a template demonstrating how to integrate [TailwindCSS](https://tailwindcss.com/) with the [Leptos](https://github.com/leptos-rs/leptos) web framework and the [cargo-leptos](https://github.com/akesson/cargo-leptos) tool.
|
||||
|
||||
If you don't have `cargo-leptos` installed you can install it with
|
||||
|
||||
@@ -52,13 +52,11 @@ If you're using VS Code, add the following to your `settings.json`
|
||||
"css.validate": false,
|
||||
```
|
||||
|
||||
|
||||
Install [Tailwind CSS Intellisense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss).
|
||||
|
||||
Install "VS Browser" extension, a browser at the right window.
|
||||
Allow vscode Ports forward: 3000, 3001.
|
||||
|
||||
|
||||
## Notes about Tooling
|
||||
|
||||
By default, `cargo-leptos` uses `nightly` Rust, `cargo-generate`, and `sass`. If you run into any trouble, you may need to install one or more of these tools.
|
||||
@@ -72,8 +70,8 @@ By default, `cargo-leptos` uses `nightly` Rust, `cargo-generate`, and `sass`. If
|
||||
## Alternatives to cargo-leptos
|
||||
|
||||
This crate can be run without `cargo-leptos`, using `wasm-pack` and `cargo`. To do so, you'll need to install some other tools.
|
||||
|
||||
1. `cargo install wasm-pack`
|
||||
0. `cargo install wasm-pack`
|
||||
1. Edit the `[package.metadata.leptos]` section and set `site-root` to `"."`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time. You'll also want to edit the call to `get_configuration()` to pass in `Some(Cargo.toml)`, so that Leptos will read the settings instead of cargo-leptos. If you do so, your file/folder names cannot include dashes.
|
||||
|
||||
### Server Side Rendering With Hydration
|
||||
|
||||
@@ -91,15 +89,16 @@ Then run the server with `cargo run` to serve the server side rendered HTML and
|
||||
cargo run --no-default-features --features=ssr
|
||||
```
|
||||
|
||||
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above before running
|
||||
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above before running
|
||||
> `cargo run`
|
||||
|
||||
### Client Side Rendering
|
||||
|
||||
You'll need to install trunk to client side render this bundle.
|
||||
|
||||
1. `cargo install trunk`
|
||||
Then the site can be served with `trunk serve --open`
|
||||
Then the site can be served with `trunk serve --open`
|
||||
|
||||
## Attribution
|
||||
|
||||
Many thanks to GreatGreg for putting together this guide. You can find the original, with added details, [here](https://github.com/gbj/leptos/discussions/125).
|
||||
Many thanks to GreatGreg for putting together this guide. You can find the original, with added details, [here](https://github.com/leptos-rs/leptos/discussions/125).
|
||||
|
||||
@@ -17,7 +17,9 @@ cfg_if! {
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars.
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
|
||||
let addr = conf.leptos_options.site_address.clone();
|
||||
|
||||
// Generate the list of routes in your Leptos App
|
||||
|
||||
@@ -25,7 +25,7 @@ cargo leptos build --release
|
||||
## Server Side Rendering without cargo-leptos
|
||||
To run it as a server side app with hydration, you'll need to have wasm-pack installed.
|
||||
|
||||
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"pkg"`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS file is no longer processed by cargo-leptos. Building to alternative folders is not supported at this time
|
||||
0. Edit the `[package.metadata.leptos]` section and set `site-root` to `"."`. You'll also want to change the path of the `<StyleSheet / >` component in the root component to point towards the CSS file in the root. This tells leptos that the WASM/JS files generated by wasm-pack are available at `./pkg` and that the CSS files are no longer processed by cargo-leptos. Building to alternative folders is not supported at this time. You'll also want to edit the call to `get_configuration()` to pass in `Some(Cargo.toml)`, so that Leptos will read the settings instead of cargo-leptos. If you do so, your file/folder names cannot include dashes.
|
||||
1. Install wasm-pack
|
||||
```bash
|
||||
cargo install wasm-pack
|
||||
|
||||
@@ -26,7 +26,9 @@ cfg_if! {
|
||||
|
||||
crate::todo::register_server_functions();
|
||||
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars.
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
|
||||
let addr = conf.leptos_options.site_address.clone();
|
||||
|
||||
// Generate the list of routes in your Leptos App
|
||||
|
||||
@@ -26,7 +26,8 @@ if #[cfg(feature = "ssr")] {
|
||||
|
||||
crate::todo::register_server_functions();
|
||||
|
||||
let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
|
||||
// Setting this to None means we'll be using cargo-leptos and its env vars
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
let leptos_options = conf.leptos_options;
|
||||
let addr = leptos_options.site_address.clone();
|
||||
let routes = generate_route_list(|cx| view! { cx, <TodoApp/> }).await;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[package]
|
||||
name = "leptos_actix"
|
||||
version = {workspace = true}
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/gbj/leptos"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Actix integrations for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -259,20 +259,22 @@ where
|
||||
let options = options.clone();
|
||||
let app_fn = app_fn.clone();
|
||||
let res_options = ResponseOptions::default();
|
||||
let status = RouterStatusContext::default();
|
||||
|
||||
async move {
|
||||
let app = {
|
||||
let app_fn = app_fn.clone();
|
||||
let res_options = res_options.clone();
|
||||
let status = status.clone();
|
||||
move |cx| {
|
||||
provide_contexts(cx, &req, res_options);
|
||||
provide_contexts(cx, &req, res_options, status);
|
||||
(app_fn)(cx).into_view(cx)
|
||||
}
|
||||
};
|
||||
|
||||
let (head, tail) = html_parts(&options);
|
||||
|
||||
stream_app(app, head, tail, res_options).await
|
||||
stream_app(app, head, tail, res_options, status).await
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -336,6 +338,7 @@ where
|
||||
let app_fn = app_fn.clone();
|
||||
let data_fn = data_fn.clone();
|
||||
let res_options = ResponseOptions::default();
|
||||
let status = RouterStatusContext::default();
|
||||
|
||||
async move {
|
||||
let data = match data_fn(req.clone()).await {
|
||||
@@ -347,24 +350,31 @@ where
|
||||
let app = {
|
||||
let app_fn = app_fn.clone();
|
||||
let res_options = res_options.clone();
|
||||
let status = status.clone();
|
||||
move |cx| {
|
||||
provide_contexts(cx, &req, res_options);
|
||||
provide_contexts(cx, &req, res_options, status);
|
||||
(app_fn)(cx, data).into_view(cx)
|
||||
}
|
||||
};
|
||||
|
||||
let (head, tail) = html_parts(&options);
|
||||
|
||||
stream_app(app, head, tail, res_options).await
|
||||
stream_app(app, head, tail, res_options, status).await
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn provide_contexts(cx: leptos::Scope, req: &HttpRequest, res_options: ResponseOptions) {
|
||||
fn provide_contexts(
|
||||
cx: leptos::Scope,
|
||||
req: &HttpRequest,
|
||||
res_options: ResponseOptions,
|
||||
status: RouterStatusContext,
|
||||
) {
|
||||
let path = leptos_corrected_path(req);
|
||||
|
||||
let integration = ServerIntegration { path };
|
||||
provide_context(cx, RouterIntegrationContext::new(integration));
|
||||
provide_context(cx, status);
|
||||
provide_context(cx, MetaContext::new());
|
||||
provide_context(cx, res_options);
|
||||
provide_context(cx, req.clone());
|
||||
@@ -385,6 +395,7 @@ async fn stream_app(
|
||||
head: String,
|
||||
tail: String,
|
||||
res_options: ResponseOptions,
|
||||
router_status: RouterStatusContext,
|
||||
) -> HttpResponse<BoxBody> {
|
||||
let (stream, runtime, _) = render_to_stream_with_prefix_undisposed(app, move |cx| {
|
||||
let head = use_context::<MetaContext>(cx)
|
||||
@@ -411,7 +422,15 @@ async fn stream_app(
|
||||
let res_options = res_options.0.read().await;
|
||||
|
||||
let (status, mut headers) = (res_options.status, res_options.headers.clone());
|
||||
let status = status.unwrap_or_default();
|
||||
let status = status.unwrap_or_else(|| {
|
||||
router_status
|
||||
.status
|
||||
.read()
|
||||
.ok()
|
||||
.and_then(|s| s.map(|s| StatusCode::from_u16(s).ok()))
|
||||
.flatten()
|
||||
.unwrap_or_default()
|
||||
});
|
||||
|
||||
let complete_stream = futures::stream::iter([
|
||||
first_chunk.unwrap(),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[package]
|
||||
name = "leptos_axum"
|
||||
version = {workspace = true}
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/gbj/leptos"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "Axum integrations for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -318,6 +318,8 @@ where
|
||||
let default_res_options = ResponseOptions::default();
|
||||
let res_options2 = default_res_options.clone();
|
||||
let res_options3 = default_res_options.clone();
|
||||
let router_status = RouterStatusContext::default();
|
||||
let router_status2 = router_status.clone();
|
||||
|
||||
async move {
|
||||
// Need to get the path and query string of the Request
|
||||
@@ -403,6 +405,7 @@ where
|
||||
cx,
|
||||
RouterIntegrationContext::new(integration),
|
||||
);
|
||||
provide_context(cx, router_status2);
|
||||
provide_context(cx, MetaContext::new());
|
||||
provide_context(cx, req_parts);
|
||||
provide_context(cx, default_res_options);
|
||||
@@ -471,9 +474,16 @@ where
|
||||
Box::pin(complete_stream) as PinnedHtmlStream
|
||||
));
|
||||
|
||||
if let Some(status) = res_options.status {
|
||||
*res.status_mut() = status
|
||||
}
|
||||
let status = res_options.status.unwrap_or_else(|| {
|
||||
router_status
|
||||
.status
|
||||
.read()
|
||||
.ok()
|
||||
.and_then(|s| s.map(|s| StatusCode::from_u16(s).ok()))
|
||||
.flatten()
|
||||
.unwrap_or_default()
|
||||
});
|
||||
*res.status_mut() = status;
|
||||
let mut res_headers = res_options.headers.clone();
|
||||
res.headers_mut().extend(res_headers.drain());
|
||||
|
||||
|
||||
@@ -19,33 +19,33 @@
|
||||
//! 1. You need to enable the `"stable"` flag in `Cargo.toml`: `leptos = { version = "0.0", features = ["stable"] }`
|
||||
//! 2. `nightly` enables the function call syntax for accessing and setting signals. If you’re using `stable`,
|
||||
//! you’ll just call `.get()`, `.set()`, or `.update()` manually. Check out the
|
||||
//! [`counters_stable` example](https://github.com/gbj/leptos/blob/main/examples/counters_stable/src/main.rs)
|
||||
//! [`counters_stable` example](https://github.com/leptos-rs/leptos/blob/main/examples/counters_stable/src/main.rs)
|
||||
//! for examples of the correct API.
|
||||
//!
|
||||
//! # Learning by Example
|
||||
//!
|
||||
//! These docs are a work in progress. If you want to see what Leptos is capable of, check out
|
||||
//! the [examples](https://github.com/gbj/leptos/tree/main/examples):
|
||||
//! - [`counter`](https://github.com/gbj/leptos/tree/main/examples/counter) is the classic
|
||||
//! the [examples](https://github.com/leptos-rs/leptos/tree/main/examples):
|
||||
//! - [`counter`](https://github.com/leptos-rs/leptos/tree/main/examples/counter) is the classic
|
||||
//! counter example, showing the basics of client-side rendering and reactive DOM updates
|
||||
//! - [`counters`](https://github.com/gbj/leptos/tree/main/examples/counter) introduces parent-child
|
||||
//! - [`counters`](https://github.com/leptos-rs/leptos/tree/main/examples/counters) introduces parent-child
|
||||
//! communication via contexts, and the `<For/>` component for efficient keyed list updates.
|
||||
//! - [`parent_child`](https://github.com/gbj/leptos/tree/main/examples/parent_child) shows four different
|
||||
//! - [`parent_child`](https://github.com/leptos-rs/leptos/tree/main/examples/parent_child) shows four different
|
||||
//! ways a parent component can communicate with a child, including passing a closure, context, and more
|
||||
//! - [`todomvc`](https://github.com/gbj/leptos/tree/main/examples/todomvc) implements the classic to-do
|
||||
//! - [`todomvc`](https://github.com/leptos-rs/leptos/tree/main/examples/todomvc) implements the classic to-do
|
||||
//! app in Leptos. This is a good example of a complete, simple app. In particular, you might want to
|
||||
//! see how we use [create_effect] to [serialize JSON to `localStorage`](https://github.com/gbj/leptos/blob/16f084a71268ac325fbc4a5e50c260df185eadb6/examples/todomvc/src/lib.rs#L164)
|
||||
//! and [reactively call DOM methods](https://github.com/gbj/leptos/blob/6d7c36655c9e7dcc3a3ad33d2b846a3f00e4ae74/examples/todomvc/src/lib.rs#L291)
|
||||
//! on [references to elements](https://github.com/gbj/leptos/blob/6d7c36655c9e7dcc3a3ad33d2b846a3f00e4ae74/examples/todomvc/src/lib.rs#L254).
|
||||
//! - [`fetch`](https://github.com/gbj/leptos/tree/main/examples/fetch) introduces
|
||||
//! see how we use [create_effect] to [serialize JSON to `localStorage`](https://github.com/leptos-rs/leptos/blob/16f084a71268ac325fbc4a5e50c260df185eadb6/examples/todomvc/src/lib.rs#L164)
|
||||
//! and [reactively call DOM methods](https://github.com/leptos-rs/leptos/blob/6d7c36655c9e7dcc3a3ad33d2b846a3f00e4ae74/examples/todomvc/src/lib.rs#L291)
|
||||
//! on [references to elements](https://github.com/leptos-rs/leptos/blob/6d7c36655c9e7dcc3a3ad33d2b846a3f00e4ae74/examples/todomvc/src/lib.rs#L254).
|
||||
//! - [`fetch`](https://github.com/leptos-rs/leptos/tree/main/examples/fetch) introduces
|
||||
//! [Resource](leptos_reactive::Resource)s, which allow you to integrate arbitrary `async` code like an
|
||||
//! HTTP request within your reactive code.
|
||||
//! - [`router`](https://github.com/gbj/leptos/tree/main/examples/router) shows how to use Leptos’s nested router
|
||||
//! - [`router`](https://github.com/leptos-rs/leptos/tree/main/examples/router) shows how to use Leptos’s nested router
|
||||
//! to enable client-side navigation and route-specific, reactive data loading.
|
||||
//! - [`todomvc`](https://github.com/gbj/leptos/tree/main/examples/todomvc) shows the basics of building an
|
||||
//! - [`todomvc`](https://github.com/leptos-rs/leptos/tree/main/examples/todomvc) shows the basics of building an
|
||||
//! isomorphic web app. Both the server and the client import the same app code from the `todomvc` example.
|
||||
//! The server renders the app directly to an HTML string, and the client hydrates that HTML to make it interactive.
|
||||
//! - [`hackernews`](https://github.com/gbj/leptos/tree/main/examples/hackernews) pulls everything together.
|
||||
//! - [`hackernews`](https://github.com/leptos-rs/leptos/tree/main/examples/hackernews) pulls everything together.
|
||||
//! It integrates calls to a real external REST API, routing, server-side rendering and hydration to create
|
||||
//! a fully-functional PEMPA that works as intended even before WASM has loaded and begun to run.
|
||||
//!
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos_dom::{Component, DynChild, Fragment, IntoView};
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
use leptos_dom::{HydrationCtx, HydrationKey};
|
||||
use leptos_dom::HydrationCtx;
|
||||
use leptos_dom::{DynChild, Fragment, IntoView};
|
||||
use leptos_macro::component;
|
||||
use leptos_reactive::{provide_context, Scope, SuspenseContext};
|
||||
use std::rc::Rc;
|
||||
@@ -73,7 +73,7 @@ where
|
||||
|
||||
let orig_child = Rc::new(children);
|
||||
|
||||
Component::new("Suspense", move |cx| {
|
||||
leptos_dom::custom(cx, leptos_dom::Custom::new("leptos-suspense")).child({
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
let current_id = HydrationCtx::peek();
|
||||
|
||||
@@ -104,13 +104,9 @@ where
|
||||
¤t_id.to_string(),
|
||||
{
|
||||
let current_id = current_id.clone();
|
||||
let fragment_id = HydrationKey {
|
||||
previous: current_id.previous,
|
||||
offset: current_id.offset + 1
|
||||
};
|
||||
move || {
|
||||
HydrationCtx::continue_from(fragment_id);
|
||||
orig_child(cx)
|
||||
HydrationCtx::continue_from(current_id.clone());
|
||||
DynChild::new(move || orig_child(cx))
|
||||
.into_view(cx)
|
||||
.render_to_string(cx)
|
||||
.to_string()
|
||||
|
||||
@@ -152,7 +152,10 @@ impl TryFrom<String> for Env {
|
||||
}
|
||||
|
||||
/// Loads [LeptosOptions] from a Cargo.toml with layered overrides. If an env var is specified, like `LEPTOS_ENV`,
|
||||
/// it will override a setting in the file.
|
||||
/// it will override a setting in the file. It takes in an optional path to a Cargo.toml file. If None is provided,
|
||||
/// you'll need to set the options as environment variables or rely on the defaults. This is the preferred
|
||||
/// approach for cargo-leptos. If Some("./Cargo.toml") is provided, Leptos will read in the settings itself. This
|
||||
/// option currently does not allow dashes in file or foldernames, as all dashes become underscores
|
||||
pub async fn get_configuration(path: Option<&str>) -> Result<ConfFile, LeptosConfigError> {
|
||||
if let Some(path) = path {
|
||||
let text = fs::read_to_string(path).map_err(|_| LeptosConfigError::ConfigNotFound)?;
|
||||
|
||||
@@ -4,7 +4,7 @@ version = { workspace = true }
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/gbj/leptos"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "DOM operations for the Leptos web framework."
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -165,6 +165,66 @@ pub struct Custom {
|
||||
id: HydrationKey,
|
||||
}
|
||||
|
||||
impl Custom {
|
||||
pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
|
||||
let name = name.into();
|
||||
let id = HydrationCtx::id();
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
let element = if HydrationCtx::is_hydrating() {
|
||||
if let Some(el) = crate::document().get_element_by_id(&format!("_{id}")) {
|
||||
#[cfg(debug_assertions)]
|
||||
assert_eq!(
|
||||
el.node_name().to_ascii_uppercase(),
|
||||
name.to_ascii_uppercase(),
|
||||
"SSR and CSR elements have the same `TopoId` but different node \
|
||||
kinds. This is either a discrepancy between SSR and CSR rendering
|
||||
logic, which is considered a bug, or it can also be a \
|
||||
leptos hydration issue."
|
||||
);
|
||||
|
||||
el.remove_attribute("id").unwrap();
|
||||
|
||||
el.unchecked_into()
|
||||
} else if let Ok(Some(el)) =
|
||||
crate::document().query_selector(&format!("[leptos-hk=_{id}]"))
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
assert_eq!(
|
||||
el.node_name().to_ascii_uppercase(),
|
||||
name.to_ascii_uppercase(),
|
||||
"SSR and CSR elements have the same `TopoId` but different node \
|
||||
kinds. This is either a discrepancy between SSR and CSR rendering
|
||||
logic, which is considered a bug, or it can also be a \
|
||||
leptos hydration issue."
|
||||
);
|
||||
|
||||
el.remove_attribute("leptos-hk").unwrap();
|
||||
|
||||
el.unchecked_into()
|
||||
} else {
|
||||
gloo::console::warn!(
|
||||
"element with id",
|
||||
format!("_{id}"),
|
||||
"not found, ignoring it for hydration"
|
||||
);
|
||||
|
||||
crate::document().create_element(&name).unwrap()
|
||||
}
|
||||
} else {
|
||||
crate::document().create_element(&name).unwrap()
|
||||
};
|
||||
|
||||
Self {
|
||||
name,
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
element: element.unchecked_into(),
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
impl std::ops::Deref for Custom {
|
||||
type Target = web_sys::HtmlElement;
|
||||
|
||||
@@ -149,45 +149,19 @@ pub fn render_to_stream_with_prefix_undisposed(
|
||||
|
||||
// resources and fragments
|
||||
// stream HTML for each <Suspense/> as it resolves
|
||||
let fragments = fragments.map(|(fragment_id, id_before_suspense, html)| {
|
||||
cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
_ = id_before_suspense;
|
||||
// Debug-mode <Suspense/>-replacement code
|
||||
format!(
|
||||
r#"
|
||||
<template id="{fragment_id}f">{html}</template>
|
||||
<script>
|
||||
var start = document.getElementById("_{fragment_id}o");
|
||||
var end = document.getElementById("_{fragment_id}c");
|
||||
var range = new Range();
|
||||
range.setStartBefore(start.nextSibling.nextSibling);
|
||||
range.setEndAfter(end.previousSibling.previousSibling);
|
||||
range.deleteContents();
|
||||
var tpl = document.getElementById("{fragment_id}f");
|
||||
end.parentNode.insertBefore(tpl.content.cloneNode(true), end.previousSibling);
|
||||
</script>
|
||||
"#
|
||||
)
|
||||
} else {
|
||||
// Release-mode <Suspense/>-replacement code
|
||||
format!(
|
||||
r#"
|
||||
<template id="{fragment_id}f">{html}</template>
|
||||
<script>
|
||||
var start = document.getElementById("_{id_before_suspense}");
|
||||
var end = document.getElementById("_{fragment_id}");
|
||||
var range = new Range();
|
||||
range.setStartAfter(start);
|
||||
range.setEndBefore(end);
|
||||
range.deleteContents();
|
||||
var tpl = document.getElementById("{fragment_id}f");
|
||||
end.parentNode.insertBefore(tpl.content.cloneNode(true), end.previousSibling);
|
||||
</script>
|
||||
"#
|
||||
)
|
||||
}
|
||||
}
|
||||
// TODO can remove id_before_suspense entirely now
|
||||
let fragments = fragments.map(|(fragment_id, _, html)| {
|
||||
format!(
|
||||
r#"
|
||||
<template id="{fragment_id}f">{html}</template>
|
||||
<script>
|
||||
var placeholder = document.getElementById("_{fragment_id}");
|
||||
var tpl = document.getElementById("{fragment_id}f");
|
||||
placeholder.textContent = "";
|
||||
placeholder.append(tpl.content.cloneNode(true));
|
||||
</script>
|
||||
"#
|
||||
)
|
||||
});
|
||||
// stream data for each Resource as it resolves
|
||||
let resources = serializers.map(|(id, json)| {
|
||||
@@ -425,6 +399,7 @@ impl View {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn to_kebab_case(name: &str) -> String {
|
||||
if name.is_empty() {
|
||||
return String::new();
|
||||
|
||||
@@ -4,7 +4,7 @@ version = { workspace = true }
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/gbj/leptos"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "view macro for the Leptos web framework."
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -49,7 +49,7 @@ pub fn server_macro_impl(args: proc_macro::TokenStream, s: TokenStream2) -> Resu
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let url = format!("{}/{}", span.source_file().path().to_string_lossy(), fn_name_as_str).replace('/', "-");
|
||||
#[cfg(target_os = "windows")]
|
||||
let url = format!("{}/{}", span.source_file().path().to_string_lossy(), fn_name_as_str).replace("\\", "-");
|
||||
let url = format!("{}\\{}", span.source_file().path().to_string_lossy(), fn_name_as_str).replace("\\", "-");
|
||||
} else {
|
||||
let url = fn_name_as_str;
|
||||
}
|
||||
|
||||
@@ -554,7 +554,7 @@ fn element_to_tokens(cx: &Ident, node: &NodeElement, mut parent_type: TagType) -
|
||||
let tag = node.name.to_string();
|
||||
let name = if is_custom_element(&tag) {
|
||||
let name = node.name.to_string();
|
||||
quote! { leptos::leptos_dom::custom(#cx, #name) }
|
||||
quote! { leptos::leptos_dom::custom(#cx, leptos::leptos_dom::Custom::new(#name)) }
|
||||
} else if is_svg_element(&tag) {
|
||||
let name = &node.name;
|
||||
parent_type = TagType::Svg;
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
use crate::{create_memo, IntoSignalSetter, RwSignal, Scope, Signal, SignalSetter};
|
||||
|
||||
/// derives a reactive slice from an [RwSignal](crate::RwSignal)
|
||||
/// Derives a reactive slice of an [RwSignal](crate::RwSignal).
|
||||
///
|
||||
/// Slices have the same guarantees as [Memos](crate::Memo),
|
||||
/// Slices have the same guarantees as [Memos](crate::Memo):
|
||||
/// they only emit their value when it has actually been changed.
|
||||
///
|
||||
/// slices need a getter and a setter, and you must make sure that
|
||||
/// Slices need a getter and a setter, and you must make sure that
|
||||
/// the setter and getter only touch their respective field and nothing else.
|
||||
/// They optimally should not have any side effects.
|
||||
///
|
||||
/// you can use slices whenever you want to react to only parts
|
||||
/// of a bigger signal, the prime example would be state management
|
||||
/// where you want all state variables grouped up but also need
|
||||
/// You can use slices whenever you want to react to only parts
|
||||
/// of a bigger signal. The prime example would be state management,
|
||||
/// where you want all state variables grouped together, but also need
|
||||
/// fine-grained signals for each or some of these variables.
|
||||
/// In the example below, setting an auth token will only trigger
|
||||
/// the token signal, but none of the other derived signals.
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos_reactive::*;
|
||||
/// # let (cx, disposer) = raw_scope_and_disposer(create_runtime());
|
||||
@@ -49,13 +48,14 @@ use crate::{create_memo, IntoSignalSetter, RwSignal, Scope, Signal, SignalSetter
|
||||
/// );
|
||||
/// let count_token_updates = create_rw_signal(cx, 0);
|
||||
/// count_token_updates.with(|counter| assert_eq!(counter, &0));
|
||||
/// create_effect(cx, move |_| {
|
||||
/// token.with(|_| {});
|
||||
/// create_isomorphic_effect(cx, move |_| {
|
||||
/// _ = token.with(|_| {});
|
||||
/// count_token_updates.update(|counter| *counter += 1)
|
||||
/// });
|
||||
/// count_token_updates.with(|counter| assert_eq!(counter, &1));
|
||||
/// set_token.set("this is not a token!".into());
|
||||
/// // token was updated with the new token
|
||||
/// token.with(|token| assert_eq!(token, "this is not a token!"));
|
||||
/// count_token_updates.with(|counter| assert_eq!(counter, &2));
|
||||
/// set_dark_mode.set(true);
|
||||
/// // since token didn't change, there was also no update emitted
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! # Leptos Meta
|
||||
//!
|
||||
//! Leptos Meta allows you to modify content in a document’s `<head>` from within components
|
||||
//! using the [Leptos](https://github.com/gbj/leptos) web framework.
|
||||
//! using the [Leptos](https://github.com/leptos-rs/leptos) web framework.
|
||||
//!
|
||||
//! Document metadata is updated automatically when running in the browser. For server-side
|
||||
//! rendering, after the component tree is rendered to HTML, [MetaContext::dehydrate] can generate
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::{use_head, TextProp};
|
||||
/// <main>
|
||||
/// <Meta charset="utf-8"/>
|
||||
/// <Meta name="description" content="A Leptos fan site."/>
|
||||
/// <Meta http_equiv="refresh" content="3;url=https://github.com/gbj/leptos"/>
|
||||
/// <Meta http_equiv="refresh" content="3;url=https://github.com/leptos-rs/leptos"/>
|
||||
/// </main>
|
||||
/// }
|
||||
/// }
|
||||
|
||||
@@ -138,7 +138,7 @@ impl RouteContext {
|
||||
self.inner.params
|
||||
}
|
||||
|
||||
pub(crate) fn base(cx: Scope, path: &str, fallback: Option<fn() -> View>) -> Self {
|
||||
pub(crate) fn base(cx: Scope, path: &str, fallback: Option<fn(Scope) -> View>) -> Self {
|
||||
Self {
|
||||
inner: Rc::new(RouteContextInner {
|
||||
cx,
|
||||
@@ -148,7 +148,7 @@ impl RouteContext {
|
||||
path: path.to_string(),
|
||||
original_path: path.to_string(),
|
||||
params: create_memo(cx, |_| ParamsMap::new()),
|
||||
outlet: Box::new(move || fallback.map(|f| f().into_view(cx))),
|
||||
outlet: Box::new(move || fallback.as_ref().map(move |f| f(cx))),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
use cfg_if::cfg_if;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
rc::Rc,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use leptos::*;
|
||||
use thiserror::Error;
|
||||
@@ -28,7 +32,7 @@ pub fn Router(
|
||||
base: Option<&'static str>,
|
||||
/// A fallback that should be shown if no route is matched.
|
||||
#[prop(optional)]
|
||||
fallback: Option<fn() -> View>,
|
||||
fallback: Option<fn(Scope) -> View>,
|
||||
/// The `<Router/>` should usually wrap your whole page. It can contain
|
||||
/// any elements, and should include a [Routes](crate::Routes) component somewhere
|
||||
/// to define and display [Route](crate::Route)s.
|
||||
@@ -61,6 +65,14 @@ pub(crate) struct RouterContextInner {
|
||||
set_state: WriteSignal<State>,
|
||||
}
|
||||
|
||||
/// Context type that indicates the status of the last request
|
||||
/// (i.e., whether it was not found, or had an error.)
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct RouterStatusContext {
|
||||
pub status: Arc<RwLock<Option<u16>>>,
|
||||
pub message: Arc<RwLock<Option<String>>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for RouterContextInner {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("RouterContextInner")
|
||||
@@ -80,7 +92,7 @@ impl RouterContext {
|
||||
pub(crate) fn new(
|
||||
cx: Scope,
|
||||
base: Option<&'static str>,
|
||||
fallback: Option<fn() -> View>,
|
||||
fallback: Option<fn(Scope) -> View>,
|
||||
) -> Self {
|
||||
cfg_if! {
|
||||
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::{
|
||||
expand_optionals, get_route_matches, join_paths, Branch, Matcher, RouteDefinition,
|
||||
RouteMatch,
|
||||
},
|
||||
RouteContext, RouterContext,
|
||||
RouteContext, RouterContext, RouterStatusContext,
|
||||
};
|
||||
|
||||
/// Contains route definitions and manages the actual routing process.
|
||||
@@ -28,6 +28,7 @@ pub fn Routes(
|
||||
log::warn!("<Routes/> component should be nested within a <Router/>.");
|
||||
panic!()
|
||||
});
|
||||
let base_route = router.base();
|
||||
|
||||
let mut branches = Vec::new();
|
||||
let id_before = HydrationCtx::peek();
|
||||
@@ -189,19 +190,35 @@ pub fn Routes(
|
||||
});
|
||||
|
||||
// show the root route
|
||||
let router_status = use_context::<RouterStatusContext>(cx);
|
||||
let root = create_memo(cx, move |prev| {
|
||||
provide_context(cx, route_states);
|
||||
let router_status = router_status.clone();
|
||||
route_states.with(|state| {
|
||||
let root = state.routes.borrow();
|
||||
let root = root.get(0);
|
||||
if let Some(route) = root {
|
||||
provide_context(cx, route.clone());
|
||||
}
|
||||
|
||||
if prev.is_none() || !root_equal.get() {
|
||||
root.as_ref().map(|route| route.outlet().into_view(cx))
|
||||
if state.routes.borrow().is_empty() {
|
||||
if let Some(status) = router_status {
|
||||
if let Ok(mut lock) = status.status.write() {
|
||||
*lock = Some(404);
|
||||
}
|
||||
}
|
||||
Some(base_route.outlet().into_view(cx))
|
||||
} else {
|
||||
prev.cloned().unwrap()
|
||||
if let Some(status) = router_status {
|
||||
if let Ok(mut lock) = status.status.write() {
|
||||
*lock = None;
|
||||
}
|
||||
}
|
||||
let root = state.routes.borrow();
|
||||
let root = root.get(0);
|
||||
if let Some(route) = root {
|
||||
provide_context(cx, route.clone());
|
||||
}
|
||||
|
||||
if prev.is_none() || !root_equal.get() {
|
||||
root.as_ref().map(|route| route.outlet().into_view(cx))
|
||||
} else {
|
||||
prev.cloned().unwrap()
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user