diff --git a/examples/assets/radio.css b/examples/assets/radio.css new file mode 100644 index 000000000..214731c35 --- /dev/null +++ b/examples/assets/radio.css @@ -0,0 +1,43 @@ +body { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; +} + +button { + margin: 10px; + padding: 10px; + border: none; + border-radius: 5px; + background-color: #f0f0f0; + cursor: pointer; +} + +#pause { + background-color: #ff0000; +} + +#play { + background-color: #00ff00; +} + + +.bounce { + animation: boomBox 0.5s infinite; +} + +@keyframes boomBox { + 0% { + transform: scale(1.0); + } + + 50% { + transform: scale(2); + } + + 100% { + transform: scale(1.0); + } +} diff --git a/examples/assets/router.css b/examples/assets/router.css new file mode 100644 index 000000000..eabe84d84 --- /dev/null +++ b/examples/assets/router.css @@ -0,0 +1,13 @@ +#navbar { + display: flex; + justify-content: flex-start; + gap: 1rem; + align-items: center; +} + +#blog-list { + display: flex; + flex-direction: column; + align-items: start; + gap: 1rem; +} diff --git a/examples/read_size.rs b/examples/read_size.rs index 0a9f802b8..932c831c7 100644 --- a/examples/read_size.rs +++ b/examples/read_size.rs @@ -1,4 +1,9 @@ -#![allow(clippy::await_holding_refcell_ref)] +//! Read the size of elements using the MountedData struct. +//! +//! Whenever an Element is finally mounted to the Dom, its data is avaiable to be read. +//! These fields can typically only be read asynchronously, since various renderers need to release the main thread to +//! perform layout and painting. + use std::rc::Rc; use dioxus::{html::geometry::euclid::Rect, prelude::*}; @@ -33,6 +38,7 @@ fn app() -> Element { let read_dims = move |_| async move { let read = div_element.read(); let client_rect = read.as_ref().map(|el| el.get_client_rect()); + if let Some(client_rect) = client_rect { if let Ok(rect) = client_rect.await { dimensions.set(rect); diff --git a/examples/readme.rs b/examples/readme.rs index d29a24bdc..7c5240086 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -1,3 +1,10 @@ +//! The example from the readme! +//! +//! This example demonstrates how to create a simple counter app with dioxus. The `Signal` type wraps inner values, +//! making them `Copy`, allowing them to be freely used in closures and and async functions. `Signal` also provides +//! helper methods like AddAssign, SubAssign, toggle, etc, to make it easy to update the value without running +//! into lock issues. + use dioxus::prelude::*; fn main() { diff --git a/examples/reducer.rs b/examples/reducer.rs index cc685a8e1..082714ff5 100644 --- a/examples/reducer.rs +++ b/examples/reducer.rs @@ -15,12 +15,16 @@ fn app() -> Element { let mut state = use_signal(|| PlayerState { is_playing: false }); rsx!( - div { - h1 {"Select an option"} - h3 { "The radio is... ", {state.read().is_playing()}, "!" } - button { onclick: move |_| state.write().reduce(PlayerAction::Pause), "Pause" } - button { onclick: move |_| state.write().reduce(PlayerAction::Play), "Play" } + style { {include_str!("./assets/radio.css")} } + h1 {"Select an option"} + + // Add some cute animations if the radio is playing! + div { class: if state.read().is_playing { "bounce" }, + "The radio is... ", {state.read().is_playing()}, "!" } + + button { id: "play", onclick: move |_| state.write().reduce(PlayerAction::Pause), "Pause" } + button { id: "pause", onclick: move |_| state.write().reduce(PlayerAction::Play), "Play" } ) } diff --git a/examples/router.rs b/examples/router.rs index 5230d92a6..7b41e94e1 100644 --- a/examples/router.rs +++ b/examples/router.rs @@ -1,32 +1,59 @@ +//! An advanced usage of the router with nested routes and redirects. +//! +//! Dioxus implements an enum-based router, which allows you to define your routes in a type-safe way. +//! However, since we need to bake quite a bit of logic into the enum, we have to add some extra syntax. +//! +//! Note that you don't need to use advanced features like nest, redirect, etc, since these can all be implemented +//! manually, but they are provided as a convenience. + use dioxus::prelude::*; fn main() { launch_desktop(|| { rsx! { + style { {include_str!("./assets/router.css")} } Router:: {} } }); } +// Turn off rustfmt since we're doing layouts and routes in the same enum #[derive(Routable, Clone, Debug, PartialEq)] #[rustfmt::skip] enum Route { + // Wrap Home in a Navbar Layout #[layout(NavBar)] + // The default route is always "/" unless otherwise specified #[route("/")] Home {}, + + // Wrap the next routes in a layout and a nest #[nest("/blog")] - #[layout(Blog)] - #[route("/")] - BlogList {}, - #[route("/:name")] - BlogPost { name: String }, - #[end_layout] + #[layout(Blog)] + // At "/blog", we want to show a list of blog posts + #[route("/")] + BlogList {}, + + // At "/blog/:name", we want to show a specific blog post, using the name slug + #[route("/:name")] + BlogPost { name: String }, + + // We need to end the blog layout and nest + // Note we don't need either - we could've just done `/blog/` and `/blog/:name` without nesting, + // but it's a bit cleaner this way + #[end_layout] #[end_nest] + + // And the regular page layout #[end_layout] + + // Add some redirects for the `/myblog` route #[nest("/myblog")] #[redirect("/", || Route::BlogList {})] #[redirect("/:name", |name: String| Route::BlogPost { name })] #[end_nest] + + // Finally, we need to handle the 404 page #[route("/:..route")] PageNotFound { route: Vec, @@ -36,15 +63,9 @@ enum Route { #[component] fn NavBar() -> Element { rsx! { - nav { - ul { - li { - Link { to: Route::Home {}, "Home" } - } - li { - Link { to: Route::BlogList {}, "Blog" } - } - } + nav { id: "navbar", + Link { to: Route::Home {}, "Home" } + Link { to: Route::BlogList {}, "Blog" } } Outlet:: {} } @@ -67,30 +88,31 @@ fn Blog() -> Element { fn BlogList() -> Element { rsx! { h2 { "Choose a post" } - ul { - li { - Link { - to: Route::BlogPost { - name: "Blog post 1".into(), - }, - "Read the first blog post" - } + div { id: "blog-list", + Link { to: Route::BlogPost { name: "Blog post 1".into() }, + "Read the first blog post" } - li { - Link { - to: Route::BlogPost { - name: "Blog post 2".into(), - }, - "Read the second blog post" - } + Link { to: Route::BlogPost { name: "Blog post 2".into() }, + "Read the second blog post" } } } } +// We can use the `name` slug to show a specific blog post +// In theory we could read from the filesystem or a database here #[component] fn BlogPost(name: String) -> Element { - rsx! { h2 { "Blog Post: {name}" } } + let contents = match name.as_str() { + "Blog post 1" => "This is the first blog post. It's not very interesting.", + "Blog post 2" => "This is the second blog post. It's not very interesting either.", + _ => "This blog post doesn't exist.", + }; + + rsx! { + h2 { "{name}" } + p { "{contents}" } + } } #[component] diff --git a/examples/scroll_to_top.rs b/examples/scroll_to_top.rs index 1c042b912..b69a86c51 100644 --- a/examples/scroll_to_top.rs +++ b/examples/scroll_to_top.rs @@ -1,3 +1,10 @@ +//! Scroll elements using their MountedData +//! +//! Dioxus exposes a few helpful APIs around elements (mimicking the DOM APIs) to allow you to interact with elements +//! across the renderers. This includes scrolling, reading dimensions, and more. +//! +//! In this example we demonstrate how to scroll to the top of the page using the `scroll_to` method on the `MountedData` + use dioxus::prelude::*; fn main() { diff --git a/examples/shortcut.rs b/examples/shortcut.rs index d7e849870..d0192b978 100644 --- a/examples/shortcut.rs +++ b/examples/shortcut.rs @@ -1,3 +1,10 @@ +//! Add global shortcuts to your app while a component is active +//! +//! This demo shows how to add a global shortcut to your app that toggles a signal. You could use this to implement +//! a raycast-type app, or to add a global shortcut to your app that toggles a component on and off. +//! +//! These are *global* shortcuts, so they will work even if your app is not in focus. + use dioxus::desktop::use_global_shortcut; use dioxus::prelude::*; diff --git a/examples/shorthand.rs b/examples/shorthand.rs index c53e0146b..5b016ab93 100644 --- a/examples/shorthand.rs +++ b/examples/shorthand.rs @@ -1,3 +1,5 @@ +//! Dioxus supports shorthand syntax for creating elements and components. + use dioxus::prelude::*; fn main() { diff --git a/examples/signals.rs b/examples/signals.rs index 5b217c508..0accddc56 100644 --- a/examples/signals.rs +++ b/examples/signals.rs @@ -1,3 +1,11 @@ +//! A simple example demonstrating how to use signals to modify state from several different places. +//! +//! This simlpe example implements a counter that can be incremented, decremented, and paused. It also demonstrates +//! that background tasks in use_futures can modify the value as well. +//! +//! Most signals implement Into>, making ReadOnlySignal a good default type when building new +//! library components that don't need to modify their values. + use dioxus::prelude::*; use std::time::Duration; diff --git a/examples/simple_list.rs b/examples/simple_list.rs index 67e9e64ff..1d33d4f0c 100644 --- a/examples/simple_list.rs +++ b/examples/simple_list.rs @@ -1,3 +1,7 @@ +//! A few ways of mapping elements into rsx! syntax +//! +//! Rsx allows anything that's an iterator where the output type implements Into, so you can use any of the following: + use dioxus::prelude::*; fn main() { diff --git a/examples/simple_router.rs b/examples/simple_router.rs index 7ad44fbb3..44a06040e 100644 --- a/examples/simple_router.rs +++ b/examples/simple_router.rs @@ -1,20 +1,32 @@ -#![allow(non_snake_case)] +//! A simple example of a router with a few routes and a nav bar. use dioxus::prelude::*; +fn main() { + // Launch the router, using our `Route` component as the generic type + // This will automatically boot the app to "/" unless otherwise specified + launch(|| rsx! { Router:: {} }); +} + +/// By default, the Routable derive will use the name of the variant as the route +/// You can also specify a specific component by adding the Component name to the `#[route]` attribute +#[rustfmt::skip] #[derive(Routable, Clone, PartialEq)] enum Route { + // Wrap the app in a Nav layout #[layout(Nav)] - #[route("/")] - Homepage {}, + #[route("/")] + Homepage {}, - #[route("/blog/:id")] - Blog { id: String }, + #[route("/blog/:id")] + Blog { id: String }, } #[component] fn Homepage() -> Element { - rsx! { h1 { "Welcome home" } } + rsx! { + h1 { "Welcome home" } + } } #[component] @@ -25,6 +37,9 @@ fn Blog(id: String) -> Element { } } +/// A simple nav bar that links to the homepage and blog pages +/// +/// The `Route` enum gives up typesafe routes, allowing us to rename routes and serialize them automatically #[component] fn Nav() -> Element { rsx! { @@ -52,7 +67,3 @@ fn Nav() -> Element { div { Outlet:: {} } } } - -fn main() { - launch_desktop(|| rsx! { Router:: {} }); -} diff --git a/examples/spread.rs b/examples/spread.rs index 763a48e12..8bc0f8b59 100644 --- a/examples/spread.rs +++ b/examples/spread.rs @@ -1,3 +1,8 @@ +//! This example demonstrates how to use the spread operator to pass attributes to child components. +//! +//! This lets components like the `Link` allow the user to extend the attributes of the underlying `a` tag. +//! These attributes are bundled into a `Vec` which can be spread into the child component using the `..` operator. + use dioxus::prelude::*; fn main() { diff --git a/examples/ssr.rs b/examples/ssr.rs index e2a6acd90..c9e759e1c 100644 --- a/examples/ssr.rs +++ b/examples/ssr.rs @@ -1,6 +1,9 @@ //! Example: SSR //! //! This example shows how we can render the Dioxus Virtualdom using SSR. +//! Dioxus' SSR is quite comprehensive and can generate a number of utility markers for things like hydration. +//! +//! You can also render without any markers to get a clean HTML output. use dioxus::prelude::*; diff --git a/examples/stale_memo.rs b/examples/stale_memo.rs deleted file mode 100644 index f44c7297a..000000000 --- a/examples/stale_memo.rs +++ /dev/null @@ -1,36 +0,0 @@ -use dioxus::prelude::*; - -fn main() { - launch_desktop(app); -} - -fn app() -> Element { - let mut state = use_signal(|| 0); - let mut depth = use_signal(|| 1_usize); - - if depth() == 5 { - return rsx! { - div { "Max depth reached" } - button { onclick: move |_| depth -= 1, "Remove depth" } - }; - } - - let items = use_memo(move || (0..depth()).map(|f| f as _).collect::>()); - - rsx! { - button { onclick: move |_| state += 1, "Increment" } - button { onclick: move |_| depth += 1, "Add depth" } - button { - onclick: move |_| async move { - depth += 1; - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - dbg!(items.read()); - // if depth() is 5, this will be the old since the memo hasn't been re-computed - // use_memos are only re-computed when the signals they capture change - // *and* they are used in the current render - // If the use_memo isn't used, it can't be re-computed! - }, - "Add depth with sleep" - } - } -} diff --git a/examples/streams.rs b/examples/streams.rs index 8f692596a..5e62c35aa 100644 --- a/examples/streams.rs +++ b/examples/streams.rs @@ -1,3 +1,5 @@ +//! Handle async streams using use_future and awaiting the next value. + use dioxus::prelude::*; use futures_util::{future, stream, Stream, StreamExt}; use std::time::Duration; @@ -10,8 +12,11 @@ fn app() -> Element { let mut count = use_signal(|| 10); use_future(move || async move { + // Create the stream. + // This could be a network request, a file read, or any other async operation. let mut stream = some_stream(); + // Await the next value from the stream. while let Some(second) = stream.next().await { count.set(second); } diff --git a/examples/suspense.rs b/examples/suspense.rs index 2fa9757fe..05c651dea 100644 --- a/examples/suspense.rs +++ b/examples/suspense.rs @@ -1,5 +1,3 @@ -#![allow(non_snake_case)] - //! Suspense in Dioxus //! //! Currently, `rsx!` does not accept futures as values. To achieve the functionality @@ -49,6 +47,7 @@ fn app() -> Element { /// This component will re-render when the future has finished /// Suspense is achieved my moving the future into only the component that /// actually renders the data. +#[component] fn Doggo() -> Element { let mut fut = use_resource(move || async move { #[derive(serde::Deserialize)] diff --git a/examples/svg.rs b/examples/svg.rs index 022a2aa81..ae71f2bb9 100644 --- a/examples/svg.rs +++ b/examples/svg.rs @@ -1,4 +1,10 @@ -// Thanks to @japsu and their project https://github.com/japsu/jatsi for the example! +//! Thanks to @japsu and their project https://github.com/japsu/jatsi for the example! +//! +//! This example shows how to create a simple dice rolling app using SVG and Dioxus. +//! The `svg` element and its children have a custom namespace, and are attached using different methods than regular +//! HTML elements. Any element can specify a custom namespace by using the `namespace` meta attribute. +//! +//! If you `go-to-definition` on the `svg` element, you'll see its custom namespace. use dioxus::prelude::*; use rand::{thread_rng, Rng}; diff --git a/examples/video_stream.rs b/examples/video_stream.rs index 7f5fd80ed..9c3c4c919 100644 --- a/examples/video_stream.rs +++ b/examples/video_stream.rs @@ -1,3 +1,7 @@ +//! Using `wry`'s http module, we can stream a video file from the local file system. +//! +//! You could load in any file type, but this example uses a video file. + use dioxus::desktop::wry::http; use dioxus::desktop::wry::http::Response; use dioxus::desktop::{use_asset_handler, AssetRequest}; @@ -9,25 +13,14 @@ use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; const VIDEO_PATH: &str = "./examples/assets/test_video.mp4"; fn main() { - let video_file = PathBuf::from(VIDEO_PATH); - if !video_file.exists() { - tokio::runtime::Runtime::new() - .unwrap() - .block_on(async move { - println!("Downloading video file..."); - let video_url = - "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"; - let mut response = reqwest::get(video_url).await.unwrap(); - let mut file = tokio::fs::File::create(&video_file).await.unwrap(); - while let Some(chunk) = response.chunk().await.unwrap() { - file.write_all(&chunk).await.unwrap(); - } - }); - } + // For the sake of this example, we will download the video file if it doesn't exist + ensure_video_is_loaded(); + launch_desktop(app); } fn app() -> Element { + // Any request to /videos will be handled by this handler use_asset_handler("videos", move |request, responder| { // Using dioxus::spawn works, but is slower than a dedicated thread tokio::task::spawn(async move { @@ -184,3 +177,21 @@ async fn get_stream_response( http_response.map_err(Into::into) } + +fn ensure_video_is_loaded() { + let video_file = PathBuf::from(VIDEO_PATH); + if !video_file.exists() { + tokio::runtime::Runtime::new() + .unwrap() + .block_on(async move { + println!("Downloading video file..."); + let video_url = + "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"; + let mut response = reqwest::get(video_url).await.unwrap(); + let mut file = tokio::fs::File::create(&video_file).await.unwrap(); + while let Some(chunk) = response.chunk().await.unwrap() { + file.write_all(&chunk).await.unwrap(); + } + }); + } +}