Browse Source

Update more examples, add css for more examples

Jonathan Kelley 1 year ago
parent
commit
cfc119cce2

+ 43 - 0
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);
+    }
+}

+ 13 - 0
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;
+}

+ 7 - 1
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);

+ 7 - 0
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() {

+ 9 - 5
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" }
     )
 }
 

+ 53 - 31
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::<Route> {}
         }
     });
 }
 
+// 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<String>,
@@ -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::<Route> {}
     }
@@ -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]

+ 7 - 0
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() {

+ 7 - 0
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::*;
 

+ 2 - 0
examples/shorthand.rs

@@ -1,3 +1,5 @@
+//! Dioxus supports shorthand syntax for creating elements and components.
+
 use dioxus::prelude::*;
 
 fn main() {

+ 8 - 0
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<ReadOnlySignal<T>>, 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;
 

+ 4 - 0
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<Element>, so you can use any of the following:
+
 use dioxus::prelude::*;
 
 fn main() {

+ 21 - 10
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::<Route> {} });
+}
+
+/// 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::<Route> {} }
     }
 }
-
-fn main() {
-    launch_desktop(|| rsx! { Router::<Route> {} });
-}

+ 5 - 0
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<Attribute>` which can be spread into the child component using the `..` operator.
+
 use dioxus::prelude::*;
 
 fn main() {

+ 3 - 0
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::*;
 

+ 0 - 36
examples/stale_memo.rs

@@ -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::<Vec<isize>>());
-
-    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"
-        }
-    }
-}

+ 5 - 0
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);
         }

+ 1 - 2
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)]

+ 7 - 1
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};

+ 26 - 15
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();
+                }
+            });
+    }
+}