initial commit
This commit is contained in:
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Generated by nix
|
||||||
|
.direnv/
|
||||||
|
result/
|
||||||
|
|
||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/debug
|
||||||
|
/target
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
265
AGENTS.md
Normal file
265
AGENTS.md
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
You are an expert [0.7 Dioxus](https://dioxuslabs.com/learn/0.7) assistant. Dioxus 0.7 changes every api in dioxus. Only use this up to date documentation. `cx`, `Scope`, and `use_state` are gone
|
||||||
|
|
||||||
|
Provide concise code examples with detailed descriptions
|
||||||
|
|
||||||
|
# Dioxus Dependency
|
||||||
|
|
||||||
|
You can add Dioxus to your `Cargo.toml` like this:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
dioxus = { version = "0.7.1" }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["web", "webview", "server"]
|
||||||
|
web = ["dioxus/web"]
|
||||||
|
webview = ["dioxus/desktop"]
|
||||||
|
server = ["dioxus/server"]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Launching your application
|
||||||
|
|
||||||
|
You need to create a main function that sets up the Dioxus runtime and mounts your root component.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dioxus::launch(App);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn App() -> Element {
|
||||||
|
rsx! { "Hello, Dioxus!" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then serve with `dx serve`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -sSL http://dioxus.dev/install.sh | sh
|
||||||
|
dx serve
|
||||||
|
```
|
||||||
|
|
||||||
|
# UI with RSX
|
||||||
|
|
||||||
|
```rust
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: "container", // Attribute
|
||||||
|
color: "red", // Inline styles
|
||||||
|
width: if condition { "100%" }, // Conditional attributes
|
||||||
|
"Hello, Dioxus!"
|
||||||
|
}
|
||||||
|
// Prefer loops over iterators
|
||||||
|
for i in 0..5 {
|
||||||
|
div { "{i}" } // use elements or components directly in loops
|
||||||
|
}
|
||||||
|
if condition {
|
||||||
|
div { "Condition is true!" } // use elements or components directly in conditionals
|
||||||
|
}
|
||||||
|
|
||||||
|
{children} // Expressions are wrapped in brace
|
||||||
|
{(0..5).map(|i| rsx! { span { "Item {i}" } })} // Iterators must be wrapped in braces
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Assets
|
||||||
|
|
||||||
|
The asset macro can be used to link to local files to use in your project. All links start with `/` and are relative to the root of your project.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
rsx! {
|
||||||
|
img {
|
||||||
|
src: asset!("/assets/image.png"),
|
||||||
|
alt: "An image",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Styles
|
||||||
|
|
||||||
|
The `document::Stylesheet` component will inject the stylesheet into the `<head>` of the document
|
||||||
|
|
||||||
|
```rust
|
||||||
|
rsx! {
|
||||||
|
document::Stylesheet {
|
||||||
|
href: asset!("/assets/styles.css"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Components
|
||||||
|
|
||||||
|
Components are the building blocks of apps
|
||||||
|
|
||||||
|
* Component are functions annotated with the `#[component]` macro.
|
||||||
|
* The function name must start with a capital letter or contain an underscore.
|
||||||
|
* A component re-renders only under two conditions:
|
||||||
|
1. Its props change (as determined by `PartialEq`).
|
||||||
|
2. An internal reactive state it depends on is updated.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[component]
|
||||||
|
fn Input(mut value: Signal<String>) -> Element {
|
||||||
|
rsx! {
|
||||||
|
input {
|
||||||
|
value,
|
||||||
|
oninput: move |e| {
|
||||||
|
*value.write() = e.value();
|
||||||
|
},
|
||||||
|
onkeydown: move |e| {
|
||||||
|
if e.key() == Key::Enter {
|
||||||
|
value.write().clear();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each component accepts function arguments (props)
|
||||||
|
|
||||||
|
* Props must be owned values, not references. Use `String` and `Vec<T>` instead of `&str` or `&[T]`.
|
||||||
|
* Props must implement `PartialEq` and `Clone`.
|
||||||
|
* To make props reactive and copy, you can wrap the type in `ReadOnlySignal`. Any reactive state like memos and resources that read `ReadOnlySignal` props will automatically re-run when the prop changes.
|
||||||
|
|
||||||
|
# State
|
||||||
|
|
||||||
|
A signal is a wrapper around a value that automatically tracks where it's read and written. Changing a signal's value causes code that relies on the signal to rerun.
|
||||||
|
|
||||||
|
## Local State
|
||||||
|
|
||||||
|
The `use_signal` hook creates state that is local to a single component. You can call the signal like a function (e.g. `my_signal()`) to clone the value, or use `.read()` to get a reference. `.write()` gets a mutable reference to the value.
|
||||||
|
|
||||||
|
Use `use_memo` to create a memoized value that recalculates when its dependencies change. Memos are useful for expensive calculations that you don't want to repeat unnecessarily.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[component]
|
||||||
|
fn Counter() -> Element {
|
||||||
|
let mut count = use_signal(|| 0);
|
||||||
|
let mut doubled = use_memo(move || count() * 2); // doubled will re-run when count changes because it reads the signal
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
h1 { "Count: {count}" } // Counter will re-render when count changes because it reads the signal
|
||||||
|
h2 { "Doubled: {doubled}" }
|
||||||
|
button {
|
||||||
|
onclick: move |_| *count.write() += 1, // Writing to the signal rerenders Counter
|
||||||
|
"Increment"
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
onclick: move |_| count.with_mut(|count| *count += 1), // use with_mut to mutate the signal
|
||||||
|
"Increment with with_mut"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Context API
|
||||||
|
|
||||||
|
The Context API allows you to share state down the component tree. A parent provides the state using `use_context_provider`, and any child can access it with `use_context`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[component]
|
||||||
|
fn App() -> Element {
|
||||||
|
let mut theme = use_signal(|| "light".to_string());
|
||||||
|
use_context_provider(|| theme); // Provide a type to children
|
||||||
|
rsx! { Child {} }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn Child() -> Element {
|
||||||
|
let theme = use_context::<Signal<String>>(); // Consume the same type
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
"Current theme: {theme}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Async
|
||||||
|
|
||||||
|
For state that depends on an asynchronous operation (like a network request), Dioxus provides a hook called `use_resource`. This hook manages the lifecycle of the async task and provides the result to your component.
|
||||||
|
|
||||||
|
* The `use_resource` hook takes an `async` closure. It re-runs this closure whenever any signals it depends on (reads) are updated
|
||||||
|
* The `Resource` object returned can be in several states when read:
|
||||||
|
1. `None` if the resource is still loading
|
||||||
|
2. `Some(value)` if the resource has successfully loaded
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let mut dog = use_resource(move || async move {
|
||||||
|
// api request
|
||||||
|
});
|
||||||
|
|
||||||
|
match dog() {
|
||||||
|
Some(dog_info) => rsx! { Dog { dog_info } },
|
||||||
|
None => rsx! { "Loading..." },
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Routing
|
||||||
|
|
||||||
|
All possible routes are defined in a single Rust `enum` that derives `Routable`. Each variant represents a route and is annotated with `#[route("/path")]`. Dynamic Segments can capture parts of the URL path as parameters by using `:name` in the route string. These become fields in the enum variant.
|
||||||
|
|
||||||
|
The `Router<Route> {}` component is the entry point that manages rendering the correct component for the current URL.
|
||||||
|
|
||||||
|
You can use the `#[layout(NavBar)]` to create a layout shared between pages and place an `Outlet<Route> {}` inside your layout component. The child routes will be rendered in the outlet.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Routable, Clone, PartialEq)]
|
||||||
|
enum Route {
|
||||||
|
#[layout(NavBar)] // This will use NavBar as the layout for all routes
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
#[route("/blog/:id")] // Dynamic segment
|
||||||
|
BlogPost { id: i32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn NavBar() -> Element {
|
||||||
|
rsx! {
|
||||||
|
a { href: "/", "Home" }
|
||||||
|
Outlet<Route> {} // Renders Home or BlogPost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn App() -> Element {
|
||||||
|
rsx! { Router::<Route> {} }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml
|
||||||
|
dioxus = { version = "0.7.1", features = ["router"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
# Fullstack
|
||||||
|
|
||||||
|
Fullstack enables server rendering and ipc calls. It uses Cargo features (`server` and a client feature like `web`) to split the code into a server and client binaries.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
dioxus = { version = "0.7.1", features = ["fullstack"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Server Functions
|
||||||
|
|
||||||
|
Use the `#[post]` / `#[get]` macros to define an `async` function that will only run on the server. On the server, this macro generates an API endpoint. On the client, it generates a function that makes an HTTP request to that endpoint.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[post("/api/double/:path/&query")]
|
||||||
|
async fn double_server(number: i32, path: String, query: i32) -> Result<i32, ServerFnError> {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
|
Ok(number * 2)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hydration
|
||||||
|
|
||||||
|
Hydration is the process of making a server-rendered HTML page interactive on the client. The server sends the initial HTML, and then the client-side runs, attaches event listeners, and takes control of future rendering.
|
||||||
|
|
||||||
|
### Errors
|
||||||
|
The initial UI rendered by the component on the client must be identical to the UI rendered on the server.
|
||||||
|
|
||||||
|
* Use the `use_server_future` hook instead of `use_resource`. It runs the future on the server, serializes the result, and sends it to the client, ensuring the client has the data immediately for its first render.
|
||||||
|
* Any code that relies on browser-specific APIs (like accessing `localStorage`) must be run *after* hydration. Place this code inside a `use_effect` hook.
|
||||||
6438
Cargo.lock
generated
Normal file
6438
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
Cargo.toml
Normal file
21
Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "squad-quote-store"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Reed Krantz <programming@krantz.one>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dioxus = { version = "0.7.1", features = ["router", "fullstack"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["web"]
|
||||||
|
# The feature that are only required for the web = ["dioxus/web"] build target should be optional and only enabled in the web = ["dioxus/web"] feature
|
||||||
|
web = ["dioxus/web"]
|
||||||
|
# The feature that are only required for the desktop = ["dioxus/desktop"] build target should be optional and only enabled in the desktop = ["dioxus/desktop"] feature
|
||||||
|
desktop = ["dioxus/desktop"]
|
||||||
|
# The feature that are only required for the mobile = ["dioxus/mobile"] build target should be optional and only enabled in the mobile = ["dioxus/mobile"] feature
|
||||||
|
mobile = ["dioxus/mobile"]
|
||||||
|
# The feature that are only required for the server = ["dioxus/server"] build target should be optional and only enabled in the server = ["dioxus/server"] feature
|
||||||
|
server = ["dioxus/server"]
|
||||||
21
Dioxus.toml
Normal file
21
Dioxus.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[application]
|
||||||
|
|
||||||
|
[web.app]
|
||||||
|
|
||||||
|
# HTML title tag content
|
||||||
|
title = "squad-quote-store"
|
||||||
|
|
||||||
|
# include `assets` in web platform
|
||||||
|
[web.resource]
|
||||||
|
|
||||||
|
# Additional CSS style files
|
||||||
|
style = []
|
||||||
|
|
||||||
|
# Additional JavaScript files
|
||||||
|
script = []
|
||||||
|
|
||||||
|
[web.resource.dev]
|
||||||
|
|
||||||
|
# Javascript code file
|
||||||
|
# serve: [dev-server] only
|
||||||
|
script = []
|
||||||
58
README.md
Normal file
58
README.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Development
|
||||||
|
|
||||||
|
Your new jumpstart project includes basic organization with an organized `assets` folder and a `components` folder.
|
||||||
|
If you chose to develop with the router feature, you will also have a `views` folder.
|
||||||
|
|
||||||
|
```
|
||||||
|
project/
|
||||||
|
├─ assets/ # Any assets that are used by the app should be placed here
|
||||||
|
├─ src/
|
||||||
|
│ ├─ main.rs # The entrypoint for the app. It also defines the routes for the app.
|
||||||
|
│ ├─ components/
|
||||||
|
│ │ ├─ mod.rs # Defines the components module
|
||||||
|
│ │ ├─ hero.rs # The Hero component for use in the home page
|
||||||
|
│ │ ├─ echo.rs # The echo component uses server functions to communicate with the server
|
||||||
|
│ ├─ views/ # The views each route will render in the app.
|
||||||
|
│ │ ├─ mod.rs # Defines the module for the views route and re-exports the components for each route
|
||||||
|
│ │ ├─ blog.rs # The component that will render at the /blog/:id route
|
||||||
|
│ │ ├─ home.rs # The component that will render at the / route
|
||||||
|
├─ Cargo.toml # The Cargo.toml file defines the dependencies and feature flags for your project
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automatic Tailwind (Dioxus 0.7+)
|
||||||
|
|
||||||
|
As of Dioxus 0.7, there no longer is a need to manually install tailwind. Simply `dx serve` and you're good to go!
|
||||||
|
|
||||||
|
Automatic tailwind is supported by checking for a file called `tailwind.css` in your app's manifest directory (next to Cargo.toml). To customize the file, use the dioxus.toml:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[application]
|
||||||
|
tailwind_input = "my.css"
|
||||||
|
tailwind_output = "assets/out.css"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tailwind Manual Install
|
||||||
|
|
||||||
|
To use tailwind plugins or manually customize tailwind, you can can install the Tailwind CLI and use it directly.
|
||||||
|
|
||||||
|
1. Install npm: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm
|
||||||
|
2. Install the Tailwind CSS CLI: https://tailwindcss.com/docs/installation/tailwind-cli
|
||||||
|
3. Run the following command in the root of the project to start the Tailwind CSS compiler:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @tailwindcss/cli -i ./input.css -o ./assets/tailwind.css --watch
|
||||||
|
```
|
||||||
|
|
||||||
|
### Serving Your App
|
||||||
|
|
||||||
|
Run the following command in the root of your project to start developing with the default platform:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dx serve --platform web
|
||||||
|
```
|
||||||
|
|
||||||
|
To run for a different platform, use the `--platform platform` flag. E.g.
|
||||||
|
```bash
|
||||||
|
dx serve --platform desktop
|
||||||
|
```
|
||||||
|
|
||||||
BIN
assets/favicon.ico
Normal file
BIN
assets/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
20
assets/header.svg
Normal file
20
assets/header.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 23 KiB |
8
assets/styling/blog.css
Normal file
8
assets/styling/blog.css
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#blog {
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blog a {
|
||||||
|
color: #ffffff;
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
34
assets/styling/echo.css
Normal file
34
assets/styling/echo.css
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#echo {
|
||||||
|
width: 360px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-top: 50px;
|
||||||
|
background-color: #1e222d;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#echo>h4 {
|
||||||
|
margin: 0px 0px 15px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#echo>input {
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px white solid;
|
||||||
|
background-color: transparent;
|
||||||
|
color: #ffffff;
|
||||||
|
transition: border-bottom-color 0.2s ease;
|
||||||
|
outline: none;
|
||||||
|
display: block;
|
||||||
|
padding: 0px 0px 5px 0px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#echo>input:focus {
|
||||||
|
border-bottom-color: #6d85c6;
|
||||||
|
}
|
||||||
|
|
||||||
|
#echo>p {
|
||||||
|
margin: 20px 0px 0px auto;
|
||||||
|
}
|
||||||
42
assets/styling/main.css
Normal file
42
assets/styling/main.css
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
body {
|
||||||
|
background-color: #0f1116;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hero {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#links {
|
||||||
|
width: 400px;
|
||||||
|
text-align: left;
|
||||||
|
font-size: x-large;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#links a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin: 10px 0px;
|
||||||
|
border: white 1px solid;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#links a:hover {
|
||||||
|
background-color: #1f1f1f;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
max-width: 1200px;
|
||||||
|
}
|
||||||
16
assets/styling/navbar.css
Normal file
16
assets/styling/navbar.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#navbar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a {
|
||||||
|
color: #ffffff;
|
||||||
|
margin-right: 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#navbar a:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #91a4d2;
|
||||||
|
}
|
||||||
0
assets/tailwind.css
Normal file
0
assets/tailwind.css
Normal file
98
flake.lock
generated
Normal file
98
flake.lock
generated
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"crane": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1767744144,
|
||||||
|
"narHash": "sha256-9/9ntI0D+HbN4G0TrK3KmHbTvwgswz7p8IEJsWyef8Q=",
|
||||||
|
"owner": "ipetkov",
|
||||||
|
"repo": "crane",
|
||||||
|
"rev": "2fb033290bf6b23f226d4c8b32f7f7a16b043d7e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "ipetkov",
|
||||||
|
"repo": "crane",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-parts": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1768135262,
|
||||||
|
"narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1768127708,
|
||||||
|
"narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-lib": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1765674936,
|
||||||
|
"narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixpkgs.lib",
|
||||||
|
"rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixpkgs.lib",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"crane": "crane",
|
||||||
|
"flake-parts": "flake-parts",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"rust-overlay": "rust-overlay"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-overlay": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1768186348,
|
||||||
|
"narHash": "sha256-nkpIe3zkpeoFuOl8xBpexulECsHLQ9Ljg1gW3bPCjSI=",
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"rev": "af69e497567a5945a64057717bc9b17c8478097e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
92
flake.nix
Normal file
92
flake.nix
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
description = "Rust tool";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
|
|
||||||
|
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||||
|
|
||||||
|
crane.url = "github:ipetkov/crane";
|
||||||
|
|
||||||
|
rust-overlay = {
|
||||||
|
url = "github:oxalica/rust-overlay";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = {
|
||||||
|
nixpkgs,
|
||||||
|
flake-parts,
|
||||||
|
crane,
|
||||||
|
rust-overlay,
|
||||||
|
...
|
||||||
|
} @ inputs:
|
||||||
|
flake-parts.lib.mkFlake {inherit inputs;} {
|
||||||
|
systems = ["aarch64-linux" "x86_64-linux" "aarch64-darwin" "x86_64-darwin"];
|
||||||
|
|
||||||
|
perSystem = {system, ...}: let
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [(import rust-overlay)];
|
||||||
|
};
|
||||||
|
|
||||||
|
craneLib = (crane.mkLib pkgs).overrideToolchain (
|
||||||
|
p:
|
||||||
|
p.rust-bin.stable.latest.default.override {
|
||||||
|
targets = ["wasm32-unknown-unknown"];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
# Build *just* the cargo dependencies, so we can reuse
|
||||||
|
# all of that work (e.g. via cachix) when running in CI
|
||||||
|
cargoArtifacts = craneLib.buildDepsOnly {
|
||||||
|
inherit src;
|
||||||
|
};
|
||||||
|
in rec {
|
||||||
|
# Formatter for nix files, available through 'nix fmt'
|
||||||
|
formatter = pkgs.alejandra;
|
||||||
|
|
||||||
|
# Your custom packages
|
||||||
|
# Accessible through 'nix build', 'nix shell', 'nix run', etc
|
||||||
|
packages = {
|
||||||
|
default = craneLib.buildPackage {
|
||||||
|
inherit src cargoArtifacts;
|
||||||
|
|
||||||
|
# runtime dependencies
|
||||||
|
# buildInputs = [];
|
||||||
|
|
||||||
|
# build dependencies
|
||||||
|
# nativeBuildInputs = [pkgs.pkg-config];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
checks = {
|
||||||
|
clippy = craneLib.cargoClippy {
|
||||||
|
inherit src cargoArtifacts;
|
||||||
|
cargoClippyExtraArgs = "-- --deny warnings";
|
||||||
|
};
|
||||||
|
|
||||||
|
fmt = craneLib.cargoFmt {
|
||||||
|
inherit src cargoArtifacts;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Dev Shell that lets you enter an environment with all the necessary utilites
|
||||||
|
# Available through 'nix develop'
|
||||||
|
# Also can be activated automatically if direnv is installed on the system with 'direnv allow'
|
||||||
|
devShells.default = craneLib.devShell {
|
||||||
|
inherit cargoArtifacts checks;
|
||||||
|
|
||||||
|
# extra tooling dependencies
|
||||||
|
packages = with pkgs; [
|
||||||
|
rust-analyzer
|
||||||
|
dioxus-cli
|
||||||
|
];
|
||||||
|
|
||||||
|
inputsFrom = [packages.default];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
61
src/components/echo.rs
Normal file
61
src/components/echo.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
const ECHO_CSS: Asset = asset!("/assets/styling/echo.css");
|
||||||
|
|
||||||
|
/// Echo component that demonstrates fullstack server functions.
|
||||||
|
#[component]
|
||||||
|
pub fn Echo() -> Element {
|
||||||
|
// use_signal is a hook. Hooks in dioxus must be run in a consistent order every time the component is rendered.
|
||||||
|
// That means they can't be run inside other hooks, async blocks, if statements, or loops.
|
||||||
|
//
|
||||||
|
// use_signal is a hook that creates a state for the component. It takes a closure that returns the initial value of the state.
|
||||||
|
// The state is automatically tracked and will rerun any other hooks or components that read it whenever it changes.
|
||||||
|
let mut response = use_signal(|| String::new());
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
document::Link { rel: "stylesheet", href: ECHO_CSS }
|
||||||
|
|
||||||
|
div {
|
||||||
|
id: "echo",
|
||||||
|
h4 { "ServerFn Echo" }
|
||||||
|
input {
|
||||||
|
placeholder: "Type here to echo...",
|
||||||
|
// `oninput` is an event handler that will run when the input changes. It can return either nothing or a future
|
||||||
|
// that will be run when the event runs.
|
||||||
|
oninput: move |event| async move {
|
||||||
|
// When we call the echo_server function from the client, it will fire a request to the server and return
|
||||||
|
// the response. It handles serialization and deserialization of the request and response for us.
|
||||||
|
let data = echo_server(event.value()).await.unwrap();
|
||||||
|
|
||||||
|
// After we have the data from the server, we can set the state of the signal to the new value.
|
||||||
|
// Since we read the `response` signal later in this component, the component will rerun.
|
||||||
|
response.set(data);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signals can be called like a function to clone the current value of the signal
|
||||||
|
if !response().is_empty() {
|
||||||
|
p {
|
||||||
|
"Server echoed: "
|
||||||
|
// Since we read the signal inside this component, the component "subscribes" to the signal. Whenever
|
||||||
|
// the signal changes, the component will rerun.
|
||||||
|
i { "{response}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server functions let us define public APIs on the server that can be called like a normal async function from the client.
|
||||||
|
// Each server function needs to be annotated with the `#[post]`/`#[get]` attributes, accept and return serializable types, and return
|
||||||
|
// a `Result` with the error type [`ServerFnError`].
|
||||||
|
//
|
||||||
|
// When the server function is called from the client, it will just serialize the arguments, call the API, and deserialize the
|
||||||
|
// response.
|
||||||
|
#[post("/api/echo")]
|
||||||
|
async fn echo_server(input: String) -> Result<String> {
|
||||||
|
// The body of server function like this comment are only included on the server. If you have any server-only logic like
|
||||||
|
// database queries, you can put it here. Any imports for the server function should either be imported inside the function
|
||||||
|
// or imported under a `#[cfg(feature = "server")]` block.
|
||||||
|
Ok(input)
|
||||||
|
}
|
||||||
25
src/components/hero.rs
Normal file
25
src/components/hero.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
const HEADER_SVG: Asset = asset!("/assets/header.svg");
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Hero() -> Element {
|
||||||
|
rsx! {
|
||||||
|
// We can create elements inside the rsx macro with the element name followed by a block of attributes and children.
|
||||||
|
div {
|
||||||
|
// Attributes should be defined in the element before any children
|
||||||
|
id: "hero",
|
||||||
|
// After all attributes are defined, we can define child elements and components
|
||||||
|
img { src: HEADER_SVG, id: "header" }
|
||||||
|
div { id: "links",
|
||||||
|
// The RSX macro also supports text nodes surrounded by quotes
|
||||||
|
a { href: "https://dioxuslabs.com/learn/0.7/", "📚 Learn Dioxus" }
|
||||||
|
a { href: "https://dioxuslabs.com/awesome", "🚀 Awesome Dioxus" }
|
||||||
|
a { href: "https://github.com/dioxus-community/", "📡 Community Libraries" }
|
||||||
|
a { href: "https://github.com/DioxusLabs/sdk", "⚙️ Dioxus Development Kit" }
|
||||||
|
a { href: "https://marketplace.visualstudio.com/items?itemName=DioxusLabs.dioxus", "💫 VSCode Extension" }
|
||||||
|
a { href: "https://discord.gg/XgGxMSkvUM", "👋 Community Discord" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/components/mod.rs
Normal file
9
src/components/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
//! The components module contains all shared components for our app. Components are the building blocks of dioxus apps.
|
||||||
|
//! They can be used to defined common UI elements like buttons, forms, and modals. In this template, we define a Hero
|
||||||
|
//! component and an Echo component for fullstack apps to be used in our app.
|
||||||
|
|
||||||
|
mod hero;
|
||||||
|
pub use hero::Hero;
|
||||||
|
|
||||||
|
mod echo;
|
||||||
|
pub use echo::Echo;
|
||||||
66
src/main.rs
Normal file
66
src/main.rs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// The dioxus prelude contains a ton of common items used in dioxus apps. It's a good idea to import wherever you
|
||||||
|
// need dioxus
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
use views::{Blog, Home, Navbar};
|
||||||
|
|
||||||
|
/// Define a components module that contains all shared components for our app.
|
||||||
|
mod components;
|
||||||
|
/// Define a views module that contains the UI for all Layouts and Routes for our app.
|
||||||
|
mod views;
|
||||||
|
|
||||||
|
/// The Route enum is used to define the structure of internal routes in our app. All route enums need to derive
|
||||||
|
/// the [`Routable`] trait, which provides the necessary methods for the router to work.
|
||||||
|
///
|
||||||
|
/// Each variant represents a different URL pattern that can be matched by the router. If that pattern is matched,
|
||||||
|
/// the components for that route will be rendered.
|
||||||
|
#[derive(Debug, Clone, Routable, PartialEq)]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
enum Route {
|
||||||
|
// The layout attribute defines a wrapper for all routes under the layout. Layouts are great for wrapping
|
||||||
|
// many routes with a common UI like a navbar.
|
||||||
|
#[layout(Navbar)]
|
||||||
|
// The route attribute defines the URL pattern that a specific route matches. If that pattern matches the URL,
|
||||||
|
// the component for that route will be rendered. The component name that is rendered defaults to the variant name.
|
||||||
|
#[route("/")]
|
||||||
|
Home {},
|
||||||
|
// The route attribute can include dynamic parameters that implement [`std::str::FromStr`] and [`std::fmt::Display`] with the `:` syntax.
|
||||||
|
// In this case, id will match any integer like `/blog/123` or `/blog/-456`.
|
||||||
|
#[route("/blog/:id")]
|
||||||
|
// Fields of the route variant will be passed to the component as props. In this case, the blog component must accept
|
||||||
|
// an `id` prop of type `i32`.
|
||||||
|
Blog { id: i32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can import assets in dioxus with the `asset!` macro. This macro takes a path to an asset relative to the crate root.
|
||||||
|
// The macro returns an `Asset` type that will display as the path to the asset in the browser or a local path in desktop bundles.
|
||||||
|
const FAVICON: Asset = asset!("/assets/favicon.ico");
|
||||||
|
// The asset macro also minifies some assets like CSS and JS to make bundled smaller
|
||||||
|
const MAIN_CSS: Asset = asset!("/assets/styling/main.css");
|
||||||
|
const TAILWIND_CSS: Asset = asset!("/assets/tailwind.css");
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// The `launch` function is the main entry point for a dioxus app. It takes a component and renders it with the platform feature
|
||||||
|
// you have enabled
|
||||||
|
dioxus::launch(App);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// App is the main component of our app. Components are the building blocks of dioxus apps. Each component is a function
|
||||||
|
/// that takes some props and returns an Element. In this case, App takes no props because it is the root of our app.
|
||||||
|
///
|
||||||
|
/// Components should be annotated with `#[component]` to support props, better error messages, and autocomplete
|
||||||
|
#[component]
|
||||||
|
fn App() -> Element {
|
||||||
|
// The `rsx!` macro lets us define HTML inside of rust. It expands to an Element with all of our HTML inside.
|
||||||
|
rsx! {
|
||||||
|
// In addition to element and text (which we will see later), rsx can contain other components. In this case,
|
||||||
|
// we are using the `document::Link` component to add a link to our favicon and main CSS file into the head of our app.
|
||||||
|
document::Link { rel: "icon", href: FAVICON }
|
||||||
|
document::Link { rel: "stylesheet", href: MAIN_CSS }
|
||||||
|
document::Link { rel: "stylesheet", href: TAILWIND_CSS }
|
||||||
|
|
||||||
|
// The router component renders the route enum we defined above. It will handle synchronization of the URL and render
|
||||||
|
// the layouts and components for the active route.
|
||||||
|
Router::<Route> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/views/blog.rs
Normal file
39
src/views/blog.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
use crate::Route;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
const BLOG_CSS: Asset = asset!("/assets/styling/blog.css");
|
||||||
|
|
||||||
|
/// The Blog page component that will be rendered when the current route is `[Route::Blog]`
|
||||||
|
///
|
||||||
|
/// The component takes a `id` prop of type `i32` from the route enum. Whenever the id changes, the component function will be
|
||||||
|
/// re-run and the rendered HTML will be updated.
|
||||||
|
#[component]
|
||||||
|
pub fn Blog(id: i32) -> Element {
|
||||||
|
rsx! {
|
||||||
|
document::Link { rel: "stylesheet", href: BLOG_CSS }
|
||||||
|
|
||||||
|
div {
|
||||||
|
id: "blog",
|
||||||
|
|
||||||
|
// Content
|
||||||
|
h1 { "This is blog #{id}!" }
|
||||||
|
p { "In blog #{id}, we show how the Dioxus router works and how URL parameters can be passed as props to our route components." }
|
||||||
|
|
||||||
|
// Navigation links
|
||||||
|
// The `Link` component lets us link to other routes inside our app. It takes a `to` prop of type `Route` and
|
||||||
|
// any number of child nodes.
|
||||||
|
Link {
|
||||||
|
// The `to` prop is the route that the link should navigate to. We can use the `Route` enum to link to the
|
||||||
|
// blog page with the id of -1. Since we are using an enum instead of a string, all of the routes will be checked
|
||||||
|
// at compile time to make sure they are valid.
|
||||||
|
to: Route::Blog { id: id - 1 },
|
||||||
|
"Previous"
|
||||||
|
}
|
||||||
|
span { " <---> " }
|
||||||
|
Link {
|
||||||
|
to: Route::Blog { id: id + 1 },
|
||||||
|
"Next"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/views/home.rs
Normal file
11
src/views/home.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
use crate::components::{Echo, Hero};
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
/// The Home page component that will be rendered when the current route is `[Route::Home]`
|
||||||
|
#[component]
|
||||||
|
pub fn Home() -> Element {
|
||||||
|
rsx! {
|
||||||
|
Hero {}
|
||||||
|
Echo {}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/views/mod.rs
Normal file
18
src/views/mod.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
//! The views module contains the components for all Layouts and Routes for our app. Each layout and route in our [`Route`]
|
||||||
|
//! enum will render one of these components.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! The [`Home`] and [`Blog`] components will be rendered when the current route is [`Route::Home`] or [`Route::Blog`] respectively.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! The [`Navbar`] component will be rendered on all pages of our app since every page is under the layout. The layout defines
|
||||||
|
//! a common wrapper around all child routes.
|
||||||
|
|
||||||
|
mod home;
|
||||||
|
pub use home::Home;
|
||||||
|
|
||||||
|
mod blog;
|
||||||
|
pub use blog::Blog;
|
||||||
|
|
||||||
|
mod navbar;
|
||||||
|
pub use navbar::Navbar;
|
||||||
32
src/views/navbar.rs
Normal file
32
src/views/navbar.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
use crate::Route;
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
const NAVBAR_CSS: Asset = asset!("/assets/styling/navbar.css");
|
||||||
|
|
||||||
|
/// The Navbar component that will be rendered on all pages of our app since every page is under the layout.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// This layout component wraps the UI of [Route::Home] and [Route::Blog] in a common navbar. The contents of the Home and Blog
|
||||||
|
/// routes will be rendered under the outlet inside this component
|
||||||
|
#[component]
|
||||||
|
pub fn Navbar() -> Element {
|
||||||
|
rsx! {
|
||||||
|
document::Link { rel: "stylesheet", href: NAVBAR_CSS }
|
||||||
|
|
||||||
|
div {
|
||||||
|
id: "navbar",
|
||||||
|
Link {
|
||||||
|
to: Route::Home {},
|
||||||
|
"Home"
|
||||||
|
}
|
||||||
|
Link {
|
||||||
|
to: Route::Blog { id: 1 },
|
||||||
|
"Blog"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The `Outlet` component is used to render the next component inside the layout. In this case, it will render either
|
||||||
|
// the [`Home`] or [`Blog`] component depending on the current route.
|
||||||
|
Outlet::<Route> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
tailwind.css
Normal file
1
tailwind.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
Reference in New Issue
Block a user